using System.Collections; using System.Collections.Generic; using UnityEngine; using System.Runtime.InteropServices; #if UNITY_IOS using UnityEngine.iOS; #endif public enum HapticTypes { Selection, Success, Warning, Failure, LightImpact, MediumImpact, HeavyImpact, SoftImpact, RigidImpact, Vibrate } namespace MoreMountains.NiceVibrations { /// /// This class will allow you to trigger vibrations and haptic feedbacks on both iOS and Android, /// or on each specific platform independently. /// /// For haptics patterns, it takes inspiration from the iOS guidelines : /// https://developer.apple.com/ios/human-interface-guidelines/user-interaction/feedback /// Of course the iOS haptics are called directly as they are, and they're crudely reproduced on Android. /// Feel free to tweak the patterns or create your own. /// /// Here's a brief overview of the patterns : /// /// - selection : light /// - success : light / heavy /// - warning : heavy / medium /// - failure : medium / medium / heavy / light /// - light /// - medium /// - heavy /// /// public static class MMVibrationManager { // INTERFACE --------------------------------------------------------------------------------------------------------- public static long SoftDuration = 20; public static long RigidDuration = 20; public static long LightDuration = 20; public static long MediumDuration = 40; public static long HeavyDuration = 80; public static int SoftAmplitude = 10; public static int RigidAmplitude = 120; public static int LightAmplitude = 40; public static int MediumAmplitude = 120; public static int HeavyAmplitude = 255; private static int _sdkVersion = -1; private static long[] _successPattern = { 0, LightDuration, LightDuration, HeavyDuration }; private static int[] _successPatternAmplitude = { 0, LightAmplitude, 0, HeavyAmplitude }; private static long[] _warningPattern = { 0, HeavyDuration, LightDuration, MediumDuration }; private static int[] _warningPatternAmplitude = { 0, HeavyAmplitude, 0, MediumAmplitude }; private static long[] _failurePattern = { 0, MediumDuration, LightDuration, MediumDuration, LightDuration, HeavyDuration, LightDuration, LightDuration }; private static int[] _failurePatternAmplitude = { 0, MediumAmplitude, 0, MediumAmplitude, 0, HeavyAmplitude, 0, LightAmplitude }; /// /// Returns true if the current platform is Android, false otherwise. /// public static bool Android() { #if UNITY_ANDROID && !UNITY_EDITOR return true; #else return false; #endif } /// /// Returns true if the current platform is iOS, false otherwise /// /// true, if O was ied, false otherwise. public static bool iOS() { #if UNITY_IOS && !UNITY_EDITOR return true; #else return false; #endif } /// /// Triggers a simple vibration /// public static void Vibrate() { if (Android()) { AndroidVibrate(MediumDuration); } else if (iOS()) { iOSTriggerHaptics(HapticTypes.MediumImpact); } } /// /// Triggers a haptic feedback of the specified type /// /// Type. public static void Haptic(HapticTypes type) { if (Android()) { switch (type) { case HapticTypes.Selection: AndroidVibrate(LightDuration, LightAmplitude); break; case HapticTypes.Success: AndroidVibrate(_successPattern, _successPatternAmplitude, -1); break; case HapticTypes.Warning: AndroidVibrate(_warningPattern, _warningPatternAmplitude, -1); break; case HapticTypes.Failure: AndroidVibrate(_failurePattern, _failurePatternAmplitude, -1); break; case HapticTypes.LightImpact: AndroidVibrate(LightDuration, LightAmplitude); break; case HapticTypes.MediumImpact: AndroidVibrate(MediumDuration, MediumAmplitude); break; case HapticTypes.HeavyImpact: AndroidVibrate(HeavyDuration, HeavyAmplitude); break; case HapticTypes.SoftImpact: AndroidVibrate(SoftDuration, SoftAmplitude); break; case HapticTypes.RigidImpact: AndroidVibrate(RigidDuration, RigidAmplitude); break; } } else if (iOS()) { iOSTriggerHaptics(type); } } // INTERFACE END --------------------------------------------------------------------------------------------------------- // Android --------------------------------------------------------------------------------------------------------- // Android Vibration reference can be found at : // https://developer.android.com/reference/android/os/Vibrator.html // And there starting v26, with support for amplitude : // https://developer.android.com/reference/android/os/VibrationEffect.html #if UNITY_ANDROID && !UNITY_EDITOR private static AndroidJavaClass UnityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer"); private static AndroidJavaObject CurrentActivity = UnityPlayer.GetStatic("currentActivity"); private static AndroidJavaObject AndroidVibrator = CurrentActivity.Call("getSystemService", "vibrator"); private static AndroidJavaClass VibrationEffectClass; private static AndroidJavaObject VibrationEffect; private static int DefaultAmplitude; #else private static AndroidJavaClass UnityPlayer; private static AndroidJavaObject CurrentActivity; private static AndroidJavaObject AndroidVibrator = null; private static AndroidJavaClass VibrationEffectClass = null; private static AndroidJavaObject VibrationEffect; private static int DefaultAmplitude; #endif /// /// Requests a default vibration on Android, for the specified duration, in milliseconds /// /// Milliseconds. public static void AndroidVibrate(long milliseconds) { if (!Android()) { return; } AndroidVibrator.Call("vibrate", milliseconds); } /// /// Requests a vibration of the specified amplitude and duration. If amplitude is not supported by the device's SDK, a default vibration will be requested /// /// Milliseconds. /// Amplitude. public static void AndroidVibrate(long milliseconds, int amplitude) { if (!Android()) { return; } // amplitude is only supported if ((AndroidSDKVersion() < 26)) { AndroidVibrate(milliseconds); } else { VibrationEffectClassInitialization(); VibrationEffect = VibrationEffectClass.CallStatic("createOneShot", new object[] { milliseconds, amplitude }); AndroidVibrator.Call("vibrate", VibrationEffect); } } // Requests a vibration on Android for the specified pattern and optional repeat // Straight out of the Android documentation : // Pass in an array of ints that are the durations for which to turn on or off the vibrator in milliseconds. // The first value indicates the number of milliseconds to wait before turning the vibrator on. // The next value indicates the number of milliseconds for which to keep the vibrator on before turning it off. // Subsequent values alternate between durations in milliseconds to turn the vibrator off or to turn the vibrator on. // repeat: the index into pattern at which to repeat, or -1 if you don't want to repeat. public static void AndroidVibrate(long[] pattern, int repeat) { if (!Android()) { return; } if ((AndroidSDKVersion() < 26)) { AndroidVibrator.Call("vibrate", pattern, repeat); } else { VibrationEffectClassInitialization(); VibrationEffect = VibrationEffectClass.CallStatic("createWaveform", new object[] { pattern, repeat }); AndroidVibrator.Call("vibrate", VibrationEffect); } } /// /// Requests a vibration on Android for the specified pattern, amplitude and optional repeat /// /// Pattern. /// Amplitudes. /// Repeat. public static void AndroidVibrate(long[] pattern, int[] amplitudes, int repeat) { if (!Android()) { return; } if ((AndroidSDKVersion() < 26)) { AndroidVibrator.Call("vibrate", pattern, repeat); } else { VibrationEffectClassInitialization(); VibrationEffect = VibrationEffectClass.CallStatic("createWaveform", new object[] { pattern, amplitudes, repeat }); AndroidVibrator.Call("vibrate", VibrationEffect); } } /// /// Stops all Android vibrations that may be active /// public static void AndroidCancelVibrations() { if (!Android()) { return; } AndroidVibrator.Call("cancel"); } /// /// Initializes the VibrationEffectClass if needed. /// private static void VibrationEffectClassInitialization() { if (VibrationEffectClass == null) { VibrationEffectClass = new AndroidJavaClass("android.os.VibrationEffect"); } } /// /// Returns the current Android SDK version as an int /// /// The SDK version. public static int AndroidSDKVersion() { if (_sdkVersion == -1) { int apiLevel = int.Parse(SystemInfo.operatingSystem.Substring(SystemInfo.operatingSystem.IndexOf("-") + 1, 3)); _sdkVersion = apiLevel; return apiLevel; } else { return _sdkVersion; } } // Android End --------------------------------------------------------------------------------------------------------- // iOS ---------------------------------------------------------------------------------------------------------------- // The following will only work if the iOSHapticInterface.m file is in a Plugins folder in your project. // It's a pretty straightforward implementation of iOS's UIFeedbackGenerator's methods. // You can learn more about them there : https://developer.apple.com/documentation/uikit/uifeedbackgenerator #if UNITY_IOS && !UNITY_EDITOR [DllImport("__Internal")] private static extern void InstantiateFeedbackGenerators(); [DllImport("__Internal")] private static extern void ReleaseFeedbackGenerators(); [DllImport("__Internal")] private static extern void SelectionHaptic(); [DllImport("__Internal")] private static extern void SuccessHaptic(); [DllImport("__Internal")] private static extern void WarningHaptic(); [DllImport("__Internal")] private static extern void FailureHaptic(); [DllImport("__Internal")] private static extern void LightImpactHaptic(); [DllImport("__Internal")] private static extern void MediumImpactHaptic(); [DllImport("__Internal")] private static extern void HeavyImpactHaptic(); [DllImport("__Internal")] private static extern void SoftImpactHaptic(); [DllImport("__Internal")] private static extern void RigidImpactHaptic(); #else private static void InstantiateFeedbackGenerators() { } private static void ReleaseFeedbackGenerators() { } private static void SelectionHaptic() { } private static void SuccessHaptic() { } private static void WarningHaptic() { } private static void FailureHaptic() { } private static void LightImpactHaptic() { } private static void MediumImpactHaptic() { } private static void HeavyImpactHaptic() { } private static void SoftImpactHaptic() { } private static void RigidImpactHaptic() { } #endif private static bool iOSHapticsInitialized = false; /// /// Call this method to initialize the haptics. If you forget to do it, Nice Vibrations will do it for you the first time you /// call iOSTriggerHaptics. It's better if you do it though. /// public static void iOSInitializeHaptics() { if (!iOS()) { return; } Debug.Log("[MMVibrationManager::iOSInitializeHaptics] iOSInitializeHaptics"); InstantiateFeedbackGenerators(); iOSHapticsInitialized = true; } /// /// Releases the feedback generators, usually you'll want to call this at OnDisable(); or anytime you know you won't need /// vibrations anymore. /// public static void iOSReleaseHaptics() { if (!iOS()) { return; } ReleaseFeedbackGenerators(); } /// /// This methods tests the current device generation against a list of devices that don't support haptics, and returns true if haptics are supported, false otherwise. /// /// true, if supported was hapticsed, false otherwise. public static bool HapticsSupported() { bool hapticsSupported = false; #if UNITY_IOS DeviceGeneration generation = Device.generation; if ((generation == DeviceGeneration.iPhone3G) || (generation == DeviceGeneration.iPhone3GS) || (generation == DeviceGeneration.iPodTouch1Gen) || (generation == DeviceGeneration.iPodTouch2Gen) || (generation == DeviceGeneration.iPodTouch3Gen) || (generation == DeviceGeneration.iPodTouch4Gen) || (generation == DeviceGeneration.iPhone4) || (generation == DeviceGeneration.iPhone4S) || (generation == DeviceGeneration.iPhone5) || (generation == DeviceGeneration.iPhone5C) || (generation == DeviceGeneration.iPhone5S) || (generation == DeviceGeneration.iPhone6) || (generation == DeviceGeneration.iPhone6Plus) || (generation == DeviceGeneration.iPhone6S) || (generation == DeviceGeneration.iPhone6SPlus)) { hapticsSupported = false; } else { hapticsSupported = true; } #endif return hapticsSupported; } /// /// iOS only : triggers a haptic feedback of the specified type /// /// Type. public static void iOSTriggerHaptics(HapticTypes type) { if (!iOS()) { return; } if (!iOSHapticsInitialized) { iOSInitializeHaptics(); } // this will trigger a standard vibration on all the iOS devices that don't support haptic feedback if (HapticsSupported()) { switch (type) { case HapticTypes.Selection: SelectionHaptic(); break; case HapticTypes.Success: SuccessHaptic(); break; case HapticTypes.Warning: WarningHaptic(); break; case HapticTypes.Failure: FailureHaptic(); break; case HapticTypes.LightImpact: LightImpactHaptic(); break; case HapticTypes.MediumImpact: MediumImpactHaptic(); break; case HapticTypes.HeavyImpact: HeavyImpactHaptic(); break; case HapticTypes.SoftImpact: SoftImpactHaptic(); break; case HapticTypes.RigidImpact: RigidImpactHaptic(); break; } } else { #if UNITY_IOS Handheld.Vibrate(); #endif } } /// /// Returns a string containing iOS SDK informations /// /// The OSSDK version. public static string iOSSDKVersion() { #if UNITY_IOS && !UNITY_EDITOR return Device.systemVersion; #else return null; #endif } // iOS End ---------------------------------------------------------------------------------------------------------------- } }