Skip to content

Commit aff7a19

Browse files
authored
FIX: some actions not being triggered when other duplicate control bindings exist (ISXB-254). (#1600)
* CHANGE: shortcut support disabled by default * add test and fix shortcut specific tests * review changes. fix changelog entry * review comments: add shortcut feature toggle to the Project Settings * review comments: modify changelog * formatting * review comments: improve descriptions * remove deprecation warning * more specific property name
1 parent 91762ca commit aff7a19

4 files changed

Lines changed: 141 additions & 8 deletions

File tree

Assets/Tests/InputSystem/CoreTests_Actions.cs

Lines changed: 108 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,50 @@
3030
// in terms of complexity.
3131
partial class CoreTests
3232
{
33+
[Test]
34+
[Category("Actions")]
35+
public void Actions_WhenShortcutsDisabled_AllConflictingActionsTrigger()
36+
{
37+
var keyboard = InputSystem.AddDevice<Keyboard>();
38+
39+
var map1 = new InputActionMap("map1");
40+
var action1 = map1.AddAction(name: "action1");
41+
action1.AddCompositeBinding("2DVector")
42+
.With("Up", "<Keyboard>/w")
43+
.With("Down", "<Keyboard>/s")
44+
.With("Left", "<Keyboard>/a")
45+
.With("Right", "<Keyboard>/d");
46+
47+
var map2 = new InputActionMap("map2");
48+
var action2 = map2.AddAction(name: "action2");
49+
action2.AddCompositeBinding("2DVector")
50+
.With("Up", "<Keyboard>/w")
51+
.With("Down", "<Keyboard>/s")
52+
.With("Left", "<Keyboard>/a")
53+
.With("Right", "<Keyboard>/d");
54+
var action3 = map2.AddAction(name: "action3", binding: "<Keyboard>/w");
55+
56+
map1.Enable();
57+
map2.Enable();
58+
59+
Press(keyboard.wKey);
60+
61+
// All Actions were triggered
62+
Assert.That(action1.WasPerformedThisFrame());
63+
Assert.That(action2.WasPerformedThisFrame());
64+
Assert.That(action3.WasPerformedThisFrame());
65+
}
66+
3367
// Premise: Binding the same control multiple times in different ways from multiple concurrently active
3468
// actions should result in the input system figuring out which *one* action gets to act on the input.
3569
[Test]
3670
[Category("Actions")]
3771
[TestCase(true)]
3872
[TestCase(false)]
39-
public void Actions_CanConsumeInput(bool legacyComposites)
73+
public void Actions_WhenShortcutsEnabled_CanConsumeInput(bool legacyComposites)
4074
{
75+
InputSystem.settings.SetInternalFeatureFlag(InputFeatureNames.kDisableShortcutSupport, false);
76+
4177
var keyboard = InputSystem.AddDevice<Keyboard>();
4278

4379
var map = new InputActionMap();
@@ -124,13 +160,11 @@ public void Actions_CanConsumeInput(bool legacyComposites)
124160
Assert.That(!action3.WasPerformedThisFrame());
125161
}
126162

127-
// For now, maintain a kill switch for the new behavior for users to have an out where the
128-
// the behavior is simply breaking their project.
129163
[Test]
130164
[Category("Actions")]
131-
public void Actions_CanDisableShortcutSupport()
165+
public void Actions_ShortcutSupportDisabledByDefault()
132166
{
133-
InputSystem.settings.SetInternalFeatureFlag(InputFeatureNames.kDisableShortcutSupport, true);
167+
Assert.That(InputSystem.settings.IsFeatureEnabled(InputFeatureNames.kDisableShortcutSupport), Is.True);
134168

135169
var keyboard = InputSystem.AddDevice<Keyboard>();
136170

@@ -216,8 +250,10 @@ public void Actions_CanBindMultipleShortcutSequencesBasedOnSameModifiers()
216250
[TestCase("leftShift", "leftAlt", "space", true)]
217251
[TestCase("leftShift", null, "space", false)]
218252
[TestCase("leftShift", "leftAlt", "space", false)]
219-
public void Actions_PressingShortcutSequenceInWrongOrder_DoesNotTriggerShortcut(string modifier1, string modifier2, string binding, bool legacyComposites)
253+
public void Actions_WhenShortcutsEnabled_PressingShortcutSequenceInWrongOrder_DoesNotTriggerShortcut(string modifier1, string modifier2, string binding, bool legacyComposites)
220254
{
255+
InputSystem.settings.SetInternalFeatureFlag(InputFeatureNames.kDisableShortcutSupport, false);
256+
221257
var keyboard = InputSystem.AddDevice<Keyboard>();
222258

223259
var action = new InputAction();
@@ -292,8 +328,10 @@ public void Actions_PressingShortcutSequenceInWrongOrder_DoesNotTriggerShortcut_
292328

293329
[Test]
294330
[Category("Actions")]
295-
public void Actions_CanHaveShortcutsWithButtonsUsingInitialStateChecks()
331+
public void Actions_WhenShortcutsAreEnabled_CanHaveShortcutsWithButtonsUsingInitialStateChecks()
296332
{
333+
InputSystem.settings.SetInternalFeatureFlag(InputFeatureNames.kDisableShortcutSupport, false);
334+
297335
var keyboard = InputSystem.AddDevice<Keyboard>();
298336

299337
var map = new InputActionMap();
@@ -3052,7 +3090,7 @@ public void Actions_CanRecordActions_AndReadValueAsObject()
30523090
action2.Disable();
30533091
Set(gamepad.leftTrigger, 0.234f);
30543092

3055-
Assert.That(trace, Performed(action2, value: -0.123f).AndThen(Performed(action1, value: 0.123f)));
3093+
Assert.That(trace, Performed(action1, value: 0.123f).AndThen(Performed(action2, value: -0.123f)));
30563094
}
30573095
}
30583096

@@ -10158,6 +10196,68 @@ public void Actions_WithMultipleCompositeBindings_WithoutEvaluateMagnitude_Works
1015810196
Assert.That(values[0].Position, Is.EqualTo(new Vector2(1, 1)));
1015910197
}
1016010198

10199+
// FIX: This test is currently checking if shortcut support is enabled by testing that the unwanted behaviour exists.
10200+
// This test should be repurposed once that behaviour is fixed.
10201+
[Test]
10202+
[Category("Actions")]
10203+
[TestCase(true)]
10204+
[TestCase(false)]
10205+
public void Actions_ImprovedShortcutSupport_ConsumesWASD(bool shortcutsEnabled)
10206+
{
10207+
InputSystem.settings.SetInternalFeatureFlag(InputFeatureNames.kDisableShortcutSupport, !shortcutsEnabled);
10208+
10209+
var keyboard = InputSystem.AddDevice<Keyboard>();
10210+
10211+
var map1 = new InputActionMap("map1");
10212+
var action1 = map1.AddAction(name: "action1");
10213+
action1.AddCompositeBinding("2DVector")
10214+
.With("Up", "<Keyboard>/w")
10215+
.With("Down", "<Keyboard>/s")
10216+
.With("Left", "<Keyboard>/a")
10217+
.With("Right", "<Keyboard>/d");
10218+
10219+
var map2 = new InputActionMap("map2");
10220+
var action2 = map2.AddAction(name: "action2");
10221+
action2.AddCompositeBinding("2DVector")
10222+
.With("Up", "<Keyboard>/w")
10223+
.With("Down", "<Keyboard>/s")
10224+
.With("Left", "<Keyboard>/a")
10225+
.With("Right", "<Keyboard>/d");
10226+
var action3 = map2.AddAction(name: "action3", binding: "<Keyboard>/w");
10227+
10228+
var asset = ScriptableObject.CreateInstance<InputActionAsset>();
10229+
asset.AddActionMap(map1);
10230+
asset.AddActionMap(map2);
10231+
10232+
map1.Enable();
10233+
LogAssert.NoUnexpectedReceived();
10234+
10235+
map2.Enable();
10236+
10237+
int action1Count = 0;
10238+
int action2Count = 0;
10239+
int action3Count = 0;
10240+
action1.started += ctx => action1Count++;
10241+
action2.started += ctx => action2Count++;
10242+
action3.started += ctx => action3Count++;
10243+
10244+
Press(keyboard.wKey);
10245+
if (shortcutsEnabled)
10246+
{
10247+
// First action with the most bindings is the ONLY one to trigger
10248+
Assert.That(action1Count, Is.EqualTo(1));
10249+
Assert.That(action2Count, Is.EqualTo(0));
10250+
Assert.That(action3Count, Is.EqualTo(0));
10251+
}
10252+
else
10253+
{
10254+
// All actions were triggered
10255+
Assert.That(action1Count, Is.EqualTo(1));
10256+
Assert.That(action2Count, Is.EqualTo(1));
10257+
Assert.That(action3Count, Is.EqualTo(1));
10258+
}
10259+
}
10260+
1016110261
[Test]
1016210262
[Category("Actions")]
1016310263
[Ignore("TODO")]

Packages/com.unity.inputsystem/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ however, it has to be formatted properly to pass verification tests.
1414
- Fixed `ArgumentNullException` when opening the Prefab Overrides window and selecting a component with an `InputAction`.
1515
- Fixed `{fileID: 0}` getting appended to `ProjectSettings.asset` file when building a project ([case ISXB-296](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-296)).
1616
- Fixed `Type of instance in array does not match expected type` assertion when using PlayerInput in combination with Control Schemes and Interactions ([case ISXB-282](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-282)).
17+
- The `InputActions consume their inputs` behaviour for shortcut support introduced in v1.4 is opt-in now and can be enabled via the project settings ([case ISXB-254](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-254))).
1718
- Fixed Memory alignment issue with deserialized InputEventTraces that could cause infinite loops when playing back replays ([case ISXB-317](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-317)).
1819
- Fixed an InvalidOperationException when using Hold interaction, and by extension any interaction that changes to performed state after a timeout ([case ISXB-332](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-330)).
1920
- Fixed `Given object is neither an InputAction nor an InputActionMap` when using `InputActionTrace` on input action from an input action asset ([case ISXB-29](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-29)).

Packages/com.unity.inputsystem/InputSystem/Editor/Settings/InputSettingsProvider.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,19 @@ public override void OnGUI(string searchContext)
145145
EditorGUILayout.Space();
146146
EditorGUILayout.PropertyField(m_EditorInputBehaviorInPlayMode, m_EditorInputBehaviorInPlayModeContent);
147147

148+
EditorGUILayout.Space();
149+
EditorGUILayout.LabelField("Improved Shortcut Support", EditorStyles.boldLabel);
150+
EditorGUILayout.Space();
151+
EditorGUILayout.PropertyField(m_ShortcutKeysConsumeInputs, m_ShortcutKeysConsumeInputsContent);
152+
if (m_ShortcutKeysConsumeInputs.boolValue)
153+
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. "
154+
+ "Input consumption is performed in priority order, with the action containing the greatest number of bindings checked first. "
155+
+ "Therefore actions requiring less keypresses will not be triggered if an action using more keypresses is triggered and has overlapping controls. "
156+
+ "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. "
157+
+ "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. "
158+
+ "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. "
159+
, MessageType.None);
160+
148161
if (EditorGUI.EndChangeCheck())
149162
Apply();
150163
}
@@ -268,6 +281,7 @@ private void InitializeWithCurrentSettings()
268281
m_DefaultHoldTime = m_SettingsObject.FindProperty("m_DefaultHoldTime");
269282
m_TapRadius = m_SettingsObject.FindProperty("m_TapRadius");
270283
m_MultiTapDelayTime = m_SettingsObject.FindProperty("m_MultiTapDelayTime");
284+
m_ShortcutKeysConsumeInputs = m_SettingsObject.FindProperty("m_ShortcutKeysConsumeInputs");
271285

