using System.Collections.Generic; using UnityEngine; using BaseGames.Core.Assets; using BaseGames.Core.Events; using BaseGames.UI.MainMenu; // 复用通用菜单按钮视图 MainMenuButtonView namespace BaseGames.UI { /// /// 数据驱动暂停菜单(仿 )。 /// 据 在运行时生成按钮、派发动作;生命周期/焦点/淡入由 统一处理。 /// 策划改 UI_PauseMenuConfig 即可增删/重排/改标签/改动作,零代码;样式改 UI_PauseScreen / UI_MainMenu_Button 预制件。 /// public class DataDrivenPauseMenuController : UIPanelBase { [Header("数据表 / 按钮列表")] [SerializeField] private PauseMenuConfigSO _config; [Tooltip("按钮的父节点(通常挂 VerticalLayoutGroup)。")] [SerializeField] private Transform _container; [SerializeField] private MainMenuButtonView _buttonPrefab; [Header("Event Channels")] [SerializeField] private VoidEventChannelSO _onResumeRequested; [SerializeField] private SceneLoadRequestEventChannelSO _onSceneLoadRequest; private IUIManager _uiManager; private readonly List _buttons = new(); private MainMenuButtonView _firstButton; /// 本面板因某个菜单动作(继续 / 返回主菜单)而关闭——它们已自行处理游戏状态, /// OnPanelClose 不应再发恢复事件。ESC / 手柄 B 取消关闭时此标志为 false,需要补发恢复。 private bool _resumeHandledByAction; // 暂停面板由 UIManager 开启,此时 ServiceLocator 已就绪 protected override void OnPanelOpen() { _uiManager = GetService(); BuildMenu(); } protected override void OnPanelClose() { _uiManager = null; // 被弹出栈(ESC / 手柄 B 取消)关闭时也要恢复游戏:否则 GameManager 停在 Paused、 // 输入留在 UI map,Gameplay 的 Pause 键被禁用,导致暂停菜单关闭后再也无法呼出。 // // 仅在「真正出栈」时恢复——被上层面板(设置等 Replace 压栈)覆盖时本面板的 OnDisable // 同样触发,但此时本面板仍是栈顶(UINavigator.Push 先停用下层再压入新面板),不应恢复。 // 区分依据:出栈时 UINavigator.Pop 已先出栈,栈顶不再是本面板;覆盖时栈顶仍是本面板。 // 「继续」「返回主菜单」已各自处理状态,由 _resumeHandledByAction 抑制重复 / 误恢复。 var nav = GetService(); bool poppedOff = nav == null || nav.Top != this; if (poppedOff && !_resumeHandledByAction) _onResumeRequested?.Raise(); _resumeHandledByAction = false; } /// 默认焦点 / 焦点恢复回到首个按钮。 protected override GameObject ResolveFirstSelected() => _firstButton != null ? _firstButton.Button.gameObject : null; /// 据配置重建按钮列表(public 以便编辑器预览/测试)。 public void BuildMenu() { ClearMenu(); if (_config == null || _container == null || _buttonPrefab == null) return; foreach (var item in _config.Items) { var view = Instantiate(_buttonPrefab, _container); view.gameObject.SetActive(true); var captured = item; view.Bind(item.labelKey, item.icon, () => Dispatch(captured)); _buttons.Add(view); if (_firstButton == null) _firstButton = view; } } private void ClearMenu() { _buttons.Clear(); _firstButton = null; if (_container == null) return; for (int i = _container.childCount - 1; i >= 0; i--) { var child = _container.GetChild(i).gameObject; if (Application.isPlaying) Destroy(child); else DestroyImmediate(child); } } // ── 动作派发 ────────────────────────────────────────────────────────── private void Dispatch(PauseMenuConfigSO.Item item) { switch (item.action) { case PauseMenuAction.Resume: _resumeHandledByAction = true; // 自行发恢复,避免 OnPanelClose 重复发 _onResumeRequested?.Raise(); _uiManager?.CloseTopPanel(); break; case PauseMenuAction.OpenSettings: _uiManager?.OpenPanel(PanelId.Settings); break; case PauseMenuAction.OpenPanel: _uiManager?.OpenPanel(item.targetPanel); break; case PauseMenuAction.ReturnToMainMenu: // 不发恢复:须保持 Paused 状态,使 SceneLoaded(MainMenu) 时 Paused→MainMenu 为合法转换。 _resumeHandledByAction = true; _uiManager?.CloseTopPanel(); _onSceneLoadRequest?.Raise(new SceneLoadRequest { SceneName = string.IsNullOrEmpty(item.sceneKey) ? AddressKeys.SceneMainMenu : item.sceneKey, TransitionType = TransitionType.Scene, }); break; case PauseMenuAction.Quit: Application.Quit(); break; case PauseMenuAction.RaiseEvent: item.eventChannel?.Raise(); break; } } } }