Last active
March 22, 2026 15:10
-
-
Save HofiOne/85bf05784ae112b31ed95dc47c87944e to your computer and use it in GitHub Desktop.
Native Android device vibration
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| if UNITY_ANDROID | |
| // Cache Android vibrator objects to avoid allocations on every call | |
| private static AndroidJavaObject cachedVibrator = null; | |
| private static bool supportsAmplitudeControl = false; | |
| private static bool vibratorInitialized = false; | |
| private static void InitializeAndroidVibrator() | |
| { | |
| if (vibratorInitialized) | |
| return; | |
| vibratorInitialized = true; | |
| try { | |
| using (AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer")) | |
| using (AndroidJavaObject currentActivity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity")) { | |
| // Cache the vibrator - don't dispose it, we'll reuse it | |
| cachedVibrator = currentActivity.Call<AndroidJavaObject>("getSystemService", "vibrator"); | |
| if (cachedVibrator != null && cachedVibrator.Call<bool>("hasVibrator")) { | |
| // Check API level once and cache the result | |
| // Android API 26+ (Oreo 8.0) supports VibrationEffect with amplitude control | |
| using (AndroidJavaClass buildVersion = new AndroidJavaClass("android.os.Build$VERSION")) { | |
| int sdkInt = buildVersion.GetStatic<int>("SDK_INT"); | |
| supportsAmplitudeControl = sdkInt >= 26; | |
| } | |
| } | |
| else { | |
| cachedVibrator = null; | |
| supportsAmplitudeControl = false; | |
| } | |
| } | |
| } | |
| catch (System.Exception e) { | |
| if (GameLog.Enabled()) { | |
| GameLog.LogError($"Android Vibrator initialization failed: {e.Message}"); | |
| } | |
| cachedVibrator = null; | |
| supportsAmplitudeControl = false; | |
| } | |
| } | |
| public static void Vibrate(float intensity, float duration) | |
| { | |
| #if ! UNITY_EDITOR | |
| return; | |
| #endif | |
| // Initialize vibrator on first use | |
| if (false == vibratorInitialized) | |
| InitializeAndroidVibrator(); | |
| if (cachedVibrator == null) | |
| return; | |
| try { | |
| // Match iOS behavior: clamp intensity and enforce minimum duration | |
| intensity = Mathf.Clamp(intensity, 0.0f, 1.0f); | |
| duration = Mathf.Max(0.02f, duration); // iOS minimum: 20ms | |
| // Convert duration from seconds to milliseconds | |
| long durationMs = (long)(duration * 1000f); | |
| if (supportsAmplitudeControl) { | |
| // iOS uses sharpness=0.2 (rumble-like). Android motors are harsher, | |
| // so reduce intensity and use non-linear curve to better match iOS feel | |
| // Intensity curve: square root for softer ramp-up (more rumble-like) | |
| float adjustedIntensity = Mathf.Sqrt(intensity) * 0.75f; // Softer mapping | |
| // Map 0.0-1.0 to 40-220 (avoid extremes, match iOS rumble characteristic) | |
| int amplitude = Mathf.Clamp((int)(adjustedIntensity * 180f + 40f), 40, 220); | |
| using (AndroidJavaClass vibrationEffectClass = new AndroidJavaClass("android.os.VibrationEffect")) { | |
| AndroidJavaObject vibrationEffect = vibrationEffectClass.CallStatic<AndroidJavaObject>( | |
| "createOneShot", durationMs, amplitude); | |
| cachedVibrator.Call("vibrate", vibrationEffect); | |
| vibrationEffect.Dispose(); | |
| } | |
| } | |
| else { | |
| // Fallback for older Android versions (no intensity control) | |
| cachedVibrator.Call("vibrate", durationMs); | |
| } | |
| } | |
| catch (System.Exception e) { | |
| if (GameLog.Enabled()) { | |
| GameLog.LogError($"Android Vibrate failed: {e.Message}"); | |
| } | |
| } | |
| } | |
| #endif // #if UNITY_ANDROID |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment