From bc66728a65ba22a135c2ef8d62b6520311da7de0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E6=80=9D=E6=B5=B7?= <1464576565@qq.com> Date: Thu, 9 Apr 2026 15:21:49 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=80=A7=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Navigation/UXNavigationRuntime.cs | 18 +++-- .../Navigation/UXNavigationScope.cs | 79 ++++++++++++------- .../UXComponent/Selectable/UXSelectable.cs | 5 +- 3 files changed, 67 insertions(+), 35 deletions(-) diff --git a/Runtime/UXComponent/Navigation/UXNavigationRuntime.cs b/Runtime/UXComponent/Navigation/UXNavigationRuntime.cs index 3f764d8..a9374be 100644 --- a/Runtime/UXComponent/Navigation/UXNavigationRuntime.cs +++ b/Runtime/UXComponent/Navigation/UXNavigationRuntime.cs @@ -9,11 +9,13 @@ namespace UnityEngine.UI internal sealed class UXNavigationRuntime : MonoBehaviour { private static UXNavigationRuntime _instance; + private static readonly string CacheLayerName = $"Layer{(int)UILayer.All}-{UILayer.All}"; private const float DiscoveryInterval = 0.5f; private readonly HashSet _scopeSet = new(); private readonly List _scopes = new(32); private readonly HashSet _interactiveLayerRoots = new(); + private readonly List _holderBuffer = new(32); private Transform _uiCanvasRoot; private UXNavigationScope _topScope; @@ -116,6 +118,7 @@ namespace UnityEngine.UI _topScope = null; } + scope.IsAvailable = false; scope.SetNavigationSuppressed(false); _scopes.Remove(scope); MarkDiscoveryDirty(); @@ -185,11 +188,10 @@ namespace UnityEngine.UI return; } - string cacheLayerName = $"Layer{(int)UILayer.All}-{UILayer.All}"; for (int i = 0; i < _uiCanvasRoot.childCount; i++) { Transform child = _uiCanvasRoot.GetChild(i); - if (child == null || child.name == cacheLayerName) + if (child == null || child.name == CacheLayerName) { continue; } @@ -217,10 +219,11 @@ namespace UnityEngine.UI continue; } - UIHolderObjectBase[] holders = layerRoot.GetComponentsInChildren(true); - for (int i = 0; i < holders.Length; i++) + _holderBuffer.Clear(); + layerRoot.GetComponentsInChildren(true, _holderBuffer); + for (int i = 0; i < _holderBuffer.Count; i++) { - UIHolderObjectBase holder = holders[i]; + UIHolderObjectBase holder = _holderBuffer[i]; if (holder == null || holder.GetComponent() != null || IsNavigationSkipped(holder.transform)) @@ -233,6 +236,8 @@ namespace UnityEngine.UI } } + _holderBuffer.Clear(); + if (addedScope) { for (int i = 0; i < _scopes.Count; i++) @@ -254,6 +259,7 @@ namespace UnityEngine.UI } bool available = IsScopeAvailable(scope); + scope.IsAvailable = available; if (scope.WasAvailable != available) { scope.WasAvailable = available; @@ -348,7 +354,7 @@ namespace UnityEngine.UI continue; } - bool suppress = IsScopeAvailable(scope) + bool suppress = scope.IsAvailable && _topScope != null && !ReferenceEquals(scope, _topScope) && _topScope.BlockLowerScopes diff --git a/Runtime/UXComponent/Navigation/UXNavigationScope.cs b/Runtime/UXComponent/Navigation/UXNavigationScope.cs index bba65f8..14559de 100644 --- a/Runtime/UXComponent/Navigation/UXNavigationScope.cs +++ b/Runtime/UXComponent/Navigation/UXNavigationScope.cs @@ -21,16 +21,20 @@ namespace UnityEngine.UI [SerializeField, Header("自动选中首个可用控件")] private bool _autoSelectFirstAvailable = true; private readonly List _cachedSelectables = new(16); + private readonly HashSet _cachedSelectableSet = new(); private readonly Dictionary _baselineNavigation = new(); private readonly List _removeBuffer = new(8); + private readonly List _selectableScanBuffer = new(16); private Canvas _cachedCanvas; private UIHolderObjectBase _cachedHolder; private Selectable _lastSelected; private bool _cacheDirty = true; private bool _navigationSuppressed; + private int _cachedHierarchyDepth = -1; internal ulong ActivationSerial { get; set; } + internal bool IsAvailable { get; set; } internal bool WasAvailable { get; set; } public Selectable DefaultSelectable @@ -98,6 +102,14 @@ namespace UnityEngine.UI _cacheDirty = true; } + private void OnTransformParentChanged() + { + _cacheDirty = true; + _cachedCanvas = null; + _cachedHolder = null; + _cachedHierarchyDepth = -1; + } + #if UNITY_EDITOR private void OnValidate() { @@ -107,6 +119,11 @@ namespace UnityEngine.UI internal int GetHierarchyDepth() { + if (_cachedHierarchyDepth >= 0) + { + return _cachedHierarchyDepth; + } + int depth = 0; Transform current = transform; while (current != null) @@ -115,7 +132,8 @@ namespace UnityEngine.UI current = current.parent; } - return depth; + _cachedHierarchyDepth = depth; + return _cachedHierarchyDepth; } internal bool Owns(GameObject target) @@ -151,7 +169,7 @@ namespace UnityEngine.UI for (int i = 0; i < _cachedSelectables.Count; i++) { Selectable selectable = _cachedSelectables[i]; - if (IsSelectableValid(selectable)) + if (IsSelectableUsable(selectable)) { return selectable; } @@ -171,7 +189,7 @@ namespace UnityEngine.UI for (int i = 0; i < _cachedSelectables.Count; i++) { - if (IsSelectableValid(_cachedSelectables[i])) + if (IsSelectableUsable(_cachedSelectables[i])) { return true; } @@ -187,12 +205,7 @@ namespace UnityEngine.UI return; } - Selectable selectable = selectedObject.GetComponent(); - if (selectable == null) - { - selectable = selectedObject.GetComponentInParent(); - } - + Selectable selectable = GetSelectableFromObject(selectedObject); if (IsSelectableValid(selectable)) { _lastSelected = selectable; @@ -201,18 +214,7 @@ namespace UnityEngine.UI internal bool IsSelectableOwnedAndValid(GameObject selectedObject) { - if (selectedObject == null || !Owns(selectedObject)) - { - return false; - } - - Selectable selectable = selectedObject.GetComponent(); - if (selectable == null) - { - selectable = selectedObject.GetComponentInParent(); - } - - return IsSelectableValid(selectable); + return IsSelectableValid(GetSelectableFromObject(selectedObject)); } internal void SetNavigationSuppressed(bool suppressed) @@ -259,6 +261,7 @@ namespace UnityEngine.UI _cacheDirty = false; _cachedSelectables.Clear(); + _cachedSelectableSet.Clear(); if (_explicitSelectables != null && _explicitSelectables.Count > 0) { @@ -269,17 +272,18 @@ namespace UnityEngine.UI } else { - Selectable[] selectables = GetComponentsInChildren(true); - for (int i = 0; i < selectables.Length; i++) + _selectableScanBuffer.Clear(); + GetComponentsInChildren(true, _selectableScanBuffer); + for (int i = 0; i < _selectableScanBuffer.Count; i++) { - TryAddSelectable(selectables[i]); + TryAddSelectable(_selectableScanBuffer[i]); } } _removeBuffer.Clear(); foreach (var pair in _baselineNavigation) { - if (!_cachedSelectables.Contains(pair.Key)) + if (!_cachedSelectableSet.Contains(pair.Key)) { _removeBuffer.Add(pair.Key); } @@ -289,6 +293,8 @@ namespace UnityEngine.UI { _baselineNavigation.Remove(_removeBuffer[i]); } + + _selectableScanBuffer.Clear(); } public void InvalidateSelectableCache() @@ -298,7 +304,7 @@ namespace UnityEngine.UI private void TryAddSelectable(Selectable selectable) { - if (selectable == null || !Owns(selectable.gameObject)) + if (selectable == null || !Owns(selectable.gameObject) || !_cachedSelectableSet.Add(selectable)) { return; } @@ -311,11 +317,28 @@ namespace UnityEngine.UI } private bool IsSelectableValid(Selectable selectable) + { + return IsSelectableUsable(selectable) + && (_cachedSelectableSet.Contains(selectable) || Owns(selectable.gameObject)); + } + + private static bool IsSelectableUsable(Selectable selectable) { return selectable != null && selectable.IsActive() - && selectable.IsInteractable() - && Owns(selectable.gameObject); + && selectable.IsInteractable(); + } + + private static Selectable GetSelectableFromObject(GameObject selectedObject) + { + if (selectedObject == null) + { + return null; + } + + return selectedObject.TryGetComponent(out Selectable selectable) + ? selectable + : selectedObject.GetComponentInParent(); } } } diff --git a/Runtime/UXComponent/Selectable/UXSelectable.cs b/Runtime/UXComponent/Selectable/UXSelectable.cs index e625c09..84d93e9 100644 --- a/Runtime/UXComponent/Selectable/UXSelectable.cs +++ b/Runtime/UXComponent/Selectable/UXSelectable.cs @@ -17,6 +17,8 @@ namespace UnityEngine.UI { [SerializeField] private List m_ChildTransitions = new(); + private SelectionState _state; + void StartChildColorTween(TransitionData transitionData, Color targetColor, bool instant) { if (transitionData.targetGraphic == null) @@ -68,7 +70,8 @@ namespace UnityEngine.UI protected override void DoStateTransition(SelectionState state, bool instant) { base.DoStateTransition(state, instant); - + if (_state == state) return; + _state = state; for (int i = 0; i < m_ChildTransitions.Count; i++) { TransitionData transitionData = m_ChildTransitions[i];