Added Feel plugin
This commit is contained in:
8
Assets/External/Feel/NiceVibrations/Scripts/Components.meta
vendored
Normal file
8
Assets/External/Feel/NiceVibrations/Scripts/Components.meta
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 688d27f50942c40c39cb42dc1e5eab7a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
3
Assets/External/Feel/NiceVibrations/Scripts/Components/AssemblyInfo.cs
vendored
Normal file
3
Assets/External/Feel/NiceVibrations/Scripts/Components/AssemblyInfo.cs
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("NiceVibrationTests")]
|
||||
18
Assets/External/Feel/NiceVibrations/Scripts/Components/AssemblyInfo.cs.meta
vendored
Normal file
18
Assets/External/Feel/NiceVibrations/Scripts/Components/AssemblyInfo.cs.meta
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e0924103a050c4bbc88d415b79a67df2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 183370
|
||||
packageName: Feel
|
||||
packageVersion: 5.9.1
|
||||
assetPath: Assets/Feel/NiceVibrations/Scripts/Components/AssemblyInfo.cs
|
||||
uploadId: 830868
|
||||
253
Assets/External/Feel/NiceVibrations/Scripts/Components/DeviceCapabilities.cs
vendored
Normal file
253
Assets/External/Feel/NiceVibrations/Scripts/Components/DeviceCapabilities.cs
vendored
Normal file
@@ -0,0 +1,253 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
#if (UNITY_IOS && !UNITY_EDITOR)
|
||||
using UnityEngine.iOS;
|
||||
#endif
|
||||
|
||||
namespace Lofelt.NiceVibrations
|
||||
{
|
||||
/// <summary>
|
||||
/// A class containing properties that describe the current device capabilities for use with
|
||||
/// Nice Vibrations
|
||||
/// </summary>
|
||||
///
|
||||
/// This class describes the capabilities of an iOS or Android device, gamepads are not handled
|
||||
/// by it.
|
||||
public static class DeviceCapabilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Property that holds the current RuntimePlatform
|
||||
/// </summary>
|
||||
public static RuntimePlatform platform { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Property that holds the current platform version.
|
||||
/// </summary>
|
||||
/// iOS version on iOS, Android API level on Android or 0 otherwise.
|
||||
public static int platformVersion { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the device meets the requirements to play advanced haptics with
|
||||
/// Nice Vibrations
|
||||
/// </summary>
|
||||
///
|
||||
/// Advanced requirements means that the device can play back <c>.haptic</c> clips.
|
||||
/// While devices that don't meet the advanced requirements can not play back <c>.haptic</c>
|
||||
/// clips, they can still play back simpler fallback haptics as long as
|
||||
/// \ref isVersionSupported is <c>true</c>.
|
||||
///
|
||||
/// While DeviceCapabilities.isVersionSupported only checks the OS version, this method
|
||||
/// additionally checks the device capabilities.
|
||||
///
|
||||
/// The required device capabilities are:
|
||||
/// - iOS: iPhone >= 8
|
||||
/// - Android: Amplitude control for the <c>Vibrator</c>
|
||||
///
|
||||
/// You don't usually need to check this property. All other methods in HapticController
|
||||
/// will check \ref meetsAdvancedRequirements before calling into <c>LofeltHaptics</c>.
|
||||
/// In case the device does not support advanced haptics there is a possibility of fallback
|
||||
/// haptics based on presets.
|
||||
public static bool meetsAdvancedRequirements
|
||||
{
|
||||
get
|
||||
{
|
||||
return _meetsAdvancedRequirements;
|
||||
}
|
||||
}
|
||||
private static bool _meetsAdvancedRequirements;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the OS version is high enough to play haptics with Nice Vibrations.
|
||||
/// </summary>
|
||||
///
|
||||
/// The minimum required versions are:
|
||||
/// - iOS >= 11
|
||||
/// - Android API level >= 17
|
||||
///
|
||||
/// This only checks the minimum supported OS version in terms of API and does not guarantee
|
||||
/// that advanced haptics with amplitude control can be recreated, For that check with
|
||||
/// \ref meetsAdvancedRequirements.
|
||||
public static bool isVersionSupported { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the device is capable of amplitude control in order to recreate
|
||||
/// advanced haptics.
|
||||
/// </summary>
|
||||
public static bool hasAmplitudeControl
|
||||
{
|
||||
get
|
||||
{
|
||||
return _hasAmplitudeControl;
|
||||
}
|
||||
}
|
||||
private static bool _hasAmplitudeControl;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the device is capable of changing the frequency of haptic signals
|
||||
/// </summary>
|
||||
public static bool hasFrequencyControl
|
||||
{
|
||||
get
|
||||
{
|
||||
return _hasFrequencyControl;
|
||||
}
|
||||
}
|
||||
private static bool _hasFrequencyControl;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the device is capable of real-time amplitude modulation of haptic signals
|
||||
/// </summary>
|
||||
public static bool hasAmplitudeModulation
|
||||
{
|
||||
get
|
||||
{
|
||||
return _hasAmplitudeModulation;
|
||||
}
|
||||
}
|
||||
private static bool _hasAmplitudeModulation;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the device is capable of real-time frequency modulation of haptic signals
|
||||
/// </summary>
|
||||
public static bool hasFrequencyModulation
|
||||
{
|
||||
get
|
||||
{
|
||||
return _hasFrequencyModulation;
|
||||
}
|
||||
}
|
||||
private static bool _hasFrequencyModulation;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the device is capable of natively reproducing emphasized haptics
|
||||
/// </summary>
|
||||
public static bool hasEmphasis
|
||||
{
|
||||
get
|
||||
{
|
||||
return _hasEmphasis;
|
||||
}
|
||||
}
|
||||
private static bool _hasEmphasis;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the device is capable of emulating emphasized haptics
|
||||
/// </summary>
|
||||
public static bool canEmulateEmphasis
|
||||
{
|
||||
get
|
||||
{
|
||||
return _canEmulateEmphasis;
|
||||
}
|
||||
}
|
||||
private static bool _canEmulateEmphasis;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the device is capable of looping haptic clips
|
||||
/// </summary>
|
||||
public static bool canLoop
|
||||
{
|
||||
get
|
||||
{
|
||||
return _canLoop;
|
||||
}
|
||||
}
|
||||
private static bool _canLoop;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor that fills in the only the DeviceCapabilities platform version properties.
|
||||
/// </summary>
|
||||
/// This is separate of Init() because we need to first check the version numbers before
|
||||
/// initializing <c>LofeltHaptics</c>
|
||||
static DeviceCapabilities()
|
||||
{
|
||||
platform = Application.platform;
|
||||
platformVersion = 0;
|
||||
isVersionSupported = false;
|
||||
|
||||
#if (UNITY_ANDROID && !UNITY_EDITOR)
|
||||
platformVersion = int.Parse(SystemInfo.operatingSystem.Substring(SystemInfo.operatingSystem.IndexOf("-") + 1, 3));
|
||||
const int minimumSupportedAndroidSDKVersion = 17;
|
||||
isVersionSupported = platformVersion >= minimumSupportedAndroidSDKVersion;
|
||||
#elif (UNITY_IOS && !UNITY_EDITOR)
|
||||
string versionString = Device.systemVersion;
|
||||
string[] versionArray = versionString.Split('.');
|
||||
platformVersion = int.Parse(versionArray[0]);
|
||||
const int minimumSupportedIOSVersion = 11;
|
||||
isVersionSupported = platformVersion >= minimumSupportedIOSVersion;
|
||||
|
||||
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)
|
||||
|| (generation == DeviceGeneration.iPhoneSE1Gen)
|
||||
|| (generation == DeviceGeneration.iPad1Gen)
|
||||
|| (generation == DeviceGeneration.iPad2Gen)
|
||||
|| (generation == DeviceGeneration.iPad3Gen)
|
||||
|| (generation == DeviceGeneration.iPad4Gen)
|
||||
|| (generation == DeviceGeneration.iPad5Gen)
|
||||
|| (generation == DeviceGeneration.iPadAir1)
|
||||
|| (generation == DeviceGeneration.iPadAir2)
|
||||
|| (generation == DeviceGeneration.iPadMini1Gen)
|
||||
|| (generation == DeviceGeneration.iPadMini2Gen)
|
||||
|| (generation == DeviceGeneration.iPadMini3Gen)
|
||||
|| (generation == DeviceGeneration.iPadMini4Gen)
|
||||
|| (generation == DeviceGeneration.iPadPro10Inch1Gen)
|
||||
|| (generation == DeviceGeneration.iPadPro10Inch2Gen)
|
||||
|| (generation == DeviceGeneration.iPadPro11Inch)
|
||||
|| (generation == DeviceGeneration.iPadPro1Gen)
|
||||
|| (generation == DeviceGeneration.iPadPro2Gen)
|
||||
|| (generation == DeviceGeneration.iPadPro3Gen)
|
||||
|| (generation == DeviceGeneration.iPadUnknown)
|
||||
|| (generation == DeviceGeneration.iPodTouch1Gen)
|
||||
|| (generation == DeviceGeneration.iPodTouch2Gen)
|
||||
|| (generation == DeviceGeneration.iPodTouch3Gen)
|
||||
|| (generation == DeviceGeneration.iPodTouch4Gen)
|
||||
|| (generation == DeviceGeneration.iPodTouch5Gen)
|
||||
|| (generation == DeviceGeneration.iPodTouch6Gen)
|
||||
|| (generation == DeviceGeneration.iPhone6SPlus))
|
||||
{
|
||||
isVersionSupported = false;
|
||||
}
|
||||
|
||||
#elif (UNITY_EDITOR)
|
||||
isVersionSupported = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Function that initializes the rest of the DeviceCapabilities properties.
|
||||
/// Must be called after <c>LofeltHaptics</c> was initialized.
|
||||
/// </summary>
|
||||
public static void Init()
|
||||
{
|
||||
#if (UNITY_ANDROID && !UNITY_EDITOR)
|
||||
_hasAmplitudeControl = LofeltHaptics.DeviceMeetsMinimumPlatformRequirements();
|
||||
_canEmulateEmphasis = LofeltHaptics.DeviceMeetsMinimumPlatformRequirements();
|
||||
_canLoop = LofeltHaptics.DeviceMeetsMinimumPlatformRequirements();
|
||||
#elif (UNITY_IOS && !UNITY_EDITOR)
|
||||
_hasAmplitudeControl = LofeltHaptics.DeviceMeetsMinimumPlatformRequirements();
|
||||
_hasFrequencyControl = LofeltHaptics.DeviceMeetsMinimumPlatformRequirements();
|
||||
_hasAmplitudeModulation = LofeltHaptics.DeviceMeetsMinimumPlatformRequirements();
|
||||
_hasFrequencyModulation = LofeltHaptics.DeviceMeetsMinimumPlatformRequirements();
|
||||
_hasEmphasis = LofeltHaptics.DeviceMeetsMinimumPlatformRequirements();
|
||||
_canLoop = LofeltHaptics.DeviceMeetsMinimumPlatformRequirements();
|
||||
#endif
|
||||
_meetsAdvancedRequirements = LofeltHaptics.DeviceMeetsMinimumPlatformRequirements();
|
||||
}
|
||||
}
|
||||
}
|
||||
18
Assets/External/Feel/NiceVibrations/Scripts/Components/DeviceCapabilities.cs.meta
vendored
Normal file
18
Assets/External/Feel/NiceVibrations/Scripts/Components/DeviceCapabilities.cs.meta
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ca68228d4301d47fab6a64b6d285e2dd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 183370
|
||||
packageName: Feel
|
||||
packageVersion: 5.9.1
|
||||
assetPath: Assets/Feel/NiceVibrations/Scripts/Components/DeviceCapabilities.cs
|
||||
uploadId: 830868
|
||||
543
Assets/External/Feel/NiceVibrations/Scripts/Components/Gamepad.cs
vendored
Normal file
543
Assets/External/Feel/NiceVibrations/Scripts/Components/Gamepad.cs
vendored
Normal file
@@ -0,0 +1,543 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Timers;
|
||||
using UnityEngine;
|
||||
|
||||
// There are 3 conditions for working gamepad support in Nice Vibrations:
|
||||
//
|
||||
// 1. NICE_VIBRATIONS_INPUTSYSTEM_INSTALLED - The input system package needs to be installed.
|
||||
// See https://docs.unity3d.com/Packages/com.unity.inputsystem@1.0/manual/Installation.html#installing-the-package
|
||||
// This is set by Nice Vibrations' assembly definition file, using a version define.
|
||||
// See https://docs.unity3d.com/Manual/ScriptCompilationAssemblyDefinitionFiles.html#define-symbols
|
||||
// about version defines, and see Lofelt.NiceVibrations.asmdef for the usage in Nice Vibrations.
|
||||
//
|
||||
// 2. ENABLE_INPUT_SYSTEM - The input system needs to be enabled in the project settings.
|
||||
// See https://docs.unity3d.com/Packages/com.unity.inputsystem@1.0/manual/Installation.html#enabling-the-new-input-backends
|
||||
// This define is set by Unity, see https://docs.unity3d.com/Manual/PlatformDependentCompilation.html
|
||||
//
|
||||
// 3. NICE_VIBRATIONS_DISABLE_GAMEPAD_SUPPORT - This is a user-defined define which needs to be not set.
|
||||
// NICE_VIBRATIONS_DISABLE_GAMEPAD_SUPPORT is not set by default. It can be set by a user in the
|
||||
// player settings to disable gamepad support completely. One reason to do this is to reduce the
|
||||
// size of a HapticClip asset, as setting this define changes to HapticImporter to not add the
|
||||
// GamepadRumble to the HapticClip. Changing this define requires re-importing all .haptic clip
|
||||
// assets to update HapticClip's GamepadRumble.
|
||||
//
|
||||
// If any of the 3 conditions is not met, GamepadRumbler doesn't contain any calls into
|
||||
// UnityEngine.InputSystem, and CanPlay() always returns false.
|
||||
#if ((!UNITY_ANDROID && !UNITY_IOS) || UNITY_EDITOR) && NICE_VIBRATIONS_INPUTSYSTEM_INSTALLED && ENABLE_INPUT_SYSTEM && !NICE_VIBRATIONS_DISABLE_GAMEPAD_SUPPORT
|
||||
using UnityEngine.InputSystem;
|
||||
#endif
|
||||
|
||||
namespace Lofelt.NiceVibrations
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains a vibration pattern to make a gamepad rumble.
|
||||
/// </summary>
|
||||
///
|
||||
/// GamepadRumble contains the information on when to set what motor speeds on a gamepad
|
||||
/// to make it rumble with a specific pattern.
|
||||
///
|
||||
/// GamepadRumble has three arrays of the same length representing the rumble pattern. The
|
||||
/// entries for each array index describe for how long to turn on the gamepad's vibration
|
||||
/// motors, at what speed.
|
||||
[Serializable]
|
||||
public struct GamepadRumble
|
||||
{
|
||||
/// <summary>
|
||||
/// The duration, in milliseconds, that the motors will be turned on at the speed set
|
||||
/// in \ref lowFrequencyMotorSpeeds and \ref highFrequencyMotorSpeeds at the same array
|
||||
/// index
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
public int[] durationsMs;
|
||||
|
||||
/// <summary>
|
||||
/// The total duration of the GamepadRumble, in milliseconds
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
public int totalDurationMs;
|
||||
|
||||
/// <summary>
|
||||
/// The motor speeds of the low frequency motor
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
public float[] lowFrequencyMotorSpeeds;
|
||||
|
||||
/// <summary>
|
||||
/// The motor speeds of the high frequency motor
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
public float[] highFrequencyMotorSpeeds;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the GamepadRumble is valid and also not empty
|
||||
/// </summary>
|
||||
/// <returns>Whether the GamepadRumble is valid</returns>
|
||||
public bool IsValid()
|
||||
{
|
||||
return durationsMs != null &&
|
||||
lowFrequencyMotorSpeeds != null &&
|
||||
highFrequencyMotorSpeeds != null &&
|
||||
durationsMs.Length == lowFrequencyMotorSpeeds.Length &&
|
||||
durationsMs.Length == highFrequencyMotorSpeeds.Length &&
|
||||
durationsMs.Length > 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Vibrates a gamepad based on a GamepadRumble rumble pattern.
|
||||
/// </summary>
|
||||
///
|
||||
/// GamepadRumbler can load and play back a GamepadRumble pattern on the current
|
||||
/// gamepad.
|
||||
///
|
||||
/// This is a low-level class that normally doesn't need to be used directly. Instead,
|
||||
/// you can use HapticSource and HapticController to play back haptic clips, as those
|
||||
/// classes support gamepads by using GamepadRumbler internally.
|
||||
public static class GamepadRumbler
|
||||
{
|
||||
#if ((!UNITY_ANDROID && !UNITY_IOS) || UNITY_EDITOR) && NICE_VIBRATIONS_INPUTSYSTEM_INSTALLED && ENABLE_INPUT_SYSTEM && !NICE_VIBRATIONS_DISABLE_GAMEPAD_SUPPORT
|
||||
// Per-gamepad state tracking
|
||||
private class GamepadState
|
||||
{
|
||||
public GamepadRumble loadedRumble;
|
||||
public bool rumbleLoaded = false;
|
||||
// This Timer is used to wait until it is time to advance to the next entry in loadedRumble.
|
||||
// When the Timer is elapsed, ProcessNextRumble() is called to set new motor speeds to the
|
||||
// gamepad.
|
||||
public Timer rumbleTimer;
|
||||
// The index of the entry of loadedRumble that is currently being played back
|
||||
public int rumbleIndex = -1;
|
||||
// The total duration of rumble entries that have been played back so far
|
||||
public long rumblePositionMs = 0;
|
||||
// Keeps track of how much time elapsed since playback was started
|
||||
public Stopwatch playbackWatch = new Stopwatch();
|
||||
/// <summary>
|
||||
/// A multiplication factor applied to the motor speeds of the low frequency motor.
|
||||
/// </summary>
|
||||
///
|
||||
/// The multiplication factor is applied to the low frequency motor speed of every
|
||||
/// GamepadRumble entry before playing it.
|
||||
///
|
||||
/// In other words, this applies a gain (for factors greater than 1.0) or an attenuation
|
||||
/// (for factors less than 1.0) to the clip. If the resulting speed of an entry is
|
||||
/// greater than 1.0, it is clipped to 1.0. The speed is clipped hard, no limiter is
|
||||
/// used.
|
||||
///
|
||||
/// The motor speed multiplication is reset when calling Load(), so Load() needs to be
|
||||
/// called first before setting the multiplication.
|
||||
///
|
||||
/// A change of the multiplication is applied to a currently playing rumble, but only
|
||||
/// for the next rumble entry, not the one currently playing.
|
||||
public float lowFrequencyMotorSpeedMultiplication = 1.0f;
|
||||
/// <summary>
|
||||
/// Same as \ref lowFrequencyMotorSpeedMultiplication, but for the high frequency speed
|
||||
/// motor.
|
||||
public float highFrequencyMotorSpeedMultiplication = 1.0f;
|
||||
public int gamepadID;
|
||||
|
||||
public GamepadState(int id)
|
||||
{
|
||||
gamepadID = id;
|
||||
rumbleTimer = new Timer();
|
||||
}
|
||||
}
|
||||
|
||||
static Dictionary<int, GamepadState> gamepadStates = new Dictionary<int, GamepadState>();
|
||||
static int currentGamepadID = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the currently selected gamepad ID.
|
||||
/// </summary>
|
||||
/// <returns>The current gamepad ID, or -1 if none is set</returns>
|
||||
public static int GetCurrentGamepadID()
|
||||
{
|
||||
return currentGamepadID;
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the GamepadRumbler.
|
||||
/// </summary>
|
||||
///
|
||||
/// This needs to be called from the main thread, which is the reason why this is a method
|
||||
/// instead of a static constructor: Sometimes Unity calls static constructors from a
|
||||
/// different thread, and an explicit Init() method gives us more control over this.
|
||||
public static void Init()
|
||||
{
|
||||
#if ((!UNITY_ANDROID && !UNITY_IOS) || UNITY_EDITOR) && NICE_VIBRATIONS_INPUTSYSTEM_INSTALLED && ENABLE_INPUT_SYSTEM && !NICE_VIBRATIONS_DISABLE_GAMEPAD_SUPPORT
|
||||
var syncContext = System.Threading.SynchronizationContext.Current;
|
||||
// Initialization is now handled per-gamepad when state is created
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether a call to Play() would trigger playback on a gamepad.
|
||||
/// </summary>
|
||||
///
|
||||
/// Playing back a rumble pattern with Play() only works if a gamepad is connected and if
|
||||
/// a GamepadRumble has been loaded with Load() before.
|
||||
///
|
||||
/// <returns>Whether a vibration can be triggered on a gamepad</returns>
|
||||
public static bool CanPlay()
|
||||
{
|
||||
#if ((!UNITY_ANDROID && !UNITY_IOS) || UNITY_EDITOR) && NICE_VIBRATIONS_INPUTSYSTEM_INSTALLED && ENABLE_INPUT_SYSTEM && !NICE_VIBRATIONS_DISABLE_GAMEPAD_SUPPORT
|
||||
return CanPlay(currentGamepadID);
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static bool CanPlay(int gamepadID)
|
||||
{
|
||||
#if ((!UNITY_ANDROID && !UNITY_IOS) || UNITY_EDITOR) && NICE_VIBRATIONS_INPUTSYSTEM_INSTALLED && ENABLE_INPUT_SYSTEM && !NICE_VIBRATIONS_DISABLE_GAMEPAD_SUPPORT
|
||||
if (!gamepadStates.ContainsKey(gamepadID))
|
||||
return false;
|
||||
|
||||
var state = gamepadStates[gamepadID];
|
||||
return IsConnected(gamepadID) && state.rumbleLoaded && state.loadedRumble.IsValid();
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if ((!UNITY_ANDROID && !UNITY_IOS) || UNITY_EDITOR) && NICE_VIBRATIONS_INPUTSYSTEM_INSTALLED && ENABLE_INPUT_SYSTEM && !NICE_VIBRATIONS_DISABLE_GAMEPAD_SUPPORT
|
||||
/// <summary>
|
||||
/// Gets the Gamepad object corresponding to the specified gamepad ID.
|
||||
/// </summary>
|
||||
///
|
||||
/// If the specified ID is out of range of the connected gamepad(s),
|
||||
/// <c>InputSystem.Gamepad.current</c> will be returned.
|
||||
///
|
||||
/// <param name="gamepadID">The ID of the gamepad to be returned.</c> </param>
|
||||
/// <returns> A <c> InputSystem.Gamepad</c> </returns>
|
||||
static UnityEngine.InputSystem.Gamepad GetGamepad(int gamepadID)
|
||||
{
|
||||
if (gamepadID >= 0)
|
||||
{
|
||||
if (gamepadID >= UnityEngine.InputSystem.Gamepad.all.Count)
|
||||
{
|
||||
return UnityEngine.InputSystem.Gamepad.current;
|
||||
}
|
||||
else
|
||||
{
|
||||
return UnityEngine.InputSystem.Gamepad.all[gamepadID];
|
||||
}
|
||||
}
|
||||
return UnityEngine.InputSystem.Gamepad.current;
|
||||
}
|
||||
|
||||
static GamepadState GetOrCreateState(int gamepadID)
|
||||
{
|
||||
if (!gamepadStates.ContainsKey(gamepadID))
|
||||
{
|
||||
var state = new GamepadState(gamepadID);
|
||||
var syncContext = System.Threading.SynchronizationContext.Current;
|
||||
state.rumbleTimer.Elapsed += (object obj, System.Timers.ElapsedEventArgs args) =>
|
||||
{
|
||||
syncContext.Post(_ =>
|
||||
{
|
||||
ProcessNextRumble(gamepadID);
|
||||
}, null);
|
||||
};
|
||||
gamepadStates[gamepadID] = state;
|
||||
}
|
||||
return gamepadStates[gamepadID];
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Set the current gamepad for haptics playback by ID.
|
||||
/// </summary>
|
||||
///
|
||||
/// This method needs be called before haptics playback, e.g. \ref HapticController.Play(),
|
||||
/// \ref HapticPatterns.PlayEmphasis(), \ref HapticPatterns.PlayConstant(), etc, for
|
||||
/// for the gamepad to be properly selected.
|
||||
///
|
||||
/// If this method isn't called, haptics will be played on <c>InputSystem.Gamepad.current</c>
|
||||
///
|
||||
/// For example, if you have 3 controllers connected, you have to choose between values 0, 1,
|
||||
/// and 2.
|
||||
///
|
||||
/// If the gamepad ID value doesn't match any connected gamepad, calling
|
||||
/// this method has no effect.
|
||||
/// <param name="gamepadID">The ID of the gamepad</param>
|
||||
public static void SetCurrentGamepad(int gamepadID)
|
||||
{
|
||||
#if ((!UNITY_ANDROID && !UNITY_IOS) || UNITY_EDITOR) && NICE_VIBRATIONS_INPUTSYSTEM_INSTALLED && ENABLE_INPUT_SYSTEM && !NICE_VIBRATIONS_DISABLE_GAMEPAD_SUPPORT
|
||||
if (gamepadID < UnityEngine.InputSystem.Gamepad.all.Count)
|
||||
{
|
||||
currentGamepadID = gamepadID;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether a gamepad is connected and recognized by Unity's input system.
|
||||
/// </summary>
|
||||
///
|
||||
/// If the input system package is not installed or not enabled, the gamepad is not
|
||||
/// recognized and treated as not connected here.
|
||||
///
|
||||
/// If the <c>NICE_VIBRATIONS_DISABLE_GAMEPAD_SUPPORT</c> define is set in the player settings,
|
||||
/// this function pretends no gamepad is connected.
|
||||
///
|
||||
/// <returns>Whether a gamepad is connected</returns>
|
||||
public static bool IsConnected()
|
||||
{
|
||||
#if ((!UNITY_ANDROID && !UNITY_IOS) || UNITY_EDITOR) && NICE_VIBRATIONS_INPUTSYSTEM_INSTALLED && ENABLE_INPUT_SYSTEM && !NICE_VIBRATIONS_DISABLE_GAMEPAD_SUPPORT
|
||||
return IsConnected(currentGamepadID);
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static bool IsConnected(int gamepadID)
|
||||
{
|
||||
#if ((!UNITY_ANDROID && !UNITY_IOS) || UNITY_EDITOR) && NICE_VIBRATIONS_INPUTSYSTEM_INSTALLED && ENABLE_INPUT_SYSTEM && !NICE_VIBRATIONS_DISABLE_GAMEPAD_SUPPORT
|
||||
return GetGamepad(gamepadID) != null;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads a rumble pattern for later playback.
|
||||
/// </summary>
|
||||
///
|
||||
/// <param name="rumble">The rumble pattern to load</param>
|
||||
public static void Load(GamepadRumble rumble)
|
||||
{
|
||||
#if ((!UNITY_ANDROID && !UNITY_IOS) || UNITY_EDITOR) && NICE_VIBRATIONS_INPUTSYSTEM_INSTALLED && ENABLE_INPUT_SYSTEM && !NICE_VIBRATIONS_DISABLE_GAMEPAD_SUPPORT
|
||||
Load(rumble, currentGamepadID);
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void Load(GamepadRumble rumble, int gamepadID)
|
||||
{
|
||||
#if ((!UNITY_ANDROID && !UNITY_IOS) || UNITY_EDITOR) && NICE_VIBRATIONS_INPUTSYSTEM_INSTALLED && ENABLE_INPUT_SYSTEM && !NICE_VIBRATIONS_DISABLE_GAMEPAD_SUPPORT
|
||||
var state = GetOrCreateState(gamepadID);
|
||||
|
||||
if (rumble.IsValid())
|
||||
{
|
||||
state.loadedRumble = rumble;
|
||||
state.rumbleLoaded = true;
|
||||
state.lowFrequencyMotorSpeedMultiplication = 1.0f;
|
||||
state.highFrequencyMotorSpeedMultiplication = 1.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
Unload(gamepadID);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plays back the rumble pattern loaded previously with Load().
|
||||
/// </summary>
|
||||
///
|
||||
/// If no rumble pattern has been loaded, or if no gamepad is connected, this method does
|
||||
/// nothing.
|
||||
public static void Play()
|
||||
{
|
||||
#if ((!UNITY_ANDROID && !UNITY_IOS) || UNITY_EDITOR) && NICE_VIBRATIONS_INPUTSYSTEM_INSTALLED && ENABLE_INPUT_SYSTEM && !NICE_VIBRATIONS_DISABLE_GAMEPAD_SUPPORT
|
||||
Play(currentGamepadID);
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void Play(int gamepadID)
|
||||
{
|
||||
#if ((!UNITY_ANDROID && !UNITY_IOS) || UNITY_EDITOR) && NICE_VIBRATIONS_INPUTSYSTEM_INSTALLED && ENABLE_INPUT_SYSTEM && !NICE_VIBRATIONS_DISABLE_GAMEPAD_SUPPORT
|
||||
if (CanPlay(gamepadID))
|
||||
{
|
||||
var state = gamepadStates[gamepadID];
|
||||
state.rumbleIndex = 0;
|
||||
state.rumblePositionMs = 0;
|
||||
state.playbackWatch.Restart();
|
||||
ProcessNextRumble(gamepadID);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops playback previously started with Play() by turning off the gamepad's motors.
|
||||
/// </summary>
|
||||
public static void Stop()
|
||||
{
|
||||
#if ((!UNITY_ANDROID && !UNITY_IOS) || UNITY_EDITOR) && NICE_VIBRATIONS_INPUTSYSTEM_INSTALLED && ENABLE_INPUT_SYSTEM && !NICE_VIBRATIONS_DISABLE_GAMEPAD_SUPPORT
|
||||
Stop(currentGamepadID);
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void Stop(int gamepadID)
|
||||
{
|
||||
#if ((!UNITY_ANDROID && !UNITY_IOS) || UNITY_EDITOR) && NICE_VIBRATIONS_INPUTSYSTEM_INSTALLED && ENABLE_INPUT_SYSTEM && !NICE_VIBRATIONS_DISABLE_GAMEPAD_SUPPORT
|
||||
if (GetGamepad(gamepadID) != null)
|
||||
{
|
||||
GetGamepad(gamepadID).ResetHaptics();
|
||||
}
|
||||
|
||||
if (gamepadStates.ContainsKey(gamepadID))
|
||||
{
|
||||
var state = gamepadStates[gamepadID];
|
||||
state.rumbleTimer.Enabled = false;
|
||||
state.rumbleIndex = -1;
|
||||
state.rumblePositionMs = 0;
|
||||
state.playbackWatch.Stop();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void StopAll()
|
||||
{
|
||||
#if ((!UNITY_ANDROID && !UNITY_IOS) || UNITY_EDITOR) && NICE_VIBRATIONS_INPUTSYSTEM_INSTALLED && ENABLE_INPUT_SYSTEM && !NICE_VIBRATIONS_DISABLE_GAMEPAD_SUPPORT
|
||||
foreach (var kvp in gamepadStates)
|
||||
{
|
||||
Stop(kvp.Key);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops playback and unloads the currently loaded GamepadRumble from memory.
|
||||
/// </summary>
|
||||
public static void Unload()
|
||||
{
|
||||
#if ((!UNITY_ANDROID && !UNITY_IOS) || UNITY_EDITOR) && NICE_VIBRATIONS_INPUTSYSTEM_INSTALLED && ENABLE_INPUT_SYSTEM && !NICE_VIBRATIONS_DISABLE_GAMEPAD_SUPPORT
|
||||
Unload(currentGamepadID);
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void Unload(int gamepadID)
|
||||
{
|
||||
#if ((!UNITY_ANDROID && !UNITY_IOS) || UNITY_EDITOR) && NICE_VIBRATIONS_INPUTSYSTEM_INSTALLED && ENABLE_INPUT_SYSTEM && !NICE_VIBRATIONS_DISABLE_GAMEPAD_SUPPORT
|
||||
if (gamepadStates.ContainsKey(gamepadID))
|
||||
{
|
||||
var state = gamepadStates[gamepadID];
|
||||
state.loadedRumble.highFrequencyMotorSpeeds = null;
|
||||
state.loadedRumble.lowFrequencyMotorSpeeds = null;
|
||||
state.loadedRumble.durationsMs = null;
|
||||
state.rumbleLoaded = false;
|
||||
Stop(gamepadID);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void SetMotorSpeedMultiplication(float lowFreq, float highFreq)
|
||||
{
|
||||
#if ((!UNITY_ANDROID && !UNITY_IOS) || UNITY_EDITOR) && NICE_VIBRATIONS_INPUTSYSTEM_INSTALLED && ENABLE_INPUT_SYSTEM && !NICE_VIBRATIONS_DISABLE_GAMEPAD_SUPPORT
|
||||
SetMotorSpeedMultiplication(lowFreq, highFreq, currentGamepadID);
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void SetMotorSpeedMultiplication(float lowFreq, float highFreq, int gamepadID)
|
||||
{
|
||||
#if ((!UNITY_ANDROID && !UNITY_IOS) || UNITY_EDITOR) && NICE_VIBRATIONS_INPUTSYSTEM_INSTALLED && ENABLE_INPUT_SYSTEM && !NICE_VIBRATIONS_DISABLE_GAMEPAD_SUPPORT
|
||||
if (gamepadStates.ContainsKey(gamepadID))
|
||||
{
|
||||
var state = gamepadStates[gamepadID];
|
||||
state.lowFrequencyMotorSpeedMultiplication = lowFreq;
|
||||
state.highFrequencyMotorSpeedMultiplication = highFreq;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if ((!UNITY_ANDROID && !UNITY_IOS) || UNITY_EDITOR) && NICE_VIBRATIONS_INPUTSYSTEM_INSTALLED && ENABLE_INPUT_SYSTEM && !NICE_VIBRATIONS_DISABLE_GAMEPAD_SUPPORT
|
||||
private static bool IncreaseRumbleIndex(int gamepadID)
|
||||
{
|
||||
if (!gamepadStates.ContainsKey(gamepadID))
|
||||
return false;
|
||||
|
||||
var state = gamepadStates[gamepadID];
|
||||
state.rumblePositionMs += state.loadedRumble.durationsMs[state.rumbleIndex];
|
||||
state.rumbleIndex++;
|
||||
if (state.rumbleIndex == state.loadedRumble.durationsMs.Length)
|
||||
{
|
||||
Stop(gamepadID);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void ProcessNextRumble(int gamepadID)
|
||||
{
|
||||
if (!gamepadStates.ContainsKey(gamepadID))
|
||||
return;
|
||||
|
||||
var state = gamepadStates[gamepadID];
|
||||
|
||||
if (state.rumbleIndex == -1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (state.rumbleIndex == state.loadedRumble.durationsMs.Length)
|
||||
{
|
||||
Stop(gamepadID);
|
||||
return;
|
||||
}
|
||||
|
||||
// Figure out for how long the current rumble entry should be played (durationToWait).
|
||||
// Due to the timer not waiting for exactly the same amount of time that we requested,
|
||||
// there can be a bit of error that we need to compensate for. For example, if the timer
|
||||
// waited for 3ms longer than we requested, we play the next rumble entry for a 3ms
|
||||
// less to compensate for that.
|
||||
// In fact, Unity triggers the timer only once per frame, so at 30 FPS, the timer
|
||||
// resolution is 32ms. That means that the timing error can be bigger than the duration
|
||||
// of the whole rumble entry, and to compensate for that, the entire rumble entry needs
|
||||
// to be skipped. That's what the loop does: It skips rumble entries to compensate for
|
||||
// timer error.
|
||||
UnityEngine.Debug.Assert(state.loadedRumble.IsValid());
|
||||
UnityEngine.Debug.Assert(state.rumbleLoaded);
|
||||
UnityEngine.Debug.Assert(state.rumbleIndex >= 0 && state.rumbleIndex <= state.loadedRumble.durationsMs.Length);
|
||||
|
||||
long elapsed = state.playbackWatch.ElapsedMilliseconds;
|
||||
long durationToWait = 0;
|
||||
while (true)
|
||||
{
|
||||
long rumbleEntryDuration = state.loadedRumble.durationsMs[state.rumbleIndex];
|
||||
long error = elapsed - state.rumblePositionMs;
|
||||
durationToWait = rumbleEntryDuration - error;
|
||||
|
||||
// If durationToWait is <= 0, the current rumble entry needs to be skipped to
|
||||
// compensate for timer error. Otherwise break and play the current rumble entry.
|
||||
if (durationToWait > 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// If the end of the rumble has been reached, return, as playback has stopped.
|
||||
if (!IncreaseRumbleIndex(gamepadID))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
float lowFrequencySpeed = state.loadedRumble.lowFrequencyMotorSpeeds[state.rumbleIndex] *
|
||||
Mathf.Max(state.lowFrequencyMotorSpeedMultiplication, 0.0f);
|
||||
float highFrequencySpeed = state.loadedRumble.highFrequencyMotorSpeeds[state.rumbleIndex] *
|
||||
Mathf.Max(state.highFrequencyMotorSpeedMultiplication, 0.0f);
|
||||
|
||||
UnityEngine.InputSystem.Gamepad currentGamepad = GetGamepad(gamepadID);
|
||||
if (currentGamepad != null)
|
||||
{
|
||||
currentGamepad.SetMotorSpeeds(lowFrequencySpeed, highFrequencySpeed);
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Set up the timer to call ProcessNextRumble() again with the next rumble entry, after
|
||||
// the duration of the current rumble entry.
|
||||
state.rumblePositionMs += state.loadedRumble.durationsMs[state.rumbleIndex];
|
||||
state.rumbleIndex++;
|
||||
state.rumbleTimer.Interval = durationToWait;
|
||||
state.rumbleTimer.AutoReset = false;
|
||||
state.rumbleTimer.Enabled = true;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
18
Assets/External/Feel/NiceVibrations/Scripts/Components/Gamepad.cs.meta
vendored
Normal file
18
Assets/External/Feel/NiceVibrations/Scripts/Components/Gamepad.cs.meta
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ef20247bd5f04449293bb8ea3982f3ac
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 183370
|
||||
packageName: Feel
|
||||
packageVersion: 5.9.1
|
||||
assetPath: Assets/Feel/NiceVibrations/Scripts/Components/Gamepad.cs
|
||||
uploadId: 830868
|
||||
35
Assets/External/Feel/NiceVibrations/Scripts/Components/HapticClip.cs
vendored
Normal file
35
Assets/External/Feel/NiceVibrations/Scripts/Components/HapticClip.cs
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace Lofelt.NiceVibrations
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an imported haptic clip asset.
|
||||
/// </summary>
|
||||
///
|
||||
/// HapticClip contains the data of a haptic clip asset imported from a <c>.haptic</c> file,
|
||||
/// in a format suitable for playing it back at runtime.
|
||||
/// A HapticClip is created by <c>HapticImporter</c> when importing a haptic clip asset
|
||||
/// in the Unity editor, and can be played back at runtime with e.g. HapticSource or
|
||||
/// HapticController::Play().
|
||||
///
|
||||
/// It contains two representations:
|
||||
/// - JSON, used for playback on iOS and Android
|
||||
/// - GamepadRumble, used for playback on gamepads with the GamepadRumbler class
|
||||
public class HapticClip : ScriptableObject
|
||||
{
|
||||
/// <summary>
|
||||
/// The JSON representation of the haptic clip, stored as a byte array encoded in UTF-8,
|
||||
/// without a null terminator
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
public byte[] json;
|
||||
|
||||
/// <summary>
|
||||
/// The haptic clip represented as a GamepadRumble struct
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
public GamepadRumble gamepadRumble;
|
||||
}
|
||||
}
|
||||
18
Assets/External/Feel/NiceVibrations/Scripts/Components/HapticClip.cs.meta
vendored
Normal file
18
Assets/External/Feel/NiceVibrations/Scripts/Components/HapticClip.cs.meta
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: df8d044f677634e749812dc987300584
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 183370
|
||||
packageName: Feel
|
||||
packageVersion: 5.9.1
|
||||
assetPath: Assets/Feel/NiceVibrations/Scripts/Components/HapticClip.cs
|
||||
uploadId: 830868
|
||||
570
Assets/External/Feel/NiceVibrations/Scripts/Components/HapticController.cs
vendored
Normal file
570
Assets/External/Feel/NiceVibrations/Scripts/Components/HapticController.cs
vendored
Normal file
@@ -0,0 +1,570 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Timers;
|
||||
|
||||
#if (UNITY_ANDROID && !UNITY_EDITOR)
|
||||
using System.Text;
|
||||
#elif (UNITY_IOS && !UNITY_EDITOR)
|
||||
using UnityEngine.iOS;
|
||||
#endif
|
||||
|
||||
namespace Lofelt.NiceVibrations
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides haptic playback functionality.
|
||||
/// </summary>
|
||||
///
|
||||
/// HapticController allows you to load and play <c>.haptic</c> clips, and
|
||||
/// provides various ways to control playback, such as seeking, looping and
|
||||
/// amplitude/frequency modulation.
|
||||
///
|
||||
/// If you need a <c>MonoBehaviour</c> API, use HapticSource and
|
||||
/// HapticReceiver instead.
|
||||
///
|
||||
/// On iOS and Android, the device is vibrated, using <c>LofeltHaptics</c>.
|
||||
/// On any platform, when a gamepad is connected, that gamepad is vibrated,
|
||||
/// using GamepadRumbler.
|
||||
///
|
||||
/// Gamepads are vibrated automatically when HapticController detects that a
|
||||
/// gamepad is connected, no special code is needed to support gamepads.
|
||||
/// Gamepads only support Load(), Play(), Stop(), \ref clipLevel and \ref
|
||||
/// outputLevel. Other features like Seek(), Loop() and \ref clipFrequencyShift
|
||||
/// will have no effect on gamepads.
|
||||
///
|
||||
/// None of the methods here are thread-safe and should only be called from
|
||||
/// the main (Unity) thread. Calling these methods from a secondary thread can
|
||||
/// cause undefined behaviour and memory leaks.
|
||||
public static class HapticController
|
||||
{
|
||||
static bool lofeltHapticsInitalized = false;
|
||||
|
||||
// Timer used to call HandleFinishedPlayback() when playback is complete
|
||||
static Timer playbackFinishedTimer = new Timer();
|
||||
|
||||
// Duration of the loaded haptic clip, in seconds
|
||||
static float clipLoadedDurationSecs = 0.0f;
|
||||
|
||||
// Whether Load() has been called before
|
||||
static bool clipLoaded = false;
|
||||
|
||||
// The value of the last call to seek()
|
||||
static float lastSeekTime = 0.0f;
|
||||
|
||||
// Flag indicating if the device supports playing back .haptic clips
|
||||
static bool deviceMeetsAdvancedRequirements = false;
|
||||
|
||||
// Flag indicating if the user enabled playback looping.
|
||||
// This does not necessarily mean that the currently active playback is looping, for
|
||||
// example gamepads don't support looping.
|
||||
static bool isLoopingEnabledByUser = false;
|
||||
|
||||
// Flag indicating if the currently active playback is looping
|
||||
static bool isPlaybackLooping = false;
|
||||
|
||||
static HapticPatterns.PresetType _fallbackPreset = HapticPatterns.PresetType.None;
|
||||
|
||||
/// <summary>
|
||||
/// The haptic preset to be played when it's not possible to play a haptic clip
|
||||
/// </summary>
|
||||
public static HapticPatterns.PresetType fallbackPreset
|
||||
{
|
||||
get { return _fallbackPreset; }
|
||||
set { _fallbackPreset = value; }
|
||||
}
|
||||
|
||||
internal static bool _hapticsEnabled = true;
|
||||
|
||||
/// <summary>
|
||||
/// Property to enable and disable global haptic playback
|
||||
/// </summary>
|
||||
public static bool hapticsEnabled
|
||||
{
|
||||
get { return _hapticsEnabled; }
|
||||
set
|
||||
{
|
||||
if (_hapticsEnabled)
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
_hapticsEnabled = value;
|
||||
}
|
||||
}
|
||||
|
||||
internal static float _outputLevel = 1.0f;
|
||||
|
||||
/// <summary>
|
||||
/// The overall haptic output level
|
||||
/// </summary>
|
||||
///
|
||||
/// It can be interpreted as the "volume control" for haptic playback.
|
||||
/// Output level is applied in combination with \ref clipLevel to the currently playing haptic clip.
|
||||
/// The combination of these two levels and the amplitude within the loaded haptic at a given moment
|
||||
/// in time determines the strength of the vibration felt on the device. \ref outputLevel is best used
|
||||
/// to increase or decrease the overall haptic level in a game.
|
||||
///
|
||||
/// As output level pertains to all clips, unlike \ref clipLevel, it persists when a new clip is loaded.
|
||||
///
|
||||
/// \ref outputLevel is a multiplication factor, it is <i>not</i> a dB value. The factor needs to be
|
||||
/// 0 or greater.
|
||||
///
|
||||
/// The combination of \ref outputLevel and \ref clipLevel can result in a gain (for factors
|
||||
/// greater than 1.0) or an attenuation (for factors less than 1.0) to the clip. If the
|
||||
/// combination of \ref outputLevel, \ref clipLevel and the amplitude within the loaded haptic
|
||||
/// is greater than 1.0, it is clipped to 1.0. Hard clipping is performed, no limiter is used.
|
||||
///
|
||||
/// On Android, an adjustment to \ref outputLevel will take effect in the next call to Play().
|
||||
/// On iOS, it will take effect right away.
|
||||
[System.ComponentModel.DefaultValue(1.0f)]
|
||||
public static float outputLevel
|
||||
{
|
||||
get { return _outputLevel; }
|
||||
set
|
||||
{
|
||||
_outputLevel = value;
|
||||
|
||||
ApplyLevelsToLofeltHaptics();
|
||||
ApplyLevelsToGamepadRumbler();
|
||||
}
|
||||
}
|
||||
|
||||
internal static float _clipLevel = 1.0f;
|
||||
|
||||
/// <summary>
|
||||
/// The level of the loaded clip
|
||||
/// </summary>
|
||||
///
|
||||
/// Clip level is applied in combination with \ref outputLevel, to the
|
||||
/// currently playing haptic clip. The combination of these two levels and the amplitude within the loaded
|
||||
/// haptic at a given moment in time determines the strength of the vibration felt on the device.
|
||||
/// \ref clipLevel is best used to adjust the level of a single clip based on game state.
|
||||
///
|
||||
/// As clip level is specific to an individual clip, unlike \ref outputLevel, it resets to
|
||||
/// 1.0 when a new clip is loaded.
|
||||
///
|
||||
/// \ref clipLevel is a multiplication factor, it is <i>not</i> a dB value. The factor needs to be
|
||||
/// 0 or greater.
|
||||
///
|
||||
/// The combination of \ref outputLevel and \ref clipLevel can result in a gain (for factors
|
||||
/// greater than 1.0) or an attenuation (for factors less than 1.0) to the clip.
|
||||
///
|
||||
/// If the combination of \ref outputLevel, \ref clipLevel and the amplitude within the loaded
|
||||
/// haptic is greater than 1.0, it is clipped to 1.0. Hard clipping is performed, no limiter is used.
|
||||
///
|
||||
/// The clip needs to be loaded with Load() before adjusting \ref clipLevel. Loading a clip
|
||||
/// resets \ref clipLevel back to the default of 1.0.
|
||||
///
|
||||
/// On Android, an adjustment to \ref clipLevel will take effect in the next call to Play(). On iOS,
|
||||
/// it will take effect right away.
|
||||
///
|
||||
/// On Android, setting the clip level should be done before calling \ref Seek(), since
|
||||
/// setting a clip level ignores the sought value.
|
||||
///
|
||||
[System.ComponentModel.DefaultValue(1.0f)]
|
||||
public static float clipLevel
|
||||
{
|
||||
get { return _clipLevel; }
|
||||
set
|
||||
{
|
||||
_clipLevel = value;
|
||||
|
||||
ApplyLevelsToLofeltHaptics();
|
||||
ApplyLevelsToGamepadRumbler();
|
||||
}
|
||||
}
|
||||
|
||||
/// Action that is invoked when Load() is called
|
||||
public static Action LoadedClipChanged;
|
||||
|
||||
/// Action that is invoked when Play() is called
|
||||
public static Action PlaybackStarted;
|
||||
|
||||
/// <summary>
|
||||
/// Action that is invoked when the playback has finished
|
||||
/// </summary>
|
||||
///
|
||||
/// This happens either when Stop() is explicitly called, or when a non-looping
|
||||
/// clip has finished playing.
|
||||
///
|
||||
/// This can be invoked spuriously, even if no haptics are currently playing, for example
|
||||
/// if Stop() is called multiple times in a row.
|
||||
public static Action PlaybackStopped;
|
||||
|
||||
// Applies the current clip level and output level as the amplitude multiplication to
|
||||
// LofeltHaptics
|
||||
private static void ApplyLevelsToLofeltHaptics()
|
||||
{
|
||||
if (Init())
|
||||
{
|
||||
LofeltHaptics.SetAmplitudeMultiplication(_outputLevel * _clipLevel);
|
||||
}
|
||||
}
|
||||
|
||||
// Applies the current clip level and output level as the motor speed multiplication to
|
||||
// GamepadRumbler
|
||||
private static void ApplyLevelsToGamepadRumbler()
|
||||
{
|
||||
#if ((!UNITY_ANDROID && !UNITY_IOS) || UNITY_EDITOR) && NICE_VIBRATIONS_INPUTSYSTEM_INSTALLED && ENABLE_INPUT_SYSTEM && !NICE_VIBRATIONS_DISABLE_GAMEPAD_SUPPORT
|
||||
GamepadRumbler.SetMotorSpeedMultiplication(_outputLevel * _clipLevel, _outputLevel * _clipLevel);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes HapticController.
|
||||
/// </summary>
|
||||
///
|
||||
/// Calling this method multiple times has no effect and is safe.
|
||||
///
|
||||
/// You do not need to call this method, HapticController automatically calls this
|
||||
/// method before any operation that needs initialization, such as Play().
|
||||
/// However it can be beneficial to call this early during startup, so the initialization
|
||||
/// time is spent at startup instead of when the first haptic is triggered during gameplay.
|
||||
/// If you have a HapticReceiver in your scene, it takes care of calling
|
||||
/// Init() during startup for you.
|
||||
///
|
||||
/// Do not call this method from a static constructor. Unity often invokes static
|
||||
/// constructors from a different thread, for example during deserialization. The
|
||||
/// initialization code is not thread-safe. This is the reason this method is not called
|
||||
/// from the static constructor of HapticController or HapticReceiver.
|
||||
///
|
||||
/// <returns>Whether the device supports the minimum requirements to play haptics</returns>
|
||||
public static bool Init()
|
||||
{
|
||||
if (!lofeltHapticsInitalized)
|
||||
{
|
||||
lofeltHapticsInitalized = true;
|
||||
|
||||
var syncContext = System.Threading.SynchronizationContext.Current;
|
||||
playbackFinishedTimer.Elapsed += (object obj, System.Timers.ElapsedEventArgs args) =>
|
||||
{
|
||||
// Timer elapsed events are called from a separate thread, so use
|
||||
// SynchronizationContext to handle it in the main thread.
|
||||
syncContext.Post(_ =>
|
||||
{
|
||||
HandleFinishedPlayback();
|
||||
}, null);
|
||||
};
|
||||
|
||||
if (DeviceCapabilities.isVersionSupported)
|
||||
{
|
||||
LofeltHaptics.Initialize();
|
||||
DeviceCapabilities.Init();
|
||||
deviceMeetsAdvancedRequirements = DeviceCapabilities.meetsAdvancedRequirements;
|
||||
}
|
||||
|
||||
GamepadRumbler.Init();
|
||||
}
|
||||
return deviceMeetsAdvancedRequirements;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads a haptic clip given in JSON format for later playback.
|
||||
/// </summary>
|
||||
///
|
||||
/// This overload of Load() is useful in cases there is only the JSON data of a haptic clip
|
||||
/// available. Due to only having the JSON data and no GamepadRumble, gamepad playback is
|
||||
/// not supported with this overload.
|
||||
///
|
||||
/// <param name="data">The haptic clip, which is the content of the
|
||||
/// <c>.haptic</c> file, a UTF-8 encoded JSON string without a null
|
||||
/// terminator</param>
|
||||
public static void Load(byte[] data)
|
||||
{
|
||||
#if ((!UNITY_ANDROID && !UNITY_IOS) || UNITY_EDITOR) && NICE_VIBRATIONS_INPUTSYSTEM_INSTALLED && ENABLE_INPUT_SYSTEM && !NICE_VIBRATIONS_DISABLE_GAMEPAD_SUPPORT
|
||||
GamepadRumbler.Unload(GamepadRumbler.GetCurrentGamepadID());
|
||||
#endif
|
||||
lastSeekTime = 0.0f;
|
||||
clipLoaded = true;
|
||||
clipLoadedDurationSecs = 0.0f;
|
||||
if (Init())
|
||||
{
|
||||
LofeltHaptics.Load(data);
|
||||
}
|
||||
clipLevel = 1.0f;
|
||||
LoadedClipChanged?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the given HapticClip for later playback.
|
||||
/// </summary>
|
||||
///
|
||||
/// This is the standard way to load a haptic clip, while the other overloads of Load()
|
||||
/// are for more specialized cases.
|
||||
///
|
||||
/// At the moment only one clip can be loaded at a time.
|
||||
///
|
||||
/// <param name="clip">The HapticClip to be loaded</param>
|
||||
public static void Load(HapticClip clip)
|
||||
{
|
||||
Load(clip.json, clip.gamepadRumble);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the haptic clip given as JSON and GamepadRumble for later playback.
|
||||
/// </summary>
|
||||
///
|
||||
/// This is an overload of Load() that is useful when a HapticClip is not available, and
|
||||
/// both the JSON and GamepadRumble are. One such case is generating both dynamically at
|
||||
/// runtime.
|
||||
///
|
||||
/// <param name="json">The haptic clip, which is the content of the <c>.haptic</c> file,
|
||||
/// a UTF-8 encoded JSON string without a null terminator</param>
|
||||
/// <param name="rumble">The GamepadRumble representation of the haptic clip</param>
|
||||
public static void Load(byte[] json, GamepadRumble rumble)
|
||||
{
|
||||
Load(json);
|
||||
#if ((!UNITY_ANDROID && !UNITY_IOS) || UNITY_EDITOR) && NICE_VIBRATIONS_INPUTSYSTEM_INSTALLED && ENABLE_INPUT_SYSTEM && !NICE_VIBRATIONS_DISABLE_GAMEPAD_SUPPORT
|
||||
GamepadRumbler.Load(rumble, GamepadRumbler.GetCurrentGamepadID());
|
||||
#endif
|
||||
// GamepadRumbler.Load() resets the motor speed multiplication to 1.0, so the levels
|
||||
// need to be applied here again
|
||||
ApplyLevelsToGamepadRumbler();
|
||||
|
||||
// Load() only sets the correct clip duration on iOS and Android, and sets it to 0.0
|
||||
// on other platforms. For the other platforms, set a clip duration based on the
|
||||
// GamepadRumble here.
|
||||
if (clipLoadedDurationSecs == 0.0f && rumble.IsValid())
|
||||
{
|
||||
clipLoadedDurationSecs = rumble.totalDurationMs / 1000.0f;
|
||||
}
|
||||
}
|
||||
|
||||
static void HandleFinishedPlayback()
|
||||
{
|
||||
lastSeekTime = 0.0f;
|
||||
isPlaybackLooping = false;
|
||||
playbackFinishedTimer.Enabled = false;
|
||||
PlaybackStopped?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plays the haptic clip that was previously loaded with Load().
|
||||
/// </summary>
|
||||
///
|
||||
/// If <c>Loop(true)</c> was called previously, the playback will be repeated
|
||||
/// until Stop() is called. Otherwise the haptic clip will only play once.
|
||||
///
|
||||
/// In case the device does not meet the requirements to play <c>.haptic</c> clips, this
|
||||
/// function will call HapticPatterns.PlayPreset() with the \ref fallbackPreset set. In this
|
||||
/// case, functionality like seeking, looping and runtime modulation won't do anything as
|
||||
/// they aren't available for haptic presets.
|
||||
public static void Play()
|
||||
{
|
||||
if (!_hapticsEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float remainingPlayDuration = 0.0f;
|
||||
bool canLoop = false;
|
||||
if (GamepadRumbler.CanPlay())
|
||||
{
|
||||
remainingPlayDuration = clipLoadedDurationSecs;
|
||||
GamepadRumbler.Play();
|
||||
}
|
||||
else if (Init())
|
||||
{
|
||||
remainingPlayDuration = Mathf.Max(clipLoadedDurationSecs - lastSeekTime, 0.0f);
|
||||
canLoop = DeviceCapabilities.canLoop;
|
||||
LofeltHaptics.Play();
|
||||
}
|
||||
else if (DeviceCapabilities.isVersionSupported)
|
||||
{
|
||||
remainingPlayDuration = HapticPatterns.GetPresetDuration(fallbackPreset);
|
||||
HapticPatterns.PlayPreset(fallbackPreset);
|
||||
}
|
||||
|
||||
isPlaybackLooping = isLoopingEnabledByUser && canLoop;
|
||||
PlaybackStarted?.Invoke();
|
||||
|
||||
//
|
||||
// Call HandleFinishedPlayback() after the playback finishes
|
||||
//
|
||||
if (remainingPlayDuration > 0.0f)
|
||||
{
|
||||
playbackFinishedTimer.Interval = remainingPlayDuration * 1000;
|
||||
playbackFinishedTimer.AutoReset = false;
|
||||
playbackFinishedTimer.Enabled = !isPlaybackLooping;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Setting playbackFinishedTimer.Interval needs an interval > 0, otherwise it will
|
||||
// throw an exception.
|
||||
// Even if the remaining play duration is 0, we still want to trigger everything
|
||||
// that happens in HandleFinishedPlayback().
|
||||
// A playback duration of 0 happens in the Unity editor, when loading the clip
|
||||
// failed or when seeking to the end of a clip.
|
||||
HandleFinishedPlayback();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Loads and plays the HapticClip given as an argument.
|
||||
/// </summary>
|
||||
///
|
||||
/// <param name="clip">The HapticClip to be played</param>
|
||||
public static void Play(HapticClip clip)
|
||||
{
|
||||
Load(clip);
|
||||
Play();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops haptic playback
|
||||
///
|
||||
/// </summary>
|
||||
public static void Stop()
|
||||
{
|
||||
|
||||
if (Init())
|
||||
{
|
||||
LofeltHaptics.Stop();
|
||||
}
|
||||
else
|
||||
{
|
||||
LofeltHaptics.StopPattern();
|
||||
}
|
||||
GamepadRumbler.Stop();
|
||||
HandleFinishedPlayback();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Jumps to a time position in the haptic clip.
|
||||
/// </summary>
|
||||
///
|
||||
/// The playback will always be stopped when this function is called.
|
||||
/// This is to match the behavior between iOS and Android, since Android needs to
|
||||
/// restart playback for seek to have effect.
|
||||
///
|
||||
/// If seeking beyond the end of the clip, Play() will not reproduce any haptics.
|
||||
/// Seeking to a negative position will seek to the beginning of the clip.
|
||||
///
|
||||
/// <param name="time">The new position within the clip, as seconds from the beginning
|
||||
/// of the clip</param>
|
||||
public static void Seek(float time)
|
||||
{
|
||||
if (Init())
|
||||
{
|
||||
LofeltHaptics.Stop();
|
||||
LofeltHaptics.Seek(time);
|
||||
}
|
||||
GamepadRumbler.Stop();
|
||||
lastSeekTime = time;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the given shift to the frequency of every breakpoint in the clip, including the
|
||||
/// emphasis.
|
||||
/// </summary>
|
||||
///
|
||||
/// In other words, this property shifts all frequencies of the clip. The frequency shift is
|
||||
/// added to each frequency value and needs to be between -1.0 and 1.0. If the resulting
|
||||
/// frequency of a breakpoint is smaller than 0.0 or greater than 1.0, it is clipped to that
|
||||
/// range. The frequency is clipped hard, no limiter is used.
|
||||
///
|
||||
/// The clip needs to be loaded with Load() first. Loading a clip resets the shift back
|
||||
/// to the default of 0.0.
|
||||
///
|
||||
/// Setting the frequency shift has no effect on Android; it only works on iOS.
|
||||
///
|
||||
/// A call to this property will change the frequency shift of a currently playing clip
|
||||
/// right away. If no clip is playing, the shift is applied in the next call to
|
||||
/// Play().
|
||||
[System.ComponentModel.DefaultValue(0.0f)]
|
||||
public static float clipFrequencyShift
|
||||
{
|
||||
set
|
||||
{
|
||||
if (Init())
|
||||
{
|
||||
LofeltHaptics.SetFrequencyShift(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the playback of a haptic clip to loop.
|
||||
/// </summary>
|
||||
///
|
||||
/// On Android, calling this will always put the playback position at the start of the clip.
|
||||
/// Also, it will only have an effect when Play() is called again.
|
||||
///
|
||||
/// On iOS, if a clip is already playing, calling this will leave the playback position as
|
||||
/// it is and repeat when it reaches the end. No need to call Play() again for
|
||||
/// changes to take effect.
|
||||
///
|
||||
/// <param name="enabled">If the value is <c>true</c>, looping will be enabled which results
|
||||
/// in repeating the playback until Stop() is called; if <c>false</c>, the haptic
|
||||
/// clip will only be played once.</param>
|
||||
public static void Loop(bool enabled)
|
||||
{
|
||||
if (Init())
|
||||
{
|
||||
LofeltHaptics.Loop(enabled);
|
||||
}
|
||||
isLoopingEnabledByUser = enabled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the loaded haptic clip is playing.
|
||||
/// </summary>
|
||||
///
|
||||
/// <returns>Whether the loaded clip is playing</returns>
|
||||
public static bool IsPlaying()
|
||||
{
|
||||
if (playbackFinishedTimer.Enabled)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return isPlaybackLooping;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops playback and resets the playback state.
|
||||
/// </summary>
|
||||
///
|
||||
/// Seek position, clip level, clip frequency shift and loop are reset to the
|
||||
/// default values.
|
||||
/// The currently loaded clip stays loaded.
|
||||
/// \ref hapticsEnabled and \ref outputLevel are not reset.
|
||||
public static void Reset()
|
||||
{
|
||||
if (clipLoaded)
|
||||
{
|
||||
Seek(0.0f);
|
||||
Stop();
|
||||
clipLevel = 1.0f;
|
||||
clipFrequencyShift = 0.0f;
|
||||
Loop(false);
|
||||
}
|
||||
fallbackPreset = HapticPatterns.PresetType.None;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes an application focus change event.
|
||||
/// </summary>
|
||||
///
|
||||
/// If you have a HapticReceiver in your scene, the HapticReceiver
|
||||
/// will take care of calling this method when needed. Otherwise it is your
|
||||
/// responsibility to do so.
|
||||
///
|
||||
/// When the application loses the focus, playback is stopped.
|
||||
///
|
||||
/// <param name="hasFocus">Whether the application now has focus</param>
|
||||
public static void ProcessApplicationFocus(bool hasFocus)
|
||||
{
|
||||
if (!hasFocus)
|
||||
{
|
||||
// While LofeltHaptics stops playback when the app loses focus,
|
||||
// calling Stop() here handles additional things such as invoking
|
||||
// the PlaybackStopped Action.
|
||||
Stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
18
Assets/External/Feel/NiceVibrations/Scripts/Components/HapticController.cs.meta
vendored
Normal file
18
Assets/External/Feel/NiceVibrations/Scripts/Components/HapticController.cs.meta
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eea19a9647af946678dbcea38129dd98
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 183370
|
||||
packageName: Feel
|
||||
packageVersion: 5.9.1
|
||||
assetPath: Assets/Feel/NiceVibrations/Scripts/Components/HapticController.cs
|
||||
uploadId: 830868
|
||||
514
Assets/External/Feel/NiceVibrations/Scripts/Components/HapticPatterns.cs
vendored
Normal file
514
Assets/External/Feel/NiceVibrations/Scripts/Components/HapticPatterns.cs
vendored
Normal file
@@ -0,0 +1,514 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Lofelt.NiceVibrations
|
||||
{
|
||||
/// <summary>
|
||||
/// A collection of methods to play simple haptic patterns.
|
||||
/// </summary>
|
||||
///
|
||||
/// Each of the methods here load and play a simple haptic clip or a
|
||||
/// haptic pattern, depending on the device capabilities.
|
||||
///
|
||||
/// None of the methods here are thread-safe and should only be called from
|
||||
/// the main (Unity) thread. Calling these methods from a secondary thread can
|
||||
/// cause undefined behaviour and memory leaks.
|
||||
///
|
||||
/// After playback has finished, the loaded clips in this class will remain
|
||||
/// loaded in HapticController.
|
||||
|
||||
public static class HapticPatterns
|
||||
{
|
||||
static String emphasisTemplate;
|
||||
static String constantTemplate;
|
||||
static NumberFormatInfo numberFormat;
|
||||
static private float[] constantPatternTime = new float[] { 0.0f, 0.0f };
|
||||
|
||||
/// <summary>
|
||||
/// Enum that represents all the types of haptic presets available
|
||||
/// </summary>
|
||||
public enum PresetType
|
||||
{
|
||||
Selection = 0,
|
||||
Success = 1,
|
||||
Warning = 2,
|
||||
Failure = 3,
|
||||
LightImpact = 4,
|
||||
MediumImpact = 5,
|
||||
HeavyImpact = 6,
|
||||
RigidImpact = 7,
|
||||
SoftImpact = 8,
|
||||
None = -1
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Structure that represents a haptic pattern with amplitude variations.
|
||||
/// </summary>
|
||||
///
|
||||
/// \ref time values have be incremental to be compatible with Preset.
|
||||
struct Pattern
|
||||
{
|
||||
public float[] time;
|
||||
public float[] amplitude;
|
||||
|
||||
static String clipJsonTemplate;
|
||||
|
||||
static Pattern()
|
||||
{
|
||||
clipJsonTemplate = (Resources.Load("nv-pattern-template") as TextAsset).text;
|
||||
}
|
||||
|
||||
public Pattern(float[] time, float[] amplitude)
|
||||
{
|
||||
this.time = time;
|
||||
this.amplitude = amplitude;
|
||||
}
|
||||
|
||||
// Converts a Pattern to a GamepadRumble
|
||||
//
|
||||
// Each pair of adjacent entries in the Pattern create one entry in the GamepadRumble.
|
||||
public GamepadRumble ToRumble()
|
||||
{
|
||||
GamepadRumble result = new GamepadRumble();
|
||||
if (time.Length <= 1)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
Debug.Assert(time.Length == amplitude.Length);
|
||||
|
||||
// The first pattern entry needs to have a time of 0.0 for the algorithm below to work
|
||||
Debug.Assert(time[0] == 0.0f);
|
||||
|
||||
int rumbleCount = time.Length - 1;
|
||||
result.durationsMs = new int[rumbleCount];
|
||||
result.lowFrequencyMotorSpeeds = new float[rumbleCount];
|
||||
result.highFrequencyMotorSpeeds = new float[rumbleCount];
|
||||
result.totalDurationMs = 0;
|
||||
for (int rumbleIndex = 0; rumbleIndex < rumbleCount; rumbleIndex++)
|
||||
{
|
||||
int patternDurationMs = (int)((time[rumbleIndex + 1] - time[rumbleIndex]) * 1000.0f);
|
||||
result.durationsMs[rumbleIndex] = patternDurationMs;
|
||||
result.lowFrequencyMotorSpeeds[rumbleIndex] = amplitude[rumbleIndex];
|
||||
result.highFrequencyMotorSpeeds[rumbleIndex] = amplitude[rumbleIndex];
|
||||
result.totalDurationMs += result.durationsMs[rumbleIndex];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Converts a Pattern to a haptic clip JSON string.
|
||||
public String ToClip()
|
||||
{
|
||||
if (clipJsonTemplate == null)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
String amplitudeEnvelope = "";
|
||||
for (int i = 0; i < time.Length; i++)
|
||||
{
|
||||
float clampedAmplitude = Mathf.Clamp(amplitude[i], 0.0f, 1.0f);
|
||||
amplitudeEnvelope += "{ \"time\":" + time[i].ToString(numberFormat) + "," +
|
||||
"\"amplitude\":" + clampedAmplitude.ToString(numberFormat) + "}";
|
||||
|
||||
// Don't add a comma to the JSON data if we're at the end of the envelope
|
||||
if (i + 1 < time.Length)
|
||||
{
|
||||
amplitudeEnvelope += ",";
|
||||
}
|
||||
}
|
||||
|
||||
return clipJsonTemplate.Replace("{amplitude-envelope}", amplitudeEnvelope);
|
||||
}
|
||||
}
|
||||
|
||||
// A haptic preset in its different representations
|
||||
//
|
||||
// A Preset has four different representations, as there are four different playback methods.
|
||||
// Each representation is created at construction time, so that playing a
|
||||
// Preset has no further conversion cost at playback time.
|
||||
internal struct Preset
|
||||
{
|
||||
// For playback on iOS, using system haptics
|
||||
public PresetType type;
|
||||
|
||||
// For playback on Android devices without amplitude control
|
||||
public float[] maximumAmplitudePattern;
|
||||
|
||||
// For playback on Android devices with amplitude control
|
||||
public byte[] jsonClip;
|
||||
|
||||
// For playback on gamepads
|
||||
#if ((!UNITY_ANDROID && !UNITY_IOS) || UNITY_EDITOR) && NICE_VIBRATIONS_INPUTSYSTEM_INSTALLED && ENABLE_INPUT_SYSTEM && !NICE_VIBRATIONS_DISABLE_GAMEPAD_SUPPORT
|
||||
public GamepadRumble gamepadRumble;
|
||||
#endif
|
||||
|
||||
public Preset(PresetType type, float[] time, float[] amplitude)
|
||||
{
|
||||
Debug.Assert(type != PresetType.None);
|
||||
Pattern pattern = new Pattern(time, amplitude);
|
||||
this.type = type;
|
||||
this.maximumAmplitudePattern = pattern.time;
|
||||
#if ((!UNITY_ANDROID && !UNITY_IOS) || UNITY_EDITOR) && NICE_VIBRATIONS_INPUTSYSTEM_INSTALLED && ENABLE_INPUT_SYSTEM && !NICE_VIBRATIONS_DISABLE_GAMEPAD_SUPPORT
|
||||
this.gamepadRumble = pattern.ToRumble();
|
||||
#endif
|
||||
this.jsonClip = System.Text.Encoding.UTF8.GetBytes(pattern.ToClip());
|
||||
}
|
||||
|
||||
public float GetDuration()
|
||||
{
|
||||
if (maximumAmplitudePattern.Length > 0)
|
||||
{
|
||||
return maximumAmplitudePattern[maximumAmplitudePattern.Length - 1];
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Predefined Preset that represents a "Selection" haptic preset
|
||||
/// </summary>
|
||||
internal static Preset Selection;
|
||||
|
||||
/// <summary>
|
||||
/// Predefined Preset that represents a "Light" haptic preset
|
||||
/// </summary>
|
||||
internal static Preset Light;
|
||||
|
||||
/// <summary>
|
||||
/// Predefined Preset that represents a "Medium" haptic preset
|
||||
/// </summary>
|
||||
internal static Preset Medium;
|
||||
|
||||
/// <summary>
|
||||
/// Predefined Preset that represents a "Heavy" haptic preset
|
||||
/// </summary>
|
||||
internal static Preset Heavy;
|
||||
|
||||
/// <summary>
|
||||
/// Predefined Preset that represents a "Rigid" haptic preset
|
||||
/// </summary>
|
||||
internal static Preset Rigid;
|
||||
|
||||
/// <summary>
|
||||
/// Predefined Preset that represents a "Soft" haptic preset
|
||||
/// </summary>
|
||||
internal static Preset Soft;
|
||||
|
||||
/// <summary>
|
||||
/// Predefined Preset that represents a "Success" haptic preset
|
||||
/// </summary>
|
||||
internal static Preset Success;
|
||||
|
||||
/// <summary>
|
||||
/// Predefined Preset that represents a "Failure" haptic preset
|
||||
/// </summary>
|
||||
internal static Preset Failure;
|
||||
|
||||
/// <summary>
|
||||
/// Predefined Preset that represents a "Warning" haptic preset
|
||||
/// </summary>
|
||||
internal static Preset Warning;
|
||||
|
||||
static HapticPatterns()
|
||||
{
|
||||
emphasisTemplate = (Resources.Load("nv-emphasis-template") as TextAsset).text;
|
||||
constantTemplate = (Resources.Load("nv-constant-template") as TextAsset).text;
|
||||
|
||||
numberFormat = new NumberFormatInfo();
|
||||
numberFormat.NumberDecimalSeparator = ".";
|
||||
|
||||
// Initialize presets after setting the number format, so that the correct decimal
|
||||
// separator is used when building the JSON representation.
|
||||
|
||||
Selection = new Preset(PresetType.Selection, new float[] { 0.0f, 0.04f },
|
||||
new float[] { 0.471f, 0.471f });
|
||||
|
||||
Light = new Preset(PresetType.LightImpact, new float[] { 0.000f, 0.040f },
|
||||
new float[] { 0.156f, 0.156f });
|
||||
|
||||
Medium = new Preset(PresetType.MediumImpact, new float[] { 0.000f, 0.080f },
|
||||
new float[] { 0.471f, 0.471f });
|
||||
|
||||
Heavy = new Preset(PresetType.HeavyImpact, new float[] { 0.0f, 0.16f },
|
||||
new float[] { 1.0f, 1.00f });
|
||||
|
||||
Rigid = new Preset(PresetType.RigidImpact, new float[] { 0.0f, 0.04f },
|
||||
new float[] { 1.0f, 1.00f });
|
||||
|
||||
Soft = new Preset(PresetType.SoftImpact, new float[] { 0.000f, 0.160f },
|
||||
new float[] { 0.156f, 0.156f });
|
||||
|
||||
Success = new Preset(PresetType.Success, new float[] { 0.0f, 0.040f, 0.080f, 0.240f },
|
||||
new float[] { 0.0f, 0.157f, 0.000f, 1.000f });
|
||||
|
||||
Failure = new Preset(PresetType.Failure,
|
||||
new float[] { 0.0f, 0.080f, 0.120f, 0.200f, 0.240f, 0.400f, 0.440f, 0.480f },
|
||||
new float[] { 0.0f, 0.470f, 0.000f, 0.470f, 0.000f, 1.000f, 0.000f, 0.157f });
|
||||
|
||||
Warning = new Preset(PresetType.Warning, new float[] { 0.0f, 0.120f, 0.240f, 0.280f },
|
||||
new float[] { 0.0f, 1.000f, 0.000f, 0.470f });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plays a single emphasis point.
|
||||
/// </summary>
|
||||
///
|
||||
/// Plays a haptic clip that consists only of one breakpoint with emphasis.
|
||||
/// On iOS, this translates to a transient, and on Android and gamepads to
|
||||
/// a quick vibration.
|
||||
///
|
||||
/// <param name="amplitude">The amplitude of the emphasis, from 0.0 to 1.0</param>
|
||||
/// <param name="frequency">The frequency of the emphasis, from 0.0 to 1.0</param>
|
||||
public static void PlayEmphasis(float amplitude, float frequency)
|
||||
{
|
||||
if (emphasisTemplate == null || !HapticController.hapticsEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Use HapticController.Play() to play a .haptic clip on mobile devices
|
||||
// that support it, or to play a gamepad rumble if a gamepad is connected.
|
||||
if (HapticController.Init() || GamepadRumbler.IsConnected())
|
||||
{
|
||||
float clampedAmplitude = Mathf.Clamp(amplitude, 0.0f, 1.0f);
|
||||
float clampedFrequency = Mathf.Clamp(frequency, 0.0f, 1.0f);
|
||||
const float duration = 0.1f;
|
||||
|
||||
String json = emphasisTemplate
|
||||
.Replace("{amplitude}", clampedAmplitude.ToString(numberFormat))
|
||||
.Replace("{frequency}", clampedFrequency.ToString(numberFormat))
|
||||
.Replace("{duration}", duration.ToString(numberFormat));
|
||||
|
||||
// This preprocessor section will only run for non-mobile platforms
|
||||
GamepadRumble rumble = new GamepadRumble();
|
||||
#if ((!UNITY_ANDROID && !UNITY_IOS) || UNITY_EDITOR) && NICE_VIBRATIONS_INPUTSYSTEM_INSTALLED && ENABLE_INPUT_SYSTEM && !NICE_VIBRATIONS_DISABLE_GAMEPAD_SUPPORT
|
||||
rumble.durationsMs = new int[] { (int)(duration * 1000) };
|
||||
rumble.lowFrequencyMotorSpeeds = new float[] { clampedAmplitude };
|
||||
rumble.highFrequencyMotorSpeeds = new float[] { clampedFrequency };
|
||||
#endif
|
||||
|
||||
HapticController.Load(System.Text.Encoding.UTF8.GetBytes(json), rumble);
|
||||
HapticController.Loop(false);
|
||||
HapticController.Play();
|
||||
}
|
||||
|
||||
// As a fallback, play a short buzz on Android, or a preset on iOS.
|
||||
else if (DeviceCapabilities.isVersionSupported)
|
||||
{
|
||||
#if (UNITY_ANDROID && !UNITY_EDITOR)
|
||||
LofeltHaptics.PlayMaximumAmplitudePattern(new float[]{ 0.0f, 0.05f });
|
||||
#elif (UNITY_IOS && !UNITY_EDITOR)
|
||||
PresetType preset = presetTypeForEmphasis(amplitude);
|
||||
LofeltHaptics.TriggerPresetHaptics((int)preset);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Automatically selects the fallback preset based on the emphasis point amplitude.
|
||||
/// </summary>
|
||||
///
|
||||
/// <param name="amplitude">The amplitude of the emphasis, from 0.0 to 1.0</param>
|
||||
static PresetType presetTypeForEmphasis(float amplitude)
|
||||
{
|
||||
if (amplitude > 0.5f)
|
||||
{
|
||||
return HapticPatterns.PresetType.HeavyImpact;
|
||||
}
|
||||
else if (amplitude <= 0.5f && amplitude > 0.3)
|
||||
{
|
||||
return HapticPatterns.PresetType.MediumImpact;
|
||||
}
|
||||
else
|
||||
{
|
||||
return HapticPatterns.PresetType.LightImpact;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plays a haptic with constant amplitude and frequency.
|
||||
/// </summary>
|
||||
///
|
||||
/// On iOS and with gamepads, you can use HapticController::clipLevel to modulate the haptic
|
||||
/// while it is playing. iOS additional supports modulating the frequency with
|
||||
/// HapticController::clipFrequencyShift.
|
||||
///
|
||||
/// When \ref DeviceCapabilities.meetsAdvancedRequirements returns false on mobile,
|
||||
/// the behavior of this method is different for iOS and Android:
|
||||
/// <ul>
|
||||
/// <li>On iOS, it will play the preset <c>HapticPatterns.PresetType.HeavyImpact</c>. </li>
|
||||
///
|
||||
/// <li>On Android, it will play a pattern with maximum amplitude for the set <c>duration</c>
|
||||
/// since there is no amplitude control.</li>
|
||||
///
|
||||
/// </ul>
|
||||
/// <param name="amplitude">Amplitude, from 0.0 to 1.0</param>
|
||||
/// <param name="frequency">Frequency, from 0.0 to 1.0</param>
|
||||
/// <param name="duration">Play duration in seconds</param>
|
||||
public static void PlayConstant(float amplitude, float frequency, float duration)
|
||||
{
|
||||
if (constantTemplate == null || !HapticController.hapticsEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float clampedAmplitude = Mathf.Clamp(amplitude, 0.0f, 1.0f);
|
||||
float clampedFrequency = Mathf.Clamp(frequency, 0.0f, 1.0f);
|
||||
float clampedDurationSecs = Mathf.Max(duration, 0.0f);
|
||||
|
||||
String json = constantTemplate
|
||||
.Replace("{duration}", clampedDurationSecs.ToString(numberFormat));
|
||||
|
||||
// This preprocessor section will only run for non-mobile platforms
|
||||
GamepadRumble rumble = new GamepadRumble();
|
||||
#if ((!UNITY_ANDROID && !UNITY_IOS) || UNITY_EDITOR) && NICE_VIBRATIONS_INPUTSYSTEM_INSTALLED && ENABLE_INPUT_SYSTEM && !NICE_VIBRATIONS_DISABLE_GAMEPAD_SUPPORT
|
||||
int rumbleDurationMs = (int)(clampedDurationSecs * 1000);
|
||||
const int rumbleEntryDurationMs = 16; // One rumble entry per frame at 60 FPS, which is the limit of what GamepadRumbler can play
|
||||
int rumbleEntryCount = rumbleDurationMs / rumbleEntryDurationMs;
|
||||
rumble.durationsMs = new int[rumbleEntryCount];
|
||||
rumble.lowFrequencyMotorSpeeds = new float[rumbleEntryCount];
|
||||
rumble.highFrequencyMotorSpeeds = new float[rumbleEntryCount];
|
||||
|
||||
// Create many rumble entries instead of just one. With just one entry, changing
|
||||
// clipLevel while the rumble is playing would have no effect, as GamepadRumbler applies
|
||||
// a change only to the next rumble entry, not the one currently playing.
|
||||
for (int i = 0; i < rumbleEntryCount; i++)
|
||||
{
|
||||
rumble.durationsMs[i] = rumbleEntryDurationMs;
|
||||
rumble.lowFrequencyMotorSpeeds[i] = 1.0f;
|
||||
rumble.highFrequencyMotorSpeeds[i] = 1.0f;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (HapticController.Init() || GamepadRumbler.IsConnected())
|
||||
{
|
||||
HapticController.Load(System.Text.Encoding.UTF8.GetBytes(json), rumble);
|
||||
HapticController.Loop(false);
|
||||
HapticController.clipLevel = clampedAmplitude;
|
||||
HapticController.clipFrequencyShift = clampedFrequency;
|
||||
HapticController.Play();
|
||||
}
|
||||
else if (DeviceCapabilities.isVersionSupported)
|
||||
{
|
||||
#if (UNITY_ANDROID && !UNITY_EDITOR)
|
||||
constantPatternTime[1] = duration;
|
||||
LofeltHaptics.PlayMaximumAmplitudePattern(constantPatternTime);
|
||||
#elif (UNITY_IOS && !UNITY_EDITOR)
|
||||
HapticPatterns.PlayPreset(PresetType.HeavyImpact);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
static Preset GetPresetForType(PresetType type)
|
||||
{
|
||||
Debug.Assert(type != PresetType.None);
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case PresetType.Selection:
|
||||
return Selection;
|
||||
case PresetType.LightImpact:
|
||||
return Light;
|
||||
case PresetType.MediumImpact:
|
||||
return Medium;
|
||||
case PresetType.HeavyImpact:
|
||||
return Heavy;
|
||||
case PresetType.RigidImpact:
|
||||
return Rigid;
|
||||
case PresetType.SoftImpact:
|
||||
return Soft;
|
||||
case PresetType.Success:
|
||||
return Success;
|
||||
case PresetType.Failure:
|
||||
return Failure;
|
||||
case PresetType.Warning:
|
||||
return Warning;
|
||||
}
|
||||
|
||||
// Silence compiler warning about not all code paths returning something
|
||||
return Medium;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plays a set of predefined haptic patterns.
|
||||
/// </summary>
|
||||
///
|
||||
/// These predefined haptic patterns are played and represented in different ways for iOS,
|
||||
/// Android and gamepads.
|
||||
///
|
||||
/// - On iOS, this function triggers system haptics that are native to iOS. Calling
|
||||
/// \ref HapticController.Stop() won't stop haptics.
|
||||
/// - On Android devices that can play <c>.haptic</c> clips (DeviceCapabilities.meetsAdvancedRequirements
|
||||
/// is <c>true</c>) and on gamepads, this function plays a haptic pattern that has a similar
|
||||
/// experience to the matching iOS system haptics.
|
||||
/// - On Android devices that can not play <c>.haptic</c> clips (DeviceCapabilities.meetsAdvancedRequirements
|
||||
/// is <c>false</c>), this function plays a haptic pattern that has a similar experience to
|
||||
/// the matching iOS system haptics, by turning the motor off and on at maximum amplitude.
|
||||
///
|
||||
/// This is a "fire-and-forget" method. Other functionalities like seeking, looping, and
|
||||
/// runtime modulation won't work after calling this method.
|
||||
///
|
||||
/// <param name="presetType">Type of preset represented by a \ref PresetType enum</param>
|
||||
public static void PlayPreset(PresetType presetType)
|
||||
{
|
||||
if (!HapticController.hapticsEnabled || presetType == PresetType.None)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Preset preset = GetPresetForType(presetType);
|
||||
|
||||
#if (UNITY_IOS && !UNITY_EDITOR)
|
||||
LofeltHaptics.TriggerPresetHaptics((int)presetType);
|
||||
return;
|
||||
#else
|
||||
if (HapticController.Init() || GamepadRumbler.IsConnected())
|
||||
{
|
||||
#if ((!UNITY_ANDROID && !UNITY_IOS) || UNITY_EDITOR) && NICE_VIBRATIONS_INPUTSYSTEM_INSTALLED && ENABLE_INPUT_SYSTEM && !NICE_VIBRATIONS_DISABLE_GAMEPAD_SUPPORT
|
||||
HapticController.Load(preset.jsonClip, preset.gamepadRumble);
|
||||
#else
|
||||
HapticController.Load(preset.jsonClip);
|
||||
#endif
|
||||
HapticController.Loop(false);
|
||||
HapticController.Play();
|
||||
return;
|
||||
}
|
||||
|
||||
if (DeviceCapabilities.isVersionSupported)
|
||||
{
|
||||
#if (UNITY_ANDROID && !UNITY_EDITOR)
|
||||
LofeltHaptics.PlayMaximumAmplitudePattern(preset.maximumAmplitudePattern);
|
||||
return;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the haptic preset duration.
|
||||
/// </summary>
|
||||
///
|
||||
/// While a preset is played back in different ways on iOS, Android and gamepads, the
|
||||
/// duration is similar for each playback method.
|
||||
///
|
||||
/// <param name="presetType"> Type of preset represented by a \ref PresetType enum </param>
|
||||
/// <returns>Returns a float with a the preset duration; if the selected preset is `None`, it returns 0</returns>
|
||||
public static float GetPresetDuration(PresetType presetType)
|
||||
{
|
||||
if (presetType == PresetType.None)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return GetPresetForType(presetType).GetDuration();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
18
Assets/External/Feel/NiceVibrations/Scripts/Components/HapticPatterns.cs.meta
vendored
Normal file
18
Assets/External/Feel/NiceVibrations/Scripts/Components/HapticPatterns.cs.meta
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e98a6cfb8386a479a8a5c3ded1f05862
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 183370
|
||||
packageName: Feel
|
||||
packageVersion: 5.9.1
|
||||
assetPath: Assets/Feel/NiceVibrations/Scripts/Components/HapticPatterns.cs
|
||||
uploadId: 830868
|
||||
108
Assets/External/Feel/NiceVibrations/Scripts/Components/HapticReceiver.cs
vendored
Normal file
108
Assets/External/Feel/NiceVibrations/Scripts/Components/HapticReceiver.cs
vendored
Normal file
@@ -0,0 +1,108 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace Lofelt.NiceVibrations
|
||||
{
|
||||
/// <summary>
|
||||
/// A <c>MonoBehaviour</c> that forwards global properties from HapticController and
|
||||
/// handles events
|
||||
/// </summary>
|
||||
///
|
||||
/// While HapticSource provides a per-clip <c>MonoBehaviour</c> API for the functionality
|
||||
/// in HapticController, HapticReceiver provides a MonoBehaviour API for
|
||||
/// the global functionality in HapticController.
|
||||
///
|
||||
/// HapticReceiver is also responsible for global event handling, such as an application
|
||||
/// focus change. To make this work correctly, your scene should have exactly one
|
||||
/// HapticReceiver component, similar to how a scene should have exactly one
|
||||
/// <c>AudioListener</c>.
|
||||
///
|
||||
/// In the future HapticReceiver might receive parameters and distance to
|
||||
/// HapticSource components, and can be used for global parameter control through Unity
|
||||
/// Editor GUI.
|
||||
[AddComponentMenu("Nice Vibrations/Haptic Receiver")]
|
||||
public class HapticReceiver : MonoBehaviour, ISerializationCallbackReceiver
|
||||
{
|
||||
// These two fields are only used for serialization and deserialization.
|
||||
// HapticController manages the output haptic level and global haptic toggle,
|
||||
// HapticReceiver forwards these properties so they are available in a
|
||||
// MonoBehaviour.
|
||||
// To be able to serialize these properties, HapticReceiver needs to have
|
||||
// fields for them. Before serialization, these fields are set to the values
|
||||
// from HapticController, and after deserialization the values are restored
|
||||
// back to HapticController.
|
||||
[SerializeField]
|
||||
[Range(0.0f, 5.0f)]
|
||||
private float _outputLevel = 1.0f;
|
||||
[SerializeField]
|
||||
private bool _hapticsEnabled = true;
|
||||
|
||||
/// <summary>
|
||||
/// Loads all fields from HapticController.
|
||||
/// </summary>
|
||||
public void OnBeforeSerialize()
|
||||
{
|
||||
_outputLevel = HapticController._outputLevel;
|
||||
_hapticsEnabled = HapticController._hapticsEnabled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes all fields to HapticController.
|
||||
/// </summary>
|
||||
public void OnAfterDeserialize()
|
||||
{
|
||||
HapticController._outputLevel = _outputLevel;
|
||||
HapticController._hapticsEnabled = _hapticsEnabled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Forwarded HapticController::outputLevel
|
||||
/// </summary>
|
||||
[System.ComponentModel.DefaultValue(1.0f)]
|
||||
public float outputLevel
|
||||
{
|
||||
get { return HapticController.outputLevel; }
|
||||
set { HapticController.outputLevel = value; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Forwarded HapticController::hapticsEnabled
|
||||
/// </summary>
|
||||
[System.ComponentModel.DefaultValue(true)]
|
||||
public bool hapticsEnabled
|
||||
{
|
||||
get { return HapticController.hapticsEnabled; }
|
||||
set { HapticController.hapticsEnabled = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes HapticController.
|
||||
/// </summary>
|
||||
///
|
||||
/// This ensures that the initialization time is spent at startup instead of when
|
||||
/// the first haptic is triggered during gameplay.
|
||||
void Start()
|
||||
{
|
||||
HapticController.Init();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Forwards an application focus change event to HapticController.
|
||||
/// </summary>
|
||||
void OnApplicationFocus(bool hasFocus)
|
||||
{
|
||||
HapticController.ProcessApplicationFocus(hasFocus);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops haptic playback on the gamepad when destroyed, to make sure the gamepad
|
||||
/// stops vibrating when quitting the application.
|
||||
/// </summary>
|
||||
void OnDestroy()
|
||||
{
|
||||
GamepadRumbler.Stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
18
Assets/External/Feel/NiceVibrations/Scripts/Components/HapticReceiver.cs.meta
vendored
Normal file
18
Assets/External/Feel/NiceVibrations/Scripts/Components/HapticReceiver.cs.meta
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ceb29a83998eb4949bc0a9c8e5662fa1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 24c63d27288824cf68c83ec01e0f3643, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 183370
|
||||
packageName: Feel
|
||||
packageVersion: 5.9.1
|
||||
assetPath: Assets/Feel/NiceVibrations/Scripts/Components/HapticReceiver.cs
|
||||
uploadId: 830868
|
||||
262
Assets/External/Feel/NiceVibrations/Scripts/Components/HapticSource.cs
vendored
Normal file
262
Assets/External/Feel/NiceVibrations/Scripts/Components/HapticSource.cs
vendored
Normal file
@@ -0,0 +1,262 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace Lofelt.NiceVibrations
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides haptic playback functionality for a single haptic clip.
|
||||
/// </summary>
|
||||
///
|
||||
/// HapticSource plays back the HapticClip assigned in the \ref clip property
|
||||
/// when calling Play(). It also provides various ways to control playback, such as
|
||||
/// seeking, looping and amplitude/frequency modulation.
|
||||
///
|
||||
/// When a gamepad is connected, the haptic clip will be played back on that gamepad.
|
||||
/// See the HapticController documentation for more details about gamepad support.
|
||||
///
|
||||
/// At the moment, playback of a haptic source is not triggered automatically
|
||||
/// by e.g. proximity between the HapticReceiver and the HapticSource,
|
||||
/// so you need to call Play() to trigger playback.
|
||||
///
|
||||
/// You can place multiple HapticSource components in your scene, with a different
|
||||
/// HapticClip assigned to each.
|
||||
///
|
||||
/// HapticSource provides a per-clip <c>MonoBehaviour</c> API for the functionality
|
||||
/// in HapticController, while HapticReceiver provides a <c>MonoBehaviour</c> API
|
||||
/// for the global functionality in HapticController.
|
||||
///
|
||||
/// <c>HapticSourceInspector</c> provides a custom editor for HapticSource for the
|
||||
/// Inspector.
|
||||
[AddComponentMenu("Nice Vibrations/Haptic Source")]
|
||||
public class HapticSource : MonoBehaviour
|
||||
{
|
||||
const int DEFAULT_PRIORITY = 128;
|
||||
|
||||
/// The HapticClip this HapticSource loads and plays.
|
||||
public HapticClip clip;
|
||||
|
||||
/// <summary>
|
||||
/// The priority of the HapticSource
|
||||
/// </summary>
|
||||
///
|
||||
/// This property is set by <c>HapticSourceInspector</c>. 0 is the highest priority and 256
|
||||
/// is the lowest priority.
|
||||
///
|
||||
/// The default value is 128.
|
||||
public int priority = DEFAULT_PRIORITY;
|
||||
|
||||
/// <summary>
|
||||
/// Jump in time position of haptic source playback.
|
||||
/// </summary>
|
||||
///
|
||||
/// Initially set to 0.0 seconds.
|
||||
/// This value can only be set when using Seek().
|
||||
float seekTime = 0.0f;
|
||||
|
||||
[SerializeField]
|
||||
HapticPatterns.PresetType _fallbackPreset = HapticPatterns.PresetType.None;
|
||||
|
||||
/// <summary>
|
||||
/// The haptic preset to be played when it's not possible to play a haptic clip
|
||||
/// </summary>
|
||||
[System.ComponentModel.DefaultValue(HapticPatterns.PresetType.None)]
|
||||
public HapticPatterns.PresetType fallbackPreset
|
||||
{
|
||||
get { return _fallbackPreset; }
|
||||
set { _fallbackPreset = value; }
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
bool _loop = false;
|
||||
|
||||
/// <summary>
|
||||
/// Set the haptic source to loop playback of the haptic clip.
|
||||
/// </summary>
|
||||
///
|
||||
/// It will only have any effect once Play() is called.
|
||||
///
|
||||
/// See HapticController::Loop() for further details.
|
||||
[System.ComponentModel.DefaultValue(false)]
|
||||
public bool loop
|
||||
{
|
||||
get { return _loop; }
|
||||
set { _loop = value; }
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
float _level = 1.0f;
|
||||
|
||||
/// <summary>
|
||||
/// The level of the haptic source
|
||||
/// </summary>
|
||||
///
|
||||
/// Haptic source level is applied in combination with output level (which can be set on either
|
||||
/// HapticReceiver or HapticController according to preference), to the currently playing
|
||||
/// haptic clip. The combination of these two levels and the amplitude within the loaded
|
||||
/// haptic at a given moment in time determines the strength of the vibration felt on the device. See
|
||||
/// HapticController::clipLevel for further details.
|
||||
[System.ComponentModel.DefaultValue(1.0)]
|
||||
public float level
|
||||
{
|
||||
get { return _level; }
|
||||
set
|
||||
{
|
||||
_level = value;
|
||||
|
||||
if (IsLoaded())
|
||||
{
|
||||
HapticController.clipLevel = _level;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
float _frequencyShift = 0.0f;
|
||||
|
||||
/// <summary>
|
||||
/// This shift is added to the frequency of every breakpoint in the clip, including the
|
||||
/// emphasis.
|
||||
/// </summary>
|
||||
///
|
||||
/// See HapticController::clipFrequencyShift for further details.
|
||||
[System.ComponentModel.DefaultValue(0.0)]
|
||||
public float frequencyShift
|
||||
{
|
||||
get { return _frequencyShift; }
|
||||
set
|
||||
{
|
||||
_frequencyShift = value;
|
||||
|
||||
if (IsLoaded())
|
||||
{
|
||||
HapticController.clipFrequencyShift = _frequencyShift;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The HapticSource that is currently loaded into HapticController.
|
||||
/// This can be null if nothing was ever loaded, or if HapticController::Load()
|
||||
/// was called directly, bypassing HapticSource.
|
||||
static HapticSource loadedHapticSource = null;
|
||||
|
||||
/// The HapticSource that was last played.
|
||||
/// This can be null if nothing was ever player, or if HapticController::Play()
|
||||
/// was called directly, bypassing HapticSource.
|
||||
/// The lastPlayedHapticSource isn't necessarily playing now, lastPlayedHapticSource
|
||||
/// will remain set even if playback has finished or was stopped.
|
||||
static HapticSource lastPlayedHapticSource = null;
|
||||
|
||||
static HapticSource()
|
||||
{
|
||||
// When HapticController::Load() or HapticController::Play() is
|
||||
// called directly, bypassing HapticSource, reset loadedHapticSource
|
||||
// and lastPlayedHapticSource.
|
||||
HapticController.LoadedClipChanged += () =>
|
||||
{
|
||||
loadedHapticSource = null;
|
||||
};
|
||||
HapticController.PlaybackStarted += () =>
|
||||
{
|
||||
lastPlayedHapticSource = null;
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads and plays back the haptic clip.
|
||||
/// </summary>
|
||||
///
|
||||
/// At the moment only one haptic clip at a time can be played. If another
|
||||
/// HapticSource is currently playing and has lower priority, its playback will
|
||||
/// be stopped.
|
||||
///
|
||||
/// If a seek time within the time range of the clip has been set with Seek(),
|
||||
/// it will jump to that position if \ref loop is <c>false</c>. If \ref loop
|
||||
/// is <c>true</c>, seeking will have no effect.
|
||||
///
|
||||
/// It will loop playback in case \ref loop is <c>true</c>.
|
||||
public void Play()
|
||||
{
|
||||
if (CanPlay())
|
||||
{
|
||||
//
|
||||
// Load
|
||||
//
|
||||
HapticController.Load(clip);
|
||||
loadedHapticSource = this;
|
||||
|
||||
//
|
||||
// Apply properties like loop, modulation and seek position
|
||||
//
|
||||
HapticController.Loop(loop);
|
||||
|
||||
HapticController.clipLevel = level;
|
||||
HapticController.clipFrequencyShift = frequencyShift;
|
||||
|
||||
if (seekTime != 0.0f && !loop)
|
||||
{
|
||||
HapticController.Seek(seekTime);
|
||||
}
|
||||
|
||||
//
|
||||
// Play
|
||||
//
|
||||
HapticController.fallbackPreset = fallbackPreset;
|
||||
HapticController.Play();
|
||||
lastPlayedHapticSource = this;
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanPlay()
|
||||
{
|
||||
return (!HapticController.IsPlaying() ||
|
||||
(lastPlayedHapticSource != null && priority <= lastPlayedHapticSource.priority));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the current HapticSource has been loaded into HapticController.
|
||||
/// </summary>
|
||||
///
|
||||
/// This is used to avoid triggering operations on HapticController while
|
||||
/// another HapticSource is loaded.
|
||||
private bool IsLoaded()
|
||||
{
|
||||
return Object.ReferenceEquals(this, loadedHapticSource);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops playback that was previously started with Play().
|
||||
/// </summary>
|
||||
public void Stop()
|
||||
{
|
||||
if (IsLoaded())
|
||||
{
|
||||
HapticController.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the time position to jump to when Play() is called.
|
||||
/// </summary>
|
||||
///
|
||||
/// It will only have an effect once Play() is called.
|
||||
///
|
||||
/// <param name="time">The position in the clip, in seconds</param>
|
||||
public void Seek(float time)
|
||||
{
|
||||
this.seekTime = time;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When a <c>GameObject</c> is disabled, stop playback if this HapticSource is
|
||||
/// playing.
|
||||
/// </summary>
|
||||
public void OnDisable()
|
||||
{
|
||||
if (HapticController.IsPlaying() && IsLoaded())
|
||||
{
|
||||
this.Stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
18
Assets/External/Feel/NiceVibrations/Scripts/Components/HapticSource.cs.meta
vendored
Normal file
18
Assets/External/Feel/NiceVibrations/Scripts/Components/HapticSource.cs.meta
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d20df93fb7de8457baa15a213a53ab19
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 7c1be57d46a3143daa1fe62dbc59772f, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 183370
|
||||
packageName: Feel
|
||||
packageVersion: 5.9.1
|
||||
assetPath: Assets/Feel/NiceVibrations/Scripts/Components/HapticSource.cs
|
||||
uploadId: 830868
|
||||
8
Assets/External/Feel/NiceVibrations/Scripts/Components/Icons.meta
vendored
Normal file
8
Assets/External/Feel/NiceVibrations/Scripts/Components/Icons.meta
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 729f668a205524058a62426043ee3083
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/External/Feel/NiceVibrations/Scripts/Components/Icons/HapticReceiverIcon.png
vendored
Normal file
BIN
Assets/External/Feel/NiceVibrations/Scripts/Components/Icons/HapticReceiverIcon.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.9 KiB |
99
Assets/External/Feel/NiceVibrations/Scripts/Components/Icons/HapticReceiverIcon.png.meta
vendored
Normal file
99
Assets/External/Feel/NiceVibrations/Scripts/Components/Icons/HapticReceiverIcon.png.meta
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 24c63d27288824cf68c83ec01e0f3643
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 11
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 1
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: -1
|
||||
aniso: -1
|
||||
mipBias: -100
|
||||
wrapU: -1
|
||||
wrapV: -1
|
||||
wrapW: -1
|
||||
nPOTScale: 1
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 0
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 0
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
applyGammaDecoding: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
pSDShowRemoveMatteOption: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 183370
|
||||
packageName: Feel
|
||||
packageVersion: 5.9.1
|
||||
assetPath: Assets/Feel/NiceVibrations/Scripts/Components/Icons/HapticReceiverIcon.png
|
||||
uploadId: 830868
|
||||
BIN
Assets/External/Feel/NiceVibrations/Scripts/Components/Icons/HapticSourceIcon.png
vendored
Normal file
BIN
Assets/External/Feel/NiceVibrations/Scripts/Components/Icons/HapticSourceIcon.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.6 KiB |
99
Assets/External/Feel/NiceVibrations/Scripts/Components/Icons/HapticSourceIcon.png.meta
vendored
Normal file
99
Assets/External/Feel/NiceVibrations/Scripts/Components/Icons/HapticSourceIcon.png.meta
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7c1be57d46a3143daa1fe62dbc59772f
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 11
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 1
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: -1
|
||||
aniso: -1
|
||||
mipBias: -100
|
||||
wrapU: -1
|
||||
wrapV: -1
|
||||
wrapW: -1
|
||||
nPOTScale: 1
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 0
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 0
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
applyGammaDecoding: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
pSDShowRemoveMatteOption: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 183370
|
||||
packageName: Feel
|
||||
packageVersion: 5.9.1
|
||||
assetPath: Assets/Feel/NiceVibrations/Scripts/Components/Icons/HapticSourceIcon.png
|
||||
uploadId: 830868
|
||||
135
Assets/External/Feel/NiceVibrations/Scripts/Components/JNIHelpers.cs
vendored
Normal file
135
Assets/External/Feel/NiceVibrations/Scripts/Components/JNIHelpers.cs
vendored
Normal file
@@ -0,0 +1,135 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#if (UNITY_ANDROID && !UNITY_EDITOR)
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Lofelt.NiceVibrations
|
||||
{
|
||||
// Android JNI call wrappers that are more efficient than AndroidJavaObject::Call()
|
||||
//
|
||||
// Calling a method via AndroidJavaObject, e.g. `lofeltHaptics.Call("play")`, is inefficient:
|
||||
// - It looks up the method by name for each call
|
||||
// - It allocates memory during method lookup and argument conversion
|
||||
//
|
||||
// JNIHelpers provides alternative Call() methods that are more efficient:
|
||||
// - It allows calling by method ID rather by method name, so that the method only needs to
|
||||
// be looked up once, not for every call
|
||||
// - It does not allocate memory for converting the arguments to jvalue[]
|
||||
//
|
||||
// In addition to that, exceptions thrown in Java are handled automatically by logging them.
|
||||
//
|
||||
// The Call() overload here do not cover all cases that AndroidJavaObject::Call() covers. For
|
||||
// example, only methods with one argument are supported, and that only for certain types. In
|
||||
// addition, not all overloads are free of allocations. This however is good enough so that the
|
||||
// calls triggered by common playback scenarios such as HapticController::Play() and
|
||||
// HapticPatterns::PlayPreset() don't allocate.
|
||||
internal static class JNIHelpers
|
||||
{
|
||||
// The array for the JNI arguments is created here, so that it doesn't need to be created
|
||||
// for every call. This saves the allocation in each call.
|
||||
// The array supports only methods with 0 or 1 argument, but that covers our needs.
|
||||
static jvalue[] jniArgs = new jvalue[1];
|
||||
|
||||
// Returns an exception message and stack trace for the given Java exception
|
||||
static String javaThrowableToString(IntPtr throwable)
|
||||
{
|
||||
IntPtr throwableClass = AndroidJNI.FindClass("java/lang/Throwable");
|
||||
IntPtr androidUtilLogClass = AndroidJNI.FindClass("android/util/Log");
|
||||
try
|
||||
{
|
||||
IntPtr toStringMethodId = AndroidJNI.GetMethodID(throwableClass, "toString", "()Ljava/lang/String;");
|
||||
IntPtr getStackTraceStringMethodId = AndroidJNI.GetStaticMethodID(androidUtilLogClass, "getStackTraceString", "(Ljava/lang/Throwable;)Ljava/lang/String;");
|
||||
string exceptionMessage = AndroidJNI.CallStringMethod(throwable, toStringMethodId, new jvalue[] { });
|
||||
jniArgs[0].l = throwable;
|
||||
string exceptionCallStack = AndroidJNI.CallStaticStringMethod(androidUtilLogClass, getStackTraceStringMethodId, jniArgs);
|
||||
return exceptionMessage + "\n" + exceptionCallStack;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (throwable != IntPtr.Zero)
|
||||
AndroidJNI.DeleteLocalRef(throwable);
|
||||
if (throwableClass != IntPtr.Zero)
|
||||
AndroidJNI.DeleteLocalRef(throwableClass);
|
||||
if (androidUtilLogClass != IntPtr.Zero)
|
||||
AndroidJNI.DeleteLocalRef(androidUtilLogClass);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Call(AndroidJavaObject obj, IntPtr methodId, jvalue[] jniArgs)
|
||||
{
|
||||
if (methodId == IntPtr.Zero)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
AndroidJNI.CallVoidMethod(obj.GetRawObject(), methodId, jniArgs);
|
||||
IntPtr throwable = AndroidJNI.ExceptionOccurred();
|
||||
if (throwable != IntPtr.Zero)
|
||||
{
|
||||
AndroidJNI.ExceptionClear();
|
||||
String exception = javaThrowableToString(throwable);
|
||||
Debug.LogError(exception);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Call(AndroidJavaObject obj, IntPtr methodId)
|
||||
{
|
||||
jniArgs[0].l = System.IntPtr.Zero;
|
||||
Call(obj, methodId, jniArgs);
|
||||
}
|
||||
|
||||
public static void Call(AndroidJavaObject obj, IntPtr methodId, float arg)
|
||||
{
|
||||
jniArgs[0].f = arg;
|
||||
Call(obj, methodId, jniArgs);
|
||||
}
|
||||
|
||||
public static void Call(AndroidJavaObject obj, IntPtr methodId, bool arg)
|
||||
{
|
||||
jniArgs[0].z = arg;
|
||||
Call(obj, methodId, jniArgs);
|
||||
}
|
||||
|
||||
public static void Call(AndroidJavaObject obj, IntPtr methodId, float[] arg)
|
||||
{
|
||||
// The allocations in the next two lines could probably be removed to optimize this
|
||||
// further.
|
||||
object[] args = new object[] { arg };
|
||||
jvalue[] jniArgs = AndroidJNIHelper.CreateJNIArgArray(args);
|
||||
try
|
||||
{
|
||||
JNIHelpers.Call(obj, methodId, jniArgs);
|
||||
}
|
||||
finally
|
||||
{
|
||||
AndroidJNIHelper.DeleteJNIArgArray(args, jniArgs);
|
||||
}
|
||||
}
|
||||
|
||||
// The method isn't yet optimized to reduce allocations, but unlike the other overloads of
|
||||
// Call(), it supports non-void return types.
|
||||
public static ReturnType Call<ReturnType>(AndroidJavaObject obj, string methodName)
|
||||
{
|
||||
try
|
||||
{
|
||||
return obj.Call<ReturnType>(methodName);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogException(ex);
|
||||
return default(ReturnType);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
#endif
|
||||
18
Assets/External/Feel/NiceVibrations/Scripts/Components/JNIHelpers.cs.meta
vendored
Normal file
18
Assets/External/Feel/NiceVibrations/Scripts/Components/JNIHelpers.cs.meta
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 309cd98b547c14b48b9f1c523a6fdc26
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 183370
|
||||
packageName: Feel
|
||||
packageVersion: 5.9.1
|
||||
assetPath: Assets/Feel/NiceVibrations/Scripts/Components/JNIHelpers.cs
|
||||
uploadId: 830868
|
||||
295
Assets/External/Feel/NiceVibrations/Scripts/Components/LofeltHaptics.cs
vendored
Normal file
295
Assets/External/Feel/NiceVibrations/Scripts/Components/LofeltHaptics.cs
vendored
Normal file
@@ -0,0 +1,295 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
using UnityEngine;
|
||||
using System;
|
||||
|
||||
#if (UNITY_ANDROID && !UNITY_EDITOR)
|
||||
using System.Text;
|
||||
using System.Runtime.InteropServices;
|
||||
#elif (UNITY_IOS && !UNITY_EDITOR)
|
||||
using UnityEngine.iOS;
|
||||
using System.Runtime.InteropServices;
|
||||
#endif
|
||||
|
||||
namespace Lofelt.NiceVibrations
|
||||
{
|
||||
/// <summary>
|
||||
/// C# wrapper for the Lofelt Studio Android and iOS SDK.
|
||||
/// </summary>
|
||||
///
|
||||
/// You should not use this class directly, use HapticController instead, or the
|
||||
/// <c>MonoBehaviour</c> classes HapticReceiver and HapticSource.
|
||||
///
|
||||
/// The Lofelt Studio Android and iOS SDK are included in Nice Vibrations as pre-compiled
|
||||
/// binary plugins.
|
||||
///
|
||||
/// Each method here delegates to either the Android or iOS SDK. The methods should only be
|
||||
/// called if DeviceMeetsMinimumPlatformRequirements() returns true, otherwise there will
|
||||
/// be runtime errors.
|
||||
///
|
||||
/// All the methods do nothing when running in the Unity editor.
|
||||
///
|
||||
/// Before calling any other method, Initialize() needs to be called.
|
||||
///
|
||||
/// Errors are printed and swallowed, no exceptions are thrown. On iOS, this happens inside
|
||||
/// the SDK, on Android this happens with try/catch blocks in this class and in JNIHelpers.
|
||||
public static class LofeltHaptics
|
||||
{
|
||||
#if (UNITY_ANDROID && !UNITY_EDITOR)
|
||||
static AndroidJavaObject lofeltHaptics;
|
||||
static AndroidJavaObject hapticPatterns;
|
||||
static long nativeController;
|
||||
|
||||
// Cache the most commonly used JNI method IDs during initialization.
|
||||
// Calling a Java method via its method ID is faster and uses less allocations than
|
||||
// calling a method by string, like e.g. 'lofeltHaptics.Call("play")'.
|
||||
static IntPtr playMethodId = IntPtr.Zero;
|
||||
static IntPtr stopMethodId = IntPtr.Zero;
|
||||
static IntPtr seekMethodId = IntPtr.Zero;
|
||||
static IntPtr loopMethodId = IntPtr.Zero;
|
||||
static IntPtr setAmplitudeMultiplicationMethodId = IntPtr.Zero;
|
||||
static IntPtr playMaximumAmplitudePattern = IntPtr.Zero;
|
||||
|
||||
[DllImport("lofelt_sdk")]
|
||||
private static extern bool lofeltHapticsLoadDirect(IntPtr controller, [In] byte[] bytes, long size);
|
||||
|
||||
#elif (UNITY_IOS && !UNITY_EDITOR)
|
||||
// imports of iOS Framework bindings
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern bool lofeltHapticsDeviceMeetsMinimumRequirementsBinding();
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern IntPtr lofeltHapticsInitBinding();
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern bool lofeltHapticsLoadBinding(IntPtr controller, [In] byte[] bytes, long size);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern bool lofeltHapticsPlayBinding(IntPtr controller);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern bool lofeltHapticsStopBinding(IntPtr controller);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern bool lofeltHapticsSeekBinding(IntPtr controller, float time);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern bool lofeltHapticsSetAmplitudeMultiplicationBinding(IntPtr controller, float factor);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern bool lofeltHapticsSetFrequencyShiftBinding(IntPtr controller, float shift);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern bool lofeltHapticsLoopBinding(IntPtr controller, bool enable);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern float lofeltHapticsGetClipDurationBinding(IntPtr controller);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern bool lofeltHapticsReleaseBinding(IntPtr controller);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern bool lofeltHapticsSystemHapticsTriggerBinding(int type);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern bool lofeltHapticsSystemHapticsInitializeBinding();
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern bool lofeltHapticsSystemHapticsReleaseBinding();
|
||||
|
||||
static IntPtr controller = IntPtr.Zero;
|
||||
|
||||
static bool systemHapticsInitialized = false;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the iOS framework or Android library plugin.
|
||||
/// </summary>
|
||||
///
|
||||
/// This needs to be called before calling any other method.
|
||||
public static void Initialize()
|
||||
{
|
||||
#if (UNITY_ANDROID && !UNITY_EDITOR)
|
||||
try
|
||||
{
|
||||
using (var unityPlayerClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
|
||||
using (var context = unityPlayerClass.GetStatic<AndroidJavaObject>("currentActivity"))
|
||||
{
|
||||
lofeltHaptics = new AndroidJavaObject("com.lofelt.haptics.LofeltHaptics", context);
|
||||
nativeController = lofeltHaptics.Call<long>("getControllerHandle");
|
||||
hapticPatterns = new AndroidJavaObject("com.lofelt.haptics.HapticPatterns", context);
|
||||
|
||||
playMethodId = AndroidJNIHelper.GetMethodID(lofeltHaptics.GetRawClass(), "play", "()V", false);
|
||||
stopMethodId = AndroidJNIHelper.GetMethodID(lofeltHaptics.GetRawClass(), "stop", "()V", false);
|
||||
seekMethodId = AndroidJNIHelper.GetMethodID(lofeltHaptics.GetRawClass(), "seek", "(F)V", false);
|
||||
loopMethodId = AndroidJNIHelper.GetMethodID(lofeltHaptics.GetRawClass(), "loop", "(Z)V", false);
|
||||
setAmplitudeMultiplicationMethodId = AndroidJNIHelper.GetMethodID(lofeltHaptics.GetRawClass(), "setAmplitudeMultiplication", "(F)V", false);
|
||||
playMaximumAmplitudePattern = AndroidJNIHelper.GetMethodID(hapticPatterns.GetRawClass(), "playMaximumAmplitudePattern", "([F)V", false);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogException(ex);
|
||||
}
|
||||
#elif (UNITY_IOS && !UNITY_EDITOR)
|
||||
lofeltHapticsSystemHapticsInitializeBinding();
|
||||
systemHapticsInitialized = true;
|
||||
controller = lofeltHapticsInitBinding();
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases the resources used by the iOS framework or Android library plugin.
|
||||
/// </summary>
|
||||
public static void Release()
|
||||
{
|
||||
#if (UNITY_ANDROID && !UNITY_EDITOR)
|
||||
try
|
||||
{
|
||||
lofeltHaptics.Dispose();
|
||||
lofeltHaptics = null;
|
||||
|
||||
hapticPatterns.Dispose();
|
||||
hapticPatterns = null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogWarning(ex);
|
||||
}
|
||||
#elif (UNITY_IOS && !UNITY_EDITOR)
|
||||
if(DeviceCapabilities.isVersionSupported) {
|
||||
lofeltHapticsSystemHapticsReleaseBinding();
|
||||
if(controller != IntPtr.Zero) {
|
||||
lofeltHapticsReleaseBinding(controller);
|
||||
controller = IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public static bool DeviceMeetsMinimumPlatformRequirements()
|
||||
{
|
||||
#if (UNITY_ANDROID && !UNITY_EDITOR)
|
||||
return JNIHelpers.Call<bool>(lofeltHaptics, "deviceMeetsMinimumRequirements");
|
||||
#elif (UNITY_IOS && !UNITY_EDITOR)
|
||||
return lofeltHapticsDeviceMeetsMinimumRequirementsBinding();
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void Load(byte[] data)
|
||||
{
|
||||
#if (UNITY_ANDROID && !UNITY_EDITOR)
|
||||
// For performance reasons, we do *not* call into the Java API with
|
||||
// `lofeltHaptics.Call("load", data)` here. Instead, we bypass the Java layer and
|
||||
// call into the native library directly, saving the costly conversion from
|
||||
// C#'s byte[] to Java's byte[].
|
||||
//
|
||||
// No exception handling needed here, lofeltHapticsLoadDirect() is a native method that
|
||||
// doesn't throw an exception and instead logs the error.
|
||||
lofeltHapticsLoadDirect((IntPtr)nativeController, data, data.Length);
|
||||
#elif (UNITY_IOS && !UNITY_EDITOR)
|
||||
lofeltHapticsLoadBinding(controller, data, data.Length);
|
||||
#endif
|
||||
}
|
||||
|
||||
public static float GetClipDuration()
|
||||
{
|
||||
#if (UNITY_ANDROID && !UNITY_EDITOR)
|
||||
return JNIHelpers.Call<float>(lofeltHaptics, "getClipDuration");
|
||||
#elif (UNITY_IOS && !UNITY_EDITOR)
|
||||
return lofeltHapticsGetClipDurationBinding(controller);
|
||||
#else
|
||||
//No haptic clip was loaded with Lofelt SDK, so it returns 0.0f
|
||||
return 0.0f;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void Play()
|
||||
{
|
||||
#if (UNITY_ANDROID && !UNITY_EDITOR)
|
||||
JNIHelpers.Call(lofeltHaptics, playMethodId);
|
||||
#elif (UNITY_IOS && !UNITY_EDITOR)
|
||||
lofeltHapticsPlayBinding(controller);
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void PlayMaximumAmplitudePattern(float[] timings)
|
||||
{
|
||||
#if (UNITY_ANDROID && !UNITY_EDITOR)
|
||||
JNIHelpers.Call(hapticPatterns, playMaximumAmplitudePattern, timings);
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void Stop()
|
||||
{
|
||||
#if (UNITY_ANDROID && !UNITY_EDITOR)
|
||||
JNIHelpers.Call(lofeltHaptics, stopMethodId);
|
||||
#elif (UNITY_IOS && !UNITY_EDITOR)
|
||||
lofeltHapticsStopBinding(controller);
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void StopPattern()
|
||||
{
|
||||
#if (UNITY_ANDROID && !UNITY_EDITOR)
|
||||
try
|
||||
{
|
||||
hapticPatterns.Call("stopPattern");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogWarning(ex);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void Seek(float time)
|
||||
{
|
||||
#if (UNITY_ANDROID && !UNITY_EDITOR)
|
||||
JNIHelpers.Call(lofeltHaptics, seekMethodId, time);
|
||||
#elif (UNITY_IOS && !UNITY_EDITOR)
|
||||
lofeltHapticsSeekBinding(controller, time);
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void SetAmplitudeMultiplication(float factor)
|
||||
{
|
||||
#if (UNITY_ANDROID && !UNITY_EDITOR)
|
||||
JNIHelpers.Call(lofeltHaptics, setAmplitudeMultiplicationMethodId, factor);
|
||||
#elif (UNITY_IOS && !UNITY_EDITOR)
|
||||
lofeltHapticsSetAmplitudeMultiplicationBinding(controller, factor);
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void SetFrequencyShift(float shift)
|
||||
{
|
||||
#if (UNITY_IOS && !UNITY_EDITOR)
|
||||
lofeltHapticsSetFrequencyShiftBinding(controller, shift);
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void Loop(bool enabled)
|
||||
{
|
||||
#if (UNITY_ANDROID && !UNITY_EDITOR)
|
||||
JNIHelpers.Call(lofeltHaptics, loopMethodId, enabled);
|
||||
#elif (UNITY_IOS && !UNITY_EDITOR)
|
||||
lofeltHapticsLoopBinding(controller, enabled);
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void TriggerPresetHaptics(int type)
|
||||
{
|
||||
#if (UNITY_IOS && !UNITY_EDITOR)
|
||||
if (!systemHapticsInitialized)
|
||||
{
|
||||
lofeltHapticsSystemHapticsInitializeBinding();
|
||||
systemHapticsInitialized = true;
|
||||
}
|
||||
lofeltHapticsSystemHapticsTriggerBinding(type);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
18
Assets/External/Feel/NiceVibrations/Scripts/Components/LofeltHaptics.cs.meta
vendored
Normal file
18
Assets/External/Feel/NiceVibrations/Scripts/Components/LofeltHaptics.cs.meta
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 921537c8cf6464a24bd55f54ec8ea0d0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 183370
|
||||
packageName: Feel
|
||||
packageVersion: 5.9.1
|
||||
assetPath: Assets/Feel/NiceVibrations/Scripts/Components/LofeltHaptics.cs
|
||||
uploadId: 830868
|
||||
8
Assets/External/Feel/NiceVibrations/Scripts/Components/Resources.meta
vendored
Normal file
8
Assets/External/Feel/NiceVibrations/Scripts/Components/Resources.meta
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 16fde46bee7134093b2ebf5df05edebb
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
33
Assets/External/Feel/NiceVibrations/Scripts/Components/Resources/nv-constant-template.txt
vendored
Normal file
33
Assets/External/Feel/NiceVibrations/Scripts/Components/Resources/nv-constant-template.txt
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"version": {
|
||||
"major": 1,
|
||||
"minor": 0,
|
||||
"patch": 0
|
||||
},
|
||||
"signals": {
|
||||
"continuous": {
|
||||
"envelopes": {
|
||||
"amplitude": [
|
||||
{
|
||||
"time": 0.0,
|
||||
"amplitude": 1.0
|
||||
},
|
||||
{
|
||||
"time": {duration},
|
||||
"amplitude": 1.0
|
||||
}
|
||||
],
|
||||
"frequency": [
|
||||
{
|
||||
"time": 0,
|
||||
"frequency": 0.0
|
||||
},
|
||||
{
|
||||
"time": {duration},
|
||||
"frequency": 0.0
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
14
Assets/External/Feel/NiceVibrations/Scripts/Components/Resources/nv-constant-template.txt.meta
vendored
Normal file
14
Assets/External/Feel/NiceVibrations/Scripts/Components/Resources/nv-constant-template.txt.meta
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 28f1ec0b82fdf434b8efa0ef0f9a9d37
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 183370
|
||||
packageName: Feel
|
||||
packageVersion: 5.9.1
|
||||
assetPath: Assets/Feel/NiceVibrations/Scripts/Components/Resources/nv-constant-template.txt
|
||||
uploadId: 830868
|
||||
37
Assets/External/Feel/NiceVibrations/Scripts/Components/Resources/nv-emphasis-template.txt
vendored
Normal file
37
Assets/External/Feel/NiceVibrations/Scripts/Components/Resources/nv-emphasis-template.txt
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"version": {
|
||||
"major": 1,
|
||||
"minor": 0,
|
||||
"patch": 0
|
||||
},
|
||||
"signals": {
|
||||
"continuous": {
|
||||
"envelopes": {
|
||||
"amplitude": [
|
||||
{
|
||||
"time": 0.0,
|
||||
"amplitude": 0.0,
|
||||
"emphasis": {
|
||||
"amplitude": {amplitude},
|
||||
"frequency": {frequency}
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": {duration},
|
||||
"amplitude": 0.0
|
||||
}
|
||||
],
|
||||
"frequency": [
|
||||
{
|
||||
"time": 0,
|
||||
"frequency": 1.0
|
||||
},
|
||||
{
|
||||
"time": {duration},
|
||||
"frequency": 1.0
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
14
Assets/External/Feel/NiceVibrations/Scripts/Components/Resources/nv-emphasis-template.txt.meta
vendored
Normal file
14
Assets/External/Feel/NiceVibrations/Scripts/Components/Resources/nv-emphasis-template.txt.meta
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5f7cb0ced88db4af08a2dd3945067cd7
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 183370
|
||||
packageName: Feel
|
||||
packageVersion: 5.9.1
|
||||
assetPath: Assets/Feel/NiceVibrations/Scripts/Components/Resources/nv-emphasis-template.txt
|
||||
uploadId: 830868
|
||||
14
Assets/External/Feel/NiceVibrations/Scripts/Components/Resources/nv-pattern-template.txt
vendored
Normal file
14
Assets/External/Feel/NiceVibrations/Scripts/Components/Resources/nv-pattern-template.txt
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"version": {
|
||||
"major": 1,
|
||||
"minor": 0,
|
||||
"patch": 0
|
||||
},
|
||||
"signals": {
|
||||
"continuous": {
|
||||
"envelopes": {
|
||||
"amplitude": [ {amplitude-envelope} ]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
14
Assets/External/Feel/NiceVibrations/Scripts/Components/Resources/nv-pattern-template.txt.meta
vendored
Normal file
14
Assets/External/Feel/NiceVibrations/Scripts/Components/Resources/nv-pattern-template.txt.meta
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: db8a2f512b50d437b8268f17384f58c8
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 183370
|
||||
packageName: Feel
|
||||
packageVersion: 5.9.1
|
||||
assetPath: Assets/Feel/NiceVibrations/Scripts/Components/Resources/nv-pattern-template.txt
|
||||
uploadId: 830868
|
||||
8
Assets/External/Feel/NiceVibrations/Scripts/Editor.meta
vendored
Normal file
8
Assets/External/Feel/NiceVibrations/Scripts/Editor.meta
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 290aee7a75d474f7985eaad9d87ac572
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
119
Assets/External/Feel/NiceVibrations/Scripts/Editor/HapticImporter.cs
vendored
Normal file
119
Assets/External/Feel/NiceVibrations/Scripts/Editor/HapticImporter.cs
vendored
Normal file
@@ -0,0 +1,119 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using System.Text;
|
||||
|
||||
#if UNITY_2020_2_OR_NEWER
|
||||
using UnityEditor.AssetImporters;
|
||||
#elif UNITY_2019_4_OR_NEWER
|
||||
using UnityEditor.Experimental.AssetImporters;
|
||||
#endif
|
||||
|
||||
namespace Lofelt.NiceVibrations
|
||||
{
|
||||
[ScriptedImporter(version: 3, ext: "haptic", AllowCaching = true)]
|
||||
/// <summary>
|
||||
/// Provides an importer for the HapticClip component.
|
||||
/// </summary>
|
||||
///
|
||||
/// The importer takes a <c>.haptic</c> file and converts it into a HapticClip.
|
||||
public class HapticImporter : ScriptedImporter
|
||||
{
|
||||
#if !NICE_VIBRATIONS_DISABLE_GAMEPAD_SUPPORT
|
||||
[DllImport("nice_vibrations_editor_plugin")]
|
||||
private static extern IntPtr nv_plugin_convert_haptic_to_gamepad_rumble([In] byte[] bytes, long size);
|
||||
|
||||
[DllImport("nice_vibrations_editor_plugin")]
|
||||
private static extern void nv_plugin_destroy(IntPtr gamepadRumble);
|
||||
|
||||
[DllImport("nice_vibrations_editor_plugin")]
|
||||
private static extern UIntPtr nv_plugin_get_length(IntPtr gamepadRumble);
|
||||
|
||||
[DllImport("nice_vibrations_editor_plugin")]
|
||||
private static extern void nv_plugin_get_durations(IntPtr gamepadRumble, [Out] int[] durations);
|
||||
|
||||
[DllImport("nice_vibrations_editor_plugin")]
|
||||
private static extern void nv_plugin_get_low_frequency_motor_speeds(IntPtr gamepadRumble, [Out] float[] lowFrequencies);
|
||||
|
||||
[DllImport("nice_vibrations_editor_plugin")]
|
||||
private static extern void nv_plugin_get_high_frequency_motor_speeds(IntPtr gamepadRumble, [Out] float[] highFrequencies);
|
||||
|
||||
// We can not use "[return: MarshalAs(UnmanagedType.LPUTF8Str)]" here, and have to use
|
||||
// IntPtr for the return type instead. Otherwise the C# runtime tries to free the returned
|
||||
// string, which is invalid as the native plugin keeps ownership of the string.
|
||||
// We use PtrToStringUTF8() to manually convert the IntPtr to a string instead.
|
||||
[DllImport("nice_vibrations_editor_plugin")]
|
||||
private static extern IntPtr nv_plugin_get_last_error();
|
||||
|
||||
[DllImport("nice_vibrations_editor_plugin")]
|
||||
private static extern UIntPtr nv_plugin_get_last_error_length();
|
||||
|
||||
// Alternative to Marshal.PtrToStringUTF8() which was introduced in .NET 5 and isn't yet
|
||||
// supported by Unity
|
||||
private string PtrToStringUTF8(IntPtr ptr, int length)
|
||||
{
|
||||
byte[] bytes = new byte[length];
|
||||
Marshal.Copy(ptr, bytes, 0, length);
|
||||
return Encoding.UTF8.GetString(bytes, 0, length);
|
||||
}
|
||||
#endif
|
||||
|
||||
public override void OnImportAsset(AssetImportContext ctx)
|
||||
{
|
||||
// Load .haptic clip from file
|
||||
var fileName = System.IO.Path.GetFileNameWithoutExtension(ctx.assetPath);
|
||||
var jsonBytes = File.ReadAllBytes(ctx.assetPath);
|
||||
var hapticClip = HapticClip.CreateInstance<HapticClip>();
|
||||
hapticClip.json = jsonBytes;
|
||||
|
||||
#if !NICE_VIBRATIONS_DISABLE_GAMEPAD_SUPPORT
|
||||
// Convert JSON to a GamepadRumble struct. The conversion algorithm is inside the native
|
||||
// library nice_vibrations_editor_plugin. That plugin is only used in the Unity editor, and
|
||||
// not at runtime.
|
||||
GamepadRumble rumble = default;
|
||||
IntPtr nativeRumble = nv_plugin_convert_haptic_to_gamepad_rumble(jsonBytes, jsonBytes.Length);
|
||||
if (nativeRumble != IntPtr.Zero)
|
||||
{
|
||||
try
|
||||
{
|
||||
uint length = (uint)nv_plugin_get_length(nativeRumble);
|
||||
rumble.durationsMs = new int[length];
|
||||
rumble.lowFrequencyMotorSpeeds = new float[length];
|
||||
rumble.highFrequencyMotorSpeeds = new float[length];
|
||||
|
||||
nv_plugin_get_durations(nativeRumble, rumble.durationsMs);
|
||||
nv_plugin_get_low_frequency_motor_speeds(nativeRumble, rumble.lowFrequencyMotorSpeeds);
|
||||
nv_plugin_get_high_frequency_motor_speeds(nativeRumble, rumble.highFrequencyMotorSpeeds);
|
||||
|
||||
int totalDurationMs = 0;
|
||||
foreach (int duration in rumble.durationsMs)
|
||||
{
|
||||
totalDurationMs += duration;
|
||||
}
|
||||
rumble.totalDurationMs = totalDurationMs;
|
||||
}
|
||||
finally
|
||||
{
|
||||
nv_plugin_destroy(nativeRumble);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var lastErrorPtr = nv_plugin_get_last_error();
|
||||
var lastErrorLength = (int)nv_plugin_get_last_error_length();
|
||||
var lastError = PtrToStringUTF8(lastErrorPtr, lastErrorLength);
|
||||
Debug.LogWarning($"Failed to convert haptic clip {ctx.assetPath} to gamepad rumble: {lastError}");
|
||||
}
|
||||
|
||||
hapticClip.gamepadRumble = rumble;
|
||||
#endif
|
||||
|
||||
// Use hapticClip as the imported asset
|
||||
ctx.AddObjectToAsset("com.lofelt.HapticClip", hapticClip);
|
||||
ctx.SetMainObject(hapticClip);
|
||||
}
|
||||
}
|
||||
}
|
||||
18
Assets/External/Feel/NiceVibrations/Scripts/Editor/HapticImporter.cs.meta
vendored
Normal file
18
Assets/External/Feel/NiceVibrations/Scripts/Editor/HapticImporter.cs.meta
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dc84fb4fa9e67485a972c887d976d004
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 183370
|
||||
packageName: Feel
|
||||
packageVersion: 5.9.1
|
||||
assetPath: Assets/Feel/NiceVibrations/Scripts/Editor/HapticImporter.cs
|
||||
uploadId: 830868
|
||||
155
Assets/External/Feel/NiceVibrations/Scripts/Editor/HapticSourceInspector.cs
vendored
Normal file
155
Assets/External/Feel/NiceVibrations/Scripts/Editor/HapticSourceInspector.cs
vendored
Normal file
@@ -0,0 +1,155 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using System.IO;
|
||||
|
||||
namespace Lofelt.NiceVibrations
|
||||
{
|
||||
[CustomEditor(typeof(HapticSource))]
|
||||
[CanEditMultipleObjects]
|
||||
/// <summary>
|
||||
/// Provides an inspector for the HapticSource component
|
||||
/// </summary>
|
||||
///
|
||||
/// The inspector lets you link a HapticSource to a HapticClip.
|
||||
public class HapticSourceInspector : Editor
|
||||
{
|
||||
string hapticsDirectory;
|
||||
|
||||
SerializedProperty hapticClip;
|
||||
SerializedProperty priority;
|
||||
SerializedProperty level;
|
||||
SerializedProperty frequencyShift;
|
||||
SerializedProperty loop;
|
||||
SerializedProperty fallbackPreset;
|
||||
|
||||
public static GUIContent hapticClipLabel = EditorGUIUtility.TrTextContent("Haptic Clip", "The HapticClip asset played by the HapticSource.");
|
||||
public static GUIContent fallbackPresetLabel = EditorGUIUtility.TrTextContent("Haptic Preset fallback", "Set the haptic preset to play in case the device doesn't support playback of haptic clips");
|
||||
public static GUIContent loopLabel = EditorGUIUtility.TrTextContent("Loop", "Set the haptic source to loop playback of the haptic clip");
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
hapticClip = serializedObject.FindProperty("clip");
|
||||
priority = serializedObject.FindProperty("priority");
|
||||
level = serializedObject.FindProperty("_level");
|
||||
frequencyShift = serializedObject.FindProperty("_frequencyShift");
|
||||
fallbackPreset = serializedObject.FindProperty("_fallbackPreset");
|
||||
loop = serializedObject.FindProperty("_loop");
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.PropertyField(hapticClip, hapticClipLabel);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.PropertyField(fallbackPreset, fallbackPresetLabel);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.PropertyField(loop, loopLabel);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
EditorGUILayout.Space();
|
||||
|
||||
CreatePrioritySlider();
|
||||
CreateLevelSlider();
|
||||
CreateFrequencyShiftSlider();
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
/// Helper function to create a priority slider for haptic source with High and Max text labels.
|
||||
void CreatePrioritySlider()
|
||||
{
|
||||
Rect position = EditorGUILayout.GetControlRect(true, EditorGUIUtility.singleLineHeight);
|
||||
|
||||
EditorGUI.IntSlider(position, priority, 0, 256);
|
||||
|
||||
// Move to next line
|
||||
position.y += EditorGUIUtility.singleLineHeight;
|
||||
|
||||
// Subtract the label
|
||||
position.x += EditorGUIUtility.labelWidth;
|
||||
position.width -= EditorGUIUtility.labelWidth;
|
||||
|
||||
// Subtract the text field width thats drawn with slider
|
||||
position.width -= EditorGUIUtility.fieldWidth;
|
||||
|
||||
GUIStyle style = GUI.skin.label;
|
||||
TextAnchor defaultAlignment = GUI.skin.label.alignment;
|
||||
style.alignment = TextAnchor.UpperLeft; EditorGUI.LabelField(position, "High", style);
|
||||
style.alignment = TextAnchor.UpperRight; EditorGUI.LabelField(position, "Low", style);
|
||||
GUI.skin.label.alignment = defaultAlignment;
|
||||
|
||||
// Allow space for the High/Low labels
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
/// Helper function to create a level slider for haptic
|
||||
/// source with labels.
|
||||
void CreateLevelSlider()
|
||||
{
|
||||
Rect position = EditorGUILayout.GetControlRect(true, EditorGUIUtility.singleLineHeight);
|
||||
|
||||
EditorGUI.Slider(position, level, 0.0f, 5.0f);
|
||||
|
||||
// Move to next line
|
||||
position.y += EditorGUIUtility.singleLineHeight;
|
||||
|
||||
// Subtract the label
|
||||
position.x += EditorGUIUtility.labelWidth;
|
||||
position.width -= EditorGUIUtility.labelWidth;
|
||||
|
||||
// Subtract the text field width thats drawn with slider
|
||||
position.width -= EditorGUIUtility.fieldWidth;
|
||||
|
||||
GUIStyle style = GUI.skin.label;
|
||||
TextAnchor defaultAlignment = GUI.skin.label.alignment;
|
||||
style.alignment = TextAnchor.UpperLeft; EditorGUI.LabelField(position, "0.0", style);
|
||||
style.alignment = TextAnchor.UpperRight; EditorGUI.LabelField(position, "5.0", style);
|
||||
GUI.skin.label.alignment = defaultAlignment;
|
||||
|
||||
// Allow space for the labels
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
/// Helper function to create a frequency shift slider for haptic
|
||||
/// source with labels.
|
||||
void CreateFrequencyShiftSlider()
|
||||
{
|
||||
Rect position = EditorGUILayout.GetControlRect(true, EditorGUIUtility.singleLineHeight);
|
||||
|
||||
EditorGUI.Slider(position, frequencyShift, -1.0f, 1.0f);
|
||||
|
||||
// Move to next line
|
||||
position.y += EditorGUIUtility.singleLineHeight;
|
||||
|
||||
// Subtract the label
|
||||
position.x += EditorGUIUtility.labelWidth;
|
||||
position.width -= EditorGUIUtility.labelWidth;
|
||||
|
||||
// Subtract the text field width thats drawn with slider
|
||||
position.width -= EditorGUIUtility.fieldWidth;
|
||||
|
||||
GUIStyle style = GUI.skin.label;
|
||||
TextAnchor defaultAlignment = GUI.skin.label.alignment;
|
||||
style.alignment = TextAnchor.UpperLeft; EditorGUI.LabelField(position, "-1.0", style);
|
||||
style.alignment = TextAnchor.UpperRight; EditorGUI.LabelField(position, "1.0", style);
|
||||
GUI.skin.label.alignment = defaultAlignment;
|
||||
|
||||
// Allow space for the labels
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
}
|
||||
}
|
||||
18
Assets/External/Feel/NiceVibrations/Scripts/Editor/HapticSourceInspector.cs.meta
vendored
Normal file
18
Assets/External/Feel/NiceVibrations/Scripts/Editor/HapticSourceInspector.cs.meta
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 90a030b5ab0574cd9880e136f5e0261c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 183370
|
||||
packageName: Feel
|
||||
packageVersion: 5.9.1
|
||||
assetPath: Assets/Feel/NiceVibrations/Scripts/Editor/HapticSourceInspector.cs
|
||||
uploadId: 830868
|
||||
17
Assets/External/Feel/NiceVibrations/Scripts/Editor/Lofelt.NiceVibrations.Editor.asmdef
vendored
Normal file
17
Assets/External/Feel/NiceVibrations/Scripts/Editor/Lofelt.NiceVibrations.Editor.asmdef
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "Lofelt.NiceVibrations.Editor",
|
||||
"references": [
|
||||
"GUID:57a0b9bc628ab4740af4b6f1f0b2e134"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
14
Assets/External/Feel/NiceVibrations/Scripts/Editor/Lofelt.NiceVibrations.Editor.asmdef.meta
vendored
Normal file
14
Assets/External/Feel/NiceVibrations/Scripts/Editor/Lofelt.NiceVibrations.Editor.asmdef.meta
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 67bc5fafbf62b48858241ce814d3d489
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 183370
|
||||
packageName: Feel
|
||||
packageVersion: 5.9.1
|
||||
assetPath: Assets/Feel/NiceVibrations/Scripts/Editor/Lofelt.NiceVibrations.Editor.asmdef
|
||||
uploadId: 830868
|
||||
165
Assets/External/Feel/NiceVibrations/Scripts/Editor/NVHapticDataDrawer.cs
vendored
Normal file
165
Assets/External/Feel/NiceVibrations/Scripts/Editor/NVHapticDataDrawer.cs
vendored
Normal file
@@ -0,0 +1,165 @@
|
||||
using Lofelt.NiceVibrations;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
|
||||
namespace MoreMountains.FeedbacksForThirdParty
|
||||
{
|
||||
/// <summary>
|
||||
/// A custom drawer for haptic data used by the NV Clip feedback
|
||||
/// </summary>
|
||||
[CustomPropertyDrawer(typeof(NVHapticData))]
|
||||
public class NVHapticDataDrawer : PropertyDrawer
|
||||
{
|
||||
/// <summary>
|
||||
/// Property height computation
|
||||
/// </summary>
|
||||
/// <param name="property"></param>
|
||||
/// <param name="label"></param>
|
||||
/// <returns></returns>
|
||||
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
|
||||
{
|
||||
NVHapticData data = property.boxedValue as NVHapticData;
|
||||
|
||||
if (data.Clip == null)
|
||||
{
|
||||
return EditorGUIUtility.singleLineHeight;
|
||||
}
|
||||
else
|
||||
{
|
||||
return EditorGUIUtility.singleLineHeight * 14;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Drawer
|
||||
/// </summary>
|
||||
/// <param name="position"></param>
|
||||
/// <param name="property"></param>
|
||||
/// <param name="label"></param>
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
SerializedProperty rumbleData = property.FindPropertyRelative("RumbleData");
|
||||
SerializedProperty totalDurationProp = rumbleData.FindPropertyRelative("totalDurationMs");
|
||||
NVHapticData data = property.boxedValue as NVHapticData;
|
||||
|
||||
if (data.Clip == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EditorGUI.LabelField(new Rect(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight),
|
||||
label);
|
||||
|
||||
float lineHeight = EditorGUIUtility.singleLineHeight;
|
||||
float y = position.y + lineHeight;
|
||||
|
||||
EditorGUI.PropertyField(new Rect(position.x, y, position.width, lineHeight), totalDurationProp);
|
||||
y += lineHeight + 2;
|
||||
|
||||
// Generate curves
|
||||
|
||||
int[] amplitudeDurations = new int[data.AmplitudePoints.Count];
|
||||
float[] amplitudeValues = new float[data.AmplitudePoints.Count];
|
||||
float[] frequencyValues = new float[data.AmplitudePoints.Count];
|
||||
for (int i = 0; i < data.AmplitudePoints.Count; i++)
|
||||
{
|
||||
var point = data.AmplitudePoints[i];
|
||||
amplitudeDurations[i] = (int)(data.AmplitudePoints[i].time * 1000f);
|
||||
amplitudeValues[i] = data.AmplitudePoints[i].emphasis.amplitude;
|
||||
frequencyValues[i] = data.FrequencyPoints[i].frequency;
|
||||
}
|
||||
|
||||
AnimationCurve amplitudeCurve = GenerateCurve(amplitudeDurations, amplitudeValues);
|
||||
AnimationCurve emphasisCurve = GenerateCurve(amplitudeDurations, frequencyValues);
|
||||
// Create rect for combined curve
|
||||
Rect amplitudeCurveRect = new Rect(position.x, y, position.width, lineHeight * 5);
|
||||
// Draw background
|
||||
EditorGUI.DrawRect(amplitudeCurveRect, new Color(0.15f, 0.15f, 0.15f));
|
||||
// Draw both curves manually
|
||||
Handles.BeginGUI();
|
||||
DrawCurveInRect(amplitudeCurve, amplitudeCurveRect, Color.yellow, data.SampleCount);
|
||||
DrawCurveInRect(emphasisCurve, amplitudeCurveRect, Color.red, data.SampleCount);
|
||||
Handles.EndGUI();
|
||||
|
||||
EditorGUI.LabelField(new Rect(position.x, y + amplitudeCurveRect.height, position.width, lineHeight),
|
||||
"Yellow = Amplitude | Red = Frequency");
|
||||
|
||||
y += lineHeight * 6;
|
||||
|
||||
// Generate curves
|
||||
AnimationCurve lowCurve = GenerateCurve(amplitudeDurations, data.RumbleData.lowFrequencyMotorSpeeds);
|
||||
AnimationCurve highCurve = GenerateCurve(amplitudeDurations, data.RumbleData.highFrequencyMotorSpeeds);
|
||||
// Create rect for combined curve
|
||||
Rect curveRect = new Rect(position.x, y, position.width, lineHeight * 5);
|
||||
// Draw background
|
||||
EditorGUI.DrawRect(curveRect, new Color(0.15f, 0.15f, 0.15f));
|
||||
// Draw both curves manually
|
||||
Handles.BeginGUI();
|
||||
DrawCurveInRect(lowCurve, curveRect, Color.green, data.SampleCount);
|
||||
DrawCurveInRect(highCurve, curveRect, Color.cyan, data.SampleCount);
|
||||
Handles.EndGUI();
|
||||
|
||||
EditorGUI.LabelField(new Rect(position.x, y + curveRect.height, position.width, lineHeight),
|
||||
"Cyan = Amplitude | Green = Frequency");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Curve drawing
|
||||
/// </summary>
|
||||
/// <param name="curve"></param>
|
||||
/// <param name="rect"></param>
|
||||
/// <param name="color"></param>
|
||||
/// <param name="sampleCount"></param>
|
||||
private void DrawCurveInRect(AnimationCurve curve, Rect rect, Color color, int sampleCount)
|
||||
{
|
||||
if (curve.length < 2)
|
||||
return;
|
||||
|
||||
Handles.color = color;
|
||||
Vector3[] points = new Vector3[sampleCount];
|
||||
|
||||
float startTime = curve.keys[0].time;
|
||||
float endTime = curve.keys[curve.length - 1].time;
|
||||
float timeRange = endTime - startTime;
|
||||
|
||||
for (int i = 0; i < points.Length; i++)
|
||||
{
|
||||
float t = Mathf.Lerp(startTime, endTime, i / (float)(points.Length - 1));
|
||||
float val = curve.Evaluate(t);
|
||||
|
||||
float x = Mathf.Lerp(rect.x, rect.xMax, (t - startTime) / timeRange);
|
||||
float y = Mathf.Lerp(rect.yMax, rect.y, val);
|
||||
|
||||
points[i] = new Vector3(x, y, 0);
|
||||
}
|
||||
|
||||
Handles.DrawAAPolyLine(2f, points);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Curve generation
|
||||
/// </summary>
|
||||
/// <param name="durationsProperty"></param>
|
||||
/// <param name="valueProperty"></param>
|
||||
/// <returns></returns>
|
||||
private AnimationCurve GenerateCurve(int[] durationsProperty, float[] valueProperty)
|
||||
{
|
||||
AnimationCurve curve = new AnimationCurve();
|
||||
|
||||
if (durationsProperty == null || valueProperty == null || durationsProperty.Length != valueProperty.Length)
|
||||
return curve;
|
||||
|
||||
float time = 0f;
|
||||
for (int i = 0; i < valueProperty.Length; i++)
|
||||
{
|
||||
int duration = durationsProperty[i];
|
||||
float speed = valueProperty[i];
|
||||
|
||||
curve.AddKey(time, speed);
|
||||
time += duration / 1000f;
|
||||
}
|
||||
|
||||
return curve;
|
||||
}
|
||||
}
|
||||
}
|
||||
9
Assets/External/Feel/NiceVibrations/Scripts/Editor/NVHapticDataDrawer.cs.meta
vendored
Normal file
9
Assets/External/Feel/NiceVibrations/Scripts/Editor/NVHapticDataDrawer.cs.meta
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 89b1ab110187b0b4ebad017b364aafea
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 183370
|
||||
packageName: Feel
|
||||
packageVersion: 5.9.1
|
||||
assetPath: Assets/Feel/NiceVibrations/Scripts/Editor/NVHapticDataDrawer.cs
|
||||
uploadId: 830868
|
||||
Reference in New Issue
Block a user