Skip to content

Instantly share code, notes, and snippets.

@ruzrobert
Last active April 2, 2024 08:16
Show Gist options
  • Star 74 You must be signed in to star a gist
  • Fork 16 You must be signed in to fork a gist
  • Save ruzrobert/d98220a3b7f71ccc90403e041967c46b to your computer and use it in GitHub Desktop.
Save ruzrobert/d98220a3b7f71ccc90403e041967c46b to your computer and use it in GitHub Desktop.
Android Vibration for Unity 3D. Supports all API: 1 - 29 (auto detect), Amplitudes, Predefined Effects, both Legacy and >=26 APIs.
using System.Diagnostics.CodeAnalysis;
using UnityEngine;
// Dont forget to add "using RDG;" to the top of your script!
namespace RDG
{
/// <summary>
/// Class for controlling Vibration. Automatically initializes before scene is loaded.
/// </summary>
public static class Vibration
{
// Component Parameters
public static logLevel LogLevel = logLevel.Disabled;
// Vibrator References
private static AndroidJavaObject vibrator = null;
private static AndroidJavaClass vibrationEffectClass = null;
private static int defaultAmplitude = 255;
// Api Level
private static int ApiLevel = 1;
private static bool doesSupportVibrationEffect () => ApiLevel >= 26; // available only from Api >= 26
private static bool doesSupportPredefinedEffect () => ApiLevel >= 29; // available only from Api >= 29
#region Initialization
private static bool isInitialized = false;
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
[SuppressMessage("Code quality", "IDE0051", Justification = "Called on scene load")]
private static void Initialize ()
{
// Add APP VIBRATION PERMISSION to the Manifest
#if UNITY_ANDROID
if (Application.isConsolePlatform) { Handheld.Vibrate(); }
#endif
// load references safely
if (isInitialized == false && Application.platform == RuntimePlatform.Android) {
// Get Api Level
using (AndroidJavaClass androidVersionClass = new AndroidJavaClass("android.os.Build$VERSION")) {
ApiLevel = androidVersionClass.GetStatic<int>("SDK_INT");
}
// Get UnityPlayer and CurrentActivity
using (AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
using (AndroidJavaObject currentActivity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity")) {
if (currentActivity != null) {
vibrator = currentActivity.Call<AndroidJavaObject>("getSystemService", "vibrator");
// if device supports vibration effects, get corresponding class
if (doesSupportVibrationEffect()) {
vibrationEffectClass = new AndroidJavaClass("android.os.VibrationEffect");
defaultAmplitude = Mathf.Clamp(vibrationEffectClass.GetStatic<int>("DEFAULT_AMPLITUDE"), 1, 255);
}
// if device supports predefined effects, get their IDs
if (doesSupportPredefinedEffect()) {
PredefinedEffect.EFFECT_CLICK = vibrationEffectClass.GetStatic<int>("EFFECT_CLICK");
PredefinedEffect.EFFECT_DOUBLE_CLICK = vibrationEffectClass.GetStatic<int>("EFFECT_DOUBLE_CLICK");
PredefinedEffect.EFFECT_HEAVY_CLICK = vibrationEffectClass.GetStatic<int>("EFFECT_HEAVY_CLICK");
PredefinedEffect.EFFECT_TICK = vibrationEffectClass.GetStatic<int>("EFFECT_TICK");
}
}
}
logAuto("Vibration component initialized", logLevel.Info);
isInitialized = true;
}
}
#endregion
#region Vibrate Public
/// <summary>
/// Vibrate for Milliseconds, with Amplitude (if available).
/// If amplitude is -1, amplitude is Disabled. If -1, device DefaultAmplitude is used. Otherwise, values between 1-255 are allowed.
/// If 'cancel' is true, Cancel() will be called automatically.
/// </summary>
public static void Vibrate (long milliseconds, int amplitude = -1, bool cancel = false)
{
string funcToStr () => string.Format("Vibrate ({0}, {1}, {2})", milliseconds, amplitude, cancel);
Initialize(); // make sure script is initialized
if (isInitialized == false) {
logAuto(funcToStr() + ": Not initialized", logLevel.Warning);
}
else if (HasVibrator() == false) {
logAuto(funcToStr() + ": Device doesn't have Vibrator", logLevel.Warning);
}
else {
if (cancel) Cancel();
if (doesSupportVibrationEffect()) {
// validate amplitude
amplitude = Mathf.Clamp(amplitude, -1, 255);
if (amplitude == -1) amplitude = 255; // if -1, disable amplitude (use maximum amplitude)
if (amplitude != 255 && HasAmplitudeControl() == false) { // if amplitude was set, but not supported, notify developer
logAuto(funcToStr() + ": Device doesn't have Amplitude Control, but Amplitude was set", logLevel.Warning);
}
if (amplitude == 0) amplitude = defaultAmplitude; // if 0, use device DefaultAmplitude
// if amplitude is not supported, use 255; if amplitude is -1, use systems DefaultAmplitude. Otherwise use user-defined value.
amplitude = HasAmplitudeControl() == false ? 255 : amplitude;
vibrateEffect(milliseconds, amplitude);
logAuto(funcToStr() + ": Effect called", logLevel.Info);
}
else {
vibrateLegacy(milliseconds);
logAuto(funcToStr() + ": Legacy called", logLevel.Info);
}
}
}
/// <summary>
/// Vibrate Pattern (pattern of durations, with format Off-On-Off-On and so on).
/// Amplitudes can be Null (for default) or array of Pattern array length with values between 1-255.
/// To cause the pattern to repeat, pass the index into the pattern array at which to start the repeat, or -1 to disable repeating.
/// If 'cancel' is true, Cancel() will be called automatically.
/// </summary>
public static void Vibrate (long[] pattern, int[] amplitudes = null, int repeat = -1, bool cancel = false)
{
string funcToStr () => string.Format("Vibrate (({0}), ({1}), {2}, {3})", arrToStr(pattern), arrToStr(amplitudes), repeat, cancel);
Initialize(); // make sure script is initialized
if (isInitialized == false) {
logAuto(funcToStr() + ": Not initialized", logLevel.Warning);
}
else if (HasVibrator() == false) {
logAuto(funcToStr() + ": Device doesn't have Vibrator", logLevel.Warning);
}
else {
// check Amplitudes array length
if (amplitudes != null && amplitudes.Length != pattern.Length) {
logAuto(funcToStr() + ": Length of Amplitudes array is not equal to Pattern array. Amplitudes will be ignored.", logLevel.Warning);
amplitudes = null;
}
// limit amplitudes between 1 and 255
if (amplitudes != null) {
clampAmplitudesArray(amplitudes);
}
// vibrate
if (cancel) Cancel();
if (doesSupportVibrationEffect()) {
if (amplitudes != null && HasAmplitudeControl() == false) {
logAuto(funcToStr() + ": Device doesn't have Amplitude Control, but Amplitudes was set", logLevel.Warning);
amplitudes = null;
}
if (amplitudes != null) {
vibrateEffect(pattern, amplitudes, repeat);
logAuto(funcToStr() + ": Effect with amplitudes called", logLevel.Info);
}
else {
vibrateEffect(pattern, repeat);
logAuto(funcToStr() + ": Effect called", logLevel.Info);
}
}
else {
vibrateLegacy(pattern, repeat);
logAuto(funcToStr() + ": Legacy called", logLevel.Info);
}
}
}
/// <summary>
/// Vibrate predefined effect (described in Vibration.PredefinedEffect). Available from Api Level >= 29.
/// If 'cancel' is true, Cancel() will be called automatically.
/// </summary>
public static void VibratePredefined (int effectId, bool cancel = false)
{
string funcToStr () => string.Format("VibratePredefined ({0})", effectId);
Initialize(); // make sure script is initialized
if (isInitialized == false) {
logAuto(funcToStr() + ": Not initialized", logLevel.Warning);
}
else if (HasVibrator() == false) {
logAuto(funcToStr() + ": Device doesn't have Vibrator", logLevel.Warning);
}
else if (doesSupportPredefinedEffect() == false) {
logAuto(funcToStr() + ": Device doesn't support Predefined Effects (Api Level >= 29)", logLevel.Warning);
}
else {
if (cancel) Cancel();
vibrateEffectPredefined(effectId);
logAuto(funcToStr() + ": Predefined effect called", logLevel.Info);
}
}
#endregion
#region Public Properties & Controls
public static long[] ParsePattern (string pattern)
{
if (pattern == null) return new long[0];
pattern = pattern.Trim();
string[] split = pattern.Split(',');
long[] timings = new long[split.Length];
for (int i = 0; i < split.Length; i++) {
if (int.TryParse(split[i].Trim(), out int duration)) {
timings[i] = duration < 0 ? 0 : duration;
}
else {
timings[i] = 0;
}
}
return timings;
}
/// <summary>
/// Returns Android Api Level
/// </summary>
public static int GetApiLevel () => ApiLevel;
/// <summary>
/// Returns Default Amplitude of device, or 0.
/// </summary>
public static int GetDefaultAmplitude () => defaultAmplitude;
/// <summary>
/// Returns true if device has vibrator
/// </summary>
public static bool HasVibrator ()
{
return vibrator != null && vibrator.Call<bool>("hasVibrator");
}
/// <summary>
/// Return true if device supports amplitude control
/// </summary>
public static bool HasAmplitudeControl ()
{
if (HasVibrator() && doesSupportVibrationEffect()) {
return vibrator.Call<bool>("hasAmplitudeControl"); // API 26+ specific
}
else {
return false; // no amplitude control below API level 26
}
}
/// <summary>
/// Tries to cancel current vibration
/// </summary>
public static void Cancel ()
{
if (HasVibrator()) {
vibrator.Call("cancel");
logAuto("Cancel (): Called", logLevel.Info);
}
}
#endregion
#region Vibrate Internal
#region Vibration Callers
private static void vibrateEffect (long milliseconds, int amplitude)
{
using (AndroidJavaObject effect = createEffect_OneShot(milliseconds, amplitude)) {
vibrator.Call("vibrate", effect);
}
}
private static void vibrateLegacy (long milliseconds)
{
vibrator.Call("vibrate", milliseconds);
}
private static void vibrateEffect (long[] pattern, int repeat)
{
using (AndroidJavaObject effect = createEffect_Waveform(pattern, repeat)) {
vibrator.Call("vibrate", effect);
}
}
private static void vibrateLegacy (long[] pattern, int repeat)
{
vibrator.Call("vibrate", pattern, repeat);
}
private static void vibrateEffect (long[] pattern, int[] amplitudes, int repeat)
{
using (AndroidJavaObject effect = createEffect_Waveform(pattern, amplitudes, repeat)) {
vibrator.Call("vibrate", effect);
}
}
private static void vibrateEffectPredefined (int effectId)
{
using (AndroidJavaObject effect = createEffect_Predefined(effectId)) {
vibrator.Call("vibrate", effect);
}
}
#endregion
#region Vibration Effect
/// <summary>
/// Wrapper for public static VibrationEffect createOneShot (long milliseconds, int amplitude). API >= 26
/// </summary>
private static AndroidJavaObject createEffect_OneShot (long milliseconds, int amplitude)
{
return vibrationEffectClass.CallStatic<AndroidJavaObject>("createOneShot", milliseconds, amplitude);
}
/// <summary>
/// Wrapper for public static VibrationEffect createPredefined (int effectId). API >= 29
/// </summary>
private static AndroidJavaObject createEffect_Predefined (int effectId)
{
return vibrationEffectClass.CallStatic<AndroidJavaObject>("createPredefined", effectId);
}
/// <summary>
/// Wrapper for public static VibrationEffect createWaveform (long[] timings, int[] amplitudes, int repeat). API >= 26
/// </summary>
private static AndroidJavaObject createEffect_Waveform (long[] timings, int[] amplitudes, int repeat)
{
return vibrationEffectClass.CallStatic<AndroidJavaObject>("createWaveform", timings, amplitudes, repeat);
}
/// <summary>
/// Wrapper for public static VibrationEffect createWaveform (long[] timings, int repeat). API >= 26
/// </summary>
private static AndroidJavaObject createEffect_Waveform (long[] timings, int repeat)
{
return vibrationEffectClass.CallStatic<AndroidJavaObject>("createWaveform", timings, repeat);
}
#endregion
#endregion
#region Internal
private static void logAuto (string text, logLevel level)
{
if (level == logLevel.Disabled) level = logLevel.Info;
if (text != null) {
if (level == logLevel.Warning && LogLevel == logLevel.Warning) {
Debug.LogWarning(text);
}
else if (level == logLevel.Info && LogLevel >= logLevel.Info) {
Debug.Log(text);
}
}
}
private static string arrToStr (long[] array) => array == null ? "null" : string.Join(", ", array);
private static string arrToStr (int[] array) => array == null ? "null" : string.Join(", ", array);
private static void clampAmplitudesArray (int[] amplitudes)
{
for (int i = 0; i < amplitudes.Length; i++) {
amplitudes[i] = Mathf.Clamp(amplitudes[i], 1, 255);
}
}
#endregion
public static class PredefinedEffect
{
public static int EFFECT_CLICK; // public static final int EFFECT_CLICK
public static int EFFECT_DOUBLE_CLICK; // public static final int EFFECT_DOUBLE_CLICK
public static int EFFECT_HEAVY_CLICK; // public static final int EFFECT_HEAVY_CLICK
public static int EFFECT_TICK; // public static final int EFFECT_TICK
}
public enum logLevel
{
Disabled,
Info,
Warning,
}
}
}
@revolt3r
Copy link

Appreciate your effort, works like a charm!

@iqfareez
Copy link

Hi. Here made an app based on the script created by ruzrobert above. For dev out there who want to test and tweak how vibration should feel on devices. Available only Android. Can download via Play Store: https://play.google.com/store/apps/details?id=com.maplerr.TestVibration .

@darro87
Copy link

darro87 commented May 22, 2020

Hi All, script seems very useful. I am getting an error in trying to implement it however, as Unity throws an error once I import the script. It complains about the following lines:

333 private static string arrToStr (long[] array) => array == null ? "null" : string.Join(", ", array);
334 private static string arrToStr (int[] array) => array == null ? "null" : string.Join(", ", array);

It says:
Error CS1503 Argument 2: cannot convert from 'long[]' to 'string[]' Assembly-CSharp
Error CS1503 Argument 2: cannot convert from 'int[]' to 'string[]' Assembly-CSharp

I don't want to mess with the code unnecessarily, as it seems that no one else got this issue from the comments above. Any ideas as to why this is happening/how to fix?

@ruzrobert
Copy link
Author

Hello @darro87! Which Unity version you are using? What are scripting configurations in Player settings (Scripting Runtime Version, Backend, Api Compatibility Level) ?

@darro87
Copy link

darro87 commented May 22, 2020

Hi,

2018.4.23f1
.NET 4.x Equivalent
Mono
.NET Standard 2.0

I adjusted/added the following to clear up the error so it'd compile. Not sure if it's doing exactly what it's intended to though.

