Skip to content

Commit 6752b5d

Browse files
authored
FIX: UI controls in MultiplayerEventSystems should not change colour unexpectedly (#1547)
1 parent 88cf9b2 commit 6752b5d

File tree

4 files changed

+77
-70
lines changed

4 files changed

+77
-70
lines changed

Assets/Tests/InputSystem/Plugins/UITests.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2526,9 +2526,7 @@ public IEnumerator UI_CanOperateMultiplayerUILocallyUsingGamepads()
25262526
Assert.That(players[1].eventSystem.currentSelectedGameObject, Is.SameAs(players[1].leftGameObject));
25272527

25282528
Assert.That(players[0].leftChildReceiver.events, Is.Empty);
2529-
Assert.That(players[0].rightChildReceiver.events,
2530-
EventSequence(
2531-
OneEvent("type", EventType.Move))); // OnMove will still get called to *attempt* a move.
2529+
Assert.That(players[0].rightChildReceiver.events, Is.Empty);
25322530

25332531
players[0].leftChildReceiver.events.Clear();
25342532
players[0].rightChildReceiver.events.Clear();

Packages/com.unity.inputsystem/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ however, it has to be formatted properly to pass verification tests.
1212

1313
### Fixed
1414
- Fix UI sometimes ignoring the first mouse click event after losing and regaining focus ([case ISXB-127](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-127).
15+
- Fixed issue when using MultiplayerEventSystems where the visual state of UI controls would change due to constant toggling of CanvasGroup.interactable on and off ([case ISXB-112](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-112)).
1516

1617

1718
## [1.4.1] - 2022-05-30

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

Lines changed: 65 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using UnityEngine.InputSystem.LowLevel;
77
using UnityEngine.InputSystem.Utilities;
88
using UnityEngine.Serialization;
9+
using UnityEngine.UI;
910
#if UNITY_EDITOR
1011
using UnityEditor;
1112
#endif
@@ -100,6 +101,21 @@ public CursorLockBehavior cursorLockBehavior
100101
set => m_CursorLockBehavior = value;
101102
}
102103

104+
/// <summary>
105+
/// A root game object to support correct navigation in local multi-player UIs.
106+
/// <remarks>
107+
/// In local multi-player games where each player has their own UI, players should not be able to navigate into
108+
/// another player's UI. Each player should have their own instance of an InputSystemUIInputModule, and this property
109+
/// should be set to the root game object containing all UI objects for that player. If set, navigation using the
110+
/// <see cref="InputSystemUIInputModule.move"/> action will be constrained to UI objects under that root.
111+
/// </remarks>
112+
/// </summary>
113+
public GameObject localMultiPlayerRoot
114+
{
115+
get => m_LocalMultiPlayerRoot;
116+
set => m_LocalMultiPlayerRoot = value;
117+
}
118+
103119
/// <summary>
104120
/// Called by <c>EventSystem</c> when the input module is made current.
105121
/// </summary>
@@ -650,12 +666,15 @@ internal void ProcessNavigation(ref NavigationModel navigationState)
650666
eventData.moveVector = moveVector;
651667
eventData.moveDir = moveDirection;
652668

653-
ExecuteEvents.Execute(eventSystem.currentSelectedGameObject, eventData, ExecuteEvents.moveHandler);
654-
usedSelectionChange = eventData.used;
669+
if (IsMoveAllowed(eventData))
670+
{
671+
ExecuteEvents.Execute(eventSystem.currentSelectedGameObject, eventData, ExecuteEvents.moveHandler);
672+
usedSelectionChange = eventData.used;
655673

656-
m_NavigationState.consecutiveMoveCount = m_NavigationState.consecutiveMoveCount + 1;
657-
m_NavigationState.lastMoveTime = time;
658-
m_NavigationState.lastMoveDirection = moveDirection;
674+
m_NavigationState.consecutiveMoveCount = m_NavigationState.consecutiveMoveCount + 1;
675+
m_NavigationState.lastMoveTime = time;
676+
m_NavigationState.lastMoveDirection = moveDirection;
677+
}
659678
}
660679
}
661680
else
@@ -686,6 +705,45 @@ internal void ProcessNavigation(ref NavigationModel navigationState)
686705
}
687706
}
688707

708+
private bool IsMoveAllowed(AxisEventData eventData)
709+
{
710+
if (m_LocalMultiPlayerRoot == null)
711+
return true;
712+
713+
if (eventSystem.currentSelectedGameObject == null)
714+
return true;
715+
716+
var selectable = eventSystem.currentSelectedGameObject.GetComponent<Selectable>();
717+
718+
if (selectable == null)
719+
return true;
720+
721+
Selectable navigationTarget = null;
722+
switch (eventData.moveDir)
723+
{
724+
case MoveDirection.Right:
725+
navigationTarget = selectable.FindSelectableOnRight();
726+
break;
727+
728+
case MoveDirection.Up:
729+
navigationTarget = selectable.FindSelectableOnUp();
730+
break;
731+
732+
case MoveDirection.Left:
733+
navigationTarget = selectable.FindSelectableOnLeft();
734+
break;
735+
736+
case MoveDirection.Down:
737+
navigationTarget = selectable.FindSelectableOnDown();
738+
break;
739+
}
740+
741+
if (navigationTarget == null)
742+
return true;
743+
744+
return navigationTarget.transform.IsChildOf(m_LocalMultiPlayerRoot.transform);
745+
}
746+
689747
[FormerlySerializedAs("m_RepeatDelay")]
690748
[Tooltip("The Initial delay (in seconds) between an initial move action and a repeated move action.")]
691749
[SerializeField]
@@ -2251,6 +2309,8 @@ private struct InputActionReferenceState
22512309
// Navigation-type input.
22522310
private NavigationModel m_NavigationState;
22532311

