Skip to content

Commit d9d85c9

Browse files
authored
FIX: first player's UI controls stop working after a second player joins (ISXB-125) (#1561)
* FIX: Add broken test to show issue with missing stateMonitors when using multiple PlayerInput/UIInputModule instances * FIX: first player's UI controls stop working after a second player joins * FIX: alternative fix for UIInputModule sharing action references using explicit Clone * FIX: UIInputModule missing in some builds * REVIEW: move uiInputModule clone into InitializeActions * REVIEW: UIInputModule instances only disables actions that the instance enabled * REVIEW: replace uiInputModule local state tracking with mono behaviour enable state * prevent uiInputModule decrementing refCount twice; SwapAction already enables/disables * review changes. renaming for clarity
1 parent 977ee8e commit d9d85c9

File tree

5 files changed

+130
-24
lines changed

5 files changed

+130
-24
lines changed

Assets/Tests/InputSystem/Plugins/PlayerInputTests.cs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2142,6 +2142,74 @@ public void PlayerInput_WhenJoinActionIsAReference_JoiningIsStillPossibleAfterDe
21422142
Assert.That(playerJoined, Is.True);
21432143
}
21442144

2145+
// https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-125
2146+
[Test]
2147+
[Category("PlayerInput")]
2148+
public void PlayerInput_WhenSecondPlayerJoins_UIInputForFirstPlayerContinuesWorking()
2149+
{
2150+
var actions = ScriptableObject.CreateInstance<InputActionAsset>();
2151+
var playerMap = actions.AddActionMap("Player");
2152+
var uiMap = actions.AddActionMap("UI");
2153+
2154+
var joinAction = playerMap.AddAction("Join", binding: "<Gamepad>/{PrimaryAction}");
2155+
joinAction.AddBinding("<Keyboard>/space");
2156+
2157+
// Left Stick is bound to UIInputModule Navigate
2158+
var navigateAction = uiMap.AddAction("Navigate", binding: "<Gamepad>/leftStick", type: InputActionType.PassThrough);
2159+
navigateAction.AddCompositeBinding("2DVector")
2160+
.With("Up", "<Keyboard>/upArrow")
2161+
.With("Down", "<Keyboard>/downArrow")
2162+
.With("Left", "<Keyboard>/leftArrow")
2163+
.With("Right", "<Keyboard>/rightArrow");
2164+
2165+
2166+
var playerPrefab = new GameObject();
2167+
playerPrefab.SetActive(false);
2168+
var prefabUIModule = playerPrefab.AddComponent<InputSystemUIInputModule>();
2169+
prefabUIModule.AssignDefaultActions();
2170+
playerPrefab.AddComponent<PlayerInput>();
2171+
playerPrefab.GetComponent<PlayerInput>().actions = actions;
2172+
playerPrefab.GetComponent<PlayerInput>().uiInputModule = prefabUIModule;
2173+
2174+
var manager = new GameObject();
2175+
manager.SetActive(false);
2176+
var playerInputManager = manager.AddComponent<PlayerInputManager>();
2177+
playerInputManager.notificationBehavior = PlayerNotifications.InvokeCSharpEvents;
2178+
playerInputManager.joinAction = new InputActionProperty(InputActionReference.Create(joinAction));
2179+
playerInputManager.joinBehavior = PlayerJoinBehavior.JoinPlayersWhenJoinActionIsTriggered;
2180+
playerInputManager.playerPrefab = playerPrefab;
2181+
manager.SetActive(true);
2182+
2183+
var gamepad = InputSystem.AddDevice<Gamepad>();
2184+
var keyboard = InputSystem.AddDevice<Keyboard>();
2185+
2186+
List<PlayerInput> joinedPlayers = new List<PlayerInput>();
2187+
playerInputManager.onPlayerJoined += input => joinedPlayers.Add(input);
2188+
2189+
// UIInputModule instance for player 1 will be bound to Gamepad
2190+
PressAndRelease(gamepad.buttonSouth);
2191+
Assert.That(joinedPlayers.Count, Is.EqualTo(1));
2192+
2193+
// Player 1's controls are functional
2194+
bool player1Moved = false;
2195+
joinedPlayers[0].uiInputModule.move.action.performed += cxt => player1Moved = true;
2196+
Set(gamepad.leftStick, new Vector2(0.2f, 0.0f));
2197+
Assert.That(player1Moved, Is.True);
2198+
2199+
Set(gamepad.leftStick, new Vector2(0.0f, 0.0f));
2200+
player1Moved = false;
2201+
2202+
// UIInputModule instance for player 2 will be bound to Keyboard
2203+
// And this should not affect player 1's controls
2204+
PressAndRelease(keyboard.spaceKey);
2205+
Assert.That(joinedPlayers.Count, Is.EqualTo(2));
2206+
Assert.That(player1Moved, Is.False);
2207+
2208+
// Player 1's controls still work after player 2 joined
2209+
Set(gamepad.leftStick, new Vector2(0.2f, 0.0f));
2210+
Assert.That(player1Moved, Is.True);
2211+
}
2212+
21452213
[Test] // Mimics what is reported in https://issuetracker.unity3d.com/product/unity/issues/guid/1347320
21462214
[Category("PlayerInput")]
21472215
public void PlayerInput_WhenOverridingDeviceLayout_LostDeviceShouldBeResolvedAndRepaired()

