diff --git a/Assets/InputSystem_Actions.inputactions b/Assets/InputSystem_Actions.inputactions new file mode 100644 index 0000000000..71c49e904c --- /dev/null +++ b/Assets/InputSystem_Actions.inputactions @@ -0,0 +1,615 @@ +{ + "version": 1, + "name": "InputSystem_Actions", + "maps": [ + { + "name": "Player", + "id": "df70fa95-8a34-4494-b137-73ab6b9c7d37", + "actions": [ + { + "name": "Move", + "type": "Value", + "id": "351f2ccd-1f9f-44bf-9bec-d62ac5c5f408", + "expectedControlType": "Vector2", + "processors": "", + "interactions": "", + "initialStateCheck": true, + "priority": 0 + }, + { + "name": "Look", + "type": "Value", + "id": "6b444451-8a00-4d00-a97e-f47457f736a8", + "expectedControlType": "Vector2", + "processors": "", + "interactions": "", + "initialStateCheck": true, + "priority": 0 + }, + { + "name": "Attack", + "type": "Button", + "id": "6c2ab1b8-8984-453a-af3d-a3c78ae1679a", + "expectedControlType": "Button", + "processors": "", + "interactions": "", + "initialStateCheck": false, + "priority": 0 + }, + { + "name": "Interact", + "type": "Button", + "id": "852140f2-7766-474d-8707-702459ba45f3", + "expectedControlType": "Button", + "processors": "", + "interactions": "Hold", + "initialStateCheck": false, + "priority": 0 + }, + { + "name": "Crouch", + "type": "Button", + "id": "27c5f898-bc57-4ee1-8800-db469aca5fe3", + "expectedControlType": "", + "processors": "", + "interactions": "", + "initialStateCheck": false, + "priority": 0 + }, + { + "name": "Jump", + "type": "Button", + "id": "f1ba0d36-48eb-4cd5-b651-1c94a6531f70", + "expectedControlType": "", + "processors": "", + "interactions": "", + "initialStateCheck": false, + "priority": 0 + }, + { + "name": "Previous", + "type": "Button", + "id": "2776c80d-3c14-4091-8c56-d04ced07a2b0", + "expectedControlType": "", + "processors": "", + "interactions": "", + "initialStateCheck": false, + "priority": 0 + }, + { + "name": "Next", + "type": "Button", + "id": "b7230bb6-fc9b-4f52-8b25-f5e19cb2c2ba", + "expectedControlType": "", + "processors": "", + "interactions": "", + "initialStateCheck": false, + "priority": 0 + }, + { + "name": "Sprint", + "type": "Button", + "id": "641cd816-40e6-41b4-8c3d-04687c349290", + "expectedControlType": "", + "processors": "", + "interactions": "", + "initialStateCheck": false, + "priority": 0 + }, + { + "name": "B", + "type": "Button", + "id": "6c4a2a83-6b28-4cbb-8b29-16b4c54503f3", + "expectedControlType": "", + "processors": "", + "interactions": "", + "initialStateCheck": false, + "priority": 1 + }, + { + "name": "Shift B", + "type": "Button", + "id": "58bf3a15-2461-4beb-90a6-9d3dbb48eb61", + "expectedControlType": "", + "processors": "", + "interactions": "", + "initialStateCheck": false, + "priority": 2 + } + ], + "bindings": [ + { + "name": "", + "id": "978bfe49-cc26-4a3d-ab7b-7d7a29327403", + "path": "/leftStick", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Move", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "WASD", + "id": "00ca640b-d935-4593-8157-c05846ea39b3", + "path": "Dpad", + "interactions": "", + "processors": "", + "groups": "", + "action": "Move", + "isComposite": true, + "isPartOfComposite": false + }, + { + "name": "up", + "id": "e2062cb9-1b15-46a2-838c-2f8d72a0bdd9", + "path": "/w", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Move", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "up", + "id": "8180e8bd-4097-4f4e-ab88-4523101a6ce9", + "path": "/upArrow", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Move", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "down", + "id": "320bffee-a40b-4347-ac70-c210eb8bc73a", + "path": "/s", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Move", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "down", + "id": "1c5327b5-f71c-4f60-99c7-4e737386f1d1", + "path": "/downArrow", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Move", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "left", + "id": "d2581a9b-1d11-4566-b27d-b92aff5fabbc", + "path": "/a", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Move", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "left", + "id": "2e46982e-44cc-431b-9f0b-c11910bf467a", + "path": "/leftArrow", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Move", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "right", + "id": "fcfe95b8-67b9-4526-84b5-5d0bc98d6400", + "path": "/d", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Move", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "right", + "id": "77bff152-3580-4b21-b6de-dcd0c7e41164", + "path": "/rightArrow", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Move", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "", + "id": "1635d3fe-58b6-4ba9-a4e2-f4b964f6b5c8", + "path": "/{Primary2DAxis}", + "interactions": "", + "processors": "", + "groups": "XR", + "action": "Move", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "3ea4d645-4504-4529-b061-ab81934c3752", + "path": "/stick", + "interactions": "", + "processors": "", + "groups": "Joystick", + "action": "Move", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "c1f7a91b-d0fd-4a62-997e-7fb9b69bf235", + "path": "/rightStick", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Look", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "8c8e490b-c610-4785-884f-f04217b23ca4", + "path": "/delta", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse;Touch", + "action": "Look", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "3e5f5442-8668-4b27-a940-df99bad7e831", + "path": "/{Hatswitch}", + "interactions": "", + "processors": "", + "groups": "Joystick", + "action": "Look", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "143bb1cd-cc10-4eca-a2f0-a3664166fe91", + "path": "/buttonWest", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Attack", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "05f6913d-c316-48b2-a6bb-e225f14c7960", + "path": "/leftButton", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Attack", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "886e731e-7071-4ae4-95c0-e61739dad6fd", + "path": "/primaryTouch/tap", + "interactions": "", + "processors": "", + "groups": ";Touch", + "action": "Attack", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "ee3d0cd2-254e-47a7-a8cb-bc94d9658c54", + "path": "/trigger", + "interactions": "", + "processors": "", + "groups": "Joystick", + "action": "Attack", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "8255d333-5683-4943-a58a-ccb207ff1dce", + "path": "/{PrimaryAction}", + "interactions": "", + "processors": "", + "groups": "XR", + "action": "Attack", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "b3c1c7f0-bd20-4ee7-a0f1-899b24bca6d7", + "path": "/enter", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Attack", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "cbac6039-9c09-46a1-b5f2-4e5124ccb5ed", + "path": "/2", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Next", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "e15ca19d-e649-4852-97d5-7fe8ccc44e94", + "path": "/dpad/right", + "interactions": "", + "processors": "", + "groups": "Gamepad", + "action": "Next", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "f2e9ba44-c423-42a7-ad56-f20975884794", + "path": "/leftShift", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Sprint", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "8cbb2f4b-a784-49cc-8d5e-c010b8c7f4e6", + "path": "/leftStickPress", + "interactions": "", + "processors": "", + "groups": "Gamepad", + "action": "Sprint", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "d8bf24bf-3f2f-4160-a97c-38ec1eb520ba", + "path": "/trigger", + "interactions": "", + "processors": "", + "groups": "XR", + "action": "Sprint", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "eb40bb66-4559-4dfa-9a2f-820438abb426", + "path": "/space", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Jump", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "daba33a1-ad0c-4742-a909-43ad1cdfbeb6", + "path": "/buttonSouth", + "interactions": "", + "processors": "", + "groups": "Gamepad", + "action": "Jump", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "603f3daf-40bd-4854-8724-93e8017f59e3", + "path": "/secondaryButton", + "interactions": "", + "processors": "", + "groups": "XR", + "action": "Jump", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "1534dc16-a6aa-499d-9c3a-22b47347b52a", + "path": "/1", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Previous", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "25060bbd-a3a6-476e-8fba-45ae484aad05", + "path": "/dpad/left", + "interactions": "", + "processors": "", + "groups": "Gamepad", + "action": "Previous", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "1c04ea5f-b012-41d1-a6f7-02e963b52893", + "path": "/e", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Interact", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "b3f66d0b-7751-423f-908b-a11c5bd95930", + "path": "/buttonNorth", + "interactions": "", + "processors": "", + "groups": "Gamepad", + "action": "Interact", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "4f4649ac-64a8-4a73-af11-b3faef356a4d", + "path": "/buttonEast", + "interactions": "", + "processors": "", + "groups": "Gamepad", + "action": "Crouch", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "36e52cba-0905-478e-a818-f4bfcb9f3b9a", + "path": "/c", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Crouch", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "06c6f56c-12dd-4017-8c41-75ae217f4d04", + "path": "/b", + "interactions": "", + "processors": "", + "groups": "", + "action": "B", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "One Modifier", + "id": "5d7ab5f3-36e5-4c42-837c-490da85bf8e7", + "path": "OneModifier(modifiersOrder=1)", + "interactions": "", + "processors": "", + "groups": "", + "action": "Shift B", + "isComposite": true, + "isPartOfComposite": false + }, + { + "name": "modifier", + "id": "797d722e-294a-4e97-b601-2ddcd1ad7654", + "path": "/leftShift", + "interactions": "", + "processors": "", + "groups": "", + "action": "Shift B", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "binding", + "id": "9f5adf57-24d0-453c-a468-1da9e3b97113", + "path": "/b", + "interactions": "", + "processors": "", + "groups": "", + "action": "Shift B", + "isComposite": false, + "isPartOfComposite": true + } + ] + } + ], + "controlSchemes": [ + { + "name": "Keyboard&Mouse", + "bindingGroup": "Keyboard&Mouse", + "devices": [ + { + "devicePath": "", + "isOptional": false, + "isOR": false + }, + { + "devicePath": "", + "isOptional": false, + "isOR": false + } + ] + }, + { + "name": "Gamepad", + "bindingGroup": "Gamepad", + "devices": [ + { + "devicePath": "", + "isOptional": false, + "isOR": false + } + ] + }, + { + "name": "Touch", + "bindingGroup": "Touch", + "devices": [ + { + "devicePath": "", + "isOptional": false, + "isOR": false + } + ] + }, + { + "name": "Joystick", + "bindingGroup": "Joystick", + "devices": [ + { + "devicePath": "", + "isOptional": false, + "isOR": false + } + ] + }, + { + "name": "XR", + "bindingGroup": "XR", + "devices": [ + { + "devicePath": "", + "isOptional": false, + "isOR": false + } + ] + } + ] +} \ No newline at end of file diff --git a/Assets/InputSystem_Actions.inputactions.meta b/Assets/InputSystem_Actions.inputactions.meta new file mode 100644 index 0000000000..6b38b043d9 --- /dev/null +++ b/Assets/InputSystem_Actions.inputactions.meta @@ -0,0 +1,14 @@ +fileFormatVersion: 2 +guid: 052faaac586de48259a63d0c4782560b +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 11500000, guid: 8404be70184654265930450def6a9037, type: 3} + generateWrapperCode: 0 + wrapperCodePath: + wrapperClassName: + wrapperCodeNamespace: diff --git a/Assets/Samples/ProjectWideActions/ProjectWideActionsExample.cs b/Assets/Samples/ProjectWideActions/ProjectWideActionsExample.cs index 8b066102dc..10d9e571bb 100644 --- a/Assets/Samples/ProjectWideActions/ProjectWideActionsExample.cs +++ b/Assets/Samples/ProjectWideActions/ProjectWideActionsExample.cs @@ -13,6 +13,8 @@ public class ProjectWideActionsExample : MonoBehaviour InputAction previous; InputAction sprint; InputAction crouch; + InputAction b; + InputAction shiftB; // Start is called before the first frame update void Start() @@ -29,6 +31,8 @@ void Start() previous = InputSystem.actions.FindAction("Player/Previous"); sprint = InputSystem.actions.FindAction("Player/Sprint"); crouch = InputSystem.actions.FindAction("Player/Crouch"); + b = InputSystem.actions.FindAction("Player/B"); + shiftB = InputSystem.actions.FindAction("Player/Shift B"); } else { @@ -41,6 +45,18 @@ void Start() attack.performed += OnAttack; attack.canceled += OnCancel; } + + if (b != null) + { + b.performed += OnB; + b.canceled += OnCancel; + } + + if (shiftB != null) + { + shiftB.performed += OnShiftB; + shiftB.canceled += OnCancel; + } } private void OnAttack(InputAction.CallbackContext ctx) @@ -53,6 +69,18 @@ private void OnCancel(InputAction.CallbackContext ctx) cube.GetComponent().material.color = Color.green; } + private void OnB(InputAction.CallbackContext ctx) + { + Debug.Log("B WAS PRESSED"); + cube.GetComponent().material.color = Color.yellow; + } + + private void OnShiftB(InputAction.CallbackContext ctx) + { + Debug.Log("SHIFT + B WAS PRESSED"); + cube.GetComponent().material.color = Color.blue; + } + void OnDestroy() { if (attack != null) @@ -60,6 +88,16 @@ void OnDestroy() attack.performed -= OnAttack; attack.canceled -= OnCancel; } + if (b != null) + { + b.performed -= OnB; + b.canceled -= OnCancel; + } + if (shiftB != null) + { + shiftB.performed -= OnShiftB; + shiftB.canceled -= OnCancel; + } } // Update is called once per frame @@ -71,6 +109,16 @@ void Update() var moveVal = move.ReadValue() * 10.0f * Time.deltaTime; cube.transform.Translate(new Vector3(moveVal.x, moveVal.y, 0)); } + + if (shiftB.IsPressed()) + { + Debug.Log("SHIFT + B WAS PRESSED"); + } + + if (b.IsPressed()) + { + Debug.Log("B WAS PRESSED"); + } } } // class ProjectWideActionsExample } // namespace UnityEngine.InputSystem.Samples.ProjectWideActions diff --git a/Assets/Tests/InputSystem/CoreTests_Actions.cs b/Assets/Tests/InputSystem/CoreTests_Actions.cs index fa24a4d915..67754becf1 100644 --- a/Assets/Tests/InputSystem/CoreTests_Actions.cs +++ b/Assets/Tests/InputSystem/CoreTests_Actions.cs @@ -1594,14 +1594,14 @@ public void Actions_ActiveBindingsHaveCorrectBindingIndicesAfterBindingResolutio // with the control (i.e. mouse.leftButton) or with action callbacks // could all appear correct because those don't actually use bindingIndex. // This issue originally manifested itself as an assert in another place in the code. - InputSystem.RegisterProcessor(); + InputSystem.RegisterProcessor(); // This test is sensitive to binding order. // It's important that the active binding is not in the first // position of the action (i.e. not at the default index). var map = new InputActionMap("map"); var action = map.AddAction("action1", binding: "/buttonSouth"); - action.AddBinding("/leftButton").WithProcessor(); // binding in 2nd position. + action.AddBinding("/leftButton").WithProcessor(); // binding in 2nd position. map.Enable(); var mouse = InputSystem.AddDevice(); @@ -4279,7 +4279,7 @@ public void Actions_WithMultipleBoundControls_CanHandleInteractionsThatTriggerOn var keyboard = InputSystem.AddDevice(); var gamepad = InputSystem.AddDevice(); - InputSystem.RegisterInteraction(); + InputSystem.RegisterInteraction(); var action = new InputAction(interactions: "releaseOnlyTest"); @@ -5526,28 +5526,13 @@ public ModificationCases() {} private static readonly Modification[] ModificationAppliesToSingleActionMap = { - Modification.AddBinding, - Modification.RemoveBinding, - Modification.ModifyBinding, - Modification.ApplyBindingOverride, - Modification.AddAction, - Modification.RemoveAction, - Modification.ChangeBindingMask, - Modification.AddDevice, - Modification.RemoveDevice, - Modification.AddDeviceGlobally, - Modification.RemoveDeviceGlobally, + CoreTests.Modification.AddBinding, CoreTests.Modification.RemoveBinding, CoreTests.Modification.ModifyBinding, CoreTests.Modification.ApplyBindingOverride, CoreTests.Modification.AddAction, CoreTests.Modification.RemoveAction, CoreTests.Modification.ChangeBindingMask, CoreTests.Modification.AddDevice, CoreTests.Modification.RemoveDevice, CoreTests.Modification.AddDeviceGlobally, CoreTests.Modification.RemoveDeviceGlobally, // Excludes: AddMap, RemoveMap }; private static readonly Modification[] ModificationAppliesToSingletonAction = { - Modification.AddBinding, - Modification.RemoveBinding, - Modification.ModifyBinding, - Modification.ApplyBindingOverride, - Modification.AddDeviceGlobally, - Modification.RemoveDeviceGlobally, + CoreTests.Modification.AddBinding, CoreTests.Modification.RemoveBinding, CoreTests.Modification.ModifyBinding, CoreTests.Modification.ApplyBindingOverride, CoreTests.Modification.AddDeviceGlobally, CoreTests.Modification.RemoveDeviceGlobally, }; public IEnumerator GetEnumerator() @@ -5615,7 +5600,7 @@ private InputActionMap CreateSingletonAction() [Test] [Category("Actions")] - [TestCaseSource(typeof(ModificationCases))] + [TestCaseSource(typeof(CoreTests.ModificationCases))] public void Actions_CanHandleModification(Modification modification, Func getActions) { // Exclude project-wide actions from this test @@ -6177,7 +6162,7 @@ public void Actions_CanAddProcessorsToActions() { var gamepad = InputSystem.AddDevice(); - InputSystem.RegisterProcessor(); + InputSystem.RegisterProcessor(); var action = new InputAction(processors: "ConstantVector2Test"); action.AddBinding("/leftStick"); action.Enable(); @@ -6203,7 +6188,7 @@ public void Actions_IncompatibleProcessorIsIgnored() { var gamepad = InputSystem.AddDevice(); - InputSystem.RegisterProcessor(); + InputSystem.RegisterProcessor(); var action = new InputAction(processors: "ConstantVector2Test"); action.AddBinding("/leftStick/x"); action.Enable(); @@ -6239,9 +6224,9 @@ public void Actions_CanAddProcessorsToBindings() { var gamepad = InputSystem.AddDevice(); - InputSystem.RegisterProcessor(); + InputSystem.RegisterProcessor(); var action = new InputAction(); - action.AddBinding("/leftStick").WithProcessor(); + action.AddBinding("/leftStick").WithProcessor(); action.Enable(); Vector2? receivedVector = null; @@ -6349,13 +6334,13 @@ public override float Process(float value, InputControl control) [Category("Actions")] public void Actions_AddingSameProcessorTwice_DoesntImpactUIHideState() { - InputSystem.RegisterProcessor(); + InputSystem.RegisterProcessor(); Assert.That(InputSystem.TryGetProcessor("ConstantFloat1Test"), Is.Not.EqualTo(null)); bool hide = InputSystem.manager.processors.ShouldHideInUI("ConstantFloat1Test"); Assert.That(hide, Is.EqualTo(false)); - InputSystem.RegisterProcessor(); + InputSystem.RegisterProcessor(); // Check we haven't caused this to alias with itself and cause it to be hidden in the UI hide = InputSystem.manager.processors.ShouldHideInUI("ConstantFloat1Test"); Assert.That(hide, Is.EqualTo(false)); @@ -6369,8 +6354,8 @@ public void Actions_AddingSameNamedProcessorWithDifferentResult_OverridesOrigina { var gamepad = InputSystem.AddDevice(); - InputSystem.RegisterProcessor("ConstantFloatTest"); - InputSystem.RegisterProcessor("ConstantFloatTest"); + InputSystem.RegisterProcessor("ConstantFloatTest"); + InputSystem.RegisterProcessor("ConstantFloatTest"); var action = new InputAction(processors: "ConstantFloatTest"); action.AddBinding("/leftTrigger"); @@ -6973,7 +6958,7 @@ public void Reset() [Category("Actions")] public void Actions_CanRegisterNewInteraction() { - InputSystem.RegisterInteraction(); + InputSystem.RegisterInteraction(); TestInteraction.s_GotInvoked = false; var gamepad = InputSystem.AddDevice("Gamepad"); @@ -9325,7 +9310,7 @@ private class CompositeWithParameters : InputBindingComposite public bool boolParameter; public EnumParameter enumParameter; - public static CompositeWithParameters s_Instance; + public static CoreTests.CompositeWithParameters s_Instance; public CompositeWithParameters() { @@ -9354,7 +9339,7 @@ public override float EvaluateMagnitude(ref InputBindingCompositeContext context [Category("Actions")] public void Actions_CanHaveParametersOnComposites() { - InputSystem.RegisterBindingComposite(); + InputSystem.RegisterBindingComposite(); // NOTE: Enums aren't supported at the JSON level. The editor uses reflection to display textual names rather // than plain integer values but underneath, enums are treated as ints. @@ -10109,7 +10094,7 @@ public void Reset() public void Actions_Vector2Composite_TriggersActionOnlyOnceWhenMultipleComponentBindingsTriggerInSingleEvent() { var keyboard = InputSystem.AddDevice(); - InputSystem.RegisterInteraction(); + InputSystem.RegisterInteraction(); var action = new InputAction(); action.AddCompositeBinding("Dpad", interactions: "log") @@ -10385,7 +10370,7 @@ public void Actions_WithMultipleComposites_CancelsIfCompositeIsReleased() var keyboard = InputSystem.AddDevice(); var gamepad = InputSystem.AddDevice(); - InputSystem.RegisterInteraction(); + InputSystem.RegisterInteraction(); var action = new InputAction(); action.AddCompositeBinding("Dpad(normalize=0)") @@ -10469,7 +10454,7 @@ public void Actions_CompositesReportControlThatTriggeredTheCompositeInCallback() var keyboard = InputSystem.AddDevice(); var gamepad = InputSystem.AddDevice(); - InputSystem.RegisterInteraction(); + InputSystem.RegisterInteraction(); var action = new InputAction(); action.AddBinding("/leftStick"); @@ -10526,7 +10511,7 @@ public void Actions_CompositesInDifferentMapsTiedToSameControlsWork() var keyboard = InputSystem.AddDevice(); var gamepad = InputSystem.AddDevice(); - InputSystem.RegisterInteraction(); + InputSystem.RegisterInteraction(); var map1 = new InputActionMap("map1"); var action1 = map1.AddAction("action"); @@ -10595,7 +10580,7 @@ public override Vector2 ReadValue(ref InputBindingCompositeContext context) [Category("Actions")] public void Actions_CanCreateCompositeWithVector2PartBinding() { - InputSystem.RegisterBindingComposite(); + InputSystem.RegisterBindingComposite(); var gamepad = InputSystem.AddDevice(); var action = new InputAction(); @@ -10630,7 +10615,7 @@ public override float ReadValue(ref InputBindingCompositeContext context) [Category("Actions")] public void Actions_CanGetSourceControlWhenReadingValueFromCompositePart() { - InputSystem.RegisterBindingComposite(); + InputSystem.RegisterBindingComposite(); var gamepad = InputSystem.AddDevice(); var action = new InputAction(); @@ -11786,7 +11771,7 @@ public void Reset() [Category("Actions")] public void Actions_InteractionContextRespectsCustomDefaultStates() { - InputSystem.RegisterInteraction(); + InputSystem.RegisterInteraction(); const string json = @" { @@ -12264,7 +12249,7 @@ private class MonoBehaviourWithActionProperty : MonoBehaviour public void Actions_Property_CanGetAction_WithNullReferenceType() { var go = new GameObject(); - var component = go.AddComponent(); + var component = go.AddComponent(); component.actionProperty = new InputActionProperty((InputActionReference)null); Assert.DoesNotThrow(() => _ = component.actionProperty.action); @@ -12278,7 +12263,7 @@ public void Actions_Property_CanGetAction_WithNullReferenceType() public void Actions_Property_CanGetAction_WithNullActionType() { var go = new GameObject(); - var component = go.AddComponent(); + var component = go.AddComponent(); component.actionProperty = new InputActionProperty((InputAction)null); Assert.DoesNotThrow(() => _ = component.actionProperty.action); @@ -12300,7 +12285,7 @@ public void Actions_Property_CanGetAction_WithDestroyedReferenceType() reference.Set(asset, "map", "action1"); var go = new GameObject(); - var component = go.AddComponent(); + var component = go.AddComponent(); component.actionProperty = new InputActionProperty(reference); Assert.That(component.actionProperty.action, Is.Not.Null); @@ -12396,7 +12381,7 @@ public struct PointerInput public float? Twist; } - public class PointerInputComposite : InputBindingComposite + public class PointerInputComposite : InputBindingComposite { [InputControl(layout = "Button")] public int contact; @@ -12419,7 +12404,7 @@ public class PointerInputComposite : InputBindingComposite [InputControl(layout = "Integer")] public int inputId; - public override PointerInput ReadValue(ref InputBindingCompositeContext context) + public override CoreTests.PointerInput ReadValue(ref InputBindingCompositeContext context) { var contact = context.ReadValueAsButton(this.contact); var pointerId = context.ReadValue(inputId); @@ -12429,7 +12414,7 @@ public override PointerInput ReadValue(ref InputBindingCompositeContext context) var position = context.ReadValue(this.position); var twist = context.ReadValue(this.twist); - return new PointerInput + return new CoreTests.PointerInput { Contact = contact, InputId = pointerId, @@ -12449,7 +12434,7 @@ public override PointerInput ReadValue(ref InputBindingCompositeContext context) [TestCase(false)] public void Actions_WithMultipleCompositeBindings_WithoutEvaluateMagnitude_Works(bool prepopulateTouchesBeforeEnablingAction) { - InputSystem.RegisterBindingComposite(); + InputSystem.RegisterBindingComposite(); InputSystem.AddDevice(); @@ -12463,10 +12448,10 @@ public void Actions_WithMultipleCompositeBindings_WithoutEvaluateMagnitude_Works .With("pressure", $"/touch{i}/pressure") .With("inputId", $"/touch{i}/touchId"); - var values = new List(); - action.started += ctx => values.Add(ctx.ReadValue()); - action.performed += ctx => values.Add(ctx.ReadValue()); - action.canceled += ctx => values.Add(ctx.ReadValue()); + var values = new List(); + action.started += ctx => values.Add(ctx.ReadValue()); + action.performed += ctx => values.Add(ctx.ReadValue()); + action.canceled += ctx => values.Add(ctx.ReadValue()); if (!prepopulateTouchesBeforeEnablingAction) // normally actions are enabled before any control actuations happen actionMap.Enable(); diff --git a/Assets/Tests/InputSystem/CoreTests_ActionsPriority.cs b/Assets/Tests/InputSystem/CoreTests_ActionsPriority.cs new file mode 100644 index 0000000000..25d73f8e5a --- /dev/null +++ b/Assets/Tests/InputSystem/CoreTests_ActionsPriority.cs @@ -0,0 +1,401 @@ +using System; +using System.Collections.Generic; +using NUnit.Framework; +using UnityEngine; +using UnityEngine.InputSystem; +using UnityEngine.InputSystem.Controls; + +internal static class PriorityTestExtensions +{ + internal static InputAction SetupTestAction(this InputActionMap map, string binding) + { + // just a typical binding + var action = map.AddAction("Action1:" + binding + " " + Guid.NewGuid()); + action.AddBinding("/" + binding); + return action; + } + + internal static InputAction SetupTestAction(this InputActionMap map, string modifier1, string binding) + { + // A shortcut with one modifier + var action = map.AddAction("Action2:" + modifier1 + " " + binding + " " + Guid.NewGuid()); + + action.AddCompositeBinding("OneModifier") + .With("Modifier", "/" + modifier1) + .With("Binding", "/" + binding); + + return action; + } + + internal static InputAction SetupTestAction(this InputActionMap map, string modifier1, string modifier2, string binding) + { + var action = map.AddAction("Action3:" + modifier1 + " " + modifier2 + " " + binding + " " + Guid.NewGuid()); + + // A shortcut with two modifiers + action.AddCompositeBinding("TwoModifiers") + .With("Modifier1", "/" + modifier1) + .With("Modifier2", "/" + modifier2) + .With("Binding", "/" + binding); + + return action; + } +} + +internal partial class CoreTests +{ + private static IEnumerable> TwoInputActionTestCases() + { + InputActionMap map = new InputActionMap("map"); + yield return new TwoInputActionDataWrapper + { + Action1 = map.SetupTestAction("ctrl", "x"), + Action2 = map.SetupTestAction("x") + }; + yield return new TwoInputActionDataWrapper + { + Action1 = map.SetupTestAction("shift", "n"), + Action2 = map.SetupTestAction("n") + }; + yield return new TwoInputActionDataWrapper + { + Action1 = map.SetupTestAction("ctrl", "shift", "h"), + Action2 = map.SetupTestAction("shift", "h") + }; + yield return new TwoInputActionDataWrapper + { + Action1 = map.SetupTestAction("ctrl", "shift", "v"), + Action2 = map.SetupTestAction("shift", "v") + }; + } + + public class TwoInputActionDataWrapper + { + public TInputAction1 Action1; + public TInputAction2 Action2; + } + + private void PressBindingsForInputActions(Keyboard keyboard, InputAction action1, InputAction action2) + { + for (int i = 0; i < action1.controls.Count; i++) + { + Debug.Log("action 1 binding pressed: " + action1.controls[i].path); + Press((ButtonControl)keyboard[action1.controls[i].name], queueEventOnly: true); + } + + for (int i = 0; i < action2.controls.Count; i++) + { + Debug.Log("action 2 binding pressed: " + action2.controls[i].name); + Press((ButtonControl)keyboard[action2.controls[i].name], queueEventOnly: true); + } + + InputSystem.Update(); + } + + private void ReleaseBindingsForActions(Keyboard keyboard, InputAction action1, InputAction action2) + { + // Cleanup key presses + for (int i = 0; i < action1.controls.Count; i++) + { + Release((ButtonControl)keyboard[action1.controls[i].name], queueEventOnly: true); + } + + for (int i = 0; i < action2.controls.Count; i++) + { + Release((ButtonControl)keyboard[action2.controls[i].name], queueEventOnly: true); + } + + InputSystem.Update(); + } + + [Test] + [Category("Actions Priority")] + [TestCaseSource(nameof(TwoInputActionTestCases))] + public void Actions_Priority_OnlyOneActionIsPerformed_WhenOnePriorityIsHigherThanOther(TwoInputActionDataWrapper twoInputActions) + { + InputSystem.settings.shortcutKeysConsumeInput = true; + var keyboard = InputSystem.AddDevice(); + + //var map = new InputActionMap("map"); + var action1 = twoInputActions.Action1; + var action2 = twoInputActions.Action2; + + // action 1's priority higher so it takes precedence + action1.Priority = 2; + action2.Priority = 1; + + action1.m_ActionMap.Enable(); + + Assert.That(action1.WasPerformedThisFrame(), Is.False); + Assert.That(action2.WasPerformedThisFrame(), Is.False); + + PressBindingsForInputActions(keyboard, action1, action2); + + // action1 is performed because action1 has a higher priority than action2. + Assert.That(action1.WasPerformedThisFrame(), Is.True); + Assert.That(action2.WasPerformedThisFrame(), Is.False); + + // Cleanup key presses + ReleaseBindingsForActions(keyboard, action1, action2); + + // Update again to be sure released is true. + InputSystem.Update(); + + Assert.That(action1.WasPerformedThisFrame(), Is.False); + Assert.That(action2.WasPerformedThisFrame(), Is.False); + } + + [Test] + [Category("Actions Priority")] + [TestCaseSource(nameof(TwoInputActionTestCases))] + public void Actions_Priority_OnlyOneActionIsPerformed_WhenOnePriorityIsHigherThanOtherInversePriorityOrder(TwoInputActionDataWrapper twoInputActions) + { + InputSystem.settings.shortcutKeysConsumeInput = true; + var keyboard = InputSystem.AddDevice(); + + var action1 = twoInputActions.Action1; + var action2 = twoInputActions.Action2; + + // action 2's priority higher so it takes precedence + action1.Priority = 1; + action2.Priority = 2; + + action1.m_ActionMap.Enable(); + + Assert.That(action1.WasPerformedThisFrame(), Is.False); + Assert.That(action2.WasPerformedThisFrame(), Is.False); + + PressBindingsForInputActions(keyboard, action1, action2); + + // action2 is performed because action1 has a higher priority than action2. + Assert.That(action1.WasPerformedThisFrame(), Is.False); + Assert.That(action2.WasPerformedThisFrame(), Is.True); + + ReleaseBindingsForActions(keyboard, action1, action2); + + Assert.That(action1.WasPerformedThisFrame(), Is.False); + Assert.That(action2.WasPerformedThisFrame(), Is.False); + } + + [Test] + [Category("Actions Priority")] + [TestCaseSource(nameof(TwoInputActionTestCases))] // TODO: Darren, Should both actions be performed this frame here?? + public void Actions_Priority_BothActionsArePerformed_DueToKeyPressOrderForShortcut(TwoInputActionDataWrapper twoInputActions) + { + InputSystem.settings.shortcutKeysConsumeInput = true; + var keyboard = InputSystem.AddDevice(); + + // We swap the order here of Action1 & Action2 so key presses are done backwards, binding before modifiers. + // This causes the opposite keys foreach test case inside TwoInputActionTestCases to be pressed first. + var smallerBindingAction = twoInputActions.Action2; + var largerBindingAction = twoInputActions.Action1; + + // Event though the priority is higher for action2 here, due to the order of the keys being pressed only Action1 will be fired. + smallerBindingAction.Priority = 1; + largerBindingAction.Priority = 2; + + smallerBindingAction.m_ActionMap.Enable(); + + Assert.That(smallerBindingAction.WasPerformedThisFrame(), Is.False); + Assert.That(largerBindingAction.WasPerformedThisFrame(), Is.False); + + PressBindingsForInputActions(keyboard, smallerBindingAction, largerBindingAction); + + // action1 is performed because action1 has a higher priority than action2. + Assert.That(smallerBindingAction.WasPerformedThisFrame(), Is.True); + Assert.That(largerBindingAction.WasPerformedThisFrame(), Is.True); + + // Cleanup key presses + ReleaseBindingsForActions(keyboard, smallerBindingAction, largerBindingAction); + + // Update again to be sure released is true. + InputSystem.Update(); + + Assert.That(smallerBindingAction.WasPerformedThisFrame(), Is.False); + Assert.That(largerBindingAction.WasPerformedThisFrame(), Is.False); + } + + [Test] + [Category("Actions Priority")] + [TestCaseSource(nameof(TwoInputActionTestCases))] + public void Actions_Priority_FirstActionFires_WhenPriorityIsEqual(TwoInputActionDataWrapper twoInputActions) // TODO: This shouldn't be the case. This should fire both!! + { + InputSystem.settings.shortcutKeysConsumeInput = true; + var keyboard = InputSystem.AddDevice(); + + var action1 = twoInputActions.Action1; + var action2 = twoInputActions.Action2; + + action1.Priority = 5; + action2.Priority = 5; + + action1.m_ActionMap.Enable(); + + PressBindingsForInputActions(keyboard, action1, action2); + + Assert.That(action1.WasPerformedThisFrame(), Is.True); + Assert.That(action2.WasPerformedThisFrame(), Is.False); + } + + [Test] + [Category("Actions Priority")] + [TestCaseSource(nameof(TwoInputActionTestCases))] + public void Actions_Priority_BothActionsFire_WhenPriorityIsZero(TwoInputActionDataWrapper twoInputActions) + { + InputSystem.settings.shortcutKeysConsumeInput = true; + var keyboard = InputSystem.AddDevice(); + + var action1 = twoInputActions.Action1; + var action2 = twoInputActions.Action2; + + action1.Priority = 0; + action2.Priority = 0; + + action1.m_ActionMap.Enable(); + + var action1WasPerformed = false; + var action2WasPerformed = false; + action1.performed += _ => action1WasPerformed = true; + action2.performed += _ => action2WasPerformed = true; + + PressBindingsForInputActions(keyboard, action1, action2); + + Assert.That(action1WasPerformed, Is.True); + Assert.That(action2WasPerformed, Is.True); + } + + private static IEnumerable> TwoInputActionNoConflictingBindingTestCases() + { + InputActionMap map = new InputActionMap("map"); + yield return new TwoInputActionDataWrapper + { + Action1 = map.SetupTestAction("ctrl", "x"), + Action2 = map.SetupTestAction("k") + }; + yield return new TwoInputActionDataWrapper + { + Action1 = map.SetupTestAction("shift", "n"), + Action2 = map.SetupTestAction("l") + }; + yield return new TwoInputActionDataWrapper + { + Action1 = map.SetupTestAction("shift", "h"), + Action2 = map.SetupTestAction("ctrl", "shift", "o") + }; + yield return new TwoInputActionDataWrapper + { + Action1 = map.SetupTestAction("ctrl", "shift", "v"), + Action2 = map.SetupTestAction("shift", "z") + }; + } + + [Test] + [Category("Actions Priority")] + [TestCaseSource(nameof(TwoInputActionNoConflictingBindingTestCases))] + public void Actions_Priority_BothActionsWithDifferentPriorityFire_WhenThereIsNoConflictingBinding(TwoInputActionDataWrapper twoInputActions) + { + InputSystem.settings.shortcutKeysConsumeInput = true; + var keyboard = InputSystem.AddDevice(); + + var action1 = twoInputActions.Action1; + var action2 = twoInputActions.Action2; + + action1.Priority = 0; + action2.Priority = 1; + + action1.m_ActionMap.Enable(); + action2.m_ActionMap.Enable(); + // + var action1WasPerformed = false; + action1.performed += _ => action1WasPerformed = true; + + Assert.That(action1.WasPerformedThisFrame(), Is.False); + Assert.That(action2.WasPerformedThisFrame(), Is.False); + + PressBindingsForInputActions(keyboard, action1, action2); + + // Different letter keys: no conflict on the same control, so both shortcuts can perform despite different priorities. + Assert.That(action1WasPerformed, Is.True); + Assert.That(action2.WasPerformedThisFrame(), Is.True); + + // TODO: Darren, trigger just the bindings again to be sure the shortcut doesn't trigger for a second time + // Press((ButtonControl)keyboard[action1.GetBind], queueEventOnly: true); + // Press((ButtonControl)keyboard[action2.controls[i].name], queueEventOnly: true); + // + // Assert.That(action1.WasPerformedThisFrame(), Is.False); + // Assert.That(action2.WasPerformedThisFrame(), Is.False); + } + + [Test] + [Category("Actions Priority")] + [TestCaseSource(nameof(TwoInputActionNoConflictingBindingTestCases))] + public void Actions_Priority_BothActionsWithDifferentPriorityFire_WhenThereIsNoConflictingBindingInverseOrder(TwoInputActionDataWrapper twoInputActions) + { + InputSystem.settings.shortcutKeysConsumeInput = true; + var keyboard = InputSystem.AddDevice(); + + var action1 = twoInputActions.Action1; + var action2 = twoInputActions.Action2; + + action1.Priority = 15; + action2.Priority = 5; + + action1.m_ActionMap.Enable(); + action2.m_ActionMap.Enable(); + // + var action1WasPerformed = false; + action1.performed += _ => action1WasPerformed = true; + + Assert.That(action1.WasPerformedThisFrame(), Is.False); + Assert.That(action2.WasPerformedThisFrame(), Is.False); + + PressBindingsForInputActions(keyboard, action1, action2); + + // Different letter keys: no conflict on the same control, so both shortcuts can perform despite different priorities. + Assert.That(action1WasPerformed, Is.True); + Assert.That(action2.WasPerformedThisFrame(), Is.True); + + // TODO: Darren, trigger just the bindings again to be sure the shortcut doesn't trigger for a second time + // Press((ButtonControl)keyboard[action1.GetBind], queueEventOnly: true); + // Press((ButtonControl)keyboard[action2.controls[i].name], queueEventOnly: true); + // + // Assert.That(action1.WasPerformedThisFrame(), Is.False); + // Assert.That(action2.WasPerformedThisFrame(), Is.False); + } + + [Test] + [Category("Actions Priority")] + [TestCaseSource(nameof(TwoInputActionNoConflictingBindingTestCases))] + public void Actions_Priority_BothActionsWithEqualPriorityFire_WhenThereIsNoConflictingBinding(TwoInputActionDataWrapper twoInputActions) + { + InputSystem.settings.shortcutKeysConsumeInput = true; + var keyboard = InputSystem.AddDevice(); + + var action1 = twoInputActions.Action1; + var action2 = twoInputActions.Action2; + + action1.Priority = 5; + action2.Priority = 5; + + action1.m_ActionMap.Enable(); + action2.m_ActionMap.Enable(); + + var action1WasPerformed = false; + action1.performed += _ => action1WasPerformed = true; + + Assert.That(action1.WasPerformedThisFrame(), Is.False); + Assert.That(action2.WasPerformedThisFrame(), Is.False); + + PressBindingsForInputActions(keyboard, action1, action2); + + // Different letter keys: no conflict on the same control, so both shortcuts can perform despite different priorities. + Assert.That(action1WasPerformed, Is.True); + Assert.That(action2.WasPerformedThisFrame(), Is.True); + + // TODO: Darren, trigger just the bindings again to be sure the shortcut doesn't trigger for a second time + // Press((ButtonControl)keyboard[action1.GetBind], queueEventOnly: true); + // Press((ButtonControl)keyboard[action2.controls[i].name], queueEventOnly: true); + // + // Assert.That(action1.WasPerformedThisFrame(), Is.False); + // Assert.That(action2.WasPerformedThisFrame(), Is.False); + } +} diff --git a/Assets/Tests/InputSystem/CoreTests_ActionsPriority.cs.meta b/Assets/Tests/InputSystem/CoreTests_ActionsPriority.cs.meta new file mode 100644 index 0000000000..fa702aebcb --- /dev/null +++ b/Assets/Tests/InputSystem/CoreTests_ActionsPriority.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: d82c47e3da544616936b989711abed4b +timeCreated: 1775041096 \ No newline at end of file diff --git a/Assets/Tests/InputSystem/InputActionCodeGeneratorActions.cs b/Assets/Tests/InputSystem/InputActionCodeGeneratorActions.cs index e70032896d..9167dc741f 100644 --- a/Assets/Tests/InputSystem/InputActionCodeGeneratorActions.cs +++ b/Assets/Tests/InputSystem/InputActionCodeGeneratorActions.cs @@ -99,7 +99,8 @@ public @InputActionCodeGeneratorActions() ""expectedControlType"": ""Button"", ""processors"": """", ""interactions"": """", - ""initialStateCheck"": false + ""initialStateCheck"": false, + ""priority"": 0 }, { ""name"": ""action2"", @@ -108,7 +109,8 @@ public @InputActionCodeGeneratorActions() ""expectedControlType"": ""Button"", ""processors"": """", ""interactions"": """", - ""initialStateCheck"": false + ""initialStateCheck"": false, + ""priority"": 0 } ], ""bindings"": [ diff --git a/Packages/com.unity.inputsystem/InputSystem/Actions/InputAction.cs b/Packages/com.unity.inputsystem/InputSystem/Actions/InputAction.cs index d7cb1eb6aa..c507011b25 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Actions/InputAction.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Actions/InputAction.cs @@ -196,6 +196,23 @@ public sealed class InputAction : ICloneable, IDisposable /// public InputActionType type => m_Type; + /// + /// Priority of this action when multiple bindings resolve to the same control. + /// + /// Effective range at runtime is 0–65535; the value is combined with control grouping data as an unsigned 16-bit integer. + /// + /// Applies to all bindings that target this action. It influences how the input system handles overlapping + /// bindings on a shared control—for example whether a performed action can mark the underlying input event as + /// handled, which affects further processing for other actions in the same group. Values 0–1 follow one path; + /// values greater than 1 follow another when the input system resolves overlapping bindings on the same control. + /// Values outside the 0–65535 range are truncated when stored in the internal representation. + /// + public int Priority + { + get => m_Priority; + set => m_Priority = value; + } + /// /// A stable, unique identifier for the action. /// @@ -971,6 +988,7 @@ public InputAction Clone() m_Interactions = m_Interactions, m_Processors = m_Processors, m_Flags = m_Flags, + m_Priority = m_Priority, }; return clone; } @@ -1790,6 +1808,7 @@ public unsafe float GetTimeoutCompletionPercentage() // For any other type of action, this is null. [SerializeField] internal InputBinding[] m_SingletonActionBindings; [SerializeField] internal ActionFlags m_Flags; + [SerializeField] internal int m_Priority; [NonSerialized] internal InputBinding? m_BindingMask; [NonSerialized] internal int m_BindingsStartIndex; diff --git a/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionMap.cs b/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionMap.cs index a07f286f7a..db280d9eef 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionMap.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionMap.cs @@ -1568,6 +1568,7 @@ internal struct ReadActionJson public string interactions; public bool passThrough; public bool initialStateCheck; + public int priority; // Bindings can either be on the action itself (in which case the action name // for each binding is implied) or listed separately in the action file. @@ -1597,6 +1598,12 @@ public InputAction ToAction(string actionName = null) actionType = InputActionType.Button; } + var clampedPriority = priority; + if (clampedPriority < 0) + clampedPriority = 0; + else if (clampedPriority > 65535) + clampedPriority = 65535; + return new InputAction(actionName ?? name, actionType) { m_Id = string.IsNullOrEmpty(id) ? null : id, @@ -1606,6 +1613,7 @@ public InputAction ToAction(string actionName = null) m_Processors = processors, m_Interactions = interactions, wantsInitialStateCheck = initialStateCheck, + m_Priority = clampedPriority, }; } } @@ -1620,6 +1628,7 @@ internal struct WriteActionJson public string processors; public string interactions; public bool initialStateCheck; + public int priority; public static WriteActionJson FromAction(InputAction action) { @@ -1632,6 +1641,7 @@ public static WriteActionJson FromAction(InputAction action) processors = action.processors, interactions = action.interactions, initialStateCheck = action.wantsInitialStateCheck, + priority = action.m_Priority, }; } } diff --git a/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionState.cs b/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionState.cs index dd4cb7d6de..eaca17dd7f 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionState.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionState.cs @@ -112,7 +112,7 @@ internal unsafe class InputActionState : IInputStateChangeMonitor, ICloneable, I public BindingState* bindingStates => memory.bindingStates; public InteractionState* interactionStates => memory.interactionStates; public int* controlIndexToBindingIndex => memory.controlIndexToBindingIndex; - public ushort* controlGroupingAndComplexity => memory.controlGroupingAndComplexity; + public ushort* controlGroupingAndPriority => memory.controlGroupingAndComplexity; public float* controlMagnitudes => memory.controlMagnitudes; public uint* enabledControls => (uint*)memory.enabledControls; @@ -151,32 +151,23 @@ private void ComputeControlGroupingIfNecessary() var disableControlGrouping = !InputSystem.settings.shortcutKeysConsumeInput; var currentGroup = 1u; + for (var i = 0; i < totalControlCount; ++i) { var control = controls[i]; - var bindingIndex = controlIndexToBindingIndex[i]; - ref var binding = ref bindingStates[bindingIndex]; + + int bindingIndex = controlIndexToBindingIndex[i]; ////REVIEW: take processors and interactions into account?? - // Compute complexity. - var complexity = 1; - if (binding.isPartOfComposite && !disableControlGrouping) - { - var compositeBindingIndex = binding.compositeOrCompositeBindingIndex; + var action = GetActionOrNull(bindingIndex); - for (var n = compositeBindingIndex + 1; n < totalBindingCount; ++n) - { - ref var partBinding = ref bindingStates[n]; - if (!partBinding.isPartOfComposite || partBinding.compositeOrCompositeBindingIndex != compositeBindingIndex) - break; - ++complexity; - } - } - controlGroupingAndComplexity[i * 2 + 1] = (ushort)complexity; + var priority = Math.Clamp(action != null && !disableControlGrouping ? action.Priority : 0, 0, 65536); + + controlGroupingAndPriority[i * 2 + 1] = (ushort)priority; // Compute grouping. If already set, skip. - if (controlGroupingAndComplexity[i * 2] == 0) + if (controlGroupingAndPriority[i * 2] == 0) { if (!disableControlGrouping) { @@ -191,11 +182,11 @@ private void ComputeControlGroupingIfNecessary() if (control != otherControl) continue; - controlGroupingAndComplexity[n * 2] = (ushort)currentGroup; + controlGroupingAndPriority[n * 2] = (ushort)currentGroup; } } - controlGroupingAndComplexity[i * 2] = (ushort)currentGroup; + controlGroupingAndPriority[i * 2] = (ushort)currentGroup; ++currentGroup; } @@ -556,7 +547,7 @@ private void RestoreActionStatesAfterReResolvingBindings(UnmanagedMemory oldStat // Restore action states. for (var actionIndex = 0; actionIndex < totalActionCount; ++actionIndex) { - ref var oldActionState = ref oldState.actionStates[actionIndex]; + ref TriggerState oldActionState = ref oldState.actionStates[actionIndex]; ref var newActionState = ref actionStates[actionIndex]; newActionState.lastCanceledInUpdate = oldActionState.lastCanceledInUpdate; @@ -1166,7 +1157,7 @@ private void EnableControls(int mapIndex, int controlStartIndex, int numControls var bindingStatePtr = &bindingStates[bindingIndex]; if (bindingStatePtr->wantsInitialStateCheck) SetInitialStateCheckPending(bindingStatePtr, true); - manager.AddStateChangeMonitor(controls[controlIndex], this, mapControlAndBindingIndex, controlGroupingAndComplexity[controlIndex * 2]); + manager.AddStateChangeMonitor(controls[controlIndex], this, mapControlAndBindingIndex, controlGroupingAndPriority[controlIndex * 2]); SetControlEnabled(controlIndex, true); } @@ -1388,7 +1379,7 @@ private long ToCombinedMapAndControlAndBindingIndex(int mapIndex, int controlInd { // We have limits on the numbers of maps, controls, and bindings we allow in any single // action state (see TriggerState.kMaxNumXXX). - var complexity = controlGroupingAndComplexity[controlIndex * 2 + 1]; + var complexity = controlGroupingAndPriority[controlIndex * 2 + 1]; var result = (long)controlIndex; result |= (long)bindingIndex << 24; result |= (long)mapIndex << 40; @@ -2478,7 +2469,7 @@ private void ChangePhaseOfActionInternal(int actionIndex, TriggerState* actionSt // When we perform an action, we mark the event handled such that FireStateChangeNotifications() // can then reset state monitors in the same group. // NOTE: We don't consume for controls at binding complexity 1. Those we fire in unison. - if (controlGroupingAndComplexity[trigger.controlIndex * 2 + 1] > 1 && + if (controlGroupingAndPriority[trigger.controlIndex * 2 + 1] > 0 && // we can end up switching to performed state from an interaction with a timeout, at which point // the original event will probably have been removed from memory, so make sure to check // we still have one @@ -4172,7 +4163,7 @@ public struct UnmanagedMemory : IDisposable ////REVIEW: make this an array of shorts rather than ints? public int* controlIndexToBindingIndex; - // Two shorts per control. First one is group number. Second one is complexity count. + // Two shorts per control. First one is group number. Second one is priority. public ushort* controlGroupingAndComplexity; public bool controlGroupingInitialized; diff --git a/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/Commands/Commands.cs b/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/Commands/Commands.cs index 3cfcf2df44..19a606650e 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/Commands/Commands.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/Commands/Commands.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using UnityEditor; +using UnityEngine; using UnityEngine.InputSystem.Editor.Lists; using UnityEngine.InputSystem.Utilities; @@ -534,6 +535,18 @@ public static Command SetCompositeBindingPartName(SerializedInputBinding binding }; } + public static Command ChangeActionPriority(SerializedInputAction inputAction, int priority) + { + return (in InputActionsEditorState state) => + { + var priorityProperty = inputAction.wrappedProperty.FindPropertyRelative(nameof(InputAction.m_Priority)); + priorityProperty.intValue = Mathf.Clamp(priority, 0, 65535); + state.serializedObject.ApplyModifiedProperties(); + state.m_Analytics?.RegisterActionEdit(); + return state; + }; + } + public static Command ChangeActionType(SerializedInputAction inputAction, InputActionType newValue) { return (in InputActionsEditorState state) => diff --git a/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/InputActionsEditorConstants.cs b/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/InputActionsEditorConstants.cs index c0c2c07c3b..25930895d9 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/InputActionsEditorConstants.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/InputActionsEditorConstants.cs @@ -34,6 +34,12 @@ internal static class InputActionsEditorConstants + "immediately trigger if any of its bound controls are currently in a non-default state. " + "This check happens implicitly for Value actions but can be explicitly enabled for Button and Pass-Through actions."; + public const string ActionPriorityTooltip = + "Priority for this action when several bindings share the same control. Applies to all bindings on the action. " + + "Effective range is 0–65535 at runtime (unsigned 16-bit). It affects how overlapping bindings are processed—for " + + "example whether a performed action can mark the input event as handled. Values 0–1 behave differently from " + + "values greater than 1 in that regard."; + public struct CommandEvents { public const string Rename = "Rename"; diff --git a/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/SerializedInputAction.cs b/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/SerializedInputAction.cs index 125c41a6a8..aa2a870fee 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/SerializedInputAction.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/SerializedInputAction.cs @@ -20,6 +20,7 @@ public SerializedInputAction(SerializedProperty serializedProperty) processors = serializedProperty.FindPropertyRelative(nameof(InputAction.m_Processors)).stringValue; propertyPath = wrappedProperty.propertyPath; initialStateCheck = ReadInitialStateCheck(serializedProperty); + priority = serializedProperty.FindPropertyRelative(nameof(InputAction.m_Priority)).intValue; actionTypeTooltip = serializedProperty.FindPropertyRelative(nameof(InputAction.m_Type)).GetTooltip(); expectedControlTypeTooltip = serializedProperty.FindPropertyRelative(nameof(InputAction.m_ExpectedControlType)).GetTooltip(); } @@ -32,6 +33,7 @@ public SerializedInputAction(SerializedProperty serializedProperty) public string processors { get; } public string propertyPath { get; } public bool initialStateCheck { get; } + public int priority { get; } public string actionTypeTooltip { get; } public string expectedControlTypeTooltip { get; } public SerializedProperty wrappedProperty { get; } @@ -60,6 +62,7 @@ public bool Equals(SerializedInputAction other) && interactions == other.interactions && processors == other.processors && initialStateCheck == other.initialStateCheck + && priority == other.priority && actionTypeTooltip == other.actionTypeTooltip && expectedControlTypeTooltip == other.expectedControlTypeTooltip && propertyPath == other.propertyPath; @@ -79,6 +82,7 @@ public override int GetHashCode() hashCode.Add(interactions); hashCode.Add(processors); hashCode.Add(initialStateCheck); + hashCode.Add(priority); hashCode.Add(actionTypeTooltip); hashCode.Add(expectedControlTypeTooltip); hashCode.Add(propertyPath); diff --git a/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/Views/ActionPropertiesView.cs b/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/Views/ActionPropertiesView.cs index 89bcf81f3e..fd31ec0ea6 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/Views/ActionPropertiesView.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/Views/ActionPropertiesView.cs @@ -89,6 +89,20 @@ public override void RedrawUI((SerializedInputAction ? , List) viewState Dispatch(Commands.ChangeActionControlType(inputAction, 0)); } + var priorityField = new IntegerField("Priority") + { + tooltip = InputActionsEditorConstants.ActionPriorityTooltip + }; + var priorityLabel = priorityField.Q