Skip to content

Commit 08349cc

Browse files
authored
FIX: Fixed unexpected control scheme switch when using OnScreenControl and pointer based schemes (ISXB-656) (#2023)
1 parent 4e8347a commit 08349cc

File tree

7 files changed

+138
-8
lines changed

7 files changed

+138
-8
lines changed

Assets/Tests/InputSystem/Plugins/OnScreenTests.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,33 @@ public void Devices_DisablingLastOnScreenControlRemovesCreatedDevice()
365365
Assert.That(InputSystem.devices, Has.None.InstanceOf<Keyboard>());
366366
}
367367

368+
[Test]
369+
[Category("Devices")]
370+
public void Devices_DisablingLastOnScreenControlDoesReportActiveControl()
371+
{
372+
var gameObject = new GameObject();
373+
374+
Assert.That(OnScreenControl.HasAnyActive, Is.False);
375+
376+
var buttonA = gameObject.AddComponent<OnScreenButton>();
377+
378+
Assert.That(OnScreenControl.HasAnyActive, Is.True);
379+
380+
var buttonB = gameObject.AddComponent<OnScreenButton>();
381+
buttonA.controlPath = "/<Keyboard>/a";
382+
buttonB.controlPath = "/<Keyboard>/b";
383+
384+
Assert.That(OnScreenControl.HasAnyActive, Is.True);
385+
386+
buttonA.enabled = false;
387+
388+
Assert.That(OnScreenControl.HasAnyActive, Is.True);
389+
390+
buttonB.enabled = false;
391+
392+
Assert.That(OnScreenControl.HasAnyActive, Is.False);
393+
}
394+
368395
// https://fogbugz.unity3d.com/f/cases/1271942
369396
[UnityTest]
370397
[Category("Devices")]

Assets/Tests/InputSystem/Plugins/PlayerInputTests.cs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
using Object = UnityEngine.Object;
1818
using Gyroscope = UnityEngine.InputSystem.Gyroscope;
1919
using Is = UnityEngine.TestTools.Constraints.Is;
20+
using UnityEngine.InputSystem.OnScreen;
2021

2122
/// <summary>
2223
/// Tests for <see cref="PlayerInput"/> and <see cref="PlayerInputManager"/>.
@@ -663,6 +664,58 @@ public void PlayerInput_CanAutoSwitchControlSchemesInSinglePlayer()
663664
}));
664665
}
665666

667+
[Test]
668+
[Category("PlayerInput")]
669+
public void PlayerInput_AutoSwitchControlSchemesInSinglePlayerWithOnScreenControl_AutoSwitchToTargetDeviceAndIgnoreMouse()
670+
{
671+
var keyboard = InputSystem.AddDevice<Keyboard>();
672+
var mouse = InputSystem.AddDevice<Mouse>();
673+
674+
var go = new GameObject();
675+
676+
var onScreenButton = go.AddComponent<OnScreenButton>();
677+
onScreenButton.enabled = false;
678+
onScreenButton.controlPath = "<Gamepad>/buttonSouth";
679+
680+
var playerInput = go.AddComponent<PlayerInput>();
681+
playerInput.defaultControlScheme = "Keyboard&Mouse";
682+
playerInput.defaultActionMap = "gameplay";
683+
playerInput.actions = InputActionAsset.FromJson(kActions);
684+
685+
Assert.That(playerInput.devices, Is.EquivalentTo(new InputDevice[] { keyboard, mouse }));
686+
687+
// enable the OnScreenButton, it should switch to Gamepad
688+
onScreenButton.enabled = true;
689+
var gamepad = onScreenButton.control.device;
690+
Assert.That(gamepad, Is.TypeOf<Gamepad>());
691+
Assert.That(playerInput.devices, Is.EquivalentTo(new InputDevice[] { gamepad }));
692+
Assert.That(playerInput.user.controlScheme, Is.Not.Null);
693+
Assert.That(playerInput.user.controlScheme.Value.name, Is.EqualTo("Gamepad"));
694+
695+
// Perform mouse move and click. to try to switch to Keyboard&Mouse scheme
696+
Move(mouse.position, new Vector2(0.123f, 0.234f));
697+
Click(mouse.leftButton);
698+
Move(mouse.position, new Vector2(100f, 100f));
699+
InputSystem.Update();
700+
701+
// The controlScheme shouldn't have changed
702+
Assert.That(playerInput.devices, Is.EquivalentTo(new[] { gamepad }));
703+
Assert.That(playerInput.user.controlScheme, Is.Not.Null);
704+
Assert.That(playerInput.user.controlScheme.Value.name, Is.EqualTo("Gamepad"));
705+
706+
// disabling the OnScreenButton to ensure that it will now switch to Keyboard&Mouse as expected
707+
onScreenButton.enabled = false;
708+
709+
// Perform mouse move and click. to try to switch to Keyboard&Mouse scheme
710+
Move(mouse.position, new Vector2(0.123f, 0.234f));
711+
Click(mouse.leftButton);
712+
Move(mouse.position, new Vector2(100f, 100f));
713+
714+
Assert.That(playerInput.devices, Is.EquivalentTo(new InputDevice[] { keyboard, mouse }));
715+
Assert.That(playerInput.user.controlScheme, Is.Not.Null);
716+
Assert.That(playerInput.user.controlScheme.Value.name, Is.EqualTo("Keyboard&Mouse"));
717+
}
718+
666719
[Test]
667720
[Category("PlayerInput")]
668721
public void PlayerInput_CanAutoSwitchControlSchemesInSinglePlayer_WithSomeDevicesSharedBetweenSchemes()

