Skip to content

Commit 5fe7190

Browse files
committed
Fixed unexpected control scheme switch when using OnScreenControl and pointer based schemes
1 parent 3a96dac commit 5fe7190

File tree

6 files changed

+144
-8
lines changed

6 files changed

+144
-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: 61 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,66 @@ 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 listener = go.AddComponent<MessageListener>();
681+
var playerInput = go.AddComponent<PlayerInput>();
682+
playerInput.defaultControlScheme = "Keyboard&Mouse";
683+
playerInput.defaultActionMap = "gameplay";
684+
playerInput.actions = InputActionAsset.FromJson(kActions);
685+
listener.messages.Clear();
686+
687+
Assert.That(playerInput.devices, Is.EquivalentTo(new InputDevice[] { keyboard, mouse }));
688+
689+
// enable the OnScreenButton, it should switch to Gamepad
690+
onScreenButton.enabled = true;
691+
var gamepad = onScreenButton.control.device;
692+
Assert.That(gamepad, Is.TypeOf<Gamepad>());
693+
Assert.That(playerInput.devices, Is.EquivalentTo(new InputDevice[] { gamepad }));
694+
Assert.That(playerInput.user.controlScheme, Is.Not.Null);
695+
Assert.That(playerInput.user.controlScheme.Value.name, Is.EqualTo("Gamepad"));
696+
697+
// Perform mouse move and click. to try to switch to Keyboard&Mouse scheme
698+
Move(mouse.position, new Vector2(0.123f, 0.234f));
699+
Click(mouse.leftButton);
700+
Move(mouse.position, new Vector2(100f, 100f));
701+
InputSystem.Update();
702+
703+
// The controlScheme shouldn't have changed
704+
Assert.That(playerInput.devices, Is.EquivalentTo(new[] { gamepad }));
705+
Assert.That(playerInput.user.controlScheme, Is.Not.Null);
706+
Assert.That(playerInput.user.controlScheme.Value.name, Is.EqualTo("Gamepad"));
707+
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+
// disabling the OnScreenButton to ensure that it will now switch to Keyboard&Mouse as expected
715+
onScreenButton.enabled = false;
716+
717+
// Perform mouse move and click. to try to switch to Keyboard&Mouse scheme
718+
Move(mouse.position, new Vector2(0.123f, 0.234f));
719+
Click(mouse.leftButton);
720+
Move(mouse.position, new Vector2(100f, 100f));
721+
722+
Assert.That(playerInput.devices, Is.EquivalentTo(new InputDevice[] { keyboard, mouse }));
723+
Assert.That(playerInput.user.controlScheme, Is.Not.Null);
724+
Assert.That(playerInput.user.controlScheme.Value.name, Is.EqualTo("Keyboard&Mouse"));
725+
}
726+
666727
[Test]
667728
[Category("PlayerInput")]
668729
public void PlayerInput_CanAutoSwitchControlSchemesInSinglePlayer_WithSomeDevicesSharedBetweenSchemes()

Packages/com.unity.inputsystem/CHANGELOG.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,16 @@ however, it has to be formatted properly to pass verification tests.
1212

1313
### Fixed
1414
- Fixed `NullReferenceException` from disconnecting and reconnecting a GXDKGamepad.
15+
- Fixed wrong mapping of Xbox Series S|X and Xbox One wireless controllers "View" button on macOS.[ISXB-385](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-385)
16+
- 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)
17+
18+
### Changed
19+
- 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.
1520

1621
### Added
1722
- Added the display of the device flag `CanRunInBackground` in device debug view.
1823
- Added analytics for programmatic `InputAction` setup via `InputActionSetupExtensions` when exiting play-mode.
1924

20-
### Fixed
21-
- Fixed wrong mapping of Xbox Series S|X and Xbox One wireless controllers "View" button on macOS.[ISXB-385](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-385)
22-
2325
## [1.11.1] - 2024-09-26
2426

2527
### Fixed

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

Lines changed: 42 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,46 @@ protected void SentDefaultValueToControl()
208217
InputSystem.QueueEvent(m_InputEventPtr);
209218
}
210219

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

216257
protected virtual void OnDisable()
217258
{
259+
--s_nbActiveInstances;
218260
if (m_Control == null)
219261
return;
220262

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 not Pointer) && actions.IsUsableWithDevice(device);
18711873
}
18721874

18731875
private void OnUnpairedDeviceUsed(InputControl control, InputEventPtr eventPtr)

0 commit comments

Comments
 (0)