| using System; |
| using System.Collections.Generic; |
| using System.Linq; |
| using System.Reflection; |
| using UnityEngine.InputSystem.Controls; |
| using NUnit.Framework; |
| using NUnit.Framework.Constraints; |
| using NUnit.Framework.Internal; |
| using Unity.Collections; |
| using UnityEngine.InputSystem.LowLevel; |
| using UnityEngine.InputSystem.Utilities; |
| using UnityEngine.TestTools; |
| using UnityEngine.TestTools.Utils; |
| #if UNITY_EDITOR |
| using UnityEditor; |
| using UnityEngine.InputSystem.Editor; |
| #endif |
|
|
| |
|
|
| |
|
|
| |
|
|
| namespace UnityEngine.InputSystem |
| { |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| public class InputTestFixture |
| { |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| [SetUp] |
| public virtual void Setup() |
| { |
| try |
| { |
| |
| m_KeyInfos = default; |
| m_IsUnityTest = default; |
| m_CurrentTest = default; |
|
|
| |
| |
| #if UNITY_EDITOR |
| InputDebuggerWindow.Disable(); |
| #endif |
|
|
| runtime = new InputTestRuntime(); |
|
|
| |
| InputSystem.SaveAndReset(enableRemoting: false, runtime: runtime); |
|
|
| |
| |
| #if UNITY_EDITOR |
| InputSystem.settings.editorInputBehaviorInPlayMode = InputSettings.EditorInputBehaviorInPlayMode.AllDeviceInputAlwaysGoesToGameView; |
| #endif |
|
|
| |
| |
| #if UNITY_EDITOR |
| if (Application.isPlaying && IsUnityTest()) |
| InputSystem.s_Manager.m_UpdateMask &= ~InputUpdateType.Editor; |
| #endif |
|
|
| |
| |
| NativeLeakDetection.Mode = NativeLeakDetectionMode.EnabledWithStackTrace; |
|
|
| |
| |
| |
| |
| |
| |
| NativeInputRuntime.instance.onUpdate = |
| (InputUpdateType updateType, ref InputEventBuffer buffer) => |
| { |
| if (InputSystem.s_Manager.ShouldRunUpdate(updateType)) |
| InputSystem.Update(updateType); |
| |
| buffer.Reset(); |
| }; |
| NativeInputRuntime.instance.onShouldRunUpdate = |
| updateType => true; |
|
|
| #if UNITY_EDITOR |
| m_OnPlayModeStateChange = OnPlayModeStateChange; |
| EditorApplication.playModeStateChanged += m_OnPlayModeStateChange; |
| #endif |
|
|
| |
| InputSystem.settings.disableRedundantEventsMerging = false; |
|
|
| |
| InputSystem.settings.SetInternalFeatureFlag(InputFeatureNames.kUseOptimizedControls, true); |
| InputSystem.settings.SetInternalFeatureFlag(InputFeatureNames.kUseReadValueCaching, true); |
| InputSystem.settings.SetInternalFeatureFlag(InputFeatureNames.kParanoidReadValueCachingChecks, true); |
| } |
| catch (Exception exception) |
| { |
| Debug.LogError("Failed to set up input system for test " + TestContext.CurrentContext.Test.Name); |
| Debug.LogException(exception); |
| throw; |
| } |
|
|
| m_Initialized = true; |
|
|
| if (InputSystem.devices.Count > 0) |
| Assert.Fail("Input system should not have devices after reset"); |
| } |
|
|
| |
| |
| |
| |
| [TearDown] |
| public virtual void TearDown() |
| { |
| if (!m_Initialized) |
| return; |
|
|
| try |
| { |
| InputSystem.Restore(); |
| runtime.Dispose(); |
|
|
| |
| #if UNITY_EDITOR |
| if (m_OnPlayModeStateChange != null) |
| EditorApplication.playModeStateChanged -= m_OnPlayModeStateChange; |
| #endif |
|
|
| |
| #if UNITY_EDITOR |
| InputDebuggerWindow.Enable(); |
| #endif |
| } |
| catch (Exception exception) |
| { |
| Debug.LogError("Failed to shut down and restore input system after test " + TestContext.CurrentContext.Test.Name); |
| Debug.LogException(exception); |
| throw; |
| } |
|
|
| m_Initialized = false; |
| } |
|
|
| private bool? m_IsUnityTest; |
| private Test m_CurrentTest; |
|
|
| |
| private bool IsUnityTest() |
| { |
| |
| |
| |
| var test = TestContext.CurrentTestExecutionContext.CurrentTest; |
| if (m_IsUnityTest.HasValue && m_CurrentTest == test) |
| return m_IsUnityTest.Value; |
|
|
| var className = test.ClassName; |
| var methodName = test.MethodName; |
|
|
| |
| |
|
|
| var type = Type.GetType(className); |
| if (type == null) |
| { |
| foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) |
| { |
| type = assembly.GetType(className); |
| if (type != null) |
| break; |
| } |
| } |
|
|
| if (type == null) |
| { |
| m_IsUnityTest = false; |
| } |
| else |
| { |
| var method = type.GetMethod(methodName); |
| m_IsUnityTest = method?.GetCustomAttribute<UnityTestAttribute>() != null; |
| } |
|
|
| m_CurrentTest = test; |
| return m_IsUnityTest.Value; |
| } |
|
|
| #if UNITY_EDITOR |
| private Action<PlayModeStateChange> m_OnPlayModeStateChange; |
| private void OnPlayModeStateChange(PlayModeStateChange change) |
| { |
| if (change == PlayModeStateChange.ExitingPlayMode && m_Initialized) |
| TearDown(); |
| } |
|
|
| #endif |
|
|
| |
| public static void AssertButtonPress<TState>(InputDevice device, TState state, params ButtonControl[] buttons) |
| where TState : struct, IInputStateTypeInfo |
| { |
| |
| InputSystem.QueueStateEvent(device, state); |
| InputSystem.Update(); |
|
|
| |
| foreach (var control in device.allControls) |
| { |
| if (!(control is ButtonControl controlAsButton)) |
| continue; |
|
|
| var isInList = buttons.Contains(controlAsButton); |
| if (!isInList) |
| Assert.That(controlAsButton.isPressed, Is.False, |
| $"Expected button {controlAsButton} to NOT be pressed"); |
| else |
| Assert.That(controlAsButton.isPressed, Is.True, |
| $"Expected button {controlAsButton} to be pressed"); |
| } |
| } |
|
|
| public static void AssertStickValues(StickControl stick, Vector2 stickValue, float up, float down, float left, |
| float right) |
| { |
| Assert.That(stick.ReadUnprocessedValue(), Is.EqualTo(stickValue)); |
|
|
| Assert.That(stick.up.ReadUnprocessedValue(), Is.EqualTo(up).Within(0.0001), "Incorrect 'up' value"); |
| Assert.That(stick.down.ReadUnprocessedValue(), Is.EqualTo(down).Within(0.0001), "Incorrect 'down' value"); |
| Assert.That(stick.left.ReadUnprocessedValue(), Is.EqualTo(left).Within(0.0001), "Incorrect 'left' value"); |
| Assert.That(stick.right.ReadUnprocessedValue(), Is.EqualTo(right).Within(0.0001), "Incorrect 'right' value"); |
| } |
|
|
| private Dictionary<Key, Tuple<string, int>> m_KeyInfos; |
| private bool m_Initialized; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| public unsafe void SetKeyboardLayout(string name, Keyboard keyboard = null) |
| { |
| if (keyboard == null) |
| { |
| keyboard = Keyboard.current; |
| if (keyboard == null) |
| throw new ArgumentException("No keyboard has been created and no keyboard has been given", nameof(keyboard)); |
| } |
|
|
| runtime.SetDeviceCommandCallback(keyboard, (id, command) => |
| { |
| if (id == QueryKeyboardLayoutCommand.Type) |
| { |
| var commandPtr = (QueryKeyboardLayoutCommand*)command; |
| commandPtr->WriteLayoutName(name); |
| return InputDeviceCommand.GenericSuccess; |
| } |
| return InputDeviceCommand.GenericFailure; |
| }); |
|
|
| |
| InputSystem.QueueConfigChangeEvent(Keyboard.current); |
| InputSystem.Update(); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| public unsafe void SetKeyInfo(Key key, string displayName, int scanCode = 0) |
| { |
| if (Keyboard.current == null) |
| InputSystem.AddDevice<Keyboard>(); |
|
|
| if (m_KeyInfos == null) |
| { |
| m_KeyInfos = new Dictionary<Key, Tuple<string, int>>(); |
|
|
| runtime.SetDeviceCommandCallback(Keyboard.current, |
| (id, commandPtr) => |
| { |
| if (commandPtr->type == QueryKeyNameCommand.Type) |
| { |
| var keyNameCommand = (QueryKeyNameCommand*)commandPtr; |
|
|
| if (m_KeyInfos.TryGetValue((Key)keyNameCommand->scanOrKeyCode, out var info)) |
| { |
| keyNameCommand->scanOrKeyCode = info.Item2; |
| StringHelpers.WriteStringToBuffer(info.Item1, (IntPtr)keyNameCommand->nameBuffer, |
| QueryKeyNameCommand.kMaxNameLength); |
| } |
|
|
| return QueryKeyNameCommand.kSize; |
| } |
|
|
| return InputDeviceCommand.GenericFailure; |
| }); |
| } |
|
|
| m_KeyInfos[key] = new Tuple<string, int>(displayName, scanCode); |
|
|
| |
| InputSystem.QueueConfigChangeEvent(Keyboard.current); |
| InputSystem.Update(); |
| } |
|
|
| |
| |
| |
| |
| |
| internal unsafe void SetCanRunInBackground(InputDevice device, bool canRunInBackground = true) |
| { |
| runtime.SetDeviceCommandCallback(device, (id, command) => |
| { |
| if (command->type == QueryCanRunInBackground.Type) |
| { |
| ((QueryCanRunInBackground*)command)->canRunInBackground = canRunInBackground; |
| return InputDeviceCommand.GenericSuccess; |
| } |
| return InputDeviceCommand.GenericFailure; |
| }); |
| } |
|
|
| public ActionConstraint Started(InputAction action, InputControl control = null, double? time = null, object value = null) |
| { |
| return new ActionConstraint(InputActionPhase.Started, action, control, time: time, duration: 0, value: value); |
| } |
|
|
| public ActionConstraint Started<TValue>(InputAction action, InputControl<TValue> control, TValue value, double? time = null) |
| where TValue : struct |
| { |
| return new ActionConstraint(InputActionPhase.Started, action, control, value, time: time, duration: 0); |
| } |
|
|
| public ActionConstraint Performed(InputAction action, InputControl control = null, double? time = null, double? duration = null, object value = null) |
| { |
| return new ActionConstraint(InputActionPhase.Performed, action, control, time: time, duration: duration, value: value); |
| } |
|
|
| public ActionConstraint Performed<TValue>(InputAction action, InputControl<TValue> control, TValue value, double? time = null, double? duration = null) |
| where TValue : struct |
| { |
| return new ActionConstraint(InputActionPhase.Performed, action, control, value, time: time, duration: duration); |
| } |
|
|
| public ActionConstraint Canceled(InputAction action, InputControl control = null, double? time = null, double? duration = null, object value = null) |
| { |
| return new ActionConstraint(InputActionPhase.Canceled, action, control, time: time, duration: duration, value: value); |
| } |
|
|
| public ActionConstraint Canceled<TValue>(InputAction action, InputControl<TValue> control, TValue value, double? time = null, double? duration = null) |
| where TValue : struct |
| { |
| return new ActionConstraint(InputActionPhase.Canceled, action, control, value, time: time, duration: duration); |
| } |
|
|
| public ActionConstraint Started<TInteraction>(InputAction action, InputControl control = null, object value = null, double? time = null) |
| where TInteraction : IInputInteraction |
| { |
| return new ActionConstraint(InputActionPhase.Started, action, control, interaction: typeof(TInteraction), time: time, |
| duration: 0, value: value); |
| } |
|
|
| public ActionConstraint Performed<TInteraction>(InputAction action, InputControl control = null, object value = null, double? time = null, double? duration = null) |
| where TInteraction : IInputInteraction |
| { |
| return new ActionConstraint(InputActionPhase.Performed, action, control, interaction: typeof(TInteraction), time: time, |
| duration: duration, value: value); |
| } |
|
|
| public ActionConstraint Canceled<TInteraction>(InputAction action, InputControl control = null, object value = null, double? time = null, double? duration = null) |
| where TInteraction : IInputInteraction |
| { |
| return new ActionConstraint(InputActionPhase.Canceled, action, control, interaction: typeof(TInteraction), time: time, |
| duration: duration, value: value); |
| } |
|
|
| |
|
|
| |
| public void Press(ButtonControl button, double time = -1, double timeOffset = 0, bool queueEventOnly = false) |
| { |
| Set(button, 1, time, timeOffset, queueEventOnly: queueEventOnly); |
| } |
|
|
| |
| public void Release(ButtonControl button, double time = -1, double timeOffset = 0, bool queueEventOnly = false) |
| { |
| Set(button, 0, time, timeOffset, queueEventOnly: queueEventOnly); |
| } |
|
|
| |
| public void PressAndRelease(ButtonControl button, double time = -1, double timeOffset = 0, bool queueEventOnly = false) |
| { |
| Press(button, time, timeOffset, queueEventOnly: true); |
| Release(button, time, timeOffset, queueEventOnly: queueEventOnly); |
| } |
|
|
| |
| public void Click(ButtonControl button, double time = -1, double timeOffset = 0, bool queueEventOnly = false) |
| { |
| PressAndRelease(button, time, timeOffset, queueEventOnly: queueEventOnly); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| public void Set<TValue>(InputDevice device, string path, TValue state, double time = -1, double timeOffset = 0, |
| bool queueEventOnly = false) |
| where TValue : struct |
| { |
| if (device == null) |
| throw new ArgumentNullException(nameof(device)); |
| if (string.IsNullOrEmpty(path)) |
| throw new ArgumentNullException(nameof(path)); |
|
|
| var control = (InputControl<TValue>)device[path]; |
| Set(control, state, time, timeOffset, queueEventOnly); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| public void Set<TValue>(InputControl<TValue> control, TValue state, double time = -1, double timeOffset = 0, bool queueEventOnly = false) |
| where TValue : struct |
| { |
| if (control == null) |
| throw new ArgumentNullException(nameof(control)); |
| if (!control.device.added) |
| throw new ArgumentException( |
| $"Device of control '{control}' has not been added to the system", nameof(control)); |
|
|
| if (IsUnityTest()) |
| queueEventOnly = true; |
|
|
| void SetUpAndQueueEvent(InputEventPtr eventPtr) |
| { |
| eventPtr.time = (time >= 0 ? time : InputState.currentTime) + timeOffset; |
| control.WriteValueIntoEvent(state, eventPtr); |
| InputSystem.QueueEvent(eventPtr); |
| } |
|
|
| |
| if (control is TouchControl) |
| { |
| using (StateEvent.From(control.device, out var eventPtr)) |
| SetUpAndQueueEvent(eventPtr); |
| } |
| else |
| { |
| |
| |
| |
| |
| |
| |
| |
| |
| using (DeltaStateEvent.From(control, out var eventPtr)) |
| SetUpAndQueueEvent(eventPtr); |
| } |
|
|
| if (!queueEventOnly) |
| InputSystem.Update(); |
| } |
|
|
| public void Move(InputControl<Vector2> positionControl, Vector2 position, Vector2? delta = null, double time = -1, double timeOffset = 0, bool queueEventOnly = false) |
| { |
| Set(positionControl, position, time: time, timeOffset: timeOffset, queueEventOnly: true); |
|
|
| var deltaControl = (Vector2Control)positionControl.device.TryGetChildControl("delta"); |
| if (deltaControl != null) |
| Set(deltaControl, delta ?? position - positionControl.ReadValue(), time: time, timeOffset: timeOffset, queueEventOnly: true); |
|
|
| if (!queueEventOnly) |
| InputSystem.Update(); |
| } |
|
|
| |
| public void BeginTouch(int touchId, Vector2 position, bool queueEventOnly = false, Touchscreen screen = null, |
| double time = -1, double timeOffset = 0, byte displayIndex = 0) |
| { |
| SetTouch(touchId, TouchPhase.Began, position, 1, queueEventOnly: queueEventOnly, screen: screen, time: time, timeOffset: timeOffset, displayIndex: displayIndex); |
| } |
|
|
| public void BeginTouch(int touchId, Vector2 position, float pressure, bool queueEventOnly = false, Touchscreen screen = null, |
| double time = -1, double timeOffset = 0) |
| { |
| SetTouch(touchId, TouchPhase.Began, position, pressure, queueEventOnly: queueEventOnly, screen: screen, time: time, timeOffset: timeOffset); |
| } |
|
|
| |
| public void MoveTouch(int touchId, Vector2 position, Vector2 delta = default, bool queueEventOnly = false, |
| Touchscreen screen = null, double time = -1, double timeOffset = 0) |
| { |
| SetTouch(touchId, TouchPhase.Moved, position, 1, delta, queueEventOnly: queueEventOnly, screen: screen, time: time, timeOffset: timeOffset); |
| } |
|
|
| public void MoveTouch(int touchId, Vector2 position, float pressure, Vector2 delta = default, bool queueEventOnly = false, |
| Touchscreen screen = null, double time = -1, double timeOffset = 0) |
| { |
| SetTouch(touchId, TouchPhase.Moved, position, pressure, delta, queueEventOnly, screen: screen, time: time, timeOffset: timeOffset); |
| } |
|
|
| |
| public void EndTouch(int touchId, Vector2 position, Vector2 delta = default, bool queueEventOnly = false, |
| Touchscreen screen = null, double time = -1, double timeOffset = 0, byte displayIndex = 0) |
| { |
| SetTouch(touchId, TouchPhase.Ended, position, 1, delta, queueEventOnly: queueEventOnly, screen: screen, time: time, timeOffset: timeOffset, displayIndex: displayIndex); |
| } |
|
|
| public void EndTouch(int touchId, Vector2 position, float pressure, Vector2 delta = default, bool queueEventOnly = false, |
| Touchscreen screen = null, double time = -1, double timeOffset = 0) |
| { |
| SetTouch(touchId, TouchPhase.Ended, position, pressure, delta, queueEventOnly, screen: screen, time: time, timeOffset: timeOffset); |
| } |
|
|
| |
| public void CancelTouch(int touchId, Vector2 position, Vector2 delta = default, bool queueEventOnly = false, |
| Touchscreen screen = null, double time = -1, double timeOffset = 0) |
| { |
| SetTouch(touchId, TouchPhase.Canceled, position, delta, queueEventOnly: queueEventOnly, screen: screen, time: time, timeOffset: timeOffset); |
| } |
|
|
| public void CancelTouch(int touchId, Vector2 position, float pressure, Vector2 delta = default, bool queueEventOnly = false, |
| Touchscreen screen = null, double time = -1, double timeOffset = 0) |
| { |
| SetTouch(touchId, TouchPhase.Canceled, position, pressure, delta, queueEventOnly, screen: screen, time: time, timeOffset: timeOffset); |
| } |
|
|
| |
| public void SetTouch(int touchId, TouchPhase phase, Vector2 position, Vector2 delta = default, |
| bool queueEventOnly = true, Touchscreen screen = null, double time = -1, double timeOffset = 0) |
| { |
| SetTouch(touchId, phase, position, 1, delta: delta, queueEventOnly: queueEventOnly, screen: screen, time: time, |
| timeOffset: timeOffset); |
| } |
|
|
| public void SetTouch(int touchId, TouchPhase phase, Vector2 position, float pressure, Vector2 delta = default, bool queueEventOnly = true, |
| Touchscreen screen = null, double time = -1, double timeOffset = 0, byte displayIndex = 0) |
| { |
| if (screen == null) |
| { |
| screen = Touchscreen.current; |
| if (screen == null) |
| screen = InputSystem.AddDevice<Touchscreen>(); |
| } |
|
|
| InputSystem.QueueStateEvent(screen, new TouchState |
| { |
| touchId = touchId, |
| phase = phase, |
| position = position, |
| delta = delta, |
| pressure = pressure, |
| displayIndex = displayIndex, |
| }, (time >= 0 ? time : InputState.currentTime) + timeOffset); |
| if (!queueEventOnly) |
| InputSystem.Update(); |
| } |
|
|
| public void Trigger<TValue>(InputAction action, InputControl<TValue> control, TValue value) |
| where TValue : struct |
| { |
| throw new NotImplementedException(); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| public void Trigger(InputAction action) |
| { |
| if (action == null) |
| throw new ArgumentNullException(nameof(action)); |
|
|
| if (!action.enabled) |
| throw new ArgumentException( |
| $"Action '{action}' must be enabled in order to be able to trigger it", nameof(action)); |
|
|
| var controls = action.controls; |
| if (controls.Count == 0) |
| throw new ArgumentException( |
| $"Action '{action}' must be bound to controls in order to be able to trigger it", nameof(action)); |
|
|
| |
| for (var i = 0; i < controls.Count; ++i) |
| { |
| if (!(controls[i] is ButtonControl button)) |
| continue; |
|
|
| |
| Set(button, 1); |
| Set(button, 0); |
|
|
| return; |
| } |
|
|
| |
| for (var i = 0; i < controls.Count; ++i) |
| { |
| if (!(controls[i] is AxisControl axis)) |
| continue; |
|
|
| |
| Set(axis, axis.ReadValue() + 0.01f); |
|
|
| return; |
| } |
|
|
| |
| throw new NotImplementedException(); |
| } |
|
|
| |
| |
| |
| internal InputTestRuntime runtime { get; private set; } |
|
|
| |
| |
| |
| |
| public double currentTime |
| { |
| get => runtime.currentTime - runtime.currentTimeOffsetToRealtimeSinceStartup; |
| set |
| { |
| runtime.currentTime = value + runtime.currentTimeOffsetToRealtimeSinceStartup; |
| runtime.dontAdvanceTimeNextDynamicUpdate = true; |
| } |
| } |
|
|
| internal float unscaledGameTime |
| { |
| get => runtime.unscaledGameTime; |
| set |
| { |
| runtime.unscaledGameTime = value; |
| runtime.dontAdvanceUnscaledGameTimeNextDynamicUpdate = true; |
| } |
| } |
|
|
| public class ActionConstraint : Constraint |
| { |
| public InputActionPhase phase { get; set; } |
| public double? time { get; set; } |
| public double? duration { get; set; } |
| public InputAction action { get; set; } |
| public InputControl control { get; set; } |
| public object value { get; set; } |
| public Type interaction { get; set; } |
|
|
| private readonly List<ActionConstraint> m_AndThen = new List<ActionConstraint>(); |
|
|
| public ActionConstraint(InputActionPhase phase, InputAction action, InputControl control, object value = null, Type interaction = null, double? time = null, double? duration = null) |
| { |
| this.phase = phase; |
| this.time = time; |
| this.duration = duration; |
| this.action = action; |
| this.control = control; |
| this.value = value; |
| this.interaction = interaction; |
|
|
| var interactionText = string.Empty; |
| if (interaction != null) |
| interactionText = InputInteraction.GetDisplayName(interaction); |
|
|
| var actionName = action.actionMap != null ? $"{action.actionMap}/{action.name}" : action.name; |
| |
| var description = $"{{ action={actionName} phase={phase}"; |
| if (time != null) |
| description += $" time={time}"; |
| if (control != null) |
| description += $" control={control}"; |
| if (value != null) |
| description += $" value={value}"; |
| if (interaction != null) |
| description += $" interaction={interactionText}"; |
| if (duration != null) |
| description += $" duration={duration}"; |
| description += " }"; |
| Description = description; |
| } |
|
|
| public override ConstraintResult ApplyTo(object actual) |
| { |
| var trace = (InputActionTrace)actual; |
| var actions = trace.ToArray(); |
|
|
| if (actions.Length == 0) |
| return new ConstraintResult(this, actual, false); |
|
|
| if (!Verify(actions[0])) |
| return new ConstraintResult(this, actual, false); |
|
|
| var i = 1; |
| foreach (var constraint in m_AndThen) |
| { |
| if (i >= actions.Length || !constraint.Verify(actions[i])) |
| return new ConstraintResult(this, actual, false); |
| ++i; |
| } |
|
|
| if (i != actions.Length) |
| return new ConstraintResult(this, actual, false); |
|
|
| return new ConstraintResult(this, actual, true); |
| } |
|
|
| private bool Verify(InputActionTrace.ActionEventPtr eventPtr) |
| { |
| |
|
|
| if (eventPtr.action != action || |
| eventPtr.phase != phase) |
| return false; |
|
|
| |
| if (time != null && !Mathf.Approximately((float)time.Value, (float)eventPtr.time)) |
| return false; |
|
|
| |
| if (duration != null && !Mathf.Approximately((float)duration.Value, (float)eventPtr.duration)) |
| return false; |
|
|
| |
| if (control != null && eventPtr.control != control) |
| return false; |
|
|
| |
| if (interaction != null && (eventPtr.interaction == null || |
| !interaction.IsInstanceOfType(eventPtr.interaction))) |
| return false; |
|
|
| |
| if (value != null) |
| { |
| var val = eventPtr.ReadValueAsObject(); |
| if (val is float f) |
| { |
| if (!Mathf.Approximately(f, Convert.ToSingle(value))) |
| return false; |
| } |
| else if (val is double d) |
| { |
| if (!Mathf.Approximately((float)d, (float)Convert.ToDouble(value))) |
| return false; |
| } |
| else if (val is Vector2 v2) |
| { |
| if (!Vector2EqualityComparer.Instance.Equals(v2, value.As<Vector2>())) |
| return false; |
| } |
| else if (val is Vector3 v3) |
| { |
| if (!Vector3EqualityComparer.Instance.Equals(v3, value.As<Vector3>())) |
| return false; |
| } |
| else if (!val.Equals(value)) |
| return false; |
| } |
|
|
| return true; |
| } |
|
|
| public ActionConstraint AndThen(ActionConstraint constraint) |
| { |
| m_AndThen.Add(constraint); |
| Description += " and\n"; |
| Description += constraint.Description; |
| return this; |
| } |
| } |
|
|
| #if UNITY_EDITOR |
| internal void SimulateDomainReload() |
| { |
| |
| |
| |
|
|
| InputSystem.s_SystemObject.OnBeforeSerialize(); |
| InputSystem.s_SystemObject = null; |
| InputSystem.InitializeInEditor(runtime); |
| } |
|
|
| #endif |
| } |
| } |
|
|