    private static string arrToStr(long[] array) => array == null ? "null" : string.Join(", ", longToString(array));
    private static string arrToStr(int[] array) => array == null ? "null" : string.Join(", ", intToString(array));

    private static string[] longToString(long[] array)
    {
        int index = 0;
        string[] str = new string[array.Length];
        foreach (long l in array)
        {
            str[index++] = l.ToString();
        }
        return str;
    }

    private static string[] intToString(int[] array)
    {
        int index = 0;
        string[] str = new string[array.Length];
        foreach (int i in array)
        {
            str[index++] = i.ToString();
        }
        return str;
    }

@ruzrobert
Copy link
Author

@darro87, yes, your code will work. It is not the best performance wise, but is good enough. Not really sure why you had this error, as I replicated it only with .NET 3.5, but no the 4.x. Anyway, it is good that your problem is solved!

@ruzrobert
Copy link
Author

@fareezMaple, thanks for using my code in your app! I've checked it, and it looks good. Especially liked the Material-style UI (made in Unity, as I understand, and I know it is a ready to use asset, but anyway).

@Kainatic
Copy link

Kainatic commented May 29, 2020

@ruzrobert Hey, haven't tested it out yet, but one feature I wanted was to toggle vibration on and off and I don't see any such code in your script. Can you help me out to get that function running? It is kinda urgent. Thanks in advance.

EDIT (Been just 15 minutes): I am now using a bool to determine if Vibrate will be called or not. If you do have a general solution in mind though, let me know. Also, I haven't yet tested it so can't comment on the functionality, but seems there's a lot more than I need right now. How can I remove the unwanted parts to lighten the weight of running this script without breaking anything? I only want defined-time vibration. No patterns, etc. Thanks.

@svojigoji
Copy link

@ruzobert Hey! I tested the code a few times , but never found a way to make my custom pattern. :( Also the Vibrate Pattern and Vibrate function are both named "public static void Vibrate()" is that how its supposed to be?

Cheers for the code <3

@ThomFoxx
Copy link

ThomFoxx commented Aug 3, 2020

@ruzrobert Just a note, At present, This does not seem to work with the Unity Device Simulator. "Exception: Field SDK_INT or type signature not found" Is the error I get. But works well in both Game Mode and on actual hardware.

@FZollR
Copy link

FZollR commented Mar 30, 2021

@ruzrobert Hello, is there any way to reach you in a private message?

@ruzrobert
Copy link
Author

@ruzrobert Hello, is there any way to reach you in a private message?

Can you reach me in linkedin?

@ruzrobert
Copy link
Author

Hello, i registered, but i need premium, i sent you a message on INstagram, is it ok? ruzrobert @.> ezt írta (időpont: 2021. márc. 31., Sze, 10:24):

@.
* commented on this gist. ------------------------------ @ruzrobert https://github.com/ruzrobert Hello, is there any way to reach you in a private message? Can you reach me in linkedin? — You are receiving this because you commented. Reply to this email directly, view it on GitHub https://gist.github.com/d98220a3b7f71ccc90403e041967c46b#gistcomment-3687899, or unsubscribe https://github.com/notifications/unsubscribe-auth/ATO7JFNCOKZJPIROPDXBDOTTGLL4FANCNFSM4JY5B3PA .

You can write me to nufriaj@gmail.com !

@iamthespark
Copy link

iamthespark commented Apr 29, 2021

Hello! Great work here. Just wanted to let you know that I tested on API 29 and this didn't work. On APIs 30 and 26 it worked very well though.

Edit: read a comment above that some devices may not react to anything less than 30ms, and I was testing 20ms on a Samsung A8 and an S9 (both API 29). So I'll try 30ms tomorrow.

@MadeSuard
Copy link

Hi robert, keep safe for you and everyone during this pandemic, i just want to know...can i use this script on my commercial project, idid see you previous comment about happy someone use the script, but i just more make sure u am not violate any your right when use this without your confirmation....thanks

@ruzrobert
Copy link
Author

Hi @MadeSuard, you can use this script in your commercial project

@MadeSuard
Copy link

Hi @MadeSuard, you can use this script in your commercial project

thank you very me for allowing me use this, its help my game more get feel, anyway seeing of "Quality" of my game, im not just going to publish it, i will try to sell it too, probably on asset store unity or sellmyapss or anywhere, but i see i need to a license for asset that not created by mine, so what should i use there??, sorry to bothering you

@ruzrobert
Copy link
Author

@MadeSuard Hello, I am not very familiar with licenses, but you can remove the namespace from the script, and use it as you want.
Remember that I am not responsible for any consequences, and the script is provided AS IS, on GitHub Gist.

@Bran04don
Copy link

Thank you very much! Works great

@aboveStars
Copy link

Works really well on 2021.3.8f1 (silicon)
Alt Text

@Paradox137
Copy link

@ruzrobert Hi from GameDev, I want to constantly play a short vibration (every frame is called), but it is amplified and instead of a short pleasant vibration, a long one is obtained

@ant5033
Copy link

ant5033 commented Feb 2, 2023

To use this function
in the AndroidManifest.xml file
Is it correct to add < uses-permission android:name="android.permission.VIBRATE" /> and use it?

RDG.VibrationManager.Vibrate((long)0.3,40);
I tried using , but I get an error

image

@ant5033
Copy link

ant5033 commented Feb 2, 2023

My device is Fold4 API 33 Level,
The Phone Vibrator Test Unity app is
Works fine on my device...
But the code I wrote gives an error T.T

@ant5033
Copy link

ant5033 commented Feb 13, 2023

I solved it. This is a very good code ! : )

@FractureFalcon
Copy link

god bless. thank you for this implementation

@ducksplash
Copy link

this is just fantastic thank you so much

@sagielightricks
Copy link

This worked for me in the past but i've just updated my phone to Android 12 (OnePlus 7T), and now it doesn't work. Any idea why this could happen?

@jackBonadies
Copy link

jackBonadies commented Oct 1, 2023

Something to note - this code checks if one has api support for predefined effects but NOT if it has hardware support. (contrast this with its checks for amplitude support which checks both). I think this code is missing method for HasPredefinedEffectSupport() which will call into https://developer.android.com/reference/android/os/Vibrator#areEffectsSupported(int[]), currently, it just silently fails (doesnt vibrate, throw error, or log).

@jackBonadies
Copy link

and the log level is broken (i.e. if you set it to info it only logs info, not info and above). If you want and accept pull requests I will make both those changes.

@wockdugle
Copy link

@ant5033
How can I use it on api level 33?
Do I just put this code in the object, allow permission, and use it? I'm a beginner.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment