479 lines
15 KiB
C#
479 lines
15 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;
|
||
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;
|
||
}
|
||
}
|
||
|
||
}
|