Assets/Tests/InputSystem/Plugins/UITests.cs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2837,6 +2837,45 @@ public IEnumerator UI_WhenMultipleInputModulesExist_ActionsAreNotDisabledUntilTh
28372837
Assert.That(pointAction.enabled, Is.False);
28382838
}
28392839

2840+
// https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-125
2841+
[Test]
2842+
[Category("UI")]
2843+
[TestCase(true)]
2844+
[TestCase(false)]
2845+
public void UI_WhenMultipleInputModulesExist_AssigningActionAssetDoesNotDisableOtherInstancesActions(bool player2StartsDisabled = false)
2846+
{
2847+
var player1_EventSystemGO = new GameObject();
2848+
player1_EventSystemGO.AddComponent<EventSystem>();
2849+
var player1_InputModule = player1_EventSystemGO.AddComponent<InputSystemUIInputModule>();
2850+
if (player2StartsDisabled)
2851+
player1_EventSystemGO.SetActive(false); // Enable player1 later
2852+
2853+
var player1_Asset = ScriptableObject.CreateInstance<InputActionAsset>();
2854+
var player1_Map = player1_Asset.AddActionMap("map");
2855+
var pointAction = player1_Map.AddAction("point", type: InputActionType.PassThrough, binding: "<Mouse>/position");
2856+
player1_InputModule.point = InputActionReference.Create(pointAction);
2857+
2858+
var player2_EventSystemGO = GameObject.Instantiate(player1_EventSystemGO);
2859+
2860+
2861+
if (player2StartsDisabled)
2862+
{
2863+
Assert.That(pointAction.enabled, Is.False);
2864+
player1_EventSystemGO.SetActive(true);
2865+
}
2866+
Assert.That(pointAction.enabled, Is.True);
2867+
2868+
var player2_Asset = ScriptableObject.CreateInstance<InputActionAsset>();
2869+
var player2_Map = player2_Asset.AddActionMap("map");
2870+
var player2_pointAction = player2_Map.AddAction("point", type: InputActionType.PassThrough, binding: "<Mouse>/position");
2871+
player2_EventSystemGO.GetComponent<InputSystemUIInputModule>().actionsAsset = player2_Asset;
2872+
2873+
if (player2StartsDisabled)
2874+
Assert.That(player2_pointAction.enabled, Is.False);
2875+
2876+
Assert.That(pointAction.enabled, Is.True);
2877+
}
2878+
28402879
[Test]
28412880
[Category("UI")]
28422881
public void UI_WhenDisablingInputModule_ActionsAreNotDisabledIfTheyWereNotEnabledByTheInputModule()

Packages/com.unity.inputsystem/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ however, it has to be formatted properly to pass verification tests.
2020
- Fixed an issue where the Input Action asset icon would not be visible during asset creation ([case ISXB-6](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-6)).
2121
- Fixed DualSense low frequency motor speed being always set to min value.
2222
- Fixed an issue where `ReadUnprocessedValueFromState` in PoseControl always returning default values.
23+
- Fix Player 1's UI controls stop working after second player joins ([case ISXB-125](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-125)))
24+
2325

2426
## [1.4.1] - 2022-05-30
2527

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1196,7 +1196,6 @@ private void InitializeActions()
11961196
if (m_Actions == null)
11971197
return;
11981198

