using System; using System.Collections; using System.Collections.Generic; using AibisDream.FixSystem; using AibisDream.Framework; using AibisDream.Kit; using AibisDream.SaveSystem; using AibisDream.UI; using UnityEngine; namespace AibisDream { public class GameManager : Singleton, IGameManager { #region 运行状态与章节数据 public GameLoopState state; private List _totalSceneSos; [Header("生产环境数据(一般不动)")] public TalkSceneSO firstTalkSo; [Header("存档恢复补充章节")] [SerializeField] private List additionalRestoreSceneSos = new(); #endregion private const float MainMenuFadeDuration = 0.5f; private BindProperty _currentTalkSceneSo; public override void OnSingletonInit() { LoadAllSceneSos(); } private void Start() { // 管理游戏开始时各部分的初始化 SettingLoader.Init(); SnapshotRegistry.EnsureInitialized(); // 场景So事件注册 _currentTalkSceneSo = new BindProperty(); StartCoroutine(AppBootstrapCoroutine()); } /// /// 启动主界面:先黑屏,待本地化与 UI 就绪后再淡出,避免玩家看到初始化过程。 /// private IEnumerator AppBootstrapCoroutine() { var playTool = UIManager.Instance.GetPanel(); UIManager.Instance.ShowPanel(); playTool?.ApplyFadeScreenCovered(true); yield return LocalizationKit.WaitUntilReady(); EnumEventSystem.Global.Send(GameLoopEnum.AppStart); yield return LocalizationKit.WaitForUIRefresh(); if (playTool != null) { yield return playTool.FadeOutAsync(MainMenuFadeDuration); } } private void LoadAllSceneSos() { _totalSceneSos = new List(); var visited = new HashSet(); var current = firstTalkSo; while (current != null && !visited.Contains(current)) { _totalSceneSos.Add(current); visited.Add(current); current = current.GetNextScene(); } } #region 游戏循环 public void StartNewGame() { // 游戏开始 EnumEventSystem.Global.Send(GameLoopEnum.GameStart); // 重置屏幕效果 ScreenEffectManager.Instance?.ResetSaturation(); // 序列 _currentTalkSceneSo.Value = firstTalkSo; StartCoroutine(LoadFirstScene()); } public void StartWithLevel(TalkSceneSO sceneSo) { // 游戏开始 EnumEventSystem.Global.Send(GameLoopEnum.GameStart); // 重置屏幕效果 ScreenEffectManager.Instance?.ResetSaturation(); _currentTalkSceneSo.Value = sceneSo; StartCoroutine(LoadFirstScene()); } public void StartWithSaveFile(string fileName) { // 游戏开始 EnumEventSystem.Global.Send(GameLoopEnum.GameStart); StartCoroutine(LoadFromSaveFile(fileName)); } public void StartWithSlot(int slotIndex) { // 游戏开始 EnumEventSystem.Global.Send(GameLoopEnum.GameStart); StartCoroutine(LoadFromSlot(slotIndex)); } private IEnumerator LoadFromSaveFile(string fileName) { yield return SaveRestoreOrchestrator.RestoreFromFile(fileName); } private IEnumerator LoadFromSlot(int slotIndex) { yield return SaveRestoreOrchestrator.RestoreFromSlot(slotIndex); } private IEnumerator LoadFirstScene() { ChapterController.Instance.UnlockChapter(_currentTalkSceneSo.Value); yield return UIManager.Instance.GetPanel().FadeInAsync(0.5f); yield return SceneLoader.Instance.LoadSceneAsync(_currentTalkSceneSo.Value.firstSceneName); yield return WaitForSceneDialogueReady(); DialogController.Instance.StartDialog(_currentTalkSceneSo.Value.yarnProject); } /// /// 等待新场景 Awake/Start 完成,并等待场景协调器(Fix / Subway / Outside 等)就绪后再开对话。 /// private static IEnumerator WaitForSceneDialogueReady() { yield return null; if (SceneHasDialogueGate()) { while (HasPendingDialogueGate()) { yield return null; } } else { yield return null; } } private static bool SceneHasDialogueGate() { return FixSystemCenter.Instance != null || SubwayCenter.Instance != null || OutsideCenter.Instance != null; } private static bool HasPendingDialogueGate() { if (FixSystemCenter.Instance is ISceneDialogueGate { IsDialogueReady: false }) { return true; } if (SubwayCenter.Instance is ISceneDialogueGate { IsDialogueReady: false }) { return true; } if (OutsideCenter.Instance is ISceneDialogueGate { IsDialogueReady: false }) { return true; } return false; } public void PauseGame() { // 系统暂停 Time.timeScale = 0; // 交互继续 EnumEventSystem.Global.Send(GameLoopEnum.PauseGame); // 音频全部暂停 AudioManager.Instance.PauseAll(); // 关闭Dialog DialogUIManager.Instance.CloseCanvas(); } public void ContinueGame() { // 系统继续 Time.timeScale = 1; // 交互继续 EnumEventSystem.Global.Send(GameLoopEnum.UnPauseGame); // 音频继续 AudioManager.Instance.UnPauseAll(); // 打开Dialog DialogUIManager.Instance.OpenCanvas(); } public void QuitGame() { // 系统继续 Time.timeScale = 1; // 重置屏幕效果 ScreenEffectManager.Instance?.ResetSaturation(); // 卸载当前场景 StartCoroutine(RestartCoroutine()); } public void QuitApp() { Application.Quit(); } private IEnumerator RestartCoroutine() { var playTool = UIManager.Instance.GetPanel(); UIManager.Instance.ShowPanel(); if (playTool != null) { yield return playTool.FadeInAsync(MainMenuFadeDuration); } // 清理所有系统注册 if (FixSystemCenter.SystemDic != null) { FixSystemCenter.SystemDic.Clear(); } playTool?.ClearPresentationEffects(); // 执行场景卸载等功能 CameraKit.Instance.ResetCamera(); yield return SceneLoader.Instance.UnloadSceneAsync(); DialogController.Instance.StopDialog(); yield return LocalizationKit.WaitUntilReady(); EnumEventSystem.Global.Send(GameLoopEnum.GameQuit); // 音频继续 AudioManager.Instance.UnPauseAll(); // 打开Dialog DialogUIManager.Instance.OpenCanvas(); // 关闭结束面板 UIManager.Instance.HidePanel(); yield return LocalizationKit.WaitForUIRefresh(); if (playTool != null) { yield return playTool.FadeOutAsync(MainMenuFadeDuration); } } #endregion #region 分辨率与屏幕模式 public void SetScreenMode(string windowMode) { int targetWidth, targetHeight; switch (windowMode) { case "FullScreen": targetWidth = Screen.currentResolution.width; targetHeight = Screen.currentResolution.height; Screen.SetResolution(targetWidth, targetHeight, FullScreenMode.FullScreenWindow); break; case "Windowed": targetWidth = 1920; targetHeight = 1080; Screen.SetResolution(targetWidth, targetHeight, FullScreenMode.Windowed); break; case "MaximizedWindow": targetWidth = Screen.currentResolution.width; targetHeight = Screen.currentResolution.height; // Windows上没有MaximizedWindow模式,所以用FullScreenWindow代替 Screen.SetResolution(targetWidth, targetHeight, FullScreenMode.FullScreenWindow); break; default: return; } StartCoroutine(WaitForResolutionAndUpdateViewport( targetWidth, targetHeight)); } private IEnumerator WaitForResolutionAndUpdateViewport( int targetWidth, int targetHeight) { float timeout = Time.unscaledTime + 1f; while ((Screen.width != targetWidth || Screen.height != targetHeight) && Time.unscaledTime < timeout) { yield return null; } EnumEventSystem.Global.Send(GameLoopEnum.ScreenResolutionChanged); } #endregion public IEnumerator NextSceneSo(string exitName = null) { var next = _currentTalkSceneSo.Value?.GetNextScene(exitName); if (next != null) { _currentTalkSceneSo.Value = next; ChapterController.Instance.UnlockChapter(_currentTalkSceneSo.Value); yield return UIManager.Instance.GetPanel().FadeInAsync(); yield return SceneLoader.Instance.LoadSceneAsync(_currentTalkSceneSo.Value.firstSceneName); yield return WaitForSceneDialogueReady(); DialogController.Instance.StartDialog(_currentTalkSceneSo.Value.yarnProject); } else { Debug.Log($"没有下个场景了 (exitName: {exitName})"); yield return null; } } public List GetAllSceneSos() { return _totalSceneSos; } public bool SetSceneSoByName(string soName) { var sceneSo = _totalSceneSos?.Find(so => so != null && so.name == soName); sceneSo ??= additionalRestoreSceneSos?.Find(so => so != null && so.name == soName); if (sceneSo == null) { Debug.LogError($"[GameManager] 找不到章节 SO:{soName}"); return false; } _currentTalkSceneSo.Value = sceneSo; return true; } public void StartDialog() { DialogController.Instance.LoadDialog(_currentTalkSceneSo.Value.yarnProject); } public TalkSceneSO GetCurrentTalkSceneSo() { return _currentTalkSceneSo?.Value; } public string GetSceneSoName() { return _currentTalkSceneSo.Value.name; } #region 事件处理 private IUnRegister[] _unRegisters; private void OnEnable() { RegisterEvents(); } private void OnDisable() { UnRegisterEvents(); } private void RegisterEvents() { _unRegisters = new IUnRegister[6]; _unRegisters[0] = EnumEventSystem.Global.Register(GameLoopEnum.GameStart, () => state.OnGame(true)); _unRegisters[1] = EnumEventSystem.Global.Register(GameLoopEnum.PauseGame, () => state.OnPause(true)); _unRegisters[2] = EnumEventSystem.Global.Register(GameLoopEnum.GameQuit, () => state.OnGame(false)); _unRegisters[3] = EnumEventSystem.Global.Register(GameLoopEnum.UnPauseGame, () => state.OnPause(false)); _unRegisters[4] = EnumEventSystem.Global.Register(InteractionEventEnum.DialogStart, () => state.OnDialog(true)); _unRegisters[5] = EnumEventSystem.Global.Register(InteractionEventEnum.DialogEnd, () => state.OnDialog(false)); } private void UnRegisterEvents() { foreach (var unRegister in _unRegisters) { unRegister.UnRegister(); } } #endregion } /// /// GameManager应该包含什么功能? /// 1. 游戏状态管理。开始/暂停/结束 /// 2. 全局设置 即SettingLoader /// 3. 场景管理 有SceneLoader类,暂无必要 /// 4. 资源管理 似无必要 /// 5. 时间管理 主要供暂停使用 /// 6. 日志和调试 日志已有 /// public interface IGameManager { #region 游戏循环相关 public void StartNewGame(); public void PauseGame(); public void ContinueGame(); public void QuitGame(); public void QuitApp(); #endregion } public struct GameLoopState { public bool isInGame; public bool isInDialog; public bool isInPause; public void OnGame(bool isInGame) { this.isInGame = isInGame; } public void OnPause(bool isInPause) { this.isInPause = isInPause; } public void OnDialog(bool isInDialog) { this.isInDialog = isInDialog; } public bool IsInputAvailable() { return isInGame && isInDialog && !isInPause; } } }