272286
m_UpdateModeContent = new GUIContent("Update Mode", "When should the Input System be updated?");
273287
m_CompensateForScreenOrientationContent = new GUIContent("Compensate Orientation", "Whether sensor input on mobile devices should be transformed to be relative to the current device orientation.");
@@ -295,6 +309,7 @@ private void InitializeWithCurrentSettings()
295309
m_DefaultHoldTimeContent = new GUIContent("Default Hold Time", "Default duration to be used for Hold interactions.");
296310
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).");
297311
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.");
312+
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.");
298313

299314
// Initialize ReorderableList for list of supported devices.
300315
var supportedDevicesProperty = m_SettingsObject.FindProperty("m_SupportedDevices");
@@ -404,6 +419,7 @@ private static string[] FindInputSettingsInProject()
404419
[NonSerialized] private SerializedProperty m_DefaultHoldTime;
405420
[NonSerialized] private SerializedProperty m_TapRadius;
406421
[NonSerialized] private SerializedProperty m_MultiTapDelayTime;
422+
[NonSerialized] private SerializedProperty m_ShortcutKeysConsumeInputs;
407423

408424
[NonSerialized] private ReorderableList m_SupportedDevices;
409425
[NonSerialized] private string[] m_AvailableInputSettingsAssets;
@@ -426,6 +442,7 @@ private static string[] FindInputSettingsInProject()
426442
private GUIContent m_DefaultHoldTimeContent;
427443
private GUIContent m_TapRadiusContent;
428444
private GUIContent m_MultiTapDelayTimeContent;
445+
private GUIContent m_ShortcutKeysConsumeInputsContent;
429446

