This commit is contained in:
2026-06-07 11:49:55 +08:00
parent ff0f3bde54
commit 1897658a00
98 changed files with 9903 additions and 13907 deletions

View File

@@ -53,13 +53,9 @@ namespace BaseGames.UI
[SerializeField] private VoidEventChannelSO _onSpellSelectOpen;
[Tooltip("打开统一背包菜单InventoryHub。对应 EVT_InventoryOpen。")]
[SerializeField] private VoidEventChannelSO _onInventoryOpen;
[Tooltip("UI 取消操作ESC / 手柄 B·Circle全局关闭栈顶面板。对应 EVT_UICancelPressed。")]
[SerializeField] private VoidEventChannelSO _onUICancelPressed;
// ── 面板栈结构 ────────────────────────────────────────────────────────
private readonly Stack<GameObject> _panelStack = new();
/// <summary>O(1) 成员判断,与 _panelStack 保持同步,替代 Stack.Contains O(n)。</summary>
private readonly HashSet<GameObject> _openPanelSet = new();
// ── 面板栈:委托给统一的 UINavigator不再自管栈─────────────────────
private IUINavigator _navigator;
private readonly Dictionary<PanelId, GameObject> _panelRegistry = new();
private readonly CompositeDisposable _subs = new();
@@ -114,6 +110,7 @@ namespace BaseGames.UI
private void OnEnable()
{
ServiceLocator.Register<IUIManager>(this);
_navigator = ServiceLocator.GetOrDefault<IUINavigator>();
_onGameStateChanged?.Subscribe(HandleGameStateChanged).AddTo(_subs);
_onPauseRequested?.Subscribe(TogglePause).AddTo(_subs);
_onFastTravelOpen?.Subscribe(OpenMap).AddTo(_subs);
@@ -122,7 +119,7 @@ namespace BaseGames.UI
_onCharmPanelOpen?.Subscribe(OpenCharmPanel).AddTo(_subs);
_onSpellSelectOpen?.Subscribe(OpenSpellSelect).AddTo(_subs);
_onInventoryOpen?.Subscribe(OpenInventory).AddTo(_subs);
_onUICancelPressed?.Subscribe(HandleUICancelPressed).AddTo(_subs);
// 取消键ESC / 手柄 B由 UINavigator 统一消费,UIManager 不再订阅。
}
private void OnDisable()
@@ -239,46 +236,44 @@ namespace BaseGames.UI
};
}
/// <summary>打开指定 GameObject 面板并压栈已在栈中则忽略O(1) 判断)。</summary>
/// <summary>打开指定 GameObject 面板:经统一 <see cref="IUINavigator"/> 压栈。</summary>
public void OpenPanel(GameObject panel)
{
if (panel == null) return;
if (!_openPanelSet.Add(panel)) return;
if (_panelStack.Count > 0) _panelStack.Peek().SetActive(false);
panel.SetActive(true);
_panelStack.Push(panel);
var uiPanel = EnsurePanel(panel);
if (uiPanel != null) Navigator?.Push(uiPanel);
}
/// <summary>关闭栈顶面板并恢复上一层(如有);上一层若实现 IFocusable 则自动恢复焦点。</summary>
public void CloseTopPanel()
/// <summary>关闭栈顶面板并恢复上一层(委托给导航器)。</summary>
public void CloseTopPanel() => Navigator?.Pop();
/// <summary>
/// 适配器:保证面板根挂有 <see cref="UIPanelBase"/>(导航器压栈对象)。
/// 既有面板若未挂则运行时补 <see cref="UISimplePanel"/> + CanvasGroup
/// 使所有游戏内面板无需逐个改脚手架即可纳入统一导航栈。
/// </summary>
private static UIPanelBase EnsurePanel(GameObject go)
{
if (_panelStack.Count == 0) return;
var top = _panelStack.Pop();
_openPanelSet.Remove(top);
top.SetActive(false);
if (_panelStack.Count > 0)
var p = go.GetComponent<UIPanelBase>();
if (p == null)
{
var restored = _panelStack.Peek();
restored.SetActive(true);
restored.GetComponent<IFocusable>()?.OnFocusRestored();
if (go.GetComponent<CanvasGroup>() == null) go.AddComponent<CanvasGroup>();
p = go.AddComponent<UISimplePanel>();
}
return p;
}
// ── 快捷事件回调 ──────────────────────────────────────────────────────
private void HandleUICancelPressed()
{
if (_panelStack.Count > 0)
CloseTopPanel();
}
private void TogglePause()
{
if (_panelRegistry.TryGetValue(PanelId.Pause, out var pausePanel)
&& _panelStack.Count > 0 && _panelStack.Peek() == pausePanel)
CloseTopPanel();
&& Navigator?.Top != null && Navigator.Top.gameObject == pausePanel)
Navigator.Pop();
else
OpenPanel(PanelId.Pause);
}
private IUINavigator Navigator => _navigator ??= ServiceLocator.GetOrDefault<IUINavigator>();
private void OpenShop(string _) => OpenPanel(PanelId.Shop);
private void OpenMap() => OpenPanel(PanelId.Map);
private void OpenCharmPanel() => OpenPanel(PanelId.CharmPanel);
@@ -335,8 +330,9 @@ namespace BaseGames.UI
}
#if UNITY_EDITOR
/// <summary>仅供 UIManagerEditor 实时可视化面板栈(由栈顶到栈底顺序)。</summary>
public GameObject[] EditorGetPanelSnapshot() => _panelStack.ToArray();
/// <summary>仅供 UIManagerEditor 实时可视化栈顶面板(栈本体已迁移至 UINavigator)。</summary>
public GameObject[] EditorGetPanelSnapshot()
=> Navigator?.Top != null ? new[] { Navigator.Top.gameObject } : System.Array.Empty<GameObject>();
#endif
[ContextMenu("测试:打开 Pause 面板")]