Files
aibis-dream/Assets/Scripts/Game Loop/GameManager.cs
T

479 lines
15 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
using UnityEngine.EventSystems;
namespace AibisDream
{
public class GameManager : Singleton<GameManager>, IGameManager
{
#region 运行状态与章节数据
public GameLoopState state;
private List<TalkSceneSO> _totalSceneSos;
[Header("生产环境数据(一般不动)")] public TalkSceneSO firstTalkSo;
[Header("存档恢复补充章节")]
[SerializeField] private List<TalkSceneSO> additionalRestoreSceneSos = new();
#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());
}
/// <summary>
/// 可等待的完整游戏会话清理。开发跳转等编排流程必须等待它结束,避免场景卸载与读档并发。
/// </summary>
public IEnumerator ResetGameSessionRoutine()
{
Time.timeScale = 1;
ScreenEffectManager.Instance?.ResetSaturation();
yield return 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);
}
EventSystem.current?.SetSelectedGameObject(null);
}
#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 bool SetSceneSoByName(string soName)
{
var sceneSo = FindSceneSoByName(soName);
if (sceneSo == null)
{
Debug.LogError($"[GameManager] 找不到章节 SO{soName}");
return false;
}
_currentTalkSceneSo.Value = sceneSo;
return true;
}
/// <summary>只查询章节资产,不改变当前章节;供严格读档预检使用。</summary>
public TalkSceneSO FindSceneSoByName(string soName)
{
var sceneSo = _totalSceneSos?.Find(so => so != null && so.name == soName);
return sceneSo ?? additionalRestoreSceneSos?.Find(so => so != null && 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;
}
}
}