Assets/Tests/InputSystem/Plugins/UITests.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2630,6 +2630,11 @@ public void UI_ClickDraggingMouseDoesNotAllocateGCMemory()
26302630
Release(mouse.leftButton);
26312631
scene.eventSystem.InvokeUpdate();
26322632

2633+
#if UNITY_2023_2_OR_NEWER // UnityEngine.InputForUI Module unavailable in earlier releases
2634+
// Process all queued UI events to ensure that next events will not make the events list capacity growing
2635+
UnityEngine.InputForUI.EventProvider.NotifyUpdate();
2636+
#endif
2637+
26332638
var kProfilerRegion = "UI_ClickDraggingDoesNotAllocateGCMemory";
26342639

26352640
// Now for real.

Packages/com.unity.inputsystem/CHANGELOG.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@ however, it has to be formatted properly to pass verification tests.
1717
- Fixed pointerId staying the same when simultaneously releasing and then pressing in the same frame on mobile using touch. [ISXB-1006](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-845)
1818
- Fixed ISubmitHandler.OnSubmit event processing when operating in Manual Update mode (ISXB-1141)
1919
- Fixed Rename mode is not entered and name is autocompleted to default when creating a new Action Map on 2022.3. [ISXB-1151](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-1151)
20+
- Fixed unexpected control scheme switch when using `OnScreenControl` and pointer based schemes which registed "Cancel" event on every frame.[ISXB-656](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-656)
2021

2122
### Changed
2223
- Added back the InputManager to InputSystem project-wide asset migration code with performance improvement (ISX-2086)
24+
- Changed `OnScreenControl` to automaticaly switch, in Single Player with autoswitch enabled, to the target device control scheme when the first component is enabled to prevent bad interactions when it start.
2325

2426
## [1.11.2] - 2024-10-16
2527

@@ -30,14 +32,12 @@ however, it has to be formatted properly to pass verification tests.
3032
- Fixed "AnalyticsResult" errors on consoles [ISXB-1107]
3133
- Fixed wrong `Display Index` value for touchscreen events.[ISXB-1101](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-1101)
3234
- Fixed event handling when using Fixed Update processing where WasPressedThisFrame could appear to true for consecutive frames [ISXB-1006](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-1006)
35+
- Removed a redundant warning when using fallback code to parse a HID descriptor. (UUM-71260)
3336

3437
### Added
3538
- Added the display of the device flag `CanRunInBackground` in device debug view.
3639
- Added analytics for programmatic `InputAction` setup via `InputActionSetupExtensions` when exiting play-mode.
3740

38-
### Fixed
39-
- Removed a redundant warning when using fallback code to parse a HID descriptor. (UUM-71260)
40-
4141
### Changed
4242
- Removed the InputManager to InputSystem project-wide asset migration code for performance improvement (ISX-2086)
4343

Packages/com.unity.inputsystem/InputSystem/Plugins/OnScreen/OnScreenControl.cs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using Unity.Collections;
33
using UnityEngine.InputSystem.Layouts;
44
using UnityEngine.InputSystem.LowLevel;
5+
using UnityEngine.InputSystem.Users;
56
using UnityEngine.InputSystem.Utilities;
67

78
////REVIEW: should we make this ExecuteInEditMode?
@@ -34,6 +35,14 @@ namespace UnityEngine.InputSystem.OnScreen
3435
/// types of device layouts (e.g. one control references 'buttonWest' on
3536
/// a gamepad and another references 'leftButton' on a mouse), then a device
3637
/// is created for each type referenced by the setup.
38+
///
39+
/// The <see cref="OnScreenControl"/> works by simulating events from the device specified in the <see cref="OnScreenControl.controlPath"/>
40+
/// property. Some parts of the Input System, such as the <see cref="PlayerInput"/> component, can be set up to
41+
/// auto-switch <see cref="PlayerInput.neverAutoSwitchControlSchemes"/> to a new device when input from them is detected.
42+
/// When a device is switched, any currently running inputs from the previously active device are cancelled.
43+
///
44+
/// To avoid this situation, you need to ensure, depending on your case, that the Mouse, Pen, Touchsceen and/or XRController devices are not used in a concurent
45+
/// control schemes of the simulated device.
3746
/// </remarks>
3847
public abstract class OnScreenControl : MonoBehaviour
3948
{
@@ -208,13 +217,45 @@ protected void SentDefaultValueToControl()
208217
InputSystem.QueueEvent(m_InputEventPtr);
209218
}
210219