430447
[NonSerialized] private InputSettingsiOSProvider m_iOSProvider;
431448

Packages/com.unity.inputsystem/InputSystem/InputSettings.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -675,6 +675,16 @@ public void SetInternalFeatureFlag(string featureName, bool enabled)
675675
if (string.IsNullOrEmpty(featureName))
676676
throw new ArgumentNullException(nameof(featureName));
677677

678+
// REMOVE: this is a temporary crutch to disable shortcut support by default but while also preserving the
679+
// existing flag name, as users are aware of that now.
680+
if (featureName == InputFeatureNames.kDisableShortcutSupport)
681+
{
682+
if (m_ShortcutKeysConsumeInputs == !enabled) return;
683+
m_ShortcutKeysConsumeInputs = !enabled;
684+
OnChange();
685+
return;
686+
}
687+
678688
if (m_FeatureFlags == null)
679689
m_FeatureFlags = new HashSet<string>();
680690

@@ -712,11 +722,16 @@ public void SetInternalFeatureFlag(string featureName, bool enabled)
712722
[SerializeField] private float m_TapRadius = 5;
713723
[SerializeField] private float m_MultiTapDelayTime = 0.75f;
714724
[SerializeField] private bool m_DisableRedundantEventsMerging = false;
725+
[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.
715726

716727
[NonSerialized] internal HashSet<string> m_FeatureFlags;
717728

718729
internal bool IsFeatureEnabled(string featureName)
719730
{
731+
// REMOVE: this is a temporary crutch to disable shortcut support by default but while also preserving the
732+
// existing flag name, as some users are aware of that now.
733+
if (featureName == InputFeatureNames.kDisableShortcutSupport) return !m_ShortcutKeysConsumeInputs;
734+
720735
return m_FeatureFlags != null && m_FeatureFlags.Contains(featureName.ToUpperInvariant());
721736
}
722737

0 commit comments

Comments
 (0)