using System.Collections; using UnityEngine; using UnityEngine.UI; using BaseGames.Core; using BaseGames.Core.Events; namespace BaseGames.UI.MainMenu { /// /// 主菜单 UI 控制器(挂载在 Scene_MainMenu 的根 Canvas 上)。 /// /// 面板结构(按 Inspector 绑定): /// ├── MainButtonsPanel — 主按钮组(新游戏 / 继续 / 设置 / 制作团队 / 退出) /// ├── SaveSlotPanel — 存档槽选择(新游戏 & 继续共用) /// ├── SettingsPanel — 设置面板 /// └── CreditsPanel — 制作团队面板 /// /// 入场动画:主按钮组从下方滑入(代码驱动,无需 Animator)。 /// /// 流程: /// 玩家选择存档槽(SaveSlotController 发布 _onSlotConfirmed) /// → 关闭存档槽面板 → 发布 SceneLoadRequest(目标游戏场景) /// → GameManager 响应,进入 LoadingScene 状态,显示加载画面,最终切换到 Gameplay。 /// public class MainMenuController : MonoBehaviour { // ── 面板引用 ────────────────────────────────────────────────────────── [Header("面板")] [SerializeField] private CanvasGroup _mainButtonsGroup; [SerializeField] private RectTransform _mainButtonsRect; // 用于滑入动画 [SerializeField] private GameObject _saveSlotPanel; [SerializeField] private GameObject _settingsPanel; [SerializeField] private GameObject _creditsPanel; // ── 按钮引用 ────────────────────────────────────────────────────────── [Header("主菜单按钮")] [SerializeField] private Button _btnNewGame; [SerializeField] private Button _btnContinue; [SerializeField] private Button _btnSettings; [SerializeField] private Button _btnCredits; [SerializeField] private Button _btnQuit; // ── 按钮(子面板关闭)──────────────────────────────────────────────── [Header("子面板关闭按钮(可选)")] [SerializeField] private Button _btnCloseSaveSlot; [SerializeField] private Button _btnCloseSettings; [SerializeField] private Button _btnCloseCredits; // ── 入场动画参数 ────────────────────────────────────────────────────── [Header("入场动画")] [Tooltip("按钮组初始偏移(像素,向下)")] [SerializeField] private float _entrySlideOffset = 80f; [Tooltip("入场动画持续时间(秒)")] [SerializeField] private float _entryDuration = 0.55f; // ── 游戏场景 ────────────────────────────────────────────────────────── [Header("场景")] [Tooltip("新游戏 / 继续后进入的第一个游戏场景(Addressable Key)")] [SerializeField] private string _firstGameSceneKey = "Scene_Game_Chapter1"; // ── Event Channels ──────────────────────────────────────────────────── [Header("Event Channels - Listen")] [SerializeField] private GameStateEventChannelSO _onGameStateChanged; [Tooltip("SaveSlotController 完成选槽后发布(携带槽索引)")] [SerializeField] private IntEventChannelSO _onSlotConfirmed; [Header("Event Channels - Raise")] [SerializeField] private SceneLoadRequestEventChannelSO _onSceneLoadRequest; // ── 内部状态 ────────────────────────────────────────────────────────── private readonly CompositeDisposable _subs = new(); private Vector2 _buttonsPanelOriginalPos; // ── 生命周期 ────────────────────────────────────────────────────────── private void Awake() { // 按钮绑定 _btnNewGame? .onClick.AddListener(OnNewGameClicked); _btnContinue?.onClick.AddListener(OnContinueClicked); _btnSettings?.onClick.AddListener(OnSettingsClicked); _btnCredits? .onClick.AddListener(OnCreditsClicked); _btnQuit? .onClick.AddListener(Application.Quit); _btnCloseSaveSlot?.onClick.AddListener(() => SetPanel(_saveSlotPanel, false)); _btnCloseSettings?.onClick.AddListener(() => SetPanel(_settingsPanel, false)); _btnCloseCredits? .onClick.AddListener(() => SetPanel(_creditsPanel, false)); // 记录按钮组原始位置(供动画使用) if (_mainButtonsRect != null) _buttonsPanelOriginalPos = _mainButtonsRect.anchoredPosition; // 初始状态:隐藏子面板,主按钮组不可见(等待入场动画) SetPanel(_saveSlotPanel, false); SetPanel(_settingsPanel, false); SetPanel(_creditsPanel, false); SetButtonsGroupVisible(false); // 刷新"继续"按钮可用性(需要至少一个有效存档) RefreshContinueButton(); } private void OnEnable() { _onGameStateChanged?.Subscribe(HandleGameStateChanged).AddTo(_subs); _onSlotConfirmed? .Subscribe(HandleSlotConfirmed).AddTo(_subs); } private void OnDisable() => _subs.Clear(); private void Start() { // 场景初始化完成后播放入场动画 StartCoroutine(PlayEntryAnimation()); } // ── 入场动画 ───────────────────────────────────────────────────────── private IEnumerator PlayEntryAnimation() { if (_mainButtonsGroup == null) yield break; Vector2 startPos = _buttonsPanelOriginalPos - new Vector2(0f, _entrySlideOffset); if (_mainButtonsRect != null) _mainButtonsRect.anchoredPosition = startPos; float elapsed = 0f; while (elapsed < _entryDuration) { float t = Mathf.SmoothStep(0f, 1f, elapsed / _entryDuration); _mainButtonsGroup.alpha = t; if (_mainButtonsRect != null) _mainButtonsRect.anchoredPosition = Vector2.Lerp(startPos, _buttonsPanelOriginalPos, t); elapsed += Time.unscaledDeltaTime; yield return null; } _mainButtonsGroup.alpha = 1f; if (_mainButtonsRect != null) _mainButtonsRect.anchoredPosition = _buttonsPanelOriginalPos; _mainButtonsGroup.interactable = true; _mainButtonsGroup.blocksRaycasts = true; } // ── 按钮回调 ───────────────────────────────────────────────────────── private void OnNewGameClicked() => SetPanel(_saveSlotPanel, true); private void OnContinueClicked() => SetPanel(_saveSlotPanel, true); private void OnSettingsClicked() => SetPanel(_settingsPanel, true); private void OnCreditsClicked() => SetPanel(_creditsPanel, true); // ── 存档槽确认 ─────────────────────────────────────────────────────── private void HandleSlotConfirmed(int _) { SetPanel(_saveSlotPanel, false); _onSceneLoadRequest?.Raise(new SceneLoadRequest { SceneName = _firstGameSceneKey, TransitionType = TransitionType.Scene, ShowLoadingScreen = true, }); } // ── 游戏状态响应 ───────────────────────────────────────────────────── private void HandleGameStateChanged(GameStateId state) { bool isMainMenu = state == GameStates.MainMenu; // 离开 MainMenu(加载游戏中)时锁定所有交互,防止重复点击 if (_mainButtonsGroup != null) { _mainButtonsGroup.interactable = isMainMenu; _mainButtonsGroup.blocksRaycasts = isMainMenu; } } // ── 工具方法 ───────────────────────────────────────────────────────── private void RefreshContinueButton() { if (_btnContinue == null) return; var saveService = ServiceLocator.GetOrDefault(); bool hasAny = saveService != null && (saveService.HasSave(0) || saveService.HasSave(1) || saveService.HasSave(2)); _btnContinue.interactable = hasAny; } private static void SetPanel(GameObject panel, bool active) { if (panel != null) panel.SetActive(active); } private void SetButtonsGroupVisible(bool visible) { if (_mainButtonsGroup == null) return; _mainButtonsGroup.alpha = visible ? 1f : 0f; _mainButtonsGroup.interactable = visible; _mainButtonsGroup.blocksRaycasts = visible; } } }