Files
aibis-dream/Assets/Scripts/Game Loop/GameManager.cs
T
dingyuntian f51a670239 refactor: 将淡入淡出迁移到 ScreenTransitionPanel
- 将 BaseYarnCommand、FixSystemNew 各状态、GameManager、
  SaveSystem、PlayToolPanel 中的 PlayToolPanel 遮罩调用
  统一迁移到 ScreenTransitionPanel
- 从 PlayToolPanel 移除 _fadeImage 及相关 fade 方法
- 从 Play Tool Panel Prefab 移除 Fade Image 子对象
2026-07-16 17:56:51 +08:00

447 lines
14 KiB
C#

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<GameManager>, IGameManager
{
#region 运行状态与章节数据
public GameLoopState state;
private List<TalkSceneSO> _totalSceneSos;
[Header("生产环境数据(一般不动)")] public TalkSceneSO firstTalkSo;
#endregion
private const float MainMenuFadeDuration = 0.5f;
private BindProperty<TalkSceneSO> _currentTalkSceneSo;
public override void OnSingletonInit()
{
LoadAllSceneSos();
}
private void Start()
{
// 管理游戏开始时各部分的初始化
SettingLoader.Init();
SnapshotRegistry.EnsureInitialized();
// 场景So事件注册
_currentTalkSceneSo = new BindProperty<TalkSceneSO>();
StartCoroutine(AppBootstrapCoroutine());
}
/// <summary>
/// 启动主界面:先黑屏,待本地化与 UI 就绪后再淡出,避免玩家看到初始化过程。
/// </summary>
private IEnumerator AppBootstrapCoroutine()
{
var transitionPanel = UIManager.Instance.GetPanel<ScreenTransitionPanel>();
UIManager.Instance.ShowPanel<ScreenTransitionPanel>();
transitionPanel?.ApplyScreenCovered(true);
yield return LocalizationKit.WaitUntilReady();
EnumEventSystem.Global.Send(GameLoopEnum.AppStart);
yield return LocalizationKit.WaitForUIRefresh();
if (transitionPanel != null)
{
yield return transitionPanel.FadeOutAsync(MainMenuFadeDuration);
}
}
private void LoadAllSceneSos()
{
_totalSceneSos = new List<TalkSceneSO>();
var visited = new HashSet<TalkSceneSO>();
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<ScreenTransitionPanel>().FadeInAsync(0.5f);
yield return SceneLoader.Instance.LoadSceneAsync(_currentTalkSceneSo.Value.firstSceneName);
yield return WaitForSceneDialogueReady();
DialogController.Instance.StartDialog(_currentTalkSceneSo.Value.yarnProject);
}
/// <summary>
/// 等待新场景 Awake/Start 完成,并等待场景协调器(Fix / Subway / Outside 等)就绪后再开对话。
/// </summary>
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<PlayToolPanel>();
var transitionPanel = UIManager.Instance.GetPanel<ScreenTransitionPanel>();
UIManager.Instance.ShowPanel<PlayToolPanel>();
UIManager.Instance.ShowPanel<ScreenTransitionPanel>();
if (transitionPanel != null)
{
yield return transitionPanel.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<EndPanel>();
yield return LocalizationKit.WaitForUIRefresh();
if (transitionPanel != null)
{
yield return transitionPanel.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<ScreenTransitionPanel>().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<TalkSceneSO> GetAllSceneSos()
{
return _totalSceneSos;
}
public void SetSceneSoByName(string soName)
{
_currentTalkSceneSo.Value = Array.Find(_totalSceneSos.ToArray(), so => so.name == soName);
}
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
}
/// <summary>
/// GameManager应该包含什么功能?
/// 1. 游戏状态管理。开始/暂停/结束
/// 2. 全局设置 即SettingLoader
/// 3. 场景管理 有SceneLoader类,暂无必要
/// 4. 资源管理 似无必要
/// 5. 时间管理 主要供暂停使用
/// 6. 日志和调试 日志已有
/// </summary>
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;
}
}
}