From 28640ebbb82a0ce3c006a389266b854da0ab3753 Mon Sep 17 00:00:00 2001 From: Darren Kelly Date: Tue, 17 Mar 2026 14:52:25 +0100 Subject: [PATCH 01/65] Comment out the shortcutKeysConsume input setting. --- Assets/Tests/InputSystem/CoreTests_Actions.cs | 16 ++++++++-------- Assets/Tests/InputSystem/CoreTests_Analytics.cs | 6 +++--- .../Actions/Composites/ButtonWithOneModifier.cs | 2 +- .../Actions/Composites/ButtonWithTwoModifiers.cs | 2 +- .../Actions/Composites/OneModifierComposite.cs | 2 +- .../Actions/Composites/TwoModifiersComposite.cs | 2 +- .../InputSystem/Actions/InputActionState.cs | 2 +- .../Editor/Analytics/InputBuildAnalytic.cs | 2 +- .../InputSystem/InputSettings.cs | 6 +++--- 9 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Assets/Tests/InputSystem/CoreTests_Actions.cs b/Assets/Tests/InputSystem/CoreTests_Actions.cs index fa24a4d915..2d454b40f3 100644 --- a/Assets/Tests/InputSystem/CoreTests_Actions.cs +++ b/Assets/Tests/InputSystem/CoreTests_Actions.cs @@ -211,7 +211,7 @@ public void Actions_WhenShortcutsDisabled_AllConflictingActionsTrigger() [TestCase(false)] public void Actions_WhenShortcutsEnabled_CanConsumeInput(bool legacyComposites) { - InputSystem.settings.shortcutKeysConsumeInput = true; + //InputSystem.settings.shortcutKeysConsumeInput = true; var keyboard = InputSystem.AddDevice(); @@ -303,7 +303,7 @@ public void Actions_WhenShortcutsEnabled_CanConsumeInput(bool legacyComposites) [Category("Actions")] public void Actions_ShortcutSupportDisabledByDefault() { - Assert.That(InputSystem.settings.shortcutKeysConsumeInput, Is.False); + //Assert.That(InputSystem.settings.shortcutKeysConsumeInput, Is.False); var keyboard = InputSystem.AddDevice(); @@ -391,7 +391,7 @@ public void Actions_CanBindMultipleShortcutSequencesBasedOnSameModifiers() [TestCase("leftShift", "leftAlt", "space", false)] public void Actions_WhenShortcutsEnabled_PressingShortcutSequenceInWrongOrder_DoesNotTriggerShortcut(string modifier1, string modifier2, string binding, bool legacyComposites) { - InputSystem.settings.shortcutKeysConsumeInput = true; + //InputSystem.settings.shortcutKeysConsumeInput = true; var keyboard = InputSystem.AddDevice(); @@ -476,7 +476,7 @@ public void Actions_WhenShortcutsDisabled_PressingShortcutSequenceInWrongOrder_D public void Actions_WhenShortcutsAreEnabled_PressingShortcutSequenceInWrongOrder_DoesNotTriggerShortcut_ExceptIfOverridden(string modifier1, string modifier2, string binding, bool legacyComposites, bool overrideModifiersNeedToBePressedFirst) { - InputSystem.settings.shortcutKeysConsumeInput = true; + //InputSystem.settings.shortcutKeysConsumeInput = true; var keyboard = InputSystem.AddDevice(); @@ -514,7 +514,7 @@ public void Actions_WhenShortcutsAreEnabled_PressingShortcutSequenceInWrongOrder [Category("Actions")] public void Actions_WhenShortcutsAreEnabled_CanHaveShortcutsWithButtonsUsingInitialStateChecks() { - InputSystem.settings.shortcutKeysConsumeInput = true; + //InputSystem.settings.shortcutKeysConsumeInput = true; var keyboard = InputSystem.AddDevice(); @@ -1684,7 +1684,7 @@ public void Actions_CanDisableAndEnableOtherAction_FromCallback() public void Actions_CanDisableAndEnable_FromCallbackWhileOtherCompositeBindingIsProgress() { // Enables "Modifier must be pressed first" behavior on all Composite Bindings - InputSystem.settings.shortcutKeysConsumeInput = true; + //InputSystem.settings.shortcutKeysConsumeInput = true; var keyboard = InputSystem.AddDevice(); var map = new InputActionMap("map"); @@ -10521,7 +10521,7 @@ public void Actions_CompositesReportControlThatTriggeredTheCompositeInCallback() public void Actions_CompositesInDifferentMapsTiedToSameControlsWork() { // This test relies on the same single input getting picked up by two different composites. - InputSystem.settings.shortcutKeysConsumeInput = false; + //InputSystem.settings.shortcutKeysConsumeInput = false; var keyboard = InputSystem.AddDevice(); var gamepad = InputSystem.AddDevice(); @@ -12502,7 +12502,7 @@ public void Actions_WithMultipleCompositeBindings_WithoutEvaluateMagnitude_Works [TestCase(false)] public void Actions_ImprovedShortcutSupport_ConsumesWASD(bool shortcutsEnabled) { - InputSystem.settings.shortcutKeysConsumeInput = shortcutsEnabled; + //InputSystem.settings.shortcutKeysConsumeInput = shortcutsEnabled; var keyboard = InputSystem.AddDevice(); diff --git a/Assets/Tests/InputSystem/CoreTests_Analytics.cs b/Assets/Tests/InputSystem/CoreTests_Analytics.cs index 7b75e0669d..7ba5dca4c6 100644 --- a/Assets/Tests/InputSystem/CoreTests_Analytics.cs +++ b/Assets/Tests/InputSystem/CoreTests_Analytics.cs @@ -450,7 +450,7 @@ public void Analytics_ShouldReportBuildAnalytics_WhenNotHavingSettingsAsset() Assert.That(data.max_queued_events_per_update, Is.EqualTo(defaultSettings.maxQueuedEventsPerUpdate)); Assert.That(data.supported_devices, Is.EqualTo(defaultSettings.supportedDevices)); Assert.That(data.disable_redundant_events_merging, Is.EqualTo(defaultSettings.disableRedundantEventsMerging)); - Assert.That(data.shortcut_keys_consume_input, Is.EqualTo(defaultSettings.shortcutKeysConsumeInput)); + //Assert.That(data.shortcut_keys_consume_input, Is.EqualTo(defaultSettings.shortcutKeysConsumeInput)); Assert.That(data.feature_optimized_controls_enabled, Is.EqualTo(defaultSettings.IsFeatureEnabled(InputFeatureNames.kUseOptimizedControls))); Assert.That(data.feature_read_value_caching_enabled, Is.EqualTo(defaultSettings.IsFeatureEnabled(InputFeatureNames.kUseReadValueCaching))); @@ -498,7 +498,7 @@ public void Analytics_ShouldReportBuildAnalytics_WhenHavingSettingsAssetWithCust customSettings.maxQueuedEventsPerUpdate = 12; customSettings.supportedDevices = Array.Empty(); customSettings.disableRedundantEventsMerging = true; - customSettings.shortcutKeysConsumeInput = true; + //customSettings.shortcutKeysConsumeInput = true; customSettings.SetInternalFeatureFlag(InputFeatureNames.kUseOptimizedControls, true); customSettings.SetInternalFeatureFlag(InputFeatureNames.kParanoidReadValueCachingChecks, true); @@ -544,7 +544,7 @@ public void Analytics_ShouldReportBuildAnalytics_WhenHavingSettingsAssetWithCust Assert.That(data.max_queued_events_per_update, Is.EqualTo(customSettings.maxQueuedEventsPerUpdate)); Assert.That(data.supported_devices, Is.EqualTo(customSettings.supportedDevices)); Assert.That(data.disable_redundant_events_merging, Is.EqualTo(customSettings.disableRedundantEventsMerging)); - Assert.That(data.shortcut_keys_consume_input, Is.EqualTo(customSettings.shortcutKeysConsumeInput)); + //Assert.That(data.shortcut_keys_consume_input, Is.EqualTo(customSettings.shortcutKeysConsumeInput)); Assert.That(data.feature_optimized_controls_enabled, Is.True); Assert.That(data.feature_read_value_caching_enabled, Is.True); diff --git a/Packages/com.unity.inputsystem/InputSystem/Actions/Composites/ButtonWithOneModifier.cs b/Packages/com.unity.inputsystem/InputSystem/Actions/Composites/ButtonWithOneModifier.cs index 75d6f333a3..06621ef5ad 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Actions/Composites/ButtonWithOneModifier.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Actions/Composites/ButtonWithOneModifier.cs @@ -196,7 +196,7 @@ protected override void FinishSetup(ref InputBindingCompositeContext context) #pragma warning restore CS0618 modifiersOrder = ModifiersOrder.Unordered; else - modifiersOrder = InputSystem.settings.shortcutKeysConsumeInput ? ModifiersOrder.Ordered : ModifiersOrder.Unordered; + modifiersOrder = /*InputSystem.settings.shortcutKeysConsumeInput ? ModifiersOrder.Ordered :*/ ModifiersOrder.Unordered; } } } diff --git a/Packages/com.unity.inputsystem/InputSystem/Actions/Composites/ButtonWithTwoModifiers.cs b/Packages/com.unity.inputsystem/InputSystem/Actions/Composites/ButtonWithTwoModifiers.cs index fcce470071..bcc78c448a 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Actions/Composites/ButtonWithTwoModifiers.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Actions/Composites/ButtonWithTwoModifiers.cs @@ -211,7 +211,7 @@ protected override void FinishSetup(ref InputBindingCompositeContext context) #pragma warning restore CS0618 modifiersOrder = ModifiersOrder.Unordered; else - modifiersOrder = InputSystem.settings.shortcutKeysConsumeInput ? ModifiersOrder.Ordered : ModifiersOrder.Unordered; + modifiersOrder = /*InputSystem.settings.shortcutKeysConsumeInput ? ModifiersOrder.Ordered :*/ ModifiersOrder.Unordered; } } } diff --git a/Packages/com.unity.inputsystem/InputSystem/Actions/Composites/OneModifierComposite.cs b/Packages/com.unity.inputsystem/InputSystem/Actions/Composites/OneModifierComposite.cs index 15c720145b..2c51fcf76a 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Actions/Composites/OneModifierComposite.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Actions/Composites/OneModifierComposite.cs @@ -214,7 +214,7 @@ protected override void FinishSetup(ref InputBindingCompositeContext context) #pragma warning restore CS0618 modifiersOrder = ModifiersOrder.Unordered; else - modifiersOrder = InputSystem.settings.shortcutKeysConsumeInput ? ModifiersOrder.Ordered : ModifiersOrder.Unordered; + modifiersOrder = /* InputSystem.settings.shortcutKeysConsumeInput ? ModifiersOrder.Ordered :*/ ModifiersOrder.Unordered; } } diff --git a/Packages/com.unity.inputsystem/InputSystem/Actions/Composites/TwoModifiersComposite.cs b/Packages/com.unity.inputsystem/InputSystem/Actions/Composites/TwoModifiersComposite.cs index 8044a628e4..8750cc41b2 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Actions/Composites/TwoModifiersComposite.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Actions/Composites/TwoModifiersComposite.cs @@ -224,7 +224,7 @@ protected override void FinishSetup(ref InputBindingCompositeContext context) #pragma warning restore CS0618 modifiersOrder = ModifiersOrder.Unordered; else - modifiersOrder = InputSystem.settings.shortcutKeysConsumeInput ? ModifiersOrder.Ordered : ModifiersOrder.Unordered; + modifiersOrder = /*InputSystem.settings.shortcutKeysConsumeInput ? ModifiersOrder.Ordered :*/ ModifiersOrder.Unordered; } } diff --git a/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionState.cs b/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionState.cs index dd4cb7d6de..b181ff14cc 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionState.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionState.cs @@ -148,7 +148,7 @@ private void ComputeControlGroupingIfNecessary() // If shortcut support is disabled, we simply put put all bindings at complexity=1 and // in their own group. - var disableControlGrouping = !InputSystem.settings.shortcutKeysConsumeInput; + var disableControlGrouping = true; // !InputSystem.settings.shortcutKeysConsumeInput; var currentGroup = 1u; for (var i = 0; i < totalControlCount; ++i) diff --git a/Packages/com.unity.inputsystem/InputSystem/Editor/Analytics/InputBuildAnalytic.cs b/Packages/com.unity.inputsystem/InputSystem/Editor/Analytics/InputBuildAnalytic.cs index 61256e5a78..ee931e2eef 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Editor/Analytics/InputBuildAnalytic.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Editor/Analytics/InputBuildAnalytic.cs @@ -188,7 +188,7 @@ public InputBuildAnalyticData(BuildReport report, InputSettings settings, InputS max_queued_events_per_update = settings.maxQueuedEventsPerUpdate; supported_devices = settings.supportedDevices.ToArray(); disable_redundant_events_merging = settings.disableRedundantEventsMerging; - shortcut_keys_consume_input = settings.shortcutKeysConsumeInput; + shortcut_keys_consume_input = false; // settings.shortcutKeysConsumeInput; feature_optimized_controls_enabled = settings.IsFeatureEnabled(InputFeatureNames.kUseOptimizedControls); feature_read_value_caching_enabled = settings.IsFeatureEnabled(InputFeatureNames.kUseReadValueCaching); diff --git a/Packages/com.unity.inputsystem/InputSystem/InputSettings.cs b/Packages/com.unity.inputsystem/InputSystem/InputSettings.cs index 1fa27fc29e..16a60318e6 100644 --- a/Packages/com.unity.inputsystem/InputSystem/InputSettings.cs +++ b/Packages/com.unity.inputsystem/InputSystem/InputSettings.cs @@ -691,7 +691,7 @@ public bool disableRedundantEventsMerging /// These conflicts may occur even between actions which belong to different Action Maps e.g. if using an UIInputModule with the Arrow Keys bound to the Navigate Action in the UI Action Map, this would interfere with other Action Maps using those keys. /// However conflicts would not occur between actions which belong to different Action Assets. /// - public bool shortcutKeysConsumeInput + /*public bool shortcutKeysConsumeInput { get => m_ShortcutKeysConsumeInputs; set @@ -702,7 +702,7 @@ public bool shortcutKeysConsumeInput m_ShortcutKeysConsumeInputs = value; OnChange(); } - } + }*/ /// /// Enable or disable an internal feature by its name. @@ -1054,7 +1054,7 @@ internal static bool AreEqual(InputSettings a, InputSettings b) a.maxQueuedEventsPerUpdate == b.maxQueuedEventsPerUpdate && CompareSets(a.supportedDevices, b.supportedDevices) && a.disableRedundantEventsMerging == b.disableRedundantEventsMerging && - a.shortcutKeysConsumeInput == b.shortcutKeysConsumeInput && + //a.shortcutKeysConsumeInput == b.shortcutKeysConsumeInput && CompareFeatureFlag(a, b, InputFeatureNames.kUseOptimizedControls) && CompareFeatureFlag(a, b, InputFeatureNames.kUseReadValueCaching) && From 1eef02c17b53708c5b210a11163a6836b4b6bed3 Mon Sep 17 00:00:00 2001 From: Darren Kelly Date: Thu, 19 Mar 2026 12:46:12 +0100 Subject: [PATCH 02/65] Add progress with solution for priority order for conflicting shortcuts. --- .../ProjectWideActionsExample.cs | 38 +++++++++++ .../InputSystem/Actions/InputActionState.cs | 64 ++++++++++++------- 2 files changed, 78 insertions(+), 24 deletions(-) diff --git a/Assets/Samples/ProjectWideActions/ProjectWideActionsExample.cs b/Assets/Samples/ProjectWideActions/ProjectWideActionsExample.cs index 8b066102dc..32c49901e2 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 diff --git a/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionState.cs b/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionState.cs index b181ff14cc..ad1f3869a7 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionState.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionState.cs @@ -148,52 +148,68 @@ private void ComputeControlGroupingIfNecessary() // If shortcut support is disabled, we simply put put all bindings at complexity=1 and // in their own group. - var disableControlGrouping = true; // !InputSystem.settings.shortcutKeysConsumeInput; + //var disableControlGrouping = true; // !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]; + ref BindingState binding = ref bindingStates[bindingIndex]; ////REVIEW: take processors and interactions into account?? // Compute complexity. var complexity = 1; - if (binding.isPartOfComposite && !disableControlGrouping) - { - var compositeBindingIndex = binding.compositeOrCompositeBindingIndex; + Debug.Log("Name " + control.name + " Action count " + memory.actionCount + " memory action state " + memory.actionStates[0].controlIndex); + + /*if (binding.isPartOfComposite && !disableControlGrouping) + {*/ + //int compositeBindingIndex = binding.compositeOrCompositeBindingIndex; - for (var n = compositeBindingIndex + 1; n < totalBindingCount; ++n) + /*for (var n = compositeBindingIndex + 1; n < totalBindingCount; ++n) + { + ref BindingState partBinding = ref bindingStates[n]; + if (partBinding.actionIndex == 10) { - ref var partBinding = ref bindingStates[n]; - if (!partBinding.isPartOfComposite || partBinding.compositeOrCompositeBindingIndex != compositeBindingIndex) - break; - ++complexity; + complexity = 2; + Debug.Log("Complexity was set to 2"); + break; } + /*if (!partBinding.isPartOfComposite || partBinding.compositeOrCompositeBindingIndex != compositeBindingIndex) + break;#1# + //++complexity; + }*/ + if (binding.actionIndex == 10) + { + complexity = 2; + Debug.Log("Complexity was set to 2"); } + + /*}*/ controlGroupingAndComplexity[i * 2 + 1] = (ushort)complexity; // Compute grouping. If already set, skip. if (controlGroupingAndComplexity[i * 2] == 0) { - if (!disableControlGrouping) + //if (!disableControlGrouping) + //{ + for (var n = 0; n < totalControlCount; ++n) { - for (var n = 0; n < totalControlCount; ++n) - { - // NOTE: We could compute group numbers based on device index + control offsets - // and thus make them work globally in a stable way. But we'd need a mechanism - // to then determine ordering of actions globally such that it is clear which - // action gets a first shot at an input. + // NOTE: We could compute group numbers based on device index + control offsets + // and thus make them work globally in a stable way. But we'd need a mechanism + // to then determine ordering of actions globally such that it is clear which + // action gets a first shot at an input. - var otherControl = controls[n]; - if (control != otherControl) - continue; + var otherControl = controls[n]; + if (control != otherControl) + continue; - controlGroupingAndComplexity[n * 2] = (ushort)currentGroup; - } + controlGroupingAndComplexity[n * 2] = (ushort)currentGroup; } + //} controlGroupingAndComplexity[i * 2] = (ushort)currentGroup; @@ -556,7 +572,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; From d4c1f3f4a2c736500cb6274400a39709afc36ee1 Mon Sep 17 00:00:00 2001 From: Darren Kelly Date: Fri, 20 Mar 2026 17:30:39 +0100 Subject: [PATCH 03/65] Add progress with investigation to switch to priority calculation. --- .../InputSystem/Actions/InputActionState.cs | 25 ++++++++++--------- .../InputSystem/Actions/InputBinding.cs | 8 ++++++ 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionState.cs b/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionState.cs index ad1f3869a7..595466891c 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; @@ -162,8 +162,9 @@ private void ComputeControlGroupingIfNecessary() ////REVIEW: take processors and interactions into account?? // Compute complexity. - var complexity = 1; - Debug.Log("Name " + control.name + " Action count " + memory.actionCount + " memory action state " + memory.actionStates[0].controlIndex); + InputBinding inputBinding = GetBinding(bindingIndex); + var priority = inputBinding.Priority; + Debug.Log("Action Name " + inputBinding.action + " Action count " + memory.actionCount + " memory action state " + memory.actionStates[0].controlIndex); /*if (binding.isPartOfComposite && !disableControlGrouping) {*/ @@ -184,15 +185,15 @@ private void ComputeControlGroupingIfNecessary() }*/ if (binding.actionIndex == 10) { - complexity = 2; + priority = 2; Debug.Log("Complexity was set to 2"); } /*}*/ - controlGroupingAndComplexity[i * 2 + 1] = (ushort)complexity; + controlGroupingAndPriority[i * 2 + 1] = (ushort)priority; // Compute grouping. If already set, skip. - if (controlGroupingAndComplexity[i * 2] == 0) + if (controlGroupingAndPriority[i * 2] == 0) { //if (!disableControlGrouping) //{ @@ -207,11 +208,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; } @@ -1182,7 +1183,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); } @@ -1404,7 +1405,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; @@ -2494,7 +2495,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] > 1 && // 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 @@ -4188,7 +4189,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/Actions/InputBinding.cs b/Packages/com.unity.inputsystem/InputSystem/Actions/InputBinding.cs index 45b4c3806c..8db37626b2 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Actions/InputBinding.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Actions/InputBinding.cs @@ -106,6 +106,12 @@ public Guid id set => m_Id = value.ToString(); } + public int Priority + { + get => m_Priority; + set => m_Priority = value; + } + /// /// Control path being bound to. /// @@ -396,6 +402,7 @@ public InputBinding(string path, string action = null, string groups = null, str m_OverridePath = default; m_OverrideInteractions = default; m_OverrideProcessors = default; + m_Priority = default; } public string GetNameOfComposite() @@ -429,6 +436,7 @@ public static InputBinding MaskByGroups(params string[] groups) [SerializeField] private string m_Name; [SerializeField] internal string m_Id; + [SerializeField] internal int m_Priority; [Tooltip("Path of the control to bind to. Matched at runtime to controls from InputDevices present at the time.\n\nCan either be " + "graphically from the control picker dropdown UI or edited manually in text mode by clicking the 'T' button. Internally, both " + "methods result in control path strings that look like, for example, \"/buttonSouth\".")] From 675afcec1cef3ee00e9617121f4e3df32f2aba09 Mon Sep 17 00:00:00 2001 From: Darren Kelly Date: Mon, 23 Mar 2026 13:22:38 +0100 Subject: [PATCH 04/65] Implement binding priority feature in Input System, including UI support and clamping logic. Add tooltip for priority field and update related classes to handle priority in bindings. --- .../InputSystem/Actions/InputActionMap.cs | 9 +++++++ .../InputSystem/Actions/InputBinding.cs | 11 +++++++++ .../UITKAssetEditor/Commands/Commands.cs | 13 ++++++++++ .../InputActionsEditorConstants.cs | 5 ++++ .../UITKAssetEditor/SerializedInputBinding.cs | 4 ++++ .../Views/BindingPropertiesView.cs | 24 +++++++++++++++++++ 6 files changed, 66 insertions(+) diff --git a/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionMap.cs b/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionMap.cs index a07f286f7a..59eae5a381 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionMap.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionMap.cs @@ -1521,9 +1521,16 @@ internal struct BindingJson public string action; public bool isComposite; public bool isPartOfComposite; + public int priority; public InputBinding ToBinding() { + var clampedPriority = priority; + if (clampedPriority < 0) + clampedPriority = 0; + else if (clampedPriority > 65535) + clampedPriority = 65535; + return new InputBinding { name = string.IsNullOrEmpty(name) ? null : name, @@ -1535,6 +1542,7 @@ public InputBinding ToBinding() groups = string.IsNullOrEmpty(groups) ? null : groups, isComposite = isComposite, isPartOfComposite = isPartOfComposite, + m_Priority = clampedPriority, }; } @@ -1551,6 +1559,7 @@ public static BindingJson FromBinding(ref InputBinding binding) groups = binding.groups, isComposite = binding.isComposite, isPartOfComposite = binding.isPartOfComposite, + priority = binding.m_Priority, }; } } diff --git a/Packages/com.unity.inputsystem/InputSystem/Actions/InputBinding.cs b/Packages/com.unity.inputsystem/InputSystem/Actions/InputBinding.cs index 8db37626b2..0a12bc42e6 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Actions/InputBinding.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Actions/InputBinding.cs @@ -106,6 +106,17 @@ public Guid id set => m_Id = value.ToString(); } + /// + /// Priority of this binding 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. + /// + /// This 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 bindings 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; 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..048de62d67 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 ChangeBindingPriority(SerializedInputBinding binding, int priority) + { + return (in InputActionsEditorState state) => + { + var priorityProperty = binding.wrappedProperty.FindPropertyRelative(nameof(InputBinding.m_Priority)); + priorityProperty.intValue = Mathf.Clamp(priority, 0, 65535); + state.serializedObject.ApplyModifiedProperties(); + state.m_Analytics?.RegisterBindingEdit(); + 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..62a9a01176 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/InputActionsEditorConstants.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/InputActionsEditorConstants.cs @@ -34,6 +34,11 @@ 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 BindingPriorityTooltip = + "Priority when several bindings share the same control. 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/SerializedInputBinding.cs b/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/SerializedInputBinding.cs index 16a81f6177..0c3e879526 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/SerializedInputBinding.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/SerializedInputBinding.cs @@ -30,6 +30,7 @@ public SerializedInputBinding(SerializedProperty serializedProperty) ? bindingGroups.Split(InputBinding.kSeparatorString, StringSplitOptions.RemoveEmptyEntries) : Array.Empty(); flags = (InputBinding.Flags)serializedProperty.FindPropertyRelative(nameof(InputBinding.m_Flags)).intValue; + priority = serializedProperty.FindPropertyRelative(nameof(InputBinding.m_Priority)).intValue; indexOfBinding = serializedProperty.GetIndexOfArrayElement(); isComposite = (flags & InputBinding.Flags.Composite) == InputBinding.Flags.Composite; isPartOfComposite = (flags & InputBinding.Flags.PartOfComposite) == InputBinding.Flags.PartOfComposite; @@ -48,6 +49,7 @@ public SerializedInputBinding(SerializedProperty serializedProperty) public string propertyPath { get; } public string[] controlSchemes { get; } public InputBinding.Flags flags { get; } + public int priority { get; } /// /// The index of this binding in the array that it is stored in. @@ -85,6 +87,7 @@ public bool Equals(SerializedInputBinding other) && processors == other.processors && action == other.action && flags == other.flags + && priority == other.priority && indexOfBinding == other.indexOfBinding && isComposite == other.isComposite && isPartOfComposite == other.isPartOfComposite @@ -107,6 +110,7 @@ public override int GetHashCode() hashCode.Add(processors); hashCode.Add(action); hashCode.Add((int)flags); + hashCode.Add(priority); hashCode.Add(indexOfBinding); hashCode.Add(isComposite); hashCode.Add(isPartOfComposite); diff --git a/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/Views/BindingPropertiesView.cs b/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/Views/BindingPropertiesView.cs index 1ef2eff60e..02ab2bf6f6 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/Views/BindingPropertiesView.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/Views/BindingPropertiesView.cs @@ -1,6 +1,7 @@ #if UNITY_EDITOR using System.Linq; using UnityEditor; +using UnityEditor.UIElements; using UnityEngine.UIElements; using UnityEngine.InputSystem.Layouts; using UnityEngine.InputSystem.Utilities; @@ -13,6 +14,7 @@ internal class BindingPropertiesView : ViewBase private readonly Foldout m_ParentFoldout; private CompositeBindingPropertiesView m_CompositeBindingPropertiesView; private CompositePartBindingPropertiesView m_CompositePartBindingPropertiesView; + private const int k_PriorityLabelWidth = 90; public BindingPropertiesView(VisualElement root, Foldout foldout, StateContainer stateContainer) : base(root, stateContainer) @@ -49,10 +51,12 @@ public override void RedrawUI(ViewState viewState) { m_ParentFoldout.text = "Composite"; m_CompositeBindingPropertiesView = CreateChildView(new CompositeBindingPropertiesView(rootElement, stateContainer)); + DrawPriorityField(binding.Value); } else if (binding.Value.isPartOfComposite) { m_CompositePartBindingPropertiesView = CreateChildView(new CompositePartBindingPropertiesView(rootElement, stateContainer)); + DrawPriorityField(binding.Value); DrawMatchingControlPaths(viewState); DrawControlSchemeToggles(viewState, binding.Value); } @@ -68,11 +72,31 @@ public override void RedrawUI(ViewState viewState) var controlPathContainer = new IMGUIContainer(controlPathEditor.OnGUI); rootElement.Add(controlPathContainer); + DrawPriorityField(binding.Value); DrawMatchingControlPaths(viewState); DrawControlSchemeToggles(viewState, binding.Value); } } + private void DrawPriorityField(SerializedInputBinding binding) + { + var priorityField = new IntegerField("Priority") + { + tooltip = InputActionsEditorConstants.BindingPriorityTooltip + }; + var priorityLabel = priorityField.Q /// - /// By default, if the setting is enabled, + /// By default, if the setting is enabled, /// and are required to be in pressed state before or at the same /// time that goes into pressed state for the composite as a whole to trigger. This means that binding to, /// for example, Ctrl+Shift+B, the ctrl and shift keys have to be pressed, in any order, before pressing the B key. /// This is the behavior usually expected with keyboard shortcuts. /// - /// If the setting is disabled, + /// If the setting is disabled, /// modifiers can be pressed after the button and the composite will still trigger. /// /// This field allows you to explicitly override this default inference. @@ -211,7 +211,7 @@ protected override void FinishSetup(ref InputBindingCompositeContext context) #pragma warning restore CS0618 modifiersOrder = ModifiersOrder.Unordered; else - modifiersOrder = InputSystem.settings.shortcutKeysConsumeInput ? ModifiersOrder.Ordered : ModifiersOrder.Unordered; + modifiersOrder = InputSystem.settings.IsShortcutComplexityModifierOrderActive ? ModifiersOrder.Ordered : ModifiersOrder.Unordered; } } } diff --git a/Packages/com.unity.inputsystem/InputSystem/Actions/Composites/OneModifierComposite.cs b/Packages/com.unity.inputsystem/InputSystem/Actions/Composites/OneModifierComposite.cs index 15c720145b..de0382945d 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Actions/Composites/OneModifierComposite.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Actions/Composites/OneModifierComposite.cs @@ -88,7 +88,7 @@ public class OneModifierComposite : InputBindingComposite /// Default value is false. /// /// - /// By default, if the setting is enabled, + /// By default, if the setting is enabled, /// if is bound to only s, then the composite requires /// to be pressed before pressing . This means that binding to, for example, /// Ctrl+B, the ctrl keys have to be pressed before pressing the B key. This is the behavior usually expected @@ -114,13 +114,13 @@ public class OneModifierComposite : InputBindingComposite public enum ModifiersOrder { /// - /// By default, if the setting is enabled, + /// By default, if the setting is enabled, /// if is bound to only s, then the composite requires /// to be pressed before pressing . This means that binding to, for example, /// Ctrl+B, the ctrl keys have to be pressed before pressing the B key. This is the behavior usually expected /// with keyboard shortcuts. /// - /// If the setting is disabled, + /// If the setting is disabled, /// modifiers can be pressed after the button and the composite will still trigger. /// Default = 0, @@ -144,13 +144,13 @@ public enum ModifiersOrder /// If set to Ordered or Unordered, the built-in logic to determine if modifiers need to be pressed first is overridden. /// /// - /// By default, if the setting is enabled, + /// By default, if the setting is enabled, /// if is bound to only s, then the composite requires /// to be pressed before pressing . This means that binding to, for example, /// Ctrl+B, the ctrl keys have to be pressed before pressing the B key. This is the behavior usually expected /// with keyboard shortcuts. /// - /// If the setting is disabled, + /// If the setting is disabled, /// modifiers can be pressed after the button and the composite will still trigger. /// /// However, when binding, for example, Ctrl+MouseDelta, it should be possible to press ctrl at any time. The default @@ -214,7 +214,7 @@ protected override void FinishSetup(ref InputBindingCompositeContext context) #pragma warning restore CS0618 modifiersOrder = ModifiersOrder.Unordered; else - modifiersOrder = InputSystem.settings.shortcutKeysConsumeInput ? ModifiersOrder.Ordered : ModifiersOrder.Unordered; + modifiersOrder = InputSystem.settings.IsShortcutComplexityModifierOrderActive ? ModifiersOrder.Ordered : ModifiersOrder.Unordered; } } diff --git a/Packages/com.unity.inputsystem/InputSystem/Actions/Composites/TwoModifiersComposite.cs b/Packages/com.unity.inputsystem/InputSystem/Actions/Composites/TwoModifiersComposite.cs index 8044a628e4..9b67108f63 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Actions/Composites/TwoModifiersComposite.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Actions/Composites/TwoModifiersComposite.cs @@ -90,7 +90,7 @@ public class TwoModifiersComposite : InputBindingComposite /// Default value is false. /// /// - /// By default, if the setting is enabled, + /// By default, if the setting is enabled, /// if is bound to only s, then the composite requires /// both and to be pressed before pressing . /// This means that binding to, for example, Ctrl+Shift+B, the ctrl and shift keys have to be pressed, in any order, @@ -116,13 +116,13 @@ public class TwoModifiersComposite : InputBindingComposite public enum ModifiersOrder { /// - /// By default, if the setting is enabled, + /// By default, if the setting is enabled, /// if is bound to only s, then the composite requires /// both and to be pressed before pressing . /// This means that binding to, for example, Ctrl+Shift+B, the ctrl and shift keys have to be pressed, in any order, /// before pressing the B key. This is the behavior usually expected with keyboard shortcuts. /// - /// If the setting is disabled, + /// If the setting is disabled, /// modifiers can be pressed after the button and the composite will still trigger. /// Default = 0, @@ -146,13 +146,13 @@ public enum ModifiersOrder /// If set to Ordered or Unordered, the built-in logic to determine if modifiers need to be pressed first is overridden. /// /// - /// By default, if the setting is enabled, + /// By default, if the setting is enabled, /// if is bound to only s, then the composite requires /// both and to be pressed before pressing . /// This means that binding to, for example, Ctrl+Shift+B, the ctrl and shift keys have to be pressed, in any order, /// before pressing the B key. This is the behavior usually expected with keyboard shortcuts. /// - /// If the setting is disabled, + /// If the setting is disabled, /// modifiers can be pressed after the button and the composite will still trigger. /// /// This field allows you to explicitly override this default inference and make the order mandatory or make it so that regardless of what @@ -224,7 +224,7 @@ protected override void FinishSetup(ref InputBindingCompositeContext context) #pragma warning restore CS0618 modifiersOrder = ModifiersOrder.Unordered; else - modifiersOrder = InputSystem.settings.shortcutKeysConsumeInput ? ModifiersOrder.Ordered : ModifiersOrder.Unordered; + modifiersOrder = InputSystem.settings.IsShortcutComplexityModifierOrderActive ? ModifiersOrder.Ordered : ModifiersOrder.Unordered; } } diff --git a/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionState.cs b/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionState.cs index c6e89f3dee..21004458dc 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionState.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionState.cs @@ -115,7 +115,8 @@ internal unsafe class InputActionState : IInputStateChangeMonitor, ICloneable, I private ushort* controlGroupingAndPriority => memory.controlGroupingAndPriority; /// - /// Layout of : interleaved ushort pairs (group id, binding priority) per control slot. + /// Layout of : interleaved ushort pairs (group id, secondary) per control slot. + /// Secondary is action priority when is enabled; otherwise composite complexity (develop behavior). /// internal static class ControlGroupingTable { @@ -165,42 +166,98 @@ private void ComputeControlGroupingIfNecessary() if (memory.controlGroupingInitialized) return; - var currentGroup = 1u; - - for (var i = 0; i < totalControlCount; ++i) + var settings = InputSystem.settings; + if (settings.IsShortcutResolutionUsingActionPriority) { - var control = controls[i]; + var currentGroup = 1u; - int bindingIndex = controlIndexToBindingIndex[i]; + for (var i = 0; i < totalControlCount; ++i) + { + var control = controls[i]; - ////REVIEW: take processors and interactions into account?? + var bindingIndex = controlIndexToBindingIndex[i]; - var action = GetActionOrNull(bindingIndex); + ////REVIEW: take processors and interactions into account?? - var priority = Math.Clamp(action != null ? action.Priority : 0, 0, 65535); + var action = GetActionOrNull(bindingIndex); - controlGroupingAndPriority[ControlGroupingTable.PriorityElementIndex(i)] = (ushort)priority; + var priority = Math.Clamp(action != null ? action.Priority : 0, 0, 65535); - // Compute grouping. If already set, skip. - if (controlGroupingAndPriority[ControlGroupingTable.GroupElementIndex(i)] == 0) - { - for (var n = 0; n < totalControlCount; ++n) + controlGroupingAndPriority[ControlGroupingTable.PriorityElementIndex(i)] = (ushort)priority; + + // Compute grouping. If already set, skip. + if (controlGroupingAndPriority[ControlGroupingTable.GroupElementIndex(i)] == 0) { - // NOTE: We could compute group numbers based on device index + control offsets - // and thus make them work globally in a stable way. But we'd need a mechanism - // to then determine ordering of actions globally such that it is clear which - // action gets a first shot at an input. + for (var n = 0; n < totalControlCount; ++n) + { + // NOTE: We could compute group numbers based on device index + control offsets + // and thus make them work globally in a stable way. But we'd need a mechanism + // to then determine ordering of actions globally such that it is clear which + // action gets a first shot at an input. - var otherControl = controls[n]; - if (control != otherControl) - continue; + var otherControl = controls[n]; + if (control != otherControl) + continue; + + controlGroupingAndPriority[ControlGroupingTable.GroupElementIndex(n)] = (ushort)currentGroup; + } + + controlGroupingAndPriority[ControlGroupingTable.GroupElementIndex(i)] = (ushort)currentGroup; - controlGroupingAndPriority[ControlGroupingTable.GroupElementIndex(n)] = (ushort)currentGroup; + ++currentGroup; } + } + } + else + { + // Complexity-based path (same as develop when shortcutKeysConsumeInput is off: disable grouping). + var disableControlGrouping = !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]; - controlGroupingAndPriority[ControlGroupingTable.GroupElementIndex(i)] = (ushort)currentGroup; + ////REVIEW: take processors and interactions into account?? - ++currentGroup; + // Compute complexity. + var complexity = 1; + if (binding.isPartOfComposite && !disableControlGrouping) + { + var compositeBindingIndex = binding.compositeOrCompositeBindingIndex; + + for (var n = compositeBindingIndex + 1; n < totalBindingCount; ++n) + { + ref var partBinding = ref bindingStates[n]; + if (!partBinding.isPartOfComposite || partBinding.compositeOrCompositeBindingIndex != compositeBindingIndex) + break; + ++complexity; + } + } + + controlGroupingAndPriority[ControlGroupingTable.PriorityElementIndex(i)] = (ushort)complexity; + + // Compute grouping. If already set, skip. + if (controlGroupingAndPriority[ControlGroupingTable.GroupElementIndex(i)] == 0) + { + if (!disableControlGrouping) + { + for (var n = 0; n < totalControlCount; ++n) + { + var otherControl = controls[n]; + if (control != otherControl) + continue; + + controlGroupingAndPriority[ControlGroupingTable.GroupElementIndex(n)] = (ushort)currentGroup; + } + } + + controlGroupingAndPriority[ControlGroupingTable.GroupElementIndex(i)] = (ushort)currentGroup; + + ++currentGroup; + } } } @@ -1037,6 +1094,9 @@ private void EnableControls(InputAction action) internal void OnActionPriorityChanged(InputAction action) { + if (!InputSystem.settings.IsShortcutResolutionUsingActionPriority) + return; + Debug.Assert(action != null, "Action must not be null"); Debug.Assert(action.m_ActionMap != null, "Action must have action map"); @@ -1446,7 +1506,7 @@ void IInputStateChangeMonitor.NotifyTimerExpired(InputControl control, double ti } /// - /// Bit pack the mapIndex, controlIndex, bindingIndex and binding-priority components into a single long monitor index value. + /// Bit pack the mapIndex, controlIndex, bindingIndex and monitor sort value into a single long monitor index value. /// /// The mapIndex value to pack. /// The controlIndex value to pack. @@ -1456,7 +1516,7 @@ void IInputStateChangeMonitor.NotifyTimerExpired(InputControl control, double ti /// monitors. While we could look up map and binding indices from control indices, keeping /// all the information together avoids having to unnecessarily jump around in memory to grab /// the various pieces of data. - /// Priority is read from data for . + /// The high bits store action priority or composite complexity depending on . /// private long ToCombinedMapAndControlAndBindingIndex(int mapIndex, int controlIndex, int bindingIndex) { @@ -1466,6 +1526,14 @@ private long ToCombinedMapAndControlAndBindingIndex(int mapIndex, int controlInd GetControlBindingPriority(controlIndex)).Packed; } + /// + /// Extract the complexity or priority component from the monitor index (high 8 bits). + /// + internal static int GetComplexityFromMonitorIndex(long mapControlAndBindingIndex) + { + return (int)((mapControlAndBindingIndex >> 48) & 0xff); + } + /// /// Process a state change that has happened in one of the controls attached /// to this action map state. @@ -2522,9 +2590,13 @@ private void ChangePhaseOfActionInternal(int actionIndex, TriggerState* actionSt newState.lastCanceledInUpdate = actionState->lastCanceledInUpdate; // When we perform an action, we mark the event handled such that FireStateChangeNotifications() - // can then reset state monitors in the same group (strictly lower binding priority only). - // NOTE: We don't consume for controls at binding priority 0. Those we fire in unison. - if (GetControlBindingPriority(trigger.controlIndex) > 0 && + // can reset other monitors (priority: strictly lower priority; complexity: same group after sort). + var settings = InputSystem.settings; + var secondary = GetControlBindingPriority(trigger.controlIndex); + var shouldConsumeHandled = settings.IsShortcutResolutionUsingActionPriority + ? secondary > 0 + : secondary > 1; + if (shouldConsumeHandled && // 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 @@ -4218,7 +4290,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 priority. + // Two shorts per control. First one is group number. Second is priority or complexity (see InputActionState.ComputeControlGroupingIfNecessary). public ushort* controlGroupingAndPriority; public bool controlGroupingInitialized; @@ -4524,6 +4596,23 @@ internal static int FindAllEnabledActions(List result) return numFound; } + /// + /// Re-resolve bindings for every live action state so control grouping and monitor indices match shortcut settings. + /// + internal static void RequestBindingResolutionAfterShortcutSettingsChange() + { + for (var i = 0; i < s_GlobalState.globalList.length; ++i) + { + var handle = s_GlobalState.globalList[i]; + if (!handle.IsAllocated || handle.Target == null) + continue; + var state = (InputActionState)handle.Target; + if (state.totalMapCount == 0) + continue; + state.maps[0].LazyResolveBindings(fullResolve: false); + } + } + ////TODO: when re-resolving, we need to preserve InteractionStates and not just reset them /// diff --git a/Packages/com.unity.inputsystem/InputSystem/Editor/Analytics/InputBuildAnalytic.cs b/Packages/com.unity.inputsystem/InputSystem/Editor/Analytics/InputBuildAnalytic.cs index 61256e5a78..1f206f43ef 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Editor/Analytics/InputBuildAnalytic.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Editor/Analytics/InputBuildAnalytic.cs @@ -189,6 +189,7 @@ public InputBuildAnalyticData(BuildReport report, InputSettings settings, InputS supported_devices = settings.supportedDevices.ToArray(); disable_redundant_events_merging = settings.disableRedundantEventsMerging; shortcut_keys_consume_input = settings.shortcutKeysConsumeInput; + shortcut_keys_use_action_priority = settings.shortcutKeysUseActionPriority; feature_optimized_controls_enabled = settings.IsFeatureEnabled(InputFeatureNames.kUseOptimizedControls); feature_read_value_caching_enabled = settings.IsFeatureEnabled(InputFeatureNames.kUseReadValueCaching); @@ -308,6 +309,11 @@ public InputBuildAnalyticData(BuildReport report, InputSettings settings, InputS /// public bool shortcut_keys_consume_input; + /// + /// Represents + /// + public bool shortcut_keys_use_action_priority; + #endregion #region Feature flag settings diff --git a/Packages/com.unity.inputsystem/InputSystem/Editor/Settings/InputSettingsProvider.cs b/Packages/com.unity.inputsystem/InputSystem/Editor/Settings/InputSettingsProvider.cs index 1ac676a022..d314413df2 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Editor/Settings/InputSettingsProvider.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Editor/Settings/InputSettingsProvider.cs @@ -24,7 +24,7 @@ internal class InputSettingsProvider : SettingsProvider, IDisposable private static readonly string[] kInputSettingsKeywords = { - "Input", "Action", "Controls", "Gamepad", "Keyboard", "Mouse", "Touch" + "Input", "Action", "Controls", "Gamepad", "Keyboard", "Mouse", "Touch", "Shortcut", "Priority", "Complexity", "Consumption" }; public static void Open() @@ -171,15 +171,29 @@ public override void OnGUI(string searchContext) EditorGUILayout.LabelField("Improved Shortcut Support", EditorStyles.boldLabel); EditorGUILayout.Space(); EditorGUILayout.PropertyField(m_ShortcutKeysConsumeInputs, m_ShortcutKeysConsumeInputsContent); - if (m_ShortcutKeysConsumeInputs.boolValue) - EditorGUILayout.HelpBox("Please note that enabling Improved Shortcut Support will cause actions with composite bindings to consume input and block any other actions which are enabled and sharing the same controls. " - + "Input consumption is performed in priority order, with the action containing the greatest number of bindings checked first. " - + "Therefore actions requiring fewer keypresses will not be triggered if an action using more keypresses is triggered and has overlapping controls. " - + "This works for shortcut keys, however in other cases this might not give the desired result, especially where there are actions with the exact same number of composite controls, in which case it is non-deterministic which action will be triggered. " - + "These conflicts may occur even between actions which belong to different Action Maps e.g. if using an UIInputModule with the Arrow Keys bound to the Navigate Action in the UI Action Map, this would interfere with other Action Maps using those keys. " - + "However conflicts would not occur between actions which belong to different Action Assets. " - + "Since event consumption only occurs for enabled actions, you can resolve unexpected issues by ensuring that only those Actions or Action Maps that are relevant to your game's current context are enabled. Enabling or disabling actions as your game or application moves between different contexts. " - , MessageType.None); + EditorGUILayout.PropertyField(m_ShortcutKeysUseActionPriority, m_ShortcutKeysUseActionPriorityContent); + if (m_ShortcutKeysUseActionPriority.boolValue && m_ShortcutKeysConsumeInputs.boolValue) + { + EditorGUILayout.HelpBox( + "Action priority shortcut resolution is enabled and takes precedence. Complexity-based consumption is also toggled on but will not be used until action priority is turned off.", + MessageType.Info); + } + else if (m_ShortcutKeysUseActionPriority.boolValue) + { + EditorGUILayout.HelpBox( + "When several enabled actions share the same control, the action with the higher Priority value is ordered first and can consume input so lower-priority actions do not trigger on the same event. " + + "Set Priority on each action in the Input Actions editor. Serialized priority values are kept when this option is disabled.", + MessageType.None); + } + else if (m_ShortcutKeysConsumeInputs.boolValue) + { + EditorGUILayout.HelpBox( + "Composite bindings can consume overlapping input using binding-chain depth (complexity): actions with more composite parts are considered before simpler bindings on the same controls. " + + "This works well for shortcut keys; when two composites have the same depth, resolution can be non-deterministic. " + + "Conflicts can occur between different action maps (for example UI navigation versus gameplay on the same keys) but not between different action assets. " + + "Since consumption applies to enabled actions only, disable maps or actions you do not need in the current context to reduce surprises.", + MessageType.None); + } if (EditorGUI.EndChangeCheck()) Apply(); @@ -300,6 +314,7 @@ private void InitializeWithCurrentSettings() m_TapRadius = m_SettingsObject.FindProperty("m_TapRadius"); m_MultiTapDelayTime = m_SettingsObject.FindProperty("m_MultiTapDelayTime"); m_ShortcutKeysConsumeInputs = m_SettingsObject.FindProperty("m_ShortcutKeysConsumeInputs"); + m_ShortcutKeysUseActionPriority = m_SettingsObject.FindProperty("m_ShortcutKeysUseActionPriority"); m_UpdateModeContent = new GUIContent("Update Mode", "When should the Input System be updated?"); #if UNITY_INPUT_SYSTEM_PLATFORM_SCROLL_DELTA @@ -330,7 +345,8 @@ private void InitializeWithCurrentSettings() m_DefaultHoldTimeContent = new GUIContent("Default Hold Time", "Default duration to be used for Hold interactions."); m_TapRadiusContent = new GUIContent("Tap Radius", "Maximum distance between two finger taps on a touch screen device allowed for the system to consider this a tap of the same touch (as opposed to a new touch)."); m_MultiTapDelayTimeContent = new GUIContent("MultiTap Delay Time", "Default delay to be allowed between taps for MultiTap interactions. Also used by by touch devices to count multi taps."); - m_ShortcutKeysConsumeInputsContent = new GUIContent("Enable Input Consumption", "Actions are exclusively triggered and will consume/block other actions sharing the same input. E.g. when pressing the 'Shift+B' keys, the associated action would trigger but any action bound to just the 'B' key would be prevented from triggering at the same time."); + m_ShortcutKeysConsumeInputsContent = new GUIContent("Complexity-Based Shortcut Resolution", "When enabled (and action priority resolution is off), composite bindings consume overlapping input using binding-chain depth (complexity), not per-action priority."); + m_ShortcutKeysUseActionPriorityContent = new GUIContent("Action Priority Shortcut Resolution", "When enabled, overlapping actions are resolved using each action's Priority value. This overrides complexity-based resolution even if it is also enabled."); // Initialize ReorderableList for list of supported devices. var supportedDevicesProperty = m_SettingsObject.FindProperty("m_SupportedDevices"); @@ -442,6 +458,7 @@ private static string[] FindInputSettingsInProject() [NonSerialized] private SerializedProperty m_TapRadius; [NonSerialized] private SerializedProperty m_MultiTapDelayTime; [NonSerialized] private SerializedProperty m_ShortcutKeysConsumeInputs; + [NonSerialized] private SerializedProperty m_ShortcutKeysUseActionPriority; [NonSerialized] private ReorderableList m_SupportedDevices; [NonSerialized] private string[] m_AvailableInputSettingsAssets; @@ -468,6 +485,7 @@ private static string[] FindInputSettingsInProject() private GUIContent m_TapRadiusContent; private GUIContent m_MultiTapDelayTimeContent; private GUIContent m_ShortcutKeysConsumeInputsContent; + private GUIContent m_ShortcutKeysUseActionPriorityContent; [NonSerialized] private InputSettingsiOSProvider m_iOSProvider; 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 fd31ec0ea6..716f725e39 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,8 @@ public override void RedrawUI((SerializedInputAction ? , List) viewState Dispatch(Commands.ChangeActionControlType(inputAction, 0)); } + var showPriority = InputSystem.settings != null && InputSystem.settings.shortcutKeysUseActionPriority; + var priorityField = new IntegerField("Priority") { tooltip = InputActionsEditorConstants.ActionPriorityTooltip @@ -101,7 +103,8 @@ public override void RedrawUI((SerializedInputAction ? , List) viewState { Dispatch(Commands.ChangeActionPriority(inputAction, evt.newValue)); }); - rootElement.Add(priorityField); + if (showPriority) + rootElement.Add(priorityField); if (inputAction.type != InputActionType.Value) { diff --git a/Packages/com.unity.inputsystem/InputSystem/InputManager.cs b/Packages/com.unity.inputsystem/InputSystem/InputManager.cs index 8295fda73a..68fde417fb 100644 --- a/Packages/com.unity.inputsystem/InputSystem/InputManager.cs +++ b/Packages/com.unity.inputsystem/InputSystem/InputManager.cs @@ -2432,6 +2432,9 @@ internal struct AvailableDevice internal InputManagerStateMonitors m_StateMonitors; private InputMetrics m_Metrics; private InputSettings m_Settings; + private bool m_HaveCachedShortcutResolutionSettings; + private bool m_CachedShortcutKeysConsumeInput; + private bool m_CachedShortcutKeysUseActionPriority; // Extract as booleans (from m_Settings) because feature check is in the hot path @@ -3066,6 +3069,15 @@ internal void ApplySettings() foreach (var device in devices) device.MarkAsStaleRecursively(); + var consume = m_Settings.shortcutKeysConsumeInput; + var useActionPriority = m_Settings.shortcutKeysUseActionPriority; + if (m_HaveCachedShortcutResolutionSettings && + (m_CachedShortcutKeysConsumeInput != consume || m_CachedShortcutKeysUseActionPriority != useActionPriority)) + InputActionState.RequestBindingResolutionAfterShortcutSettingsChange(); + m_CachedShortcutKeysConsumeInput = consume; + m_CachedShortcutKeysUseActionPriority = useActionPriority; + m_HaveCachedShortcutResolutionSettings = true; + // Let listeners know. DelegateHelpers.InvokeCallbacksSafe(ref m_SettingsChangedListeners, k_InputOnSettingsChangeMarker, "InputSystem.onSettingsChange"); diff --git a/Packages/com.unity.inputsystem/InputSystem/InputManagerStateMonitors.cs b/Packages/com.unity.inputsystem/InputSystem/InputManagerStateMonitors.cs index 8488c0d298..946149b3cf 100644 --- a/Packages/com.unity.inputsystem/InputSystem/InputManagerStateMonitors.cs +++ b/Packages/com.unity.inputsystem/InputSystem/InputManagerStateMonitors.cs @@ -270,13 +270,24 @@ internal unsafe void FireStateChangeNotifications(int deviceIndex, double intern if (!previouslyHandled && eventPtr->handled) { var groupIndex = listeners[i].groupIndex; - var handlerPriority = InputActionStateMonitorIndex.FromPacked(listener.monitorIndex).Priority; - for (var n = i + 1; n < signals.length; ++n) + if (InputSystem.settings.IsShortcutResolutionUsingActionPriority) { - if (listeners[n].groupIndex == groupIndex && listeners[n].monitor == listener.monitor) + var handlerPriority = InputActionStateMonitorIndex.FromPacked(listener.monitorIndex).Priority; + for (var n = i + 1; n < signals.length; ++n) { - var candidatePriority = InputActionStateMonitorIndex.FromPacked(listeners[n].monitorIndex).Priority; - if (candidatePriority < handlerPriority) + if (listeners[n].groupIndex == groupIndex && listeners[n].monitor == listener.monitor) + { + var candidatePriority = InputActionStateMonitorIndex.FromPacked(listeners[n].monitorIndex).Priority; + if (candidatePriority < handlerPriority) + signals.ClearBit(n); + } + } + } + else + { + for (var n = i + 1; n < signals.length; ++n) + { + if (listeners[n].groupIndex == groupIndex && listeners[n].monitor == listener.monitor) signals.ClearBit(n); } } @@ -408,17 +419,33 @@ private void RemoveAt(int i) public void SortMonitorsByIndex() { + var useActionPriority = InputSystem.settings.IsShortcutResolutionUsingActionPriority; for (var i = 1; i < signalled.length; ++i) { for (var j = i; j > 0; --j) { - var firstPriority = InputActionStateMonitorIndex.FromPacked(listeners[j - 1].monitorIndex).Priority; - var secondPriority = InputActionStateMonitorIndex.FromPacked(listeners[j].monitorIndex).Priority; - if (firstPriority >= secondPriority) - break; + if (useActionPriority) + { + var firstPriority = InputActionStateMonitorIndex.FromPacked(listeners[j - 1].monitorIndex).Priority; + var secondPriority = InputActionStateMonitorIndex.FromPacked(listeners[j].monitorIndex).Priority; + if (firstPriority >= secondPriority) + break; + } + else + { + // Sort by complexities only to keep the sort stable + // i.e. don't reverse the order of controls which have the same complexity + var firstComplexity = InputActionState.GetComplexityFromMonitorIndex(listeners[j - 1].monitorIndex); + var secondComplexity = InputActionState.GetComplexityFromMonitorIndex(listeners[j].monitorIndex); + if (firstComplexity >= secondComplexity) + break; + } listeners.SwapElements(j, j - 1); memoryRegions.SwapElements(j, j - 1); + + // We can ignore the `signalled` array here as we call this method only + // when all monitors are in non-signalled state. } } diff --git a/Packages/com.unity.inputsystem/InputSystem/InputSettings.cs b/Packages/com.unity.inputsystem/InputSystem/InputSettings.cs index 1fa27fc29e..5abf48f574 100644 --- a/Packages/com.unity.inputsystem/InputSystem/InputSettings.cs +++ b/Packages/com.unity.inputsystem/InputSystem/InputSettings.cs @@ -679,18 +679,17 @@ public bool disableRedundantEventsMerging } /// - /// Improves shortcut key support by making composite controls consume control input + /// Enables complexity-based shortcut resolution: composite bindings consume overlapping input using binding-chain depth (complexity), not per-action priority. /// /// - /// Actions are exclusively triggered and will consume/block other actions sharing the same input. - /// E.g. when pressing the 'Shift+B' keys, the associated action would trigger but any action bound to just the 'B' key would be prevented from triggering at the same time. - /// Please note that enabling this will cause actions with composite bindings to consume input and block any other actions which are enabled and sharing the same controls. - /// Input consumption is performed in priority order, with the action containing the greatest number of bindings checked first. - /// Therefore actions requiring fewer keypresses will not be triggered if an action using more keypresses is triggered and has overlapping controls. - /// This works for shortcut keys, however in other cases this might not give the desired result, especially where there are actions with the exact same number of composite controls, in which case it is non-deterministic which action will be triggered. - /// These conflicts may occur even between actions which belong to different Action Maps e.g. if using an UIInputModule with the Arrow Keys bound to the Navigate Action in the UI Action Map, this would interfere with other Action Maps using those keys. - /// However conflicts would not occur between actions which belong to different Action Assets. + /// When enabled and is disabled, actions can exclusively trigger and consume or block other actions sharing the same input. + /// For example, when pressing the 'Shift+B' keys, the associated action can trigger while an action bound only to the 'B' key is prevented from triggering at the same time. + /// Resolution uses composite complexity (more parts in the composite are considered first). Fewer-part composites do not win over a triggered higher-complexity binding on overlapping controls. + /// This works well for shortcut keys; in other cases results may be undesirable, especially when two composites have the same complexity (order is then non-deterministic). + /// Conflicts can occur between actions in different action maps (for example UI navigation on arrow keys versus gameplay on the same keys), but not between different action assets. + /// When is enabled, this setting does not select the resolution strategy; use for priority-based resolution instead. /// + /// public bool shortcutKeysConsumeInput { get => m_ShortcutKeysConsumeInputs; @@ -704,6 +703,42 @@ public bool shortcutKeysConsumeInput } } + /// + /// When enabled, shortcut overlap resolution uses each action's instead of composite complexity. + /// + /// + /// If this is enabled, it takes precedence over for resolution behavior: the system uses priority-based consumption and ordering even when complexity-based consumption is also toggled on. + /// Serialized priority values on actions are always kept; when this is off, priorities are not applied at runtime and the Priority field is hidden in the Input Actions editor. + /// + /// + public bool shortcutKeysUseActionPriority + { + get => m_ShortcutKeysUseActionPriority; + set + { + if (m_ShortcutKeysUseActionPriority == value) + return; + + m_ShortcutKeysUseActionPriority = value; + OnChange(); + } + } + + /// + /// True when overlap resolution should use (monitor ordering and consumption). + /// + internal bool IsShortcutResolutionUsingActionPriority => m_ShortcutKeysUseActionPriority; + + /// + /// True when overlap resolution should use composite binding complexity (develop behavior), not action priority. + /// + internal bool IsShortcutResolutionUsingComplexity => m_ShortcutKeysConsumeInputs && !m_ShortcutKeysUseActionPriority; + + /// + /// Modifier composites use ordered evaluation when complexity-based shortcut support is active. + /// + internal bool IsShortcutComplexityModifierOrderActive => m_ShortcutKeysConsumeInputs && !m_ShortcutKeysUseActionPriority; + /// /// Enable or disable an internal feature by its name. /// @@ -759,6 +794,7 @@ public void SetInternalFeatureFlag(string featureName, bool enabled) [SerializeField] private float m_MultiTapDelayTime = 0.75f; [SerializeField] private bool m_DisableRedundantEventsMerging = false; [SerializeField] private bool m_ShortcutKeysConsumeInputs = false; // This is the shortcut support from v1.4. Temporarily moved here as an opt-in feature, while it's issues are investigated. + [SerializeField] private bool m_ShortcutKeysUseActionPriority = false; [NonSerialized] internal HashSet m_FeatureFlags; @@ -1055,6 +1091,7 @@ internal static bool AreEqual(InputSettings a, InputSettings b) CompareSets(a.supportedDevices, b.supportedDevices) && a.disableRedundantEventsMerging == b.disableRedundantEventsMerging && a.shortcutKeysConsumeInput == b.shortcutKeysConsumeInput && + a.shortcutKeysUseActionPriority == b.shortcutKeysUseActionPriority && CompareFeatureFlag(a, b, InputFeatureNames.kUseOptimizedControls) && CompareFeatureFlag(a, b, InputFeatureNames.kUseReadValueCaching) && From 2d0bc90900325eb32e56dfdebed648bf738171e4 Mon Sep 17 00:00:00 2001 From: Darren Kelly Date: Fri, 22 May 2026 16:30:53 +0200 Subject: [PATCH 42/65] Add formatting for Actions priority tests. --- .../InputSystem/CoreTests_ActionsPriority.cs | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Assets/Tests/InputSystem/CoreTests_ActionsPriority.cs b/Assets/Tests/InputSystem/CoreTests_ActionsPriority.cs index d6eded80a0..10e8a7e630 100644 --- a/Assets/Tests/InputSystem/CoreTests_ActionsPriority.cs +++ b/Assets/Tests/InputSystem/CoreTests_ActionsPriority.cs @@ -20,7 +20,7 @@ internal static InputAction SetupTestAction(this InputActionMap map, string[] bi action.AddBinding("/" + bindings[0]); return action; } - + case 2: { var modifier = bindings[0]; @@ -53,18 +53,18 @@ internal static InputAction SetupTestAction(this InputActionMap map, string[] bi default: return null; - } + } } } internal partial class CoreTests { - private static readonly List<(string[], string[])> k_TwoInputActionTestCases = new () + private static readonly List<(string[], string[])> k_TwoInputActionTestCases = new() { - (new[]{"ctrl", "x"}, new[]{"x"}), - (new[]{"shift", "n"}, new[]{"n"}), - (new[]{"ctrl", "shift", "h"}, new[]{"shift", "h"}), - (new[]{"ctrl", "shift", "v"}, new[]{"shift", "v"}), + (new[] {"ctrl", "x"}, new[] {"x"}), + (new[] {"shift", "n"}, new[] {"n"}), + (new[] {"ctrl", "shift", "h"}, new[] {"shift", "h"}), + (new[] {"ctrl", "shift", "v"}, new[] {"shift", "v"}), }; private void PressBindingsForInputActions(Keyboard keyboard, InputAction action1, InputAction action2, InputAction action3 = null) @@ -265,13 +265,13 @@ public void Actions_Priority_BothActionsFire_WhenPriorityIsZero((string[] a1, st Assert.That(action2WasPerformed, Is.True); } - private static readonly List<(string[], string[])> k_TwoInputActionNoConflictingBindingTestCases = new () + private static readonly List<(string[], string[])> k_TwoInputActionNoConflictingBindingTestCases = new() { - (new[]{"ctrl", "x"}, new[]{"k"}), - (new[]{"shift", "n"}, new[]{"l"}), - (new[]{"shift", "h"}, new[]{"l"}), - (new[]{"shift", "h"}, new[]{"ctrl", "shift", "o"}), - (new[]{"ctrl", "shift", "v"}, new[]{"shift", "z"}) + (new[] {"ctrl", "x"}, new[] {"k"}), + (new[] {"shift", "n"}, new[] {"l"}), + (new[] {"shift", "h"}, new[] {"l"}), + (new[] {"shift", "h"}, new[] {"ctrl", "shift", "o"}), + (new[] {"ctrl", "shift", "v"}, new[] {"shift", "z"}) }; [Test] From 1947331a4617a70fe7fb16bf79669c898f8c5d33 Mon Sep 17 00:00:00 2001 From: Darren Kelly Date: Fri, 22 May 2026 16:43:42 +0200 Subject: [PATCH 43/65] Remove debugging code in example. --- .../ProjectWideActionsExample.cs | 48 ------------------- 1 file changed, 48 deletions(-) diff --git a/Assets/Samples/ProjectWideActions/ProjectWideActionsExample.cs b/Assets/Samples/ProjectWideActions/ProjectWideActionsExample.cs index 10d9e571bb..8b066102dc 100644 --- a/Assets/Samples/ProjectWideActions/ProjectWideActionsExample.cs +++ b/Assets/Samples/ProjectWideActions/ProjectWideActionsExample.cs @@ -13,8 +13,6 @@ public class ProjectWideActionsExample : MonoBehaviour InputAction previous; InputAction sprint; InputAction crouch; - InputAction b; - InputAction shiftB; // Start is called before the first frame update void Start() @@ -31,8 +29,6 @@ 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 { @@ -45,18 +41,6 @@ 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) @@ -69,18 +53,6 @@ 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) @@ -88,16 +60,6 @@ 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 @@ -109,16 +71,6 @@ 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 From f234b275d414ef467cf88d25f2ef33622ca984bc Mon Sep 17 00:00:00 2001 From: Darren Kelly Date: Fri, 22 May 2026 17:14:14 +0200 Subject: [PATCH 44/65] Add missing meta file. --- Packages/com.unity.inputsystem/InputSystem/Actions.meta | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 Packages/com.unity.inputsystem/InputSystem/Actions.meta diff --git a/Packages/com.unity.inputsystem/InputSystem/Actions.meta b/Packages/com.unity.inputsystem/InputSystem/Actions.meta new file mode 100644 index 0000000000..330a604fb5 --- /dev/null +++ b/Packages/com.unity.inputsystem/InputSystem/Actions.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0c5b615422acd45049de003ce7134437 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: From 4b53a18171725c02cf6463bfe8897be535ef9d2d Mon Sep 17 00:00:00 2001 From: Darren Kelly Date: Tue, 26 May 2026 17:40:35 +0200 Subject: [PATCH 45/65] Add fixes for failing unit tests. --- Assets/Tests/InputSystem/CoreTests_Actions.cs | 4 +- .../InputSystem/CoreTests_ActionsPriority.cs | 240 ++++++++++++++++++ 2 files changed, 243 insertions(+), 1 deletion(-) diff --git a/Assets/Tests/InputSystem/CoreTests_Actions.cs b/Assets/Tests/InputSystem/CoreTests_Actions.cs index 21eb11f7ed..f95641de70 100644 --- a/Assets/Tests/InputSystem/CoreTests_Actions.cs +++ b/Assets/Tests/InputSystem/CoreTests_Actions.cs @@ -211,7 +211,6 @@ public void Actions_WhenShortcutsDisabled_AllConflictingActionsTrigger() public void Actions_WhenShortcutsEnabled_CanConsumeInput(bool legacyComposites) { InputSystem.settings.shortcutKeysConsumeInput = true; - InputSystem.settings.shortcutKeysUseActionPriority = true; var keyboard = InputSystem.AddDevice(); @@ -240,6 +239,9 @@ public void Actions_WhenShortcutsEnabled_CanConsumeInput(bool legacyComposites) action5.Priority = 1; action1.AddBinding("/space"); + // Ordered modifier evaluation: modifier must be pressed before the button for this chord shape. + // With shortcutKeysUseActionPriority on, IsShortcutComplexityModifierOrderActive is false, so + // Default would resolve to Unordered and pressing space then shift would still satisfy the composite. action2.AddCompositeBinding(legacyComposites ? "ButtonWithOneModifier" : "OneModifier") .With("Modifier", "/shift") .With(legacyComposites ? "Button" : "Binding", "/space"); diff --git a/Assets/Tests/InputSystem/CoreTests_ActionsPriority.cs b/Assets/Tests/InputSystem/CoreTests_ActionsPriority.cs index 10e8a7e630..e4073e602f 100644 --- a/Assets/Tests/InputSystem/CoreTests_ActionsPriority.cs +++ b/Assets/Tests/InputSystem/CoreTests_ActionsPriority.cs @@ -59,6 +59,25 @@ internal static InputAction SetupTestAction(this InputActionMap map, string[] bi internal partial class CoreTests { + /// + /// Overlap resolution uses and per-control grouping written from actions. + /// + private static void EnableActionPriorityShortcutResolution() + { + InputSystem.settings.shortcutKeysUseActionPriority = true; + InputSystem.settings.shortcutKeysConsumeInput = false; + } + + /// + /// Overlap resolution uses composite binding complexity; is not applied at runtime. + /// Requires shortcut consumption on so control grouping merges slots on the same physical control. + /// + private static void EnableComplexityShortcutResolution() + { + InputSystem.settings.shortcutKeysConsumeInput = true; + InputSystem.settings.shortcutKeysUseActionPriority = false; + } + private static readonly List<(string[], string[])> k_TwoInputActionTestCases = new() { (new[] {"ctrl", "x"}, new[] {"x"}), @@ -111,6 +130,7 @@ private void ReleaseBindingsForActions(Keyboard keyboard, InputAction action1, I [TestCaseSource(nameof(k_TwoInputActionTestCases))] public void Actions_Priority_OnlyOneActionIsFired_WhenOnePriorityIsHigherThanOther((string[] a1, string[] a2) actions) { + EnableActionPriorityShortcutResolution(); var keyboard = InputSystem.AddDevice(); InputActionMap map = new InputActionMap("map"); @@ -145,6 +165,7 @@ public void Actions_Priority_OnlyOneActionIsFired_WhenOnePriorityIsHigherThanOth [TestCaseSource(nameof(k_TwoInputActionTestCases))] public void Actions_Priority_OnlyOneActionIsFired_WhenOnePriorityIsHigherThanOtherInversePriorityOrder((string[] a1, string[] a2) actions) { + EnableActionPriorityShortcutResolution(); var keyboard = InputSystem.AddDevice(); InputActionMap map = new InputActionMap("map"); @@ -179,6 +200,7 @@ public void Actions_Priority_OnlyOneActionIsFired_WhenOnePriorityIsHigherThanOth [TestCaseSource(nameof(k_TwoInputActionTestCases))] // TODO: Darren, Should both actions be performed this frame here?? public void Actions_Priority_BothActionsArePerformed_DueToKeyPressOrderForShortcut((string[] larger, string[] smaller) actions) { + EnableActionPriorityShortcutResolution(); var keyboard = InputSystem.AddDevice(); InputActionMap map = new InputActionMap("map"); @@ -219,6 +241,7 @@ public void Actions_Priority_BothActionsArePerformed_DueToKeyPressOrderForShortc [TestCaseSource(nameof(k_TwoInputActionTestCases))] public void Actions_Priority_BothActionFires_WhenPriorityIsEqual((string[] a1, string[] a2) actions) { + EnableActionPriorityShortcutResolution(); var keyboard = InputSystem.AddDevice(); InputActionMap map = new InputActionMap("map"); @@ -242,6 +265,7 @@ public void Actions_Priority_BothActionFires_WhenPriorityIsEqual((string[] a1, s [TestCaseSource(nameof(k_TwoInputActionTestCases))] public void Actions_Priority_BothActionsFire_WhenPriorityIsZero((string[] a1, string[] a2) actions) { + EnableActionPriorityShortcutResolution(); var keyboard = InputSystem.AddDevice(); InputActionMap map = new InputActionMap("map"); @@ -279,6 +303,7 @@ public void Actions_Priority_BothActionsFire_WhenPriorityIsZero((string[] a1, st [TestCaseSource(nameof(k_TwoInputActionNoConflictingBindingTestCases))] public void Actions_Priority_BothActionsWithDifferentPriorityFire_WhenThereIsNoConflictingBinding((string[] a1, string[] a2) actions) { + EnableActionPriorityShortcutResolution(); var keyboard = InputSystem.AddDevice(); InputActionMap map = new InputActionMap("map"); @@ -309,6 +334,7 @@ public void Actions_Priority_BothActionsWithDifferentPriorityFire_WhenThereIsNoC [TestCaseSource(nameof(k_TwoInputActionNoConflictingBindingTestCases))] public void Actions_Priority_BothActionsWithDifferentPriorityFire_WhenThereIsNoConflictingBindingInverseOrder((string[] a1, string[] a2) actions) { + EnableActionPriorityShortcutResolution(); var keyboard = InputSystem.AddDevice(); InputActionMap map = new InputActionMap("map"); @@ -339,6 +365,7 @@ public void Actions_Priority_BothActionsWithDifferentPriorityFire_WhenThereIsNoC [TestCaseSource(nameof(k_TwoInputActionNoConflictingBindingTestCases))] public void Actions_Priority_BothActionsWithEqualPriorityFire_WhenThereIsNoConflictingBinding((string[] a1, string[] a2) actions) { + EnableActionPriorityShortcutResolution(); var keyboard = InputSystem.AddDevice(); InputActionMap map = new InputActionMap("map"); @@ -420,6 +447,7 @@ public void Actions_Priority_InputActionStateMonitorIndex_ImplicitConversionToLo [Category("Actions Priority")] public unsafe void Actions_Priority_ControlGrouping_SamePhysicalControlSharesGroupId() { + EnableActionPriorityShortcutResolution(); var keyboard = InputSystem.AddDevice(); var map = new InputActionMap("priority_group_test"); map.AddAction("a", binding: "/z"); @@ -452,6 +480,7 @@ public unsafe void Actions_Priority_ControlGrouping_SamePhysicalControlSharesGro [Category("Actions Priority")] public unsafe void Actions_Priority_ControlGrouping_WritesPerControlSlotPriorityFromAction() { + EnableActionPriorityShortcutResolution(); var keyboard = InputSystem.AddDevice(); var map = new InputActionMap("priority_per_slot_test"); var actionLow = map.AddAction("low", binding: "/x"); @@ -494,6 +523,7 @@ public unsafe void Actions_Priority_ControlGrouping_WritesPerControlSlotPriority [Category("Actions Priority")] public IEnumerator Actions_Priority_BothActionsArePerformed_WhenAHoldAndBasicActionHaveDifferentTiming() { + EnableActionPriorityShortcutResolution(); var keyboard = InputSystem.AddDevice(); using var map = new InputActionMap("HoldChord"); @@ -543,6 +573,7 @@ public IEnumerator Actions_Priority_BothActionsArePerformed_WhenAHoldAndBasicAct [Category("Actions Priority")] public IEnumerator Actions_Priority_OnlyOneHoldActionIsPerformed_WhenOnePriorityIsHigher() { + EnableActionPriorityShortcutResolution(); var keyboard = InputSystem.AddDevice(); using var map = new InputActionMap("HoldChord"); @@ -583,4 +614,213 @@ public IEnumerator Actions_Priority_OnlyOneHoldActionIsPerformed_WhenOnePriority Release(keyboard.leftShiftKey); map.Disable(); } + + [Test] + [Category("Actions Priority")] + public unsafe void Actions_Complexity_ControlGrouping_SamePhysicalControlSharesGroupId_WhenShortcutConsumptionEnabled() + { + EnableComplexityShortcutResolution(); + + InputSystem.AddDevice(); + var map = new InputActionMap("complexity_group_test"); + map.AddAction("a", binding: "/z"); + map.AddAction("b", binding: "/z"); + map.Enable(); + + var state = map.m_State; + Assert.That(state, Is.Not.Null); + Assert.That(state.memory.controlGroupingInitialized, Is.True); + + for (var i = 0; i < state.totalControlCount; ++i) + { + for (var j = i + 1; j < state.totalControlCount; ++j) + { + if (state.controls[i] != state.controls[j]) + continue; + + var gi = InputActionState.ControlGroupingTable.GroupElementIndex(i); + var gj = InputActionState.ControlGroupingTable.GroupElementIndex(j); + Assert.That(state.memory.controlGroupingAndPriority[gi], Is.EqualTo(state.memory.controlGroupingAndPriority[gj])); + Assert.That(state.memory.controlGroupingAndPriority[gi], Is.Not.EqualTo(0)); + return; + } + } + + Assert.Fail("Expected two control slots bound to the same physical control."); + } + + [Test] + [Category("Actions Priority")] + public unsafe void Actions_Complexity_ControlGrouping_WritesPerControlSlotComplexity_NotActionPriority() + { + EnableComplexityShortcutResolution(); + + var keyboard = InputSystem.AddDevice(); + var map = new InputActionMap("complexity_per_slot_test"); + var actionLow = map.AddAction("low", binding: "/x"); + var actionHigh = map.AddAction("high", binding: "/x"); + actionLow.Priority = 4; + actionHigh.Priority = 11; + map.Enable(); + + var state = map.m_State; + Assert.That(state, Is.Not.Null); + + var lowIndex = -1; + var highIndex = -1; + for (var i = 0; i < state.totalControlCount; ++i) + { + if (state.controls[i] != keyboard.xKey) + continue; + var bindingIndex = state.controlIndexToBindingIndex[i]; + var actionIndex = state.bindingStates[bindingIndex].actionIndex; + if (actionIndex == actionLow.m_ActionIndexInState) + lowIndex = i; + else if (actionIndex == actionHigh.m_ActionIndexInState) + highIndex = i; + } + + Assert.That(lowIndex, Is.GreaterThanOrEqualTo(0)); + Assert.That(highIndex, Is.GreaterThanOrEqualTo(0)); + + var pLow = InputActionState.ControlGroupingTable.PriorityElementIndex(lowIndex); + var pHigh = InputActionState.ControlGroupingTable.PriorityElementIndex(highIndex); + // Secondary column stores composite complexity; two simple bindings on the same key both have depth 1. + Assert.That(state.memory.controlGroupingAndPriority[pLow], Is.EqualTo(1)); + Assert.That(state.memory.controlGroupingAndPriority[pHigh], Is.EqualTo(1)); + } + + [Test] + [Category("Actions Priority")] + public unsafe void Actions_Complexity_ControlGrouping_WritesHigherComplexityOnSharedControlVersusSimpleBinding() + { + EnableComplexityShortcutResolution(); + + var keyboard = InputSystem.AddDevice(); + var map = new InputActionMap("complexity_composite_vs_simple"); + var composite = map.AddAction("chord", binding: null); + composite.AddCompositeBinding("OneModifier") + .With("Modifier", "/ctrl") + .With("Binding", "/x"); + var simple = map.AddAction("plain", binding: "/x"); + composite.Priority = 0; + simple.Priority = 99; + map.Enable(); + + var state = map.m_State; + Assert.That(state, Is.Not.Null); + + var compositeXIndex = -1; + var simpleXIndex = -1; + for (var i = 0; i < state.totalControlCount; ++i) + { + if (state.controls[i] != keyboard.xKey) + continue; + var bindingIndex = state.controlIndexToBindingIndex[i]; + var actionIndex = state.bindingStates[bindingIndex].actionIndex; + if (actionIndex == composite.m_ActionIndexInState) + compositeXIndex = i; + else if (actionIndex == simple.m_ActionIndexInState) + simpleXIndex = i; + } + + Assert.That(compositeXIndex, Is.GreaterThanOrEqualTo(0)); + Assert.That(simpleXIndex, Is.GreaterThanOrEqualTo(0)); + + var pComposite = InputActionState.ControlGroupingTable.PriorityElementIndex(compositeXIndex); + var pSimple = InputActionState.ControlGroupingTable.PriorityElementIndex(simpleXIndex); + Assert.That(state.memory.controlGroupingAndPriority[pSimple], Is.EqualTo(1)); + Assert.That( + state.memory.controlGroupingAndPriority[pComposite], + Is.GreaterThan(state.memory.controlGroupingAndPriority[pSimple]), + "Composite binding chain depth should exceed a simple binding on the same physical control."); + } + + [Test] + [Category("Actions Priority")] + [TestCaseSource(nameof(k_TwoInputActionTestCases))] + public void Actions_Complexity_CompositeWinsOverlappingSimple_IgnoresActionPriority((string[] a1, string[] a2) actions) + { + EnableComplexityShortcutResolution(); + + var keyboard = InputSystem.AddDevice(); + var map = new InputActionMap("map"); + + var actionComposite = map.SetupTestAction(actions.a1); + var actionSimple = map.SetupTestAction(actions.a2); + + // Deliberately favor the simple binding in the Priority field; complexity resolution must still prefer the composite. + actionComposite.Priority = 0; + actionSimple.Priority = 100; + + map.Enable(); + + Assert.That(actionComposite.WasPerformedThisFrame(), Is.False); + Assert.That(actionSimple.WasPerformedThisFrame(), Is.False); + + PressBindingsForInputActions(keyboard, actionComposite, actionSimple); + + Assert.That(actionComposite.WasPerformedThisFrame(), Is.True); + Assert.That(actionSimple.WasPerformedThisFrame(), Is.False); + + ReleaseBindingsForActions(keyboard, actionComposite, actionSimple); + + InputSystem.Update(); + + Assert.That(actionComposite.WasPerformedThisFrame(), Is.False); + Assert.That(actionSimple.WasPerformedThisFrame(), Is.False); + } + + [Test] + [Category("Actions Priority")] + [TestCaseSource(nameof(k_TwoInputActionTestCases))] + public void Actions_Complexity_CompositeWinsOverlappingSimple_EvenWhenCompositeHasHigherPriorityField( + (string[] a1, string[] a2) actions) + { + EnableComplexityShortcutResolution(); + + var keyboard = InputSystem.AddDevice(); + var map = new InputActionMap("map"); + + var actionComposite = map.SetupTestAction(actions.a1); + var actionSimple = map.SetupTestAction(actions.a2); + + actionComposite.Priority = 100; + actionSimple.Priority = 1; + + map.Enable(); + + PressBindingsForInputActions(keyboard, actionComposite, actionSimple); + + Assert.That(actionComposite.WasPerformedThisFrame(), Is.True); + Assert.That(actionSimple.WasPerformedThisFrame(), Is.False); + + ReleaseBindingsForActions(keyboard, actionComposite, actionSimple); + } + + [Test] + [Category("Actions Priority")] + public void Actions_Complexity_BothSimpleActionsOnSameControlPerform_WhenEqualComplexity() + { + EnableComplexityShortcutResolution(); + + var keyboard = InputSystem.AddDevice(); + var map = new InputActionMap("map"); + var action1 = map.AddAction("a", binding: "/y"); + var action2 = map.AddAction("b", binding: "/y"); + action1.Priority = 2; + action2.Priority = 99; + map.Enable(); + + Press((ButtonControl)action1.controls[0], queueEventOnly: true); + Press((ButtonControl)action2.controls[0], queueEventOnly: true); + InputSystem.Update(); + + Assert.That(action1.WasPerformedThisFrame(), Is.True); + Assert.That(action2.WasPerformedThisFrame(), Is.True); + + Release((ButtonControl)action1.controls[0], queueEventOnly: true); + Release((ButtonControl)action2.controls[0], queueEventOnly: true); + InputSystem.Update(); + } } From 1db6e3026d2e46eff890434660ba0291fc0570fd Mon Sep 17 00:00:00 2001 From: Darren Kelly Date: Wed, 27 May 2026 10:58:04 +0200 Subject: [PATCH 46/65] Update documentation. --- .../com.unity.inputsystem/Documentation~/Settings.md | 2 +- .../UITKAssetEditor/InputActionsEditorConstants.cs | 6 +++--- .../InputSystem/Runtime/Actions/InputAction.cs | 11 +++++++---- .../InputSystem/Runtime/InputSettings.cs | 1 + 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/Packages/com.unity.inputsystem/Documentation~/Settings.md b/Packages/com.unity.inputsystem/Documentation~/Settings.md index 07448976df..89f0ecfd12 100644 --- a/Packages/com.unity.inputsystem/Documentation~/Settings.md +++ b/Packages/com.unity.inputsystem/Documentation~/Settings.md @@ -127,7 +127,7 @@ Under __Improved Shortcut Support__, two independent options control how the Inp | UI / API | Description | | -------- | ----------- | | __Complexity-Based Shortcut Resolution__ ([`shortcutKeysConsumeInput`](xref:UnityEngine.InputSystem.InputSettings.shortcutKeysConsumeInput)) | When enabled and action priority is off, composite bindings are ordered by automatic **complexity** (depth of the composite). The first action that performs can consume the input so simpler bindings on the same control are skipped. Modifier composites use **ordered** modifier evaluation while this mode is active. | -| __Action Priority Shortcut Resolution__ ([`shortcutKeysUseActionPriority`](xref:UnityEngine.InputSystem.InputSettings.shortcutKeysUseActionPriority)) | When enabled, resolution uses each action's [`InputAction.Priority`](xref:UnityEngine.InputSystem.InputAction.Priority). Higher priority is handled first and can consume input for lower-priority actions. The **Priority** field appears in the Input Actions editor when this is on. Serialized priority values are always kept on the asset even when this option is off. | +| __Action Priority Shortcut Resolution__ ([`shortcutKeysUseActionPriority`](xref:UnityEngine.InputSystem.InputSettings.shortcutKeysUseActionPriority)) | When enabled, resolution uses each action's [`InputAction.Priority`](xref:UnityEngine.InputSystem.InputAction.Priority). Higher priority is handled first and can consume input for lower-priority actions. A performed action with priority 0 does not mark the input event as handled for suppressing lower-priority overlaps; any priority above zero can. The **Priority** field appears in the Input Actions editor when this is on. Serialized priority values are always kept on the asset even when this option is off. | Both options default to **off**. If **Action Priority Shortcut Resolution** is on, it **takes precedence** over complexity-based resolution even if **Complexity-Based Shortcut Resolution** is also enabled. diff --git a/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/InputActionsEditorConstants.cs b/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/InputActionsEditorConstants.cs index 25930895d9..eecb3a9dba 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/InputActionsEditorConstants.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/InputActionsEditorConstants.cs @@ -36,9 +36,9 @@ internal static class InputActionsEditorConstants 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."; + + "Effective range is 0–65535 at runtime (unsigned 16-bit). When Action Priority Shortcut Resolution is enabled, " + + "higher values are ordered first; when the action performs, priority 0 does not mark the input event as handled " + + "for overlap suppression, while any value greater than 0 can."; public struct CommandEvents { diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/InputAction.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/InputAction.cs index ee1b0ca46e..9e3dcb0609 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/InputAction.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/InputAction.cs @@ -201,10 +201,13 @@ public sealed class InputAction : ICloneable, IDisposable /// /// 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. + /// Applies to all bindings that target this action. At runtime this value is used only when + /// is enabled. In that mode the system orders overlapping + /// bindings on a shared control by priority, and when the action reaches , + /// a value greater than zero can mark the underlying input event as handled so lower-priority actions in the same + /// group are suppressed; priority zero does not mark the event handled for that purpose. + /// When action priority is disabled, overlap resolution instead follows + /// (automatic composite complexity) and does not consult this property. /// Values outside the 0–65535 range are truncated when stored in the internal representation. /// public int Priority diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/InputSettings.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/InputSettings.cs index 546fa1ae81..74220921c4 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Runtime/InputSettings.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/InputSettings.cs @@ -708,6 +708,7 @@ public bool shortcutKeysConsumeInput /// /// /// If this is enabled, it takes precedence over for resolution behavior: the system uses priority-based consumption and ordering even when complexity-based consumption is also toggled on. + /// Among overlapping actions on the same control, when a higher-priority action reaches , a greater than zero can mark the input event as handled so lower-priority actions are suppressed; priority zero does not. /// Serialized priority values on actions are always kept; when this is off, priorities are not applied at runtime and the Priority field is hidden in the Input Actions editor. /// /// From 46db1d504d270308585f1a6f385ef3c778246d65 Mon Sep 17 00:00:00 2001 From: Darren Kelly Date: Wed, 27 May 2026 11:15:21 +0200 Subject: [PATCH 47/65] Fix packing issues and add tests for this case. --- .../InputSystem/CoreTests_ActionsPriority.cs | 33 +++++++++++++++++-- .../Actions/InputActionStateMonitorIndex.cs | 9 +++-- .../Runtime/Actions/InputActionState.cs | 6 ++-- 3 files changed, 39 insertions(+), 9 deletions(-) diff --git a/Assets/Tests/InputSystem/CoreTests_ActionsPriority.cs b/Assets/Tests/InputSystem/CoreTests_ActionsPriority.cs index e4073e602f..4c8877ceb8 100644 --- a/Assets/Tests/InputSystem/CoreTests_ActionsPriority.cs +++ b/Assets/Tests/InputSystem/CoreTests_ActionsPriority.cs @@ -428,10 +428,37 @@ public void Actions_Priority_InputActionStateMonitorIndex_FromPacked_MatchesCrea [Test] [Category("Actions Priority")] - public void Actions_Priority_InputActionStateMonitorIndex_PriorityUsesLowEightBitsInPackedRepresentation() + public void Actions_Priority_InputActionStateMonitorIndex_PriorityRoundTripsFullSixteenBits() { - var index = InputActionStateMonitorIndex.Create(0, 1, 0, priority: 300); - Assert.That(index.Priority, Is.EqualTo(300 & 0xff)); + var index300 = InputActionStateMonitorIndex.Create(0, 1, 0, priority: 300); + Assert.That(index300.Priority, Is.EqualTo(300)); + Assert.That(InputActionState.GetComplexityFromMonitorIndex(index300.Packed), Is.EqualTo(300)); + + var index65535 = InputActionStateMonitorIndex.Create(0, 1, 0, priority: 65535); + Assert.That(index65535.Priority, Is.EqualTo(65535)); + } + + [Test] + [Category("Actions Priority")] + public void Actions_Priority_PrioritiesAbove255_ResolveInOrder() + { + EnableActionPriorityShortcutResolution(); + var keyboard = InputSystem.AddDevice(); + var map = new InputActionMap("map"); + var lower = map.AddAction("lower", binding: "/x"); + var higher = map.AddAction("higher", binding: "/x"); + lower.Priority = 300; + higher.Priority = 400; + map.Enable(); + + Press((ButtonControl)keyboard.xKey, queueEventOnly: true); + InputSystem.Update(); + + Assert.That(higher.WasPerformedThisFrame(), Is.True); + Assert.That(lower.WasPerformedThisFrame(), Is.False); + + Release((ButtonControl)keyboard.xKey); + InputSystem.Update(); } [Test] diff --git a/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionStateMonitorIndex.cs b/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionStateMonitorIndex.cs index 9277df5b97..cd07a23d3d 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionStateMonitorIndex.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionStateMonitorIndex.cs @@ -24,7 +24,10 @@ public static InputActionStateMonitorIndex Create(int mapIndex, int controlIndex long result = controlIndex; result |= (long)bindingIndex << 24; result |= (long)mapIndex << 40; - result |= (long)priority << 48; + // Bits 48–63 hold priority or composite complexity (ushort); use ulong shift so values ≥32768 + // pack without corrupting the signed long layout. + var priorityBits = (ulong)(ushort)(priority & 0xffff); + result |= (long)(priorityBits << 48); return new InputActionStateMonitorIndex(result); } @@ -35,8 +38,8 @@ public static InputActionStateMonitorIndex Create(int mapIndex, int controlIndex public int MapIndex => (int)((m_Packed >> 40) & 0xff); /// - /// Only the low 8 bits are stored; larger values truncate when packed. + /// The high 16 bits of the packed index (matching the ushort priority/complexity slot in ). /// - public int Priority => (int)((m_Packed >> 48) & 0xff); + public int Priority => (int)(((ulong)m_Packed >> 48) & 0xffff); } } diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/InputActionState.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/InputActionState.cs index 44484801a1..cdef12a739 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/InputActionState.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/InputActionState.cs @@ -1511,7 +1511,7 @@ void IInputStateChangeMonitor.NotifyTimerExpired(InputControl control, double ti /// monitors. While we could look up map and binding indices from control indices, keeping /// all the information together avoids having to unnecessarily jump around in memory to grab /// the various pieces of data. - /// The high bits store action priority or composite complexity depending on . + /// The high 16 bits store action priority or composite complexity depending on . /// private long ToCombinedMapAndControlAndBindingIndex(int mapIndex, int controlIndex, int bindingIndex) { @@ -1522,11 +1522,11 @@ private long ToCombinedMapAndControlAndBindingIndex(int mapIndex, int controlInd } /// - /// Extract the complexity or priority component from the monitor index (high 8 bits). + /// Extract the complexity or priority component from the monitor index (high 16 bits). /// internal static int GetComplexityFromMonitorIndex(long mapControlAndBindingIndex) { - return (int)((mapControlAndBindingIndex >> 48) & 0xff); + return (int)(((ulong)mapControlAndBindingIndex >> 48) & 0xffff); } /// From 7ae8c937551784ca85b6f4d7e3d3f584eb0dcc3f Mon Sep 17 00:00:00 2001 From: Darren Kelly Date: Wed, 27 May 2026 11:36:58 +0200 Subject: [PATCH 48/65] Fix a duplicate action register issue flagged by u pr. --- .../InputSystem/CoreTests_ActionsPriority.cs | 51 +++++++++++++++++++ .../Runtime/Actions/InputActionState.cs | 21 ++++++-- 2 files changed, 68 insertions(+), 4 deletions(-) diff --git a/Assets/Tests/InputSystem/CoreTests_ActionsPriority.cs b/Assets/Tests/InputSystem/CoreTests_ActionsPriority.cs index 4c8877ceb8..94154949f5 100644 --- a/Assets/Tests/InputSystem/CoreTests_ActionsPriority.cs +++ b/Assets/Tests/InputSystem/CoreTests_ActionsPriority.cs @@ -461,6 +461,57 @@ public void Actions_Priority_PrioritiesAbove255_ResolveInOrder() InputSystem.Update(); } + [Test] + [Category("Actions Priority")] + public void Actions_Priority_ChangingPriorityWhileEnabled_ReplacesStateMonitorInsteadOfDuplicating() + { + EnableActionPriorityShortcutResolution(); + var keyboard = InputSystem.AddDevice(); + var map = new InputActionMap("map"); + var action = map.AddAction("a", binding: "/x"); + action.Priority = 1; + map.Enable(); + + var state = map.m_State; + Assert.That(state, Is.Not.Null); + + var control = keyboard.xKey; + var deviceIndex = keyboard.m_DeviceIndex; + Assert.That(deviceIndex, Is.GreaterThanOrEqualTo(0)); + + int CountMonitorsForActionStateOnControl() + { + ref var bucket = ref InputSystem.manager.m_StateMonitors.m_MonitorsPerDevice[deviceIndex]; + var c = 0; + for (var i = 0; i < bucket.count; ++i) + { + if (bucket.memoryRegions[i].sizeInBits == 0) + continue; + if (ReferenceEquals(bucket.listeners[i].monitor, state) && bucket.listeners[i].control == control) + ++c; + } + + return c; + } + + Assert.That(CountMonitorsForActionStateOnControl(), Is.EqualTo(1)); + + action.Priority = 5; + action.Priority = 10; + action.Priority = 20; + + Assert.That(CountMonitorsForActionStateOnControl(), Is.EqualTo(1)); + + var performedCount = 0; + action.performed += _ => performedCount++; + Press((ButtonControl)keyboard.xKey, queueEventOnly: true); + InputSystem.Update(); + Assert.That(performedCount, Is.EqualTo(1)); + + Release((ButtonControl)keyboard.xKey); + InputSystem.Update(); + } + [Test] [Category("Actions Priority")] public void Actions_Priority_InputActionStateMonitorIndex_ImplicitConversionToLongMatchesPackedProperty() diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/InputActionState.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/InputActionState.cs index cdef12a739..01eccc90c5 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/InputActionState.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/InputActionState.cs @@ -1127,14 +1127,27 @@ internal void OnActionPriorityChanged(InputAction action) for (var n = 0; n < controlCount; ++n) { var controlIndex = bindingState->controlStartIndex + n; - controlGroupingAndPriority[controlIndex * 2 + 1] = clampedPriority; + var prioritySlot = ControlGroupingTable.PriorityElementIndex(controlIndex); + var oldSecondaryForRemoval = controlGroupingAndPriority[prioritySlot]; if (!IsControlEnabled(controlIndex)) + { + controlGroupingAndPriority[prioritySlot] = clampedPriority; continue; + } + + // Remove using the monitor index that was registered (packed with the previous secondary value). + // `action.Priority` is already updated before we get here; `ToCombinedMapAndControlAndBindingIndex` + // reads from `controlGroupingAndPriority`, so we must not overwrite the slot before removal. + var oldMonitorIndex = InputActionStateMonitorIndex.Create(mapIndex, controlIndex, bindingIndex, + oldSecondaryForRemoval).Packed; + manager.RemoveStateChangeMonitor(controls[controlIndex], this, oldMonitorIndex); + + controlGroupingAndPriority[prioritySlot] = clampedPriority; - var mapControlAndBindingIndex = ToCombinedMapAndControlAndBindingIndex(mapIndex, controlIndex, bindingIndex); - manager.RemoveStateChangeMonitor(controls[controlIndex], this, mapControlAndBindingIndex); - manager.AddStateChangeMonitor(controls[controlIndex], this, mapControlAndBindingIndex, controlGroupingAndPriority[controlIndex * 2]); + var newMonitorIndex = ToCombinedMapAndControlAndBindingIndex(mapIndex, controlIndex, bindingIndex); + manager.AddStateChangeMonitor(controls[controlIndex], this, newMonitorIndex, + controlGroupingAndPriority[ControlGroupingTable.GroupElementIndex(controlIndex)]); } } } From 240d1480a942461cdfb50c20183f11038c710886 Mon Sep 17 00:00:00 2001 From: Darren Kelly Date: Wed, 27 May 2026 12:06:55 +0200 Subject: [PATCH 49/65] Fix an issue where changing priority on a compositeAction didn't update the monitor's packed priority. --- .../InputSystem/CoreTests_ActionsPriority.cs | 43 +++++++++++++++++++ .../Runtime/Actions/InputActionState.cs | 7 ++- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/Assets/Tests/InputSystem/CoreTests_ActionsPriority.cs b/Assets/Tests/InputSystem/CoreTests_ActionsPriority.cs index 94154949f5..9def5bfcba 100644 --- a/Assets/Tests/InputSystem/CoreTests_ActionsPriority.cs +++ b/Assets/Tests/InputSystem/CoreTests_ActionsPriority.cs @@ -512,6 +512,49 @@ int CountMonitorsForActionStateOnControl() InputSystem.Update(); } + [Test] + [Category("Actions Priority")] + public void Actions_Priority_ChangingPriorityOnCompositeAction_UpdatesMonitorPackedPriorityOnPartControls() + { + EnableActionPriorityShortcutResolution(); + var keyboard = InputSystem.AddDevice(); + var map = new InputActionMap("map"); + var shiftB = map.AddAction("shiftB"); + shiftB.AddCompositeBinding("OneModifier") + .With("Modifier", "/leftShift") + .With("Binding", "/b"); + shiftB.Priority = 3; + map.Enable(); + + var state = map.m_State; + Assert.That(state, Is.Not.Null); + + static int PackedPriorityForMonitor(InputActionState actionState, InputControl control) + { + var deviceIndex = control.device.m_DeviceIndex; + ref var bucket = ref InputSystem.manager.m_StateMonitors.m_MonitorsPerDevice[deviceIndex]; + for (var i = 0; i < bucket.count; ++i) + { + if (bucket.memoryRegions[i].sizeInBits == 0) + continue; + if (!ReferenceEquals(bucket.listeners[i].monitor, actionState) || + bucket.listeners[i].control != control) + continue; + return InputActionStateMonitorIndex.FromPacked(bucket.listeners[i].monitorIndex).Priority; + } + + return int.MinValue; + } + + Assert.That(PackedPriorityForMonitor(state, keyboard.bKey), Is.EqualTo(3)); + Assert.That(PackedPriorityForMonitor(state, keyboard.leftShiftKey), Is.EqualTo(3)); + + shiftB.Priority = 7; + + Assert.That(PackedPriorityForMonitor(state, keyboard.bKey), Is.EqualTo(7)); + Assert.That(PackedPriorityForMonitor(state, keyboard.leftShiftKey), Is.EqualTo(7)); + } + [Test] [Category("Actions Priority")] public void Actions_Priority_InputActionStateMonitorIndex_ImplicitConversionToLongMatchesPackedProperty() diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/InputActionState.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/InputActionState.cs index 01eccc90c5..f1aa1d478e 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/InputActionState.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/InputActionState.cs @@ -1127,6 +1127,9 @@ internal void OnActionPriorityChanged(InputAction action) for (var n = 0; n < controlCount; ++n) { var controlIndex = bindingState->controlStartIndex + n; + // Must match EnableControls: monitors are keyed by the binding that owns the control (composite part), + // not necessarily the binding entry we are iterating (e.g. composite root). + var bindingIndexForMonitor = controlIndexToBindingIndex[controlIndex]; var prioritySlot = ControlGroupingTable.PriorityElementIndex(controlIndex); var oldSecondaryForRemoval = controlGroupingAndPriority[prioritySlot]; @@ -1139,13 +1142,13 @@ internal void OnActionPriorityChanged(InputAction action) // Remove using the monitor index that was registered (packed with the previous secondary value). // `action.Priority` is already updated before we get here; `ToCombinedMapAndControlAndBindingIndex` // reads from `controlGroupingAndPriority`, so we must not overwrite the slot before removal. - var oldMonitorIndex = InputActionStateMonitorIndex.Create(mapIndex, controlIndex, bindingIndex, + var oldMonitorIndex = InputActionStateMonitorIndex.Create(mapIndex, controlIndex, bindingIndexForMonitor, oldSecondaryForRemoval).Packed; manager.RemoveStateChangeMonitor(controls[controlIndex], this, oldMonitorIndex); controlGroupingAndPriority[prioritySlot] = clampedPriority; - var newMonitorIndex = ToCombinedMapAndControlAndBindingIndex(mapIndex, controlIndex, bindingIndex); + var newMonitorIndex = ToCombinedMapAndControlAndBindingIndex(mapIndex, controlIndex, bindingIndexForMonitor); manager.AddStateChangeMonitor(controls[controlIndex], this, newMonitorIndex, controlGroupingAndPriority[ControlGroupingTable.GroupElementIndex(controlIndex)]); } From e52d920fb1f28658e0969d439c2432f0888fa04f Mon Sep 17 00:00:00 2001 From: Darren Kelly Date: Wed, 27 May 2026 12:14:53 +0200 Subject: [PATCH 50/65] Revert project version file that was committed by accident. --- ProjectSettings/ProjectVersion.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ProjectSettings/ProjectVersion.txt b/ProjectSettings/ProjectVersion.txt index 8744883fef..587f809d71 100644 --- a/ProjectSettings/ProjectVersion.txt +++ b/ProjectSettings/ProjectVersion.txt @@ -1,2 +1,2 @@ -m_EditorVersion: 6000.3.4f1 -m_EditorVersionWithRevision: 6000.3.4f1 (fdd3b8998ec4) +m_EditorVersion: 2022.3.62f3 +m_EditorVersionWithRevision: 2022.3.62f3 (96770f904ca7) From 21456d09082d7c49f9f97c94733427709e0e7946 Mon Sep 17 00:00:00 2001 From: Darren Kelly Date: Wed, 27 May 2026 13:08:10 +0200 Subject: [PATCH 51/65] Fix clamping and add a test for it. --- Assets/Tests/InputSystem/CoreTests_Actions.cs | 3 +-- .../InputSystem/CoreTests_ActionsPriority.cs | 17 +++++++++++++++++ .../InputActionsEditorConstants.cs | 2 +- .../InputSystem/Runtime/Actions/InputAction.cs | 8 ++++---- .../Runtime/Actions/InputActionMap.cs | 12 ++++++------ 5 files changed, 29 insertions(+), 13 deletions(-) diff --git a/Assets/Tests/InputSystem/CoreTests_Actions.cs b/Assets/Tests/InputSystem/CoreTests_Actions.cs index f95641de70..260bb37de3 100644 --- a/Assets/Tests/InputSystem/CoreTests_Actions.cs +++ b/Assets/Tests/InputSystem/CoreTests_Actions.cs @@ -505,8 +505,7 @@ public void Actions_WhenShortcutsDisabled_PressingShortcutSequenceInWrongOrder_D [TestCase("leftShift", "leftAlt", "space", true, false)] [TestCase("leftShift", null, "space", false, false)] [TestCase("leftShift", "leftAlt", "space", false, false)] - public void - Actions_WhenShortcutsAreEnabled_PressingShortcutSequenceInWrongOrder_DoesNotTriggerShortcut_ExceptIfOverridden(string modifier1, string modifier2, string binding, bool legacyComposites, bool overrideModifiersNeedToBePressedFirst) + public void Actions_WhenShortcutsAreEnabled_PressingShortcutSequenceInWrongOrder_DoesNotTriggerShortcut_ExceptIfOverridden(string modifier1, string modifier2, string binding, bool legacyComposites, bool overrideModifiersNeedToBePressedFirst) { InputSystem.settings.shortcutKeysConsumeInput = true; diff --git a/Assets/Tests/InputSystem/CoreTests_ActionsPriority.cs b/Assets/Tests/InputSystem/CoreTests_ActionsPriority.cs index 9def5bfcba..b84602df52 100644 --- a/Assets/Tests/InputSystem/CoreTests_ActionsPriority.cs +++ b/Assets/Tests/InputSystem/CoreTests_ActionsPriority.cs @@ -86,6 +86,23 @@ private static void EnableComplexityShortcutResolution() (new[] {"ctrl", "shift", "v"}, new[] {"shift", "v"}), }; + [Test] + [Category("Actions Priority")] + public void Actions_Priority_Setter_ClampsToRepresentableRange() + { + var map = new InputActionMap("m"); + var action = map.AddAction("a", binding: "/x"); + + action.Priority = -1; + Assert.That(action.Priority, Is.EqualTo(0)); + + action.Priority = 70000; + Assert.That(action.Priority, Is.EqualTo(65535)); + + action.Priority = 100; + Assert.That(action.Priority, Is.EqualTo(100)); + } + private void PressBindingsForInputActions(Keyboard keyboard, InputAction action1, InputAction action2, InputAction action3 = null) { for (int i = 0; i < action1.controls.Count; i++) diff --git a/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/InputActionsEditorConstants.cs b/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/InputActionsEditorConstants.cs index eecb3a9dba..60dd670b32 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/InputActionsEditorConstants.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/InputActionsEditorConstants.cs @@ -36,7 +36,7 @@ internal static class InputActionsEditorConstants 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). When Action Priority Shortcut Resolution is enabled, " + + "Values are clamped to 0–65535 when set (unsigned 16-bit at runtime). When Action Priority Shortcut Resolution is enabled, " + "higher values are ordered first; when the action performs, priority 0 does not mark the input event as handled " + "for overlap suppression, while any value greater than 0 can."; diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/InputAction.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/InputAction.cs index 9e3dcb0609..3a39f1293b 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/InputAction.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/InputAction.cs @@ -199,7 +199,7 @@ public sealed class InputAction : ICloneable, IDisposable /// /// 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. + /// Effective range is 0–65535. Values outside that range are clamped when set; the stored value always matches what overlap resolution uses. /// /// Applies to all bindings that target this action. At runtime this value is used only when /// is enabled. In that mode the system orders overlapping @@ -208,17 +208,17 @@ public sealed class InputAction : ICloneable, IDisposable /// group are suppressed; priority zero does not mark the event handled for that purpose. /// When action priority is disabled, overlap resolution instead follows /// (automatic composite complexity) and does not consult this property. - /// Values outside the 0–65535 range are truncated when stored in the internal representation. /// public int Priority { get => m_Priority; set { - if (m_Priority == value) + var clamped = Math.Clamp(value, 0, ushort.MaxValue); + if (m_Priority == clamped) return; - m_Priority = value; + m_Priority = clamped; m_ActionMap?.m_State?.OnActionPriorityChanged(this); } } diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/InputActionMap.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/InputActionMap.cs index b76c6d4bf5..a875b8ebbe 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/InputActionMap.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/InputActionMap.cs @@ -1598,11 +1598,7 @@ public InputAction ToAction(string actionName = null) actionType = InputActionType.Button; } - var clampedPriority = priority; - if (clampedPriority < 0) - clampedPriority = 0; - else if (clampedPriority > 65535) - clampedPriority = 65535; + var clampedPriority = Math.Clamp(priority, 0, ushort.MaxValue); return new InputAction(actionName ?? name, actionType) { @@ -2013,7 +2009,11 @@ public void OnAfterDeserialize() { var actionCount = m_Actions.Length; for (var i = 0; i < actionCount; ++i) - m_Actions[i].m_ActionMap = this; + { + var action = m_Actions[i]; + action.m_ActionMap = this; + action.m_Priority = Math.Clamp(action.m_Priority, 0, ushort.MaxValue); + } } // Make sure we don't retain any cached per-action data when using serialization From ced22e051ccae23391e1b1c3a9b02ca019fd439a Mon Sep 17 00:00:00 2001 From: Darren Kelly Date: Wed, 27 May 2026 13:18:46 +0200 Subject: [PATCH 52/65] Fixed `DefaultInputActions` Player **Fire** action missing `expectedControlType` (`Button`) in the built-in asset and embedded C# JSON, so validation, rebinding filters, and UI that rely on expected control layout recognize it as a button action again. --- .../Runtime/Plugins/PlayerInput/DefaultInputActions.cs | 2 +- .../Plugins/PlayerInput/DefaultInputActions.inputactions | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/Plugins/PlayerInput/DefaultInputActions.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/Plugins/PlayerInput/DefaultInputActions.cs index a2e3168a22..fb75e9181c 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Runtime/Plugins/PlayerInput/DefaultInputActions.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/Plugins/PlayerInput/DefaultInputActions.cs @@ -73,7 +73,7 @@ public @DefaultInputActions() ""name"": ""Fire"", ""type"": ""Button"", ""id"": ""6c2ab1b8-8984-453a-af3d-a3c78ae1679a"", - ""expectedControlType"": """", + ""expectedControlType"": ""Button"", ""processors"": """", ""interactions"": """", ""initialStateCheck"": false, diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/Plugins/PlayerInput/DefaultInputActions.inputactions b/Packages/com.unity.inputsystem/InputSystem/Runtime/Plugins/PlayerInput/DefaultInputActions.inputactions index b64f15bfea..565f68b949 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Runtime/Plugins/PlayerInput/DefaultInputActions.inputactions +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/Plugins/PlayerInput/DefaultInputActions.inputactions @@ -30,7 +30,7 @@ "name": "Fire", "type": "Button", "id": "6c2ab1b8-8984-453a-af3d-a3c78ae1679a", - "expectedControlType": "", + "expectedControlType": "Button", "processors": "", "interactions": "", "initialStateCheck": false, From ac5d101ee001f89a8ed628acc7c766ce95123c90 Mon Sep 17 00:00:00 2001 From: Darren Kelly Date: Wed, 27 May 2026 14:58:27 +0200 Subject: [PATCH 53/65] Add fix for monitor reset losing data in install runtime. --- Assets/Tests/InputSystem/CoreTests_State.cs | 19 +++++++++++++++++++ .../InputSystem/Runtime/InputManager.cs | 7 +++++-- .../Runtime/InputManagerStateMonitors.cs | 6 ++++++ 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/Assets/Tests/InputSystem/CoreTests_State.cs b/Assets/Tests/InputSystem/CoreTests_State.cs index ed5d59d8aa..0858be6fad 100644 --- a/Assets/Tests/InputSystem/CoreTests_State.cs +++ b/Assets/Tests/InputSystem/CoreTests_State.cs @@ -1887,4 +1887,23 @@ public void TODO_State_WithSingleStateAndSingleUpdate_XXXXX() ////TODO Assert.Fail(); } + + [Test] + [Category("State")] + public void State_InputManagerInstallRuntime_WithSameRuntimeInstance_DoesNotReplaceStateMonitors() + { + InputSystem.AddDevice(); + using (var map = new InputActionMap("install_runtime_monitors")) + { + map.AddAction("a", binding: "/space"); + map.Enable(); + + var monitors = InputSystem.manager.m_StateMonitors; + Assert.That(monitors, Is.Not.Null); + + InputSystem.manager.InstallRuntime(InputSystem.manager.runtime); + + Assert.That(ReferenceEquals(InputSystem.manager.m_StateMonitors, monitors), Is.True); + } + } } diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.cs index 091350ab6e..70b76f50ba 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.cs @@ -2288,12 +2288,15 @@ internal void InstallRuntime(IInputRuntime runtime) m_NativeBeforeUpdateHooked = true; } - #if UNITY_ANALYTICS || UNITY_EDITOR +#if UNITY_ANALYTICS || UNITY_EDITOR InputAnalytics.Initialize(this); m_Runtime.onShutdown = () => InputAnalytics.OnShutdown(this); #endif - m_StateMonitors = new InputManagerStateMonitors(() => m_DevicesCount, () => isProcessingEvents, m_Runtime); + // Recreate only when there is no collection yet or the runtime instance changed. Reusing the same + // IInputRuntime (e.g. test Restore()) must keep registered control monitors and pending timeouts. + if (m_StateMonitors == null || !ReferenceEquals(m_StateMonitors.CapturedRuntime, m_Runtime)) + m_StateMonitors = new InputManagerStateMonitors(() => m_DevicesCount, () => isProcessingEvents, m_Runtime); } internal void InstallGlobals() diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManagerStateMonitors.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManagerStateMonitors.cs index 946149b3cf..26ed8b9814 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManagerStateMonitors.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManagerStateMonitors.cs @@ -25,6 +25,12 @@ public InputManagerStateMonitors(Func getDeviceCount, Func isProcessi m_Runtime = runtime ?? throw new ArgumentNullException(nameof(runtime)); } + /// + /// Runtime instance this monitor collection was constructed with. Used to avoid recreating the collection + /// when is called again with the same runtime (same object identity). + /// + internal IInputRuntime CapturedRuntime => m_Runtime; + ////TODO: support combining monitors for bitfields public void AddStateChangeMonitor(InputControl control, IInputStateChangeMonitor monitor, long monitorIndex, uint groupIndex) { From 1dd4a2c0e1d5925051a4a93857ed3cd3a7bd17e5 Mon Sep 17 00:00:00 2001 From: Darren Kelly Date: Wed, 27 May 2026 21:02:36 +0200 Subject: [PATCH 54/65] Fix incorrect project settings asset! --- ProjectSettings/ProjectSettings.asset | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ProjectSettings/ProjectSettings.asset b/ProjectSettings/ProjectSettings.asset index d0451c7075..fc5fc4c552 100644 --- a/ProjectSettings/ProjectSettings.asset +++ b/ProjectSettings/ProjectSettings.asset @@ -194,11 +194,11 @@ PlayerSettings: VertexChannelCompressionMask: 4054 iPhoneSdkVersion: 988 iOSSimulatorArchitecture: 0 - iOSTargetOSVersionString: 15.0 + iOSTargetOSVersionString: 12.0 tvOSSdkVersion: 0 tvOSSimulatorArchitecture: 0 tvOSRequireExtendedGameController: 0 - tvOSTargetOSVersionString: 15.0 + tvOSTargetOSVersionString: 12.0 VisionOSSdkVersion: 0 VisionOSTargetOSVersionString: 1.0 uIPrerenderedIcon: 0 @@ -594,7 +594,7 @@ PlayerSettings: locationUsageDescription: microphoneUsageDescription: bluetoothUsageDescription: - macOSTargetOSVersion: 12.0 + macOSTargetOSVersion: 10.13.0 switchNMETAOverride: switchNetLibKey: switchSocketMemoryPoolSize: 6144 From a098cff97159bce184ab1ba32a2d5682904110e9 Mon Sep 17 00:00:00 2001 From: Darren Kelly Date: Thu, 28 May 2026 11:52:30 +0200 Subject: [PATCH 55/65] Fix issue with numbers 10 and above in priority gui, Paulius found this issue. --- .../Views/ActionPropertiesView.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) 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 716f725e39..4d5b8f638f 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/Views/ActionPropertiesView.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/Views/ActionPropertiesView.cs @@ -99,9 +99,12 @@ public override void RedrawUI((SerializedInputAction ? , List) viewState priorityLabel.style.minWidth = m_DropdownLabelWidth; priorityLabel.style.width = m_DropdownLabelWidth; priorityField.SetValueWithoutNotify(inputAction.priority); - priorityField.RegisterValueChangedCallback(evt => + priorityField.RegisterCallback(_ => CommitActionPriorityIfChanged(priorityField, inputAction)); + priorityField.RegisterCallback(evt => { - Dispatch(Commands.ChangeActionPriority(inputAction, evt.newValue)); + if (evt.keyCode != KeyCode.Return && evt.keyCode != KeyCode.KeypadEnter) + return; + priorityField.Blur(); }); if (showPriority) rootElement.Add(priorityField); @@ -120,6 +123,16 @@ public override void RedrawUI((SerializedInputAction ? , List) viewState rootElement.Add(initialStateCheck); } } + + void CommitActionPriorityIfChanged(IntegerField priorityField, SerializedInputAction inputAction) + { + var priorityProperty = inputAction.wrappedProperty.FindPropertyRelative(nameof(InputAction.m_Priority)); + var newPriority = priorityField.value; + if (newPriority == priorityProperty.intValue) + return; + + Dispatch(Commands.ChangeActionPriority(inputAction, newPriority)); + } } } From 5fc6836548e7e6d2bec72acca7b3cdce8f4cef73 Mon Sep 17 00:00:00 2001 From: Darren Kelly Date: Thu, 28 May 2026 12:10:34 +0200 Subject: [PATCH 56/65] Add better clamp functionality for priority field in UI and centralise the variables. --- .../UITKAssetEditor/Commands/Commands.cs | 2 +- .../InputActionsEditorConstants.cs | 4 +-- .../Views/ActionPropertiesView.cs | 32 ++++++++++++++++--- .../Runtime/Actions/InputAction.cs | 16 ++++++++-- .../Runtime/Actions/InputActionMap.cs | 4 +-- .../Runtime/Actions/InputActionState.cs | 4 +-- 6 files changed, 49 insertions(+), 13 deletions(-) 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 19a606650e..8d9dce4923 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/Commands/Commands.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/Commands/Commands.cs @@ -540,7 +540,7 @@ public static Command ChangeActionPriority(SerializedInputAction inputAction, in return (in InputActionsEditorState state) => { var priorityProperty = inputAction.wrappedProperty.FindPropertyRelative(nameof(InputAction.m_Priority)); - priorityProperty.intValue = Mathf.Clamp(priority, 0, 65535); + priorityProperty.intValue = InputAction.ClampPriority(priority); state.serializedObject.ApplyModifiedProperties(); state.m_Analytics?.RegisterActionEdit(); return state; diff --git a/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/InputActionsEditorConstants.cs b/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/InputActionsEditorConstants.cs index 60dd670b32..1d25d69635 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/InputActionsEditorConstants.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/InputActionsEditorConstants.cs @@ -34,9 +34,9 @@ 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 = + public static readonly string ActionPriorityTooltip = "Priority for this action when several bindings share the same control. Applies to all bindings on the action. " - + "Values are clamped to 0–65535 when set (unsigned 16-bit at runtime). When Action Priority Shortcut Resolution is enabled, " + + $"Values are clamped to {InputAction.MinPriority}–{InputAction.MaxPriority} when set (unsigned 16-bit at runtime). When Action Priority Shortcut Resolution is enabled, " + "higher values are ordered first; when the action performs, priority 0 does not mark the input event as handled " + "for overlap suppression, while any value greater than 0 can."; 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 4d5b8f638f..45ab346662 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/Views/ActionPropertiesView.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/Views/ActionPropertiesView.cs @@ -99,7 +99,8 @@ public override void RedrawUI((SerializedInputAction ? , List) viewState priorityLabel.style.minWidth = m_DropdownLabelWidth; priorityLabel.style.width = m_DropdownLabelWidth; priorityField.SetValueWithoutNotify(inputAction.priority); - priorityField.RegisterCallback(_ => CommitActionPriorityIfChanged(priorityField, inputAction)); + priorityField.RegisterCallback(_ => ScheduleCommitActionPriority(priorityField, inputAction)); + priorityField.RegisterCallback(_ => ScheduleCommitActionPriority(priorityField, inputAction)); priorityField.RegisterCallback(evt => { if (evt.keyCode != KeyCode.Return && evt.keyCode != KeyCode.KeypadEnter) @@ -124,14 +125,37 @@ public override void RedrawUI((SerializedInputAction ? , List) viewState } } + void ScheduleCommitActionPriority(IntegerField priorityField, SerializedInputAction inputAction) + { + // Coalesce FocusOut/Blur so we commit once after the IntegerField text is finalized. + priorityField.schedule.Execute(() => CommitActionPriorityIfChanged(priorityField, inputAction)); + } + void CommitActionPriorityIfChanged(IntegerField priorityField, SerializedInputAction inputAction) { var priorityProperty = inputAction.wrappedProperty.FindPropertyRelative(nameof(InputAction.m_Priority)); - var newPriority = priorityField.value; - if (newPriority == priorityProperty.intValue) + var storedPriority = priorityProperty.intValue; + var clampedPriority = ReadClampedActionPriority(priorityField, storedPriority); + + priorityField.SetValueWithoutNotify(clampedPriority); + + if (clampedPriority == storedPriority) return; - Dispatch(Commands.ChangeActionPriority(inputAction, newPriority)); + Dispatch(Commands.ChangeActionPriority(inputAction, clampedPriority)); + } + + static int ReadClampedActionPriority(IntegerField priorityField, int fallbackPriority) + { + // IntegerField only applies typed text to value on Enter; read text directly when clicking away. + if (!string.IsNullOrWhiteSpace(priorityField.text)) + { + if (int.TryParse(priorityField.text, out var parsed)) + return InputAction.ClampPriority(parsed); + return fallbackPriority; + } + + return InputAction.ClampPriority(priorityField.value); } } } diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/InputAction.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/InputAction.cs index 3a39f1293b..7c71490225 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/InputAction.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/InputAction.cs @@ -196,10 +196,22 @@ public sealed class InputAction : ICloneable, IDisposable /// public InputActionType type => m_Type; + /// + /// Minimum allowed value for . + /// + internal const int MinPriority = 0; + + /// + /// Maximum allowed value for (stored as unsigned 16-bit at runtime). + /// + internal const int MaxPriority = ushort.MaxValue; + + internal static int ClampPriority(int priority) => Math.Clamp(priority, MinPriority, MaxPriority); + /// /// Priority of this action when multiple bindings resolve to the same control. /// - /// Effective range is 0–65535. Values outside that range are clamped when set; the stored value always matches what overlap resolution uses. + /// Effective range is . Values outside that range are clamped when set; the stored value always matches what overlap resolution uses. /// /// Applies to all bindings that target this action. At runtime this value is used only when /// is enabled. In that mode the system orders overlapping @@ -214,7 +226,7 @@ public int Priority get => m_Priority; set { - var clamped = Math.Clamp(value, 0, ushort.MaxValue); + var clamped = ClampPriority(value); if (m_Priority == clamped) return; diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/InputActionMap.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/InputActionMap.cs index a875b8ebbe..21bebdf995 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/InputActionMap.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/InputActionMap.cs @@ -1598,7 +1598,7 @@ public InputAction ToAction(string actionName = null) actionType = InputActionType.Button; } - var clampedPriority = Math.Clamp(priority, 0, ushort.MaxValue); + var clampedPriority = InputAction.ClampPriority(priority); return new InputAction(actionName ?? name, actionType) { @@ -2012,7 +2012,7 @@ public void OnAfterDeserialize() { var action = m_Actions[i]; action.m_ActionMap = this; - action.m_Priority = Math.Clamp(action.m_Priority, 0, ushort.MaxValue); + action.m_Priority = InputAction.ClampPriority(action.m_Priority); } } diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/InputActionState.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/InputActionState.cs index f1aa1d478e..f3bfa45c1f 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/InputActionState.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/InputActionState.cs @@ -181,7 +181,7 @@ private void ComputeControlGroupingIfNecessary() var action = GetActionOrNull(bindingIndex); - var priority = Math.Clamp(action != null ? action.Priority : 0, 0, 65535); + var priority = InputAction.ClampPriority(action != null ? action.Priority : InputAction.MinPriority); controlGroupingAndPriority[ControlGroupingTable.PriorityElementIndex(i)] = (ushort)priority; @@ -1107,7 +1107,7 @@ internal void OnActionPriorityChanged(InputAction action) if (mapIndex < 0 || mapIndex >= totalMapCount) return; - var clampedPriority = (ushort)Math.Clamp(action.Priority, 0, ushort.MaxValue); + var clampedPriority = (ushort)InputAction.ClampPriority(action.Priority); var manager = InputSystem.manager; var bindingStartIndex = mapIndices[mapIndex].bindingStartIndex; var bindingCount = mapIndices[mapIndex].bindingCount; From 8dd0f3d2afd6ccc710d0c3c24430691047959778 Mon Sep 17 00:00:00 2001 From: Darren Kelly Date: Thu, 28 May 2026 15:08:06 +0200 Subject: [PATCH 57/65] fixup! 423c64e0759f10b9aa1df806b8d63bfef98abf6d Created by IntelliJ Git plugin for drop selected changes operation --- ProjectSettings/EditorBuildSettings.asset | 7 +- ProjectSettings/ProjectSettings.asset | 175 +++------------------- 2 files changed, 24 insertions(+), 158 deletions(-) diff --git a/ProjectSettings/EditorBuildSettings.asset b/ProjectSettings/EditorBuildSettings.asset index c94765602b..0147887ef4 100644 --- a/ProjectSettings/EditorBuildSettings.asset +++ b/ProjectSettings/EditorBuildSettings.asset @@ -5,9 +5,4 @@ EditorBuildSettings: m_ObjectHideFlags: 0 serializedVersion: 2 m_Scenes: [] - m_configObjects: - com.unity.input.settings: {fileID: 11400000, guid: 9454f4cc442b641e891a40f2fb11526f, - type: 2} - com.unity.input.settings.actions: {fileID: -944628639613478452, guid: 052faaac586de48259a63d0c4782560b, - type: 3} - m_UseUCBPForAssetBundles: 0 + m_configObjects: {} diff --git a/ProjectSettings/ProjectSettings.asset b/ProjectSettings/ProjectSettings.asset index fc5fc4c552..50e06c227a 100644 --- a/ProjectSettings/ProjectSettings.asset +++ b/ProjectSettings/ProjectSettings.asset @@ -3,7 +3,7 @@ --- !u!129 &1 PlayerSettings: m_ObjectHideFlags: 0 - serializedVersion: 28 + serializedVersion: 26 productGUID: 97965645ae37e47c485fb153bcb9de86 AndroidProfiler: 0 AndroidFilterTouchesWhenObscured: 0 @@ -46,10 +46,9 @@ PlayerSettings: defaultScreenHeight: 768 defaultScreenWidthWeb: 960 defaultScreenHeightWeb: 600 - m_StereoRenderingPath: 0 - m_ActiveColorSpace: 1 + m_StereoRenderingPath: 1 + m_ActiveColorSpace: 0 unsupportedMSAAFallback: 0 - m_SpriteBatchMaxVertexCount: 65535 m_SpriteBatchVertexThreshold: 300 m_MTRendering: 1 mipStripping: 0 @@ -70,9 +69,8 @@ PlayerSettings: androidStartInFullscreen: 1 androidRenderOutsideSafeArea: 0 androidUseSwappy: 0 - androidDisplayOptions: 1 androidBlitType: 0 - androidResizeableActivity: 0 + androidResizableWindow: 0 androidDefaultWindowWidth: 1920 androidDefaultWindowHeight: 1080 androidMinimumWindowWidth: 400 @@ -80,10 +78,10 @@ PlayerSettings: androidFullscreenMode: 1 androidAutoRotationBehavior: 1 androidPredictiveBackSupport: 1 - androidApplicationEntry: 1 defaultIsNativeResolution: 1 macRetinaSupport: 1 runInBackground: 1 + captureSingleScreen: 0 muteOtherAudioSources: 0 Prepare IOS For Recording: 0 Force IOS Speakers When Recording: 0 @@ -100,7 +98,6 @@ PlayerSettings: useMacAppStoreValidation: 0 macAppStoreCategory: public.app-category.games gpuSkinning: 0 - meshDeformation: 0 xboxPIXTextureCapture: 0 xboxEnableAvatar: 0 xboxEnableKinect: 0 @@ -132,9 +129,10 @@ PlayerSettings: switchAllowGpuScratchShrinking: 0 switchNVNMaxPublicTextureIDCount: 0 switchNVNMaxPublicSamplerIDCount: 0 - switchMaxWorkerMultiple: 8 switchNVNGraphicsFirmwareMemory: 32 - switchGraphicsJobsSyncAfterKick: 1 + switchMaxWorkerMultiple: 8 + stadiaPresentMode: 0 + stadiaTargetFramerate: 0 vulkanNumSwapchainBuffers: 3 vulkanEnableSetSRGBWrite: 0 vulkanEnablePreTransform: 0 @@ -164,7 +162,6 @@ PlayerSettings: resetResolutionOnWindowResize: 0 androidSupportedAspectRatio: 1 androidMaxAspectRatio: 2.1 - androidMinAspectRatio: 1 applicationIdentifier: Android: com.unity.inputdemo iPhone: com.unity.inputdemo @@ -176,10 +173,9 @@ PlayerSettings: tvOS: 0 overrideDefaultApplicationIdentifier: 0 AndroidBundleVersionCode: 1 - AndroidMinSdkVersion: 25 + AndroidMinSdkVersion: 22 AndroidTargetSdkVersion: 0 AndroidPreferredInstallLocation: 1 - AndroidPreferredDataLocation: 1 aotOptions: stripEngineCode: 1 iPhoneStrippingLevel: 0 @@ -187,7 +183,7 @@ PlayerSettings: ForceInternetPermission: 0 ForceSDCardPermission: 0 CreateWallpaper: 0 - androidSplitApplicationBinary: 0 + APKExpansionFiles: 0 keepLoadedShadersAlive: 0 StripUnusedMeshComponents: 0 strictShaderVariantMatching: 0 @@ -225,6 +221,7 @@ PlayerSettings: rgba: 0 iOSLaunchScreenFillPct: 100 iOSLaunchScreenSize: 100 + iOSLaunchScreenCustomXibPath: iOSLaunchScreeniPadType: 0 iOSLaunchScreeniPadImage: {fileID: 0} iOSLaunchScreeniPadBackgroundColor: @@ -232,6 +229,7 @@ PlayerSettings: rgba: 0 iOSLaunchScreeniPadFillPct: 100 iOSLaunchScreeniPadSize: 100 + iOSLaunchScreeniPadCustomXibPath: iOSLaunchScreenCustomStoryboardPath: iOSLaunchScreeniPadCustomStoryboardPath: iOSDeviceRequirements: [] @@ -268,19 +266,15 @@ PlayerSettings: useCustomGradleSettingsTemplate: 0 useCustomProguardFile: 0 AndroidTargetArchitectures: 1 - AndroidAllowedArchitectures: -1 + AndroidTargetDevices: 0 AndroidSplashScreenScale: 0 androidSplashScreen: {fileID: 0} AndroidKeystoreName: '{inproject}: {inproject}: {inproject}: ' AndroidKeyaliasName: AndroidEnableArmv9SecurityFeatures: 0 - AndroidEnableArm64MTE: 0 AndroidBuildApkPerCpuArchitecture: 0 AndroidTVCompatibility: 1 AndroidIsGame: 1 - androidAppCategory: 3 - useAndroidAppCategory: 1 - androidAppCategoryOther: AndroidEnableTango: 0 androidEnableBanner: 1 androidUseLowAccuracyLocation: 0 @@ -290,12 +284,11 @@ PlayerSettings: height: 180 banner: {fileID: 0} androidGamepadSupportLevel: 0 + chromeosInputEmulation: 1 AndroidMinifyRelease: 0 AndroidMinifyDebug: 0 AndroidValidateAppBundleSize: 1 AndroidAppBundleSizeToValidate: 200 - AndroidReportGooglePlayAppDependencies: 1 - androidSymbolsSizeThreshold: 800 m_BuildTargetIcons: [] m_BuildTargetPlatformIcons: - m_BuildTarget: Android @@ -390,103 +383,6 @@ PlayerSettings: m_Height: 36 m_Kind: 0 m_SubKind: - - m_BuildTarget: iPhone - m_Icons: - - m_Textures: [] - m_Width: 180 - m_Height: 180 - m_Kind: 0 - m_SubKind: iPhone - - m_Textures: [] - m_Width: 120 - m_Height: 120 - m_Kind: 0 - m_SubKind: iPhone - - m_Textures: [] - m_Width: 167 - m_Height: 167 - m_Kind: 0 - m_SubKind: iPad - - m_Textures: [] - m_Width: 152 - m_Height: 152 - m_Kind: 0 - m_SubKind: iPad - - m_Textures: [] - m_Width: 76 - m_Height: 76 - m_Kind: 0 - m_SubKind: iPad - - m_Textures: [] - m_Width: 120 - m_Height: 120 - m_Kind: 3 - m_SubKind: iPhone - - m_Textures: [] - m_Width: 80 - m_Height: 80 - m_Kind: 3 - m_SubKind: iPhone - - m_Textures: [] - m_Width: 80 - m_Height: 80 - m_Kind: 3 - m_SubKind: iPad - - m_Textures: [] - m_Width: 40 - m_Height: 40 - m_Kind: 3 - m_SubKind: iPad - - m_Textures: [] - m_Width: 87 - m_Height: 87 - m_Kind: 1 - m_SubKind: iPhone - - m_Textures: [] - m_Width: 58 - m_Height: 58 - m_Kind: 1 - m_SubKind: iPhone - - m_Textures: [] - m_Width: 29 - m_Height: 29 - m_Kind: 1 - m_SubKind: iPhone - - m_Textures: [] - m_Width: 58 - m_Height: 58 - m_Kind: 1 - m_SubKind: iPad - - m_Textures: [] - m_Width: 29 - m_Height: 29 - m_Kind: 1 - m_SubKind: iPad - - m_Textures: [] - m_Width: 60 - m_Height: 60 - m_Kind: 2 - m_SubKind: iPhone - - m_Textures: [] - m_Width: 40 - m_Height: 40 - m_Kind: 2 - m_SubKind: iPhone - - m_Textures: [] - m_Width: 40 - m_Height: 40 - m_Kind: 2 - m_SubKind: iPad - - m_Textures: [] - m_Width: 20 - m_Height: 20 - m_Kind: 2 - m_SubKind: iPad - - m_Textures: [] - m_Width: 1024 - m_Height: 1024 - m_Kind: 4 - m_SubKind: App Store m_BuildTargetBatching: [] m_BuildTargetShaderSettings: [] m_BuildTargetGraphicsJobs: @@ -538,7 +434,7 @@ PlayerSettings: m_APIs: 10000000 m_Automatic: 1 - m_BuildTarget: AndroidPlayer - m_APIs: 0b000000 + m_APIs: 0b00000008000000 m_Automatic: 0 m_BuildTargetVRSettings: - m_BuildTarget: Standalone @@ -560,24 +456,18 @@ PlayerSettings: iPhone: 1 tvOS: 1 m_BuildTargetGroupLightmapEncodingQuality: - - serializedVersion: 2 - m_BuildTarget: Standalone + - m_BuildTarget: Standalone m_EncodingQuality: 1 - - serializedVersion: 2 - m_BuildTarget: XboxOne + - m_BuildTarget: XboxOne m_EncodingQuality: 1 - - serializedVersion: 2 - m_BuildTarget: PS4 + - m_BuildTarget: PS4 m_EncodingQuality: 1 m_BuildTargetGroupHDRCubemapEncodingQuality: - - serializedVersion: 2 - m_BuildTarget: Standalone + - m_BuildTarget: Standalone m_EncodingQuality: 2 - - serializedVersion: 2 - m_BuildTarget: XboxOne + - m_BuildTarget: XboxOne m_EncodingQuality: 2 - - serializedVersion: 2 - m_BuildTarget: PS4 + - m_BuildTarget: PS4 m_EncodingQuality: 2 m_BuildTargetGroupLightmapSettings: [] m_BuildTargetGroupLoadStoreDebugModeSettings: [] @@ -586,7 +476,6 @@ PlayerSettings: playModeTestRunnerEnabled: 1 runPlayModeTestAsEditModeTest: 0 actionOnDotNetUnhandledException: 1 - editorGfxJobOverride: 1 enableInternalProfiler: 0 logObjCUncaughtExceptions: 1 enableCrashReportAPI: 0 @@ -739,7 +628,6 @@ PlayerSettings: switchEnableRamDiskSupport: 0 switchMicroSleepForYieldTime: 25 switchRamDiskSpaceSize: 12 - switchUpgradedPlayerSettingsToNMETA: 0 ps4NPAgeRating: 12 ps4NPTitleSecret: ps4NPTrophyPackPath: @@ -843,16 +731,8 @@ PlayerSettings: webGLMemoryGeometricGrowthStep: 0.2 webGLMemoryGeometricGrowthCap: 96 webGLPowerPreference: 2 - webGLWebAssemblyTable: 0 - webGLWebAssemblyBigInt: 0 - webGLCloseOnQuit: 0 - webWasm2023: 0 - webEnableSubmoduleStrippingCompatibility: 0 scriptingDefineSymbols: - Android: URP_COMPATIBILITY_MODE - Standalone: UNITY_ENABLE_STEAM_CONTROLLER_SUPPORT;URP_COMPATIBILITY_MODE - iPhone: URP_COMPATIBILITY_MODE - tvOS: URP_COMPATIBILITY_MODE + Standalone: UNITY_ENABLE_STEAM_CONTROLLER_SUPPORT additionalCompilerArguments: {} platformArchitecture: iPhone: 1 @@ -863,7 +743,6 @@ PlayerSettings: il2cppCompilerConfiguration: Android: 0 il2cppCodeGeneration: {} - il2cppStacktraceInformation: {} managedStrippingLevel: Android: 3 EmbeddedLinux: 1 @@ -891,7 +770,6 @@ PlayerSettings: gcWBarrierValidation: 0 apiCompatibilityLevelPerPlatform: Standalone: 3 - editorAssembliesCompatibilityLevel: 2 m_RenderingPath: 1 m_MobileRenderingPath: 1 metroPackageName: InputSystemX @@ -966,11 +844,9 @@ PlayerSettings: hmiPlayerDataPath: hmiForceSRGBBlit: 1 embeddedLinuxEnableGamepadInput: 1 - hmiCpuConfiguration: hmiLogStartupTiming: 0 - qnxGraphicConfPath: + hmiCpuConfiguration: apiCompatibilityLevel: 6 - captureStartupLogs: {} activeInputHandler: 1 windowsGamepadBackendHint: 0 cloudProjectId: @@ -984,8 +860,3 @@ PlayerSettings: platformRequiresReadableAssets: 0 virtualTexturingSupportEnabled: 0 insecureHttpOption: 0 - androidVulkanDenyFilterList: [] - androidVulkanAllowFilterList: [] - androidVulkanDeviceFilterListAsset: {fileID: 0} - d3d12DeviceFilterListAsset: {fileID: 0} - allowedHttpConnections: 3 From 4878bfa12b43721dc9073dc003acb6386dddf055 Mon Sep 17 00:00:00 2001 From: Darren Kelly Date: Thu, 28 May 2026 15:51:34 +0200 Subject: [PATCH 58/65] Fix project settings using develops version. --- ProjectSettings/ProjectSettings.asset | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/ProjectSettings/ProjectSettings.asset b/ProjectSettings/ProjectSettings.asset index 50e06c227a..d440ac678d 100644 --- a/ProjectSettings/ProjectSettings.asset +++ b/ProjectSettings/ProjectSettings.asset @@ -46,8 +46,8 @@ PlayerSettings: defaultScreenHeight: 768 defaultScreenWidthWeb: 960 defaultScreenHeightWeb: 600 - m_StereoRenderingPath: 1 - m_ActiveColorSpace: 0 + m_StereoRenderingPath: 0 + m_ActiveColorSpace: 1 unsupportedMSAAFallback: 0 m_SpriteBatchVertexThreshold: 300 m_MTRendering: 1 @@ -434,7 +434,7 @@ PlayerSettings: m_APIs: 10000000 m_Automatic: 1 - m_BuildTarget: AndroidPlayer - m_APIs: 0b00000008000000 + m_APIs: 0b000000 m_Automatic: 0 m_BuildTargetVRSettings: - m_BuildTarget: Standalone @@ -732,7 +732,10 @@ PlayerSettings: webGLMemoryGeometricGrowthCap: 96 webGLPowerPreference: 2 scriptingDefineSymbols: - Standalone: UNITY_ENABLE_STEAM_CONTROLLER_SUPPORT + Android: URP_COMPATIBILITY_MODE + Standalone: UNITY_ENABLE_STEAM_CONTROLLER_SUPPORT;URP_COMPATIBILITY_MODE + iPhone: URP_COMPATIBILITY_MODE + tvOS: URP_COMPATIBILITY_MODE additionalCompilerArguments: {} platformArchitecture: iPhone: 1 From a7bd97942dd380bc713a290f1c0ea1624a62b520 Mon Sep 17 00:00:00 2001 From: Darren Kelly Date: Thu, 28 May 2026 22:32:27 +0200 Subject: [PATCH 59/65] Fix documentation issues & remove redundant file --- Assets/InputSystem_Actions.inputactions | 615 ------------------ Assets/InputSystem_Actions.inputactions.meta | 14 - .../Documentation~/Actions.md | 29 + .../Composites/ButtonWithOneModifier.cs | 10 +- .../Composites/ButtonWithTwoModifiers.cs | 10 +- .../Composites/OneModifierComposite.cs | 10 +- .../Composites/TwoModifiersComposite.cs | 10 +- 7 files changed, 49 insertions(+), 649 deletions(-) delete mode 100644 Assets/InputSystem_Actions.inputactions delete mode 100644 Assets/InputSystem_Actions.inputactions.meta diff --git a/Assets/InputSystem_Actions.inputactions b/Assets/InputSystem_Actions.inputactions deleted file mode 100644 index 71c49e904c..0000000000 --- a/Assets/InputSystem_Actions.inputactions +++ /dev/null @@ -1,615 +0,0 @@ -{ - "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 deleted file mode 100644 index 6b38b043d9..0000000000 --- a/Assets/InputSystem_Actions.inputactions.meta +++ /dev/null @@ -1,14 +0,0 @@ -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/Packages/com.unity.inputsystem/Documentation~/Actions.md b/Packages/com.unity.inputsystem/Documentation~/Actions.md index 778266df3a..eb896f9903 100644 --- a/Packages/com.unity.inputsystem/Documentation~/Actions.md +++ b/Packages/com.unity.inputsystem/Documentation~/Actions.md @@ -152,3 +152,32 @@ When you enable an action, the Input System resolves its bindings, unless it has You can't change certain aspects of the configuration, such as action bindings, while an action is enabled. To stop actions or action maps from responding to input, call [`Disable`](xref:UnityEngine.InputSystem.InputAction.Disable). While enabled, an action actively monitors the [controls](xref:input-system-controls) it's bound to. If a bound control changes state, the action processes the change. If the control's change represents an [interaction](xref:input-system-interactions) change, the action creates a response. All of this happens during the Input System update logic. Depending on the [update mode](xref:input-system-settings#update-mode) selected in the input settings, this happens once every frame, once every fixed update, or manually if updates are set to manual. + +## Overlapping bindings and action priority + +When several enabled actions share the same physical control (for example a plain **B** key action and a **Shift+B** composite), the Input System can resolve which action should respond first. This is controlled in **Project Settings** > **Input System Package** under [Improved Shortcut Support](xref:input-system-settings#improved-shortcut-support). + +| Setting | Behavior | +| ------- | -------- | +| [Complexity-Based Shortcut Resolution](xref:UnityEngine.InputSystem.InputSettings.shortcutKeysConsumeInput) | Orders overlapping bindings by composite **complexity** (binding-chain depth). This is the default develop behavior when action priority is off. | +| [Action Priority Shortcut Resolution](xref:UnityEngine.InputSystem.InputSettings.shortcutKeysUseActionPriority) | Orders overlapping bindings by each action's [`InputAction.Priority`](xref:UnityEngine.InputSystem.InputAction.Priority). When enabled, it **takes precedence** over complexity-based resolution even if both options are on. | + +Each action has a [`Priority`](xref:UnityEngine.InputSystem.InputAction.Priority) property (range **0**–**65535**, clamped when set). The value applies to all bindings on that action. Serialized priority is always stored on the asset; at runtime it is used only when **Action Priority Shortcut Resolution** is enabled. In that case the **Priority** field is also shown in the [Input Actions Editor](xref:input-system-configuring-input). + +When action priority resolution is active: + +- Higher priority actions are notified before lower-priority actions on the same control. +- When an action reaches the **Performed** phase, priority **0** does **not** mark the input event as handled, so lower-priority actions in the same overlap group can still respond on that event. +- Any priority **greater than zero** can mark the event handled and suppress strictly lower-priority actions in the same group for that event. +- Actions with the **same** priority are not suppressed relative to each other; both can perform in the same update if their bindings fire. + +Set priority in code: + +```CSharp +fireAction.Priority = 10; +reloadAction.Priority = 5; +``` + +Or edit the **Priority** field on an action in the Input Actions Editor when **Action Priority Shortcut Resolution** is enabled. + +For composite shortcuts and complexity ordering, see [Multiple input sequences (such as keyboard shortcuts)](xref:input-system-action-bindings#multiple-input-sequences-such-as-keyboard-shortcuts). diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/Composites/ButtonWithOneModifier.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/Composites/ButtonWithOneModifier.cs index 8779c79f17..4622e4dbf4 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/Composites/ButtonWithOneModifier.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/Composites/ButtonWithOneModifier.cs @@ -80,7 +80,7 @@ public class ButtonWithOneModifier : InputBindingComposite /// still trigger. Default is false. /// /// - /// By default, if the setting is enabled, + /// By default, if is enabled and is disabled, /// is required to be in pressed state before or at the same time that /// goes into pressed state for the composite as a whole to trigger. This means that binding to, for example, Shift+B, /// the shift key has to be pressed before pressing the B key. This is the behavior usually expected with @@ -101,13 +101,13 @@ public class ButtonWithOneModifier : InputBindingComposite public enum ModifiersOrder { /// - /// By default, if the setting is enabled, + /// By default, if is enabled and is disabled, /// is required to be in pressed state before or at the same time that /// goes into pressed state for the composite as a whole to trigger. This means that binding to, for example, Shift+B, /// the shift key has to be pressed before pressing the B key. This is the behavior usually expected with /// keyboard shortcuts. /// - /// If the setting is disabled, + /// If is disabled or is enabled, /// modifiers can be pressed after the button and the composite will still trigger. /// Default = 0, @@ -131,13 +131,13 @@ public enum ModifiersOrder /// If set to Ordered or Unordered, the built-in logic to determine if modifiers need to be pressed first is overridden. /// /// - /// By default, if the setting is enabled, + /// By default, if is enabled and is disabled, /// is required to be in pressed state before or at the same time that /// goes into pressed state for the composite as a whole to trigger. This means that binding to, for example, Shift+B, /// the shift key has to be pressed before pressing the B key. This is the behavior usually expected with /// keyboard shortcuts. /// - /// If the setting is disabled, + /// If is disabled or is enabled, /// modifiers can be pressed after the button and the composite will still trigger. /// /// This parameter can be used to bypass this behavior and enforce the timing order or allow any timing between and . diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/Composites/ButtonWithTwoModifiers.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/Composites/ButtonWithTwoModifiers.cs index e0f1f7f51c..448c4034c0 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/Composites/ButtonWithTwoModifiers.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/Composites/ButtonWithTwoModifiers.cs @@ -95,7 +95,7 @@ public class ButtonWithTwoModifiers : InputBindingComposite /// and the composite will still trigger. Default is false. /// /// - /// By default, if the setting is enabled, + /// By default, if is enabled and is disabled, /// and are required to be in pressed state before or at the same /// time that goes into pressed state for the composite as a whole to trigger. This means that binding to, /// for example, Ctrl+Shift+B, the ctrl and shift keys have to be pressed, in any order, before pressing the B key. @@ -116,13 +116,13 @@ public class ButtonWithTwoModifiers : InputBindingComposite public enum ModifiersOrder { /// - /// By default, if the setting is enabled, + /// By default, if is enabled and is disabled, /// and are required to be in pressed state before or at the same /// time that goes into pressed state for the composite as a whole to trigger. This means that binding to, /// for example, Ctrl+Shift+B, the ctrl and shift keys have to be pressed, in any order, before pressing the B key. /// This is the behavior usually expected with keyboard shortcuts. /// - /// If the setting is disabled, + /// If is disabled or is enabled, /// modifiers can be pressed after the button and the composite will still trigger. /// Default = 0, @@ -146,13 +146,13 @@ public enum ModifiersOrder /// If set to Ordered or Unordered, the built-in logic to determine if modifiers need to be pressed first is overridden. /// /// - /// By default, if the setting is enabled, + /// By default, if is enabled and is disabled, /// and are required to be in pressed state before or at the same /// time that goes into pressed state for the composite as a whole to trigger. This means that binding to, /// for example, Ctrl+Shift+B, the ctrl and shift keys have to be pressed, in any order, before pressing the B key. /// This is the behavior usually expected with keyboard shortcuts. /// - /// If the setting is disabled, + /// If is disabled or is enabled, /// modifiers can be pressed after the button and the composite will still trigger. /// /// This field allows you to explicitly override this default inference. diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/Composites/OneModifierComposite.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/Composites/OneModifierComposite.cs index de0382945d..8b6fc3f1ed 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/Composites/OneModifierComposite.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/Composites/OneModifierComposite.cs @@ -88,7 +88,7 @@ public class OneModifierComposite : InputBindingComposite /// Default value is false. /// /// - /// By default, if the setting is enabled, + /// By default, if is enabled and is disabled, /// if is bound to only s, then the composite requires /// to be pressed before pressing . This means that binding to, for example, /// Ctrl+B, the ctrl keys have to be pressed before pressing the B key. This is the behavior usually expected @@ -114,13 +114,13 @@ public class OneModifierComposite : InputBindingComposite public enum ModifiersOrder { /// - /// By default, if the setting is enabled, + /// By default, if is enabled and is disabled, /// if is bound to only s, then the composite requires /// to be pressed before pressing . This means that binding to, for example, /// Ctrl+B, the ctrl keys have to be pressed before pressing the B key. This is the behavior usually expected /// with keyboard shortcuts. /// - /// If the setting is disabled, + /// If is disabled or is enabled, /// modifiers can be pressed after the button and the composite will still trigger. /// Default = 0, @@ -144,13 +144,13 @@ public enum ModifiersOrder /// If set to Ordered or Unordered, the built-in logic to determine if modifiers need to be pressed first is overridden. /// /// - /// By default, if the setting is enabled, + /// By default, if is enabled and is disabled, /// if is bound to only s, then the composite requires /// to be pressed before pressing . This means that binding to, for example, /// Ctrl+B, the ctrl keys have to be pressed before pressing the B key. This is the behavior usually expected /// with keyboard shortcuts. /// - /// If the setting is disabled, + /// If is disabled or is enabled, /// modifiers can be pressed after the button and the composite will still trigger. /// /// However, when binding, for example, Ctrl+MouseDelta, it should be possible to press ctrl at any time. The default diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/Composites/TwoModifiersComposite.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/Composites/TwoModifiersComposite.cs index 9b67108f63..bf36ecab29 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/Composites/TwoModifiersComposite.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/Composites/TwoModifiersComposite.cs @@ -90,7 +90,7 @@ public class TwoModifiersComposite : InputBindingComposite /// Default value is false. /// /// - /// By default, if the setting is enabled, + /// By default, if is enabled and is disabled, /// if is bound to only s, then the composite requires /// both and to be pressed before pressing . /// This means that binding to, for example, Ctrl+Shift+B, the ctrl and shift keys have to be pressed, in any order, @@ -116,13 +116,13 @@ public class TwoModifiersComposite : InputBindingComposite public enum ModifiersOrder { /// - /// By default, if the setting is enabled, + /// By default, if is enabled and is disabled, /// if is bound to only s, then the composite requires /// both and to be pressed before pressing . /// This means that binding to, for example, Ctrl+Shift+B, the ctrl and shift keys have to be pressed, in any order, /// before pressing the B key. This is the behavior usually expected with keyboard shortcuts. /// - /// If the setting is disabled, + /// If is disabled or is enabled, /// modifiers can be pressed after the button and the composite will still trigger. /// Default = 0, @@ -146,13 +146,13 @@ public enum ModifiersOrder /// If set to Ordered or Unordered, the built-in logic to determine if modifiers need to be pressed first is overridden. /// /// - /// By default, if the setting is enabled, + /// By default, if is enabled and is disabled, /// if is bound to only s, then the composite requires /// both and to be pressed before pressing . /// This means that binding to, for example, Ctrl+Shift+B, the ctrl and shift keys have to be pressed, in any order, /// before pressing the B key. This is the behavior usually expected with keyboard shortcuts. /// - /// If the setting is disabled, + /// If is disabled or is enabled, /// modifiers can be pressed after the button and the composite will still trigger. /// /// This field allows you to explicitly override this default inference and make the order mandatory or make it so that regardless of what From 896fcfeb55802488fe316e6eeba2ade51dace59b Mon Sep 17 00:00:00 2001 From: Darren Kelly Date: Fri, 29 May 2026 11:38:39 +0200 Subject: [PATCH 60/65] Fix naming size in settings. --- .../InputSystem/Editor/Settings/InputSettingsProvider.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Packages/com.unity.inputsystem/InputSystem/Editor/Settings/InputSettingsProvider.cs b/Packages/com.unity.inputsystem/InputSystem/Editor/Settings/InputSettingsProvider.cs index 2f6eabf6da..684dc313d2 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Editor/Settings/InputSettingsProvider.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Editor/Settings/InputSettingsProvider.cs @@ -345,8 +345,8 @@ private void InitializeWithCurrentSettings() m_DefaultHoldTimeContent = new GUIContent("Default Hold Time", "Default duration to be used for Hold interactions."); m_TapRadiusContent = new GUIContent("Tap Radius", "Maximum distance between two finger taps on a touch screen device allowed for the system to consider this a tap of the same touch (as opposed to a new touch)."); m_MultiTapDelayTimeContent = new GUIContent("MultiTap Delay Time", "Default delay to be allowed between taps for MultiTap interactions. Also used by by touch devices to count multi taps."); - m_ShortcutKeysConsumeInputsContent = new GUIContent("Complexity-Based Shortcut Resolution", "When enabled (and action priority resolution is off), composite bindings consume overlapping input using binding-chain depth (complexity), not per-action priority."); - m_ShortcutKeysUseActionPriorityContent = new GUIContent("Action Priority Shortcut Resolution", "When enabled, overlapping actions are resolved using each action's Priority value. This overrides complexity-based resolution even if it is also enabled."); + m_ShortcutKeysConsumeInputsContent = new GUIContent("Complexity Consumption", "When enabled (and action priority resolution is off), composite bindings consume overlapping input using binding-chain depth (complexity), not per-action priority."); + m_ShortcutKeysUseActionPriorityContent = new GUIContent("Priority Consumption", "When enabled, overlapping actions are resolved using each action's Priority value. This overrides complexity-based resolution even if it is also enabled."); // Initialize ReorderableList for list of supported devices. var supportedDevicesProperty = m_SettingsObject.FindProperty("m_SupportedDevices"); From be0bb156f661433854be06ed257005ee9df4c1d3 Mon Sep 17 00:00:00 2001 From: Darren Kelly Date: Mon, 1 Jun 2026 12:12:36 +0200 Subject: [PATCH 61/65] Fix code review comments from Hakan. --- .../InputSystem/CoreTests_ActionsPriority.cs | 15 +++++++++++---- .../Documentation~/Actions.md | 2 +- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/Assets/Tests/InputSystem/CoreTests_ActionsPriority.cs b/Assets/Tests/InputSystem/CoreTests_ActionsPriority.cs index b84602df52..9b2e502472 100644 --- a/Assets/Tests/InputSystem/CoreTests_ActionsPriority.cs +++ b/Assets/Tests/InputSystem/CoreTests_ActionsPriority.cs @@ -214,7 +214,7 @@ public void Actions_Priority_OnlyOneActionIsFired_WhenOnePriorityIsHigherThanOth [Test] [Category("Actions Priority")] - [TestCaseSource(nameof(k_TwoInputActionTestCases))] // TODO: Darren, Should both actions be performed this frame here?? + [TestCaseSource(nameof(k_TwoInputActionTestCases))] public void Actions_Priority_BothActionsArePerformed_DueToKeyPressOrderForShortcut((string[] larger, string[] smaller) actions) { EnableActionPriorityShortcutResolution(); @@ -421,7 +421,7 @@ public void Actions_Priority_ControlGroupingTable_StrideAndElementIndicesMatchIn [Category("Actions Priority")] public void Actions_Priority_InputActionStateMonitorIndex_RoundTripsComponents() { - var index = InputActionStateMonitorIndex.Create(mapIndex: 7, controlIndex: 0x00abcdef, bindingIndex: 0x0bcd, + InputActionStateMonitorIndex index = InputActionStateMonitorIndex.Create(mapIndex: 7, controlIndex: 0x00abcdef, bindingIndex: 0x0bcd, priority: 200); Assert.That(index.MapIndex, Is.EqualTo(7)); @@ -445,6 +445,8 @@ public void Actions_Priority_InputActionStateMonitorIndex_FromPacked_MatchesCrea [Test] [Category("Actions Priority")] + // Priority is stored as a 16-bit field in the packed index. This test ensures values above byte.MaxValue (255) + // are not silently truncated, confirming the field is ushort-wide end-to-end. public void Actions_Priority_InputActionStateMonitorIndex_PriorityRoundTripsFullSixteenBits() { var index300 = InputActionStateMonitorIndex.Create(0, 1, 0, priority: 300); @@ -457,7 +459,9 @@ public void Actions_Priority_InputActionStateMonitorIndex_PriorityRoundTripsFull [Test] [Category("Actions Priority")] - public void Actions_Priority_PrioritiesAbove255_ResolveInOrder() + // Priority is a ushort (0–65535). This verifies that values above byte.MaxValue (255) still resolve in + // the correct order, guarding against accidental byte-truncation in the sort path. + public void Actions_Priority_PrioritiesExceedingByteRange_ResolveInOrder() { EnableActionPriorityShortcutResolution(); var keyboard = InputSystem.AddDevice(); @@ -576,7 +580,7 @@ static int PackedPriorityForMonitor(InputActionState actionState, InputControl c [Category("Actions Priority")] public void Actions_Priority_InputActionStateMonitorIndex_ImplicitConversionToLongMatchesPackedProperty() { - var index = InputActionStateMonitorIndex.Create(1, 2, 3, 4); + InputActionStateMonitorIndex index = InputActionStateMonitorIndex.Create(1, 2, 3, 4); long asLong = index; Assert.That(asLong, Is.EqualTo(index.Packed)); } @@ -789,6 +793,9 @@ public unsafe void Actions_Complexity_ControlGrouping_SamePhysicalControlSharesG [Test] [Category("Actions Priority")] + // In complexity mode the secondary column of the control-grouping table holds binding-chain depth (composite + // complexity), not the action's Priority value. Two simple (non-composite) bindings on the same key each have + // depth 1 regardless of what Priority is set on their actions, because Priority is irrelevant in this mode. public unsafe void Actions_Complexity_ControlGrouping_WritesPerControlSlotComplexity_NotActionPriority() { EnableComplexityShortcutResolution(); diff --git a/Packages/com.unity.inputsystem/Documentation~/Actions.md b/Packages/com.unity.inputsystem/Documentation~/Actions.md index eb896f9903..0702989f9f 100644 --- a/Packages/com.unity.inputsystem/Documentation~/Actions.md +++ b/Packages/com.unity.inputsystem/Documentation~/Actions.md @@ -162,7 +162,7 @@ When several enabled actions share the same physical control (for example a plai | [Complexity-Based Shortcut Resolution](xref:UnityEngine.InputSystem.InputSettings.shortcutKeysConsumeInput) | Orders overlapping bindings by composite **complexity** (binding-chain depth). This is the default develop behavior when action priority is off. | | [Action Priority Shortcut Resolution](xref:UnityEngine.InputSystem.InputSettings.shortcutKeysUseActionPriority) | Orders overlapping bindings by each action's [`InputAction.Priority`](xref:UnityEngine.InputSystem.InputAction.Priority). When enabled, it **takes precedence** over complexity-based resolution even if both options are on. | -Each action has a [`Priority`](xref:UnityEngine.InputSystem.InputAction.Priority) property (range **0**–**65535**, clamped when set). The value applies to all bindings on that action. Serialized priority is always stored on the asset; at runtime it is used only when **Action Priority Shortcut Resolution** is enabled. In that case the **Priority** field is also shown in the [Input Actions Editor](xref:input-system-configuring-input). +Each action has a [`Priority`](xref:UnityEngine.InputSystem.InputAction.Priority) property (range **0**–**65535**, clamped when set; higher value = higher priority, notified first). The value applies to all bindings on that action. Serialized priority is always stored on the asset; at runtime it is used only when **Action Priority Shortcut Resolution** is enabled. In that case the **Priority** field is also shown in the [Input Actions Editor](xref:input-system-configuring-input). When action priority resolution is active: From 96a914bbeadafbc49f0696e8f91aa4034c519ea5 Mon Sep 17 00:00:00 2001 From: Darren Kelly Date: Mon, 1 Jun 2026 13:13:36 +0200 Subject: [PATCH 62/65] Add code review changes. --- Assets/Tests/InputSystem/CoreTests_ActionsPriority.cs | 1 + .../InputSystem/Actions/InputActionStateMonitorIndex.cs | 7 ++++++- .../InputSystem/Runtime/Actions/InputActionState.cs | 7 ++++--- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/Assets/Tests/InputSystem/CoreTests_ActionsPriority.cs b/Assets/Tests/InputSystem/CoreTests_ActionsPriority.cs index 9b2e502472..9ee49ab98a 100644 --- a/Assets/Tests/InputSystem/CoreTests_ActionsPriority.cs +++ b/Assets/Tests/InputSystem/CoreTests_ActionsPriority.cs @@ -968,4 +968,5 @@ public void Actions_Complexity_BothSimpleActionsOnSameControlPerform_WhenEqualCo Release((ButtonControl)action2.controls[0], queueEventOnly: true); InputSystem.Update(); } + } diff --git a/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionStateMonitorIndex.cs b/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionStateMonitorIndex.cs index cd07a23d3d..dfa768d8d7 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionStateMonitorIndex.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionStateMonitorIndex.cs @@ -6,9 +6,14 @@ namespace UnityEngine.InputSystem /// internal readonly struct InputActionStateMonitorIndex { + // Bit layout (64 bits total): + // [0–23] controlIndex (24 bits) + // [24–39] bindingIndex (16 bits) + // [40–47] mapIndex (8 bits) + // [48–63] priority or composite complexity (ushort, 16 bits) readonly long m_Packed; - public InputActionStateMonitorIndex(long packed) + private InputActionStateMonitorIndex(long packed) { m_Packed = packed; } diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/InputActionState.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/InputActionState.cs index f3bfa45c1f..4325fc1988 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/InputActionState.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/Actions/InputActionState.cs @@ -1092,11 +1092,12 @@ internal void OnActionPriorityChanged(InputAction action) if (!InputSystem.settings.IsShortcutResolutionUsingActionPriority) return; - Debug.Assert(action != null, "Action must not be null"); - Debug.Assert(action.m_ActionMap != null, "Action must have action map"); - if (action == null || action.m_ActionMap == null) + { + Debug.Assert(action != null, "Action must not be null"); + Debug.Assert(action?.m_ActionMap != null, "Action must have action map"); return; + } var actionIndex = action.m_ActionIndexInState; if (actionIndex < 0 || actionIndex >= totalActionCount) From 33ad56abe4cc88b1b554e1346c205e703317b7f4 Mon Sep 17 00:00:00 2001 From: Darren Kelly Date: Mon, 1 Jun 2026 13:38:00 +0200 Subject: [PATCH 63/65] Add brackets for code review. --- .../InputSystem/Runtime/InputManagerStateMonitors.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManagerStateMonitors.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManagerStateMonitors.cs index 26ed8b9814..612520aa4e 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManagerStateMonitors.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManagerStateMonitors.cs @@ -375,6 +375,7 @@ public void Remove(IInputStateChangeMonitor monitor, long monitorIndex, bool def return; for (var i = 0; i < signalled.length; ++i) + { if (ReferenceEquals(listeners[i].monitor, monitor) && listeners[i].monitorIndex == monitorIndex) { if (deferRemoval) @@ -391,6 +392,7 @@ public void Remove(IInputStateChangeMonitor monitor, long monitorIndex, bool def break; } + } } public void Clear() From b8852c0651d628fa5740cc96b1faafe4f4a8bb80 Mon Sep 17 00:00:00 2001 From: Darren Kelly Date: Mon, 1 Jun 2026 13:50:43 +0200 Subject: [PATCH 64/65] Change tootlips for InputSettings. --- .../Editor/Settings/InputSettingsProvider.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Packages/com.unity.inputsystem/InputSystem/Editor/Settings/InputSettingsProvider.cs b/Packages/com.unity.inputsystem/InputSystem/Editor/Settings/InputSettingsProvider.cs index 684dc313d2..575d73c9f5 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Editor/Settings/InputSettingsProvider.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Editor/Settings/InputSettingsProvider.cs @@ -181,17 +181,17 @@ public override void OnGUI(string searchContext) else if (m_ShortcutKeysUseActionPriority.boolValue) { EditorGUILayout.HelpBox( - "When several enabled actions share the same control, the action with the higher Priority value is ordered first and can consume input so lower-priority actions do not trigger on the same event. " - + "Set Priority on each action in the Input Actions editor. Serialized priority values are kept when this option is disabled.", + "When several enabled actions are bound to the same control, the one with the highest Priority is evaluated first and consumes the input, so lower-priority actions don't also trigger from that event. " + + "Set each action's Priority in the Input Actions editor. Priority values stay saved on the asset even when Action Priority Shortcut Resolution is turned off.", MessageType.None); } else if (m_ShortcutKeysConsumeInputs.boolValue) { EditorGUILayout.HelpBox( - "Composite bindings can consume overlapping input using binding-chain depth (complexity): actions with more composite parts are considered before simpler bindings on the same controls. " - + "This works well for shortcut keys; when two composites have the same depth, resolution can be non-deterministic. " - + "Conflicts can occur between different action maps (for example UI navigation versus gameplay on the same keys) but not between different action assets. " - + "Since consumption applies to enabled actions only, disable maps or actions you do not need in the current context to reduce surprises.", + "Overlapping shortcuts are resolved by composite complexity: an action whose composite has more parts (e.g. Ctrl+Shift+S) wins over one with fewer parts — or a plain binding — on the same control, and consumes the input so the simpler action doesn't also fire. " + + "Two composites of equal complexity have no guaranteed order between them. " + + "Resolution applies across action maps within the same asset (for example UI navigation vs. gameplay on the same keys), but never across separate action assets. " + + "Only enabled actions consume input, so disable maps or actions you don't need in the current context to avoid unexpected behaviour.", MessageType.None); } From f48e8393cdd35ced28fb54e33851fc4add7ba40a Mon Sep 17 00:00:00 2001 From: Darren Kelly Date: Mon, 1 Jun 2026 16:59:27 +0200 Subject: [PATCH 65/65] Update CI to ignore Unity 6.6 for now. --- Tools/CI/Settings/InputSystemSettings.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Tools/CI/Settings/InputSystemSettings.cs b/Tools/CI/Settings/InputSystemSettings.cs index 09a47e3fe8..ac78f5cf1d 100644 --- a/Tools/CI/Settings/InputSystemSettings.cs +++ b/Tools/CI/Settings/InputSystemSettings.cs @@ -127,6 +127,11 @@ public InputSystemSettings() OverridePackagePlatform(InputSystemPackage); + foreach ((string name, WrenchPackage package) in Wrench.Packages) + { + Wrench.Packages[name].EditorPlatforms[SystemType.MacOS] = new Platform(new Agent("package-ci/macos-13-arm64:v4", FlavorType.MacDefault, ResourceType.VmOsx, "M1"), SystemType.MacOS); + } + ReadMobileConfig(); var oldIOSAgent = MobileTestPlatforms[SystemType.IOS].Agent;