2312+
[NonSerialized] private GameObject m_LocalMultiPlayerRoot;
2313+
22542314
/// <summary>
22552315
/// Controls the origin point of raycasts when the cursor is locked.
22562316
/// </summary>

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

Lines changed: 10 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
#if PACKAGE_DOCS_GENERATION || UNITY_INPUT_SYSTEM_ENABLE_UI
22
using UnityEngine.EventSystems;
3-
using UnityEngine.InputSystem.Utilities;
43

54
namespace UnityEngine.InputSystem.UI
65
{
@@ -10,13 +9,13 @@ namespace UnityEngine.InputSystem.UI
109
/// </summary>
1110
/// <remarks>
1211
/// You can use the <see cref="playerRoot"/> property to specify a part of the hierarchy belonging to the current player.
13-
/// Mouse selection will ignore any game objects not within this hierarchy. For gamepad/keyboard selection, you need to make sure that
14-
/// the navigation links stay within the player's hierarchy.
12+
/// Mouse selection will ignore any game objects not within this hierarchy, and all other navigation, using keyboard or
13+
/// gamepad for example, will be constrained to game objects under that hierarchy.
1514
/// </remarks>
1615
[HelpURL(InputSystem.kDocUrl + "/manual/UISupport.html#multiplayer-uis")]
1716
public class MultiplayerEventSystem : EventSystem
1817
{
19-
[Tooltip("If set, only process mouse events for any game objects which are children of this game object.")]
18+
[Tooltip("If set, only process mouse and navigation events for any game objects which are children of this game object.")]
2019
[SerializeField] private GameObject m_PlayerRoot;
2120

2221
/// <summary>
@@ -25,86 +24,35 @@ public class MultiplayerEventSystem : EventSystem
2524
/// <remarks>
2625
/// This can either be an entire <c>Canvas</c> or just part of the hierarchy of
2726
/// a specific <c>Canvas</c>.
28-
///
29-
/// Note that if the given <c>GameObject</c> has a <c>CanvasGroup</c> component on it, its
30-
/// <c>interactable</c> property will be toggled back and forth by <see cref="MultiplayerEventSystem"/>.
31-
/// If no such component exists on the <c>GameObject</c>, one will be added automatically.
32-
///
33-
/// Only the <c>CanvasGroup</c> corresponding to the <see cref="MultiplayerEventSystem"/> that is currently
34-
/// executing its <see cref="Update"/> method (or did so last) will have <c>interactable</c> set to true.
35-
/// In other words, only the UI hierarchy corresponding to the player that is currently running a UI
36-
/// update (or that did so last) can be interacted with.
3727
/// </remarks>
3828
public GameObject playerRoot
3929
{
4030
get => m_PlayerRoot;
4131
set
4232
{
4333
m_PlayerRoot = value;
44-
InitializeCanvasGroup();
34+
InitializePlayerRoot();
4535
}
4636
}
4737

48-
private CanvasGroup m_CanvasGroup;
49-
private bool m_CanvasGroupWasAddedByUs;
50-
51-
private static int s_MultiplayerEventSystemCount;
52-
private static MultiplayerEventSystem[] s_MultiplayerEventSystems;
53-
5438
protected override void OnEnable()
5539
{
5640
base.OnEnable();
5741

58-
ArrayHelpers.AppendWithCapacity(ref s_MultiplayerEventSystems, ref s_MultiplayerEventSystemCount, this);
59-
60-
InitializeCanvasGroup();
61-
}
62-
63-
private void InitializeCanvasGroup()
64-
{
65-
if (m_PlayerRoot != null)
66-
{
67-
m_CanvasGroup = m_PlayerRoot.GetComponent<CanvasGroup>();
68-
if (m_CanvasGroup == null)
69-
{
70-
m_CanvasGroup = m_PlayerRoot.AddComponent<CanvasGroup>();
71-
m_CanvasGroupWasAddedByUs = true;
72-
}
73-
else
74-
m_CanvasGroupWasAddedByUs = false;
75-
}
76-
else
77-
{
78-
m_CanvasGroup = null;
79-
}
42+
InitializePlayerRoot();
8043
}
8144

82-
protected override void OnDisable()
45+
private void InitializePlayerRoot()
8346
{
84-
var index = s_MultiplayerEventSystems.IndexOfReference(this);
85-
if (index != -1)
86-
s_MultiplayerEventSystems.EraseAtWithCapacity(ref s_MultiplayerEventSystemCount, index);
87-
88-
if (m_CanvasGroupWasAddedByUs)
89-
Destroy(m_CanvasGroup);
90-
91-
m_CanvasGroup = default;
92-
m_CanvasGroupWasAddedByUs = default;
47+
if (m_PlayerRoot == null) return;
9348

94-
base.OnDisable();
49+
var inputModule = GetComponent<InputSystemUIInputModule>();
50+
if (inputModule != null)
51+
inputModule.localMultiPlayerRoot = m_PlayerRoot;
9552
}
9653

9754
protected override void Update()
9855
{
99-
for (var i = 0; i < s_MultiplayerEventSystemCount; ++i)
100-
{
101-
var system = s_MultiplayerEventSystems[i];
102-
if (system.m_PlayerRoot == null)
103-
continue;
104-
105-
system.m_CanvasGroup.interactable = system == this;
106-
}
107-
10856
var originalCurrent = current;
10957
current = this; // in order to avoid reimplementing half of the EventSystem class, just temporarily assign this EventSystem to be the globally current one
11058
try

0 commit comments

Comments
 (0)