| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 88cf9b2 commit 6752b5d
4 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2526,9 +2526,7 @@ public IEnumerator UI_CanOperateMultiplayerUILocallyUsingGamepads() | |||
| 2526 | 2526 | Assert.That(players[1].eventSystem.currentSelectedGameObject, Is.SameAs(players[1].leftGameObject)); | |
| 2527 | 2527 | ||
| 2528 | 2528 | 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); | ||
| 2532 | 2530 | ||
| 2533 | 2531 | players[0].leftChildReceiver.events.Clear(); | |
| 2534 | 2532 | players[0].rightChildReceiver.events.Clear(); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -12,6 +12,7 @@ however, it has to be formatted properly to pass verification tests. | |||
| 12 | 12 | ||
| 13 | 13 | ### Fixed | |
| 14 | 14 | - 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)). | ||
| 15 | 16 | ||
| 16 | 17 | ||
| 17 | 18 | ## [1.4.1] - 2022-05-30 | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -6,6 +6,7 @@ | |||
| 6 | 6 | using UnityEngine.InputSystem.LowLevel; | |
| 7 | 7 | using UnityEngine.InputSystem.Utilities; | |
| 8 | 8 | using UnityEngine.Serialization; | |
| 9 | + using UnityEngine.UI; | ||
| 9 | 10 | #if UNITY_EDITOR | |
| 10 | 11 | using UnityEditor; | |
| 11 | 12 | #endif | |
@@ -100,6 +101,21 @@ public CursorLockBehavior cursorLockBehavior | |||
| 100 | 101 | set => m_CursorLockBehavior = value; | |
| 101 | 102 | } | |
| 102 | 103 | ||
| 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 | + | ||
| 103 | 119 | /// <summary> | |
| 104 | 120 | /// Called by <c>EventSystem</c> when the input module is made current. | |
| 105 | 121 | /// </summary> | |
@@ -650,12 +666,15 @@ internal void ProcessNavigation(ref NavigationModel navigationState) | |||
| 650 | 666 | eventData.moveVector = moveVector; | |
| 651 | 667 | eventData.moveDir = moveDirection; | |
| 652 | 668 | ||
| 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; | ||
| 655 | 673 | ||
| 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 | + } | ||
| 659 | 678 | } | |
| 660 | 679 | } | |
| 661 | 680 | else | |
@@ -686,6 +705,45 @@ internal void ProcessNavigation(ref NavigationModel navigationState) | |||
| 686 | 705 | } | |
| 687 | 706 | } | |
| 688 | 707 | ||
| 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 | + | ||
| 689 | 747 | [FormerlySerializedAs("m_RepeatDelay")] | |
| 690 | 748 | [Tooltip("The Initial delay (in seconds) between an initial move action and a repeated move action.")] | |
| 691 | 749 | [SerializeField] | |
@@ -2251,6 +2309,8 @@ private struct InputActionReferenceState | |||
| 2251 | 2309 | // Navigation-type input. | |
| 2252 | 2310 | private NavigationModel m_NavigationState; | |
| 2253 | 2311 | ||
| 2312 | + [NonSerialized] private GameObject m_LocalMultiPlayerRoot; | ||
| 2313 | + | ||
| 2254 | 2314 | /// <summary> | |
| 2255 | 2315 | /// Controls the origin point of raycasts when the cursor is locked. | |
| 2256 | 2316 | /// </summary> | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,6 +1,5 @@ | |||
| 1 | 1 | #if PACKAGE_DOCS_GENERATION || UNITY_INPUT_SYSTEM_ENABLE_UI | |
| 2 | 2 | using UnityEngine.EventSystems; | |
| 3 | - using UnityEngine.InputSystem.Utilities; | ||
| 4 | 3 | ||
| 5 | 4 | namespace UnityEngine.InputSystem.UI | |
| 6 | 5 | { | |
@@ -10,13 +9,13 @@ namespace UnityEngine.InputSystem.UI | |||
| 10 | 9 | /// </summary> | |
| 11 | 10 | /// <remarks> | |
| 12 | 11 | /// 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. | ||
| 15 | 14 | /// </remarks> | |
| 16 | 15 | [HelpURL(InputSystem.kDocUrl + "/manual/UISupport.html#multiplayer-uis")] | |
| 17 | 16 | public class MultiplayerEventSystem : EventSystem | |
| 18 | 17 | { | |
| 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.")] | ||
| 20 | 19 | [SerializeField] private GameObject m_PlayerRoot; | |
| 21 | 20 | ||
| 22 | 21 | /// <summary> | |
@@ -25,86 +24,35 @@ public class MultiplayerEventSystem : EventSystem | |||
| 25 | 24 | /// <remarks> | |
| 26 | 25 | /// This can either be an entire <c>Canvas</c> or just part of the hierarchy of | |
| 27 | 26 | /// 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. | ||
| 37 | 27 | /// </remarks> | |
| 38 | 28 | public GameObject playerRoot | |
| 39 | 29 | { | |
| 40 | 30 | get => m_PlayerRoot; | |
| 41 | 31 | set | |
| 42 | 32 | { | |
| 43 | 33 | m_PlayerRoot = value; | |
| 44 | - InitializeCanvasGroup(); | ||
| 34 | + InitializePlayerRoot(); | ||
| 45 | 35 | } | |
| 46 | 36 | } | |
| 47 | 37 | ||
| 48 | - private CanvasGroup m_CanvasGroup; | ||
| 49 | - private bool m_CanvasGroupWasAddedByUs; | ||
| 50 | - | ||
| 51 | - private static int s_MultiplayerEventSystemCount; | ||
| 52 | - private static MultiplayerEventSystem[] s_MultiplayerEventSystems; | ||
| 53 | - | ||
| 54 | 38 | protected override void OnEnable() | |
| 55 | 39 | { | |
| 56 | 40 | base.OnEnable(); | |
| 57 | 41 | ||
| 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(); | ||
| 80 | 43 | } | |
| 81 | 44 | ||
| 82 | - protected override void OnDisable() | ||
| 45 | + private void InitializePlayerRoot() | ||
| 83 | 46 | { | |
| 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; | ||
| 93 | 48 | ||
| 94 | - base.OnDisable(); | ||
| 49 | + var inputModule = GetComponent<InputSystemUIInputModule>(); | ||
| 50 | + if (inputModule != null) | ||
| 51 | + inputModule.localMultiPlayerRoot = m_PlayerRoot; | ||
| 95 | 52 | } | |
| 96 | 53 | ||
| 97 | 54 | protected override void Update() | |
| 98 | 55 | { | |
| 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 | - | ||
| 108 | 56 | var originalCurrent = current; | |
| 109 | 57 | current = this; // in order to avoid reimplementing half of the EventSystem class, just temporarily assign this EventSystem to be the globally current one | |
| 110 | 58 | try | |
| Back | FazBrowse Home | New Git URL |
0 commit comments