220+
// Used by PlayerInput auto switch for scheme to prevent using Pointer device.
221+
internal static bool HasAnyActive => s_nbActiveInstances != 0;
222+
private static int s_nbActiveInstances = 0;
223+
211224
protected virtual void OnEnable()
212225
{
226+
++s_nbActiveInstances;
213227
SetupInputControl();
228+
if (m_Control == null)
229+
return;
230+
// if we are in single player and if it the first active switch to the target device.
231+
if (s_nbActiveInstances == 1 &&
232+
PlayerInput.isSinglePlayer)
233+
{
234+
var firstPlayer = PlayerInput.GetPlayerByIndex(0);
235+
if (firstPlayer?.neverAutoSwitchControlSchemes == false)
236+
{
237+
var devices = firstPlayer.devices;
238+
bool deviceFound = false;
239+
// skip is the device is already part of the current scheme
240+
foreach (var device in devices)
241+
{
242+
if (m_Control.device.deviceId == device.deviceId)
243+
{
244+
deviceFound = true;
245+
break;
246+
}
247+
}
248+
if (!deviceFound)
249+
{
250+
firstPlayer.SwitchCurrentControlScheme(m_Control.device);
251+
}
252+
}
253+
}
214254
}
215255

216256
protected virtual void OnDisable()
217257
{
258+
--s_nbActiveInstances;
218259
if (m_Control == null)
219260
return;
220261

Packages/com.unity.inputsystem/InputSystem/Plugins/OnScreen/OnScreenStick.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,15 @@ namespace UnityEngine.InputSystem.OnScreen
2323
/// <remarks>
2424
/// The <see cref="OnScreenStick"/> works by simulating events from the device specified in the <see cref="OnScreenControl.controlPath"/>
2525
/// property. Some parts of the Input System, such as the <see cref="PlayerInput"/> component, can be set up to
26-
/// auto-switch to a new device when input from them is detected. When a device is switched, any currently running
27-
/// inputs from the previously active device are cancelled. In the case of <see cref="OnScreenStick"/>, this can mean that the
28-
/// <see cref="IPointerUpHandler.OnPointerUp"/> method will be called and the stick will jump back to center, even though
29-
/// the pointer input has not physically been released.
26+
/// auto-switch <see cref="PlayerInput.neverAutoSwitchControlSchemes"/> to a new device when input from them is detected.
27+
/// When a device is switched, any currently running inputs from the previously active device are cancelled.
28+
/// In the case of <see cref="OnScreenStick"/>, this can mean that the <see cref="IPointerUpHandler.OnPointerUp"/> method will be called
29+
/// and the stick will jump back to center, even though the pointer input has not physically been released.
3030
///
3131
/// To avoid this situation, set the <see cref="useIsolatedInputActions"/> property to true. This will create a set of local
3232
/// Input Actions to drive the stick that are not cancelled when device switching occurs.
33+
/// You might also need to ensure, depending on your case, that the Mouse, Pen, Touchsceen and/or XRController devices are not used in a concurent
34+
/// control schemes of the simulated device.
3335
/// </remarks>
3436
[AddComponentMenu("Input/On-Screen Stick")]
3537
[HelpURL(InputSystem.kDocUrl + "/manual/OnScreen.html#on-screen-sticks")]

Packages/com.unity.inputsystem/InputSystem/Plugins/PlayerInput/PlayerInput.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using UnityEngine.InputSystem.LowLevel;
55
using UnityEngine.InputSystem.Users;
66
using UnityEngine.InputSystem.Utilities;
7+
using UnityEngine.InputSystem.OnScreen;
78

89
#if UNITY_EDITOR
910
using UnityEngine.InputSystem.Editor;
@@ -1867,7 +1868,8 @@ private static bool OnPreFilterUnpairedDeviceUsed(InputDevice device, InputEvent
18671868
{
18681869
// Early out if the device isn't usable with any of our control schemes.
18691870
var actions = all[0].actions;
1870-
return actions != null && actions.IsUsableWithDevice(device);
1871+
// Skip Pointer device if any OnScreenControl is active since they will use it to generate device event
1872+
return actions != null && (!OnScreenControl.HasAnyActive || !(device is Pointer)) && actions.IsUsableWithDevice(device);
18711873
}
18721874

18731875
private void OnUnpairedDeviceUsed(InputControl control, InputEventPtr eventPtr)

0 commit comments

Comments
 (0)