1199-
////REVIEW: should we *always* Instantiate()?
12001199
// Check if we need to duplicate our actions by looking at all other players. If any
12011200
// has the same actions, duplicate.
12021201
for (var i = 0; i < s_AllActivePlayersCount; ++i)

Packages/com.unity.inputsystem/InputSystem/Plugins/UI/InputSystemUIInputModule.cs

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -857,7 +857,7 @@ private void SwapAction(ref InputActionReference property, InputActionReference
857857
var oldActionNull = property?.action == null;
858858
var oldActionEnabled = property?.action != null && property.action.enabled;
859859

860-
DisableInputAction(property);
860+
TryDisableInputAction(property);
861861
property = newValue;
862862

863863
#if DEBUG
@@ -1508,16 +1508,16 @@ private void EnableAllActions()
15081508

15091509
private void DisableAllActions()
15101510
{
1511-
DisableInputAction(m_PointAction);
1512-
DisableInputAction(m_LeftClickAction);
1513-
DisableInputAction(m_RightClickAction);
1514-
DisableInputAction(m_MiddleClickAction);
1515-
DisableInputAction(m_MoveAction);
1516-
DisableInputAction(m_SubmitAction);
1517-
DisableInputAction(m_CancelAction);
1518-
DisableInputAction(m_ScrollWheelAction);
1519-
DisableInputAction(m_TrackedDeviceOrientationAction);
1520-
DisableInputAction(m_TrackedDevicePositionAction);
1511+
TryDisableInputAction(m_PointAction, true);
1512+
TryDisableInputAction(m_LeftClickAction, true);
1513+
TryDisableInputAction(m_RightClickAction, true);
1514+
TryDisableInputAction(m_MiddleClickAction, true);
1515+
TryDisableInputAction(m_MoveAction, true);
1516+
TryDisableInputAction(m_SubmitAction, true);
1517+
TryDisableInputAction(m_CancelAction, true);
1518+
TryDisableInputAction(m_ScrollWheelAction, true);
1519+
TryDisableInputAction(m_TrackedDeviceOrientationAction, true);
1520+
TryDisableInputAction(m_TrackedDevicePositionAction, true);
15211521
}
15221522

15231523
private void EnableInputAction(InputActionReference inputActionReference)
@@ -1542,14 +1542,20 @@ private void EnableInputAction(InputActionReference inputActionReference)
15421542
action.Enable();
15431543
}
15441544

1545-
private static void DisableInputAction(InputActionReference inputActionReference)
1545+
private void TryDisableInputAction(InputActionReference inputActionReference, bool isComponentDisabling = false)
15461546
{
15471547
var action = inputActionReference?.action;
15481548
if (action == null)
15491549
return;
15501550

1551-
if (!s_InputActionReferenceCounts.TryGetValue(action,
1552-
out var referenceState))
1551+
// Don't decrement refCount when we were not responsible for incrementing it.
1552+
// I.e. when we were not enabled yet. When OnDisabled is called, isActiveAndEnabled will
1553+
// already have been set to false. In that case we pass isComponentDisabling to check if we
1554+
// came from OnDisabled and therefore need to allow disabling.
1555+
if (!isActiveAndEnabled && !isComponentDisabling)
1556+
return;
1557+
1558+
if (!s_InputActionReferenceCounts.TryGetValue(action, out var referenceState))
15531559
return;
15541560

15551561
if (referenceState.refCount - 1 == 0 && referenceState.enabledByInputModule)
@@ -2212,10 +2218,6 @@ private InputActionReference UpdateReferenceForNewAsset(InputActionReference act
22122218
if (oldAction == null)
22132219
return null;
22142220

2215-
var oldActionEnabled = oldAction.enabled;
2216-
if (oldActionEnabled)
2217-
DisableInputAction(actionReference);
2218-
22192221
var oldActionMap = oldAction.actionMap;
22202222
Debug.Assert(oldActionMap != null, "Not expected to end up with a singleton action here");
22212223

@@ -2227,11 +2229,7 @@ private InputActionReference UpdateReferenceForNewAsset(InputActionReference act
22272229
if (newAction == null)
22282230
return null;
22292231

2230-
var reference = InputActionReference.Create(newAction);
2231-
if (oldActionEnabled)
2232-
EnableInputAction(reference);
2233-
2234-
return reference;
2232+
return InputActionReference.Create(newAction);
22352233
}
22362234

22372235
public InputActionAsset actionsAsset

0 commit comments

Comments
 (0)