feat: GameManager相关重构

This commit is contained in:
2026-07-17 14:24:02 +08:00
parent 2dcfeec938
commit 21d51945c3
47 changed files with 2064 additions and 1022 deletions
+204
View File
@@ -0,0 +1,204 @@
# GameManager 收敛式重构说明
> 状态:已按修订方案实现
>
> 更新日期:2026-07-17
## 1. 最终职责边界
| 组件 | 职责 |
| --- | --- |
| `GameManager` | 唯一会话命令入口;编排开始、章节跳转、读档、暂停、返回主界面和退出应用 |
| `GameSession` | GameManager 持有的纯状态对象;保存状态、校验迁移并计算能力 |
| `TalkSceneGraphIndex` | 从首章图派生运行时章节列表和恢复名称索引 |
| `SceneLoader` | 串行执行 Addressables 场景加载、卸载和场景级资源清理 |
| `SceneReadiness` | 在当前加载 Scene 内收集 `ISceneDialogueGate` 并等待就绪 |
| `SaveRestoreOrchestrator` | 存档预检和 Snapshot 内部恢复阶段 |
| `ChapterController` | 章节解锁数据及章节面板投影 |
| `ApplicationBootstrapper` | 应用设置、SnapshotRegistry、本地化及主界面启动流程 |
| `DisplaySettingsController` | 窗口模式和分辨率应用,并发送显示变化通知 |
没有引入 `NarrativeFlowController`、人工维护的 `TalkSceneCatalog` 或独立全局 Session 服务。`TalkSceneSO``SceneExit`、章节资产和 Chapter Graph Editor 数据结构保持不变。
## 2. GameSession
`GameSessionPhase`
```text
MainMenu
Starting
Transitioning
Restoring
Playing
ReturningToMenu
```
只读状态:
- `Phase`
- `CurrentTalkScene`
- `IsPaused`
- `IsDialogueActive`
- `IsActive`
- `IsBusy`
- `CanPause`
- `CanResume`
- `CanAdvanceDialogue`
外部通过 `GameManager.Session` 读取;GameManager 内部直接使用 `_session`。所有写入方法均为 `internal`GameSession 不执行协程、不操作 UI、不加载场景,也不发送全局事件。
合法迁移:
```text
MainMenu → Starting / Restoring
Playing → Transitioning / Restoring / ReturningToMenu
Starting / Transitioning → Playing / ReturningToMenu
Restoring → Playing / MainMenu / ReturningToMenu
ReturningToMenu → MainMenu
```
## 3. 生命周期事件
`GameLoopEnum` 已删除,统一使用:
```csharp
public enum GameLifecycleEvent
{
ApplicationReady,
SessionStarted,
SessionReady,
SessionPaused,
SessionResumed,
SessionEnding,
SessionEnded,
SessionFailed
}
```
显示设置通知使用 `SettingChangeEvent.DisplayChanged`
事件顺序固定为:GameManager 执行命令 → GameSession 完成状态迁移 → GameManager 发送生命周期通知。监听方不能反向修改 Session。
`DialogController.DialogueActivityChanged(bool)` 是 GameManager 更新对话活动状态的直接 C# 事件;原有对话表现事件继续供其他系统使用。
## 4. GameManager API
```csharp
public bool TryStartNewGame();
public bool TryStartChapter(TalkSceneSO chapter);
public bool TryRestoreSlot(int slotIndex, Action<RestoreResult> completed = null);
public bool TryRestoreFile(string path, RestoreOptions options,
Action<RestoreResult> completed = null);
public bool TryPause();
public bool TryResume();
public bool TryReturnToMainMenu();
public void QuitApplication();
public IReadOnlyList<TalkSceneSO> RuntimeChapters { get; }
```
开始、转场和恢复共用一个活动命令锁,重复请求直接返回 `false`。返回主界面请求可以在活动命令期间排队;当前不可中断的 Addressables 或 Provider 单步结束后,流程会在阶段边界停止,并进入唯一的返回主界面协程。
Yarn 场景命令通过以下内部可等待入口执行;章节交接是例外,由源 Runner 同步提交后交给 GameManager 独立执行:
```csharp
internal bool TryAdvanceChapterFromYarn(string exitName);
internal IEnumerator LoadSceneFromYarn(string sceneName);
internal IEnumerator UnloadSceneFromYarn();
```
`NextYarn` 是同步交接命令:它只向 GameManager 提交章节跳转并立即返回,不等待转场,也不在自身命令回调中停止 DialogueRunner。`NextYarn` 必须是当前分支最后一条可执行命令;GameManager 只等待源节点自然结束,超时视为内容错误并终止转场,不兼容后续选项、`jump` 或其他命令。`load_scene` / `unload_scene` 不替换 YarnProject,因此继续由 Yarn 等待完成。
旧 GameManager 状态字段、接口和命令 API 已删除,没有保留兼容转发层。
## 5. 章节索引
`TalkSceneGraphIndex``firstTalkSo` 开始,按 `exits` 顺序稳定深度优先遍历,并用 visited 集合处理环:
- 可达节点进入 `RuntimeChapters` 和名称索引。
- 同名不同资产会被标记为歧义,恢复预检失败。
- `ChapterController` 直接投影 `RuntimeChapters`
这份索引完全从现有创作数据派生,不引入第二份章节目录。
## 6. 场景加载与 Readiness
`SceneLoader` 使用 `AsyncOperationHandle<SceneInstance>`,加载和卸载均返回结构化 `SceneOperationResult`
- Busy、空 Key、Addressables 失败和无效 SceneInstance 均明确失败。
- 仅成功后提交当前场景名、Scene 和句柄。
- 卸载失败保留当前场景句柄与名称。
- Loading UI 与 `IsLoading``finally` 中复位。
- DOTween、Fix 注册表和 `ResourceSystem` 场景级生命周期统一由 SceneLoader 清理。
`SceneReadiness` 在刚加载的 Scene 根对象内查找 Gate,使用 unscaled time,默认超时 15 秒。无 Gate 时等待稳定一帧后成功;Gate 销毁、取消或超时返回失败。普通章节加载在 SceneLoader 后等待;读档在 Provider 后、Anchor 前等待。
## 7. 恢复边界
```text
PrepareRestore
→ 读槽位或文件
→ schema、场景、章节、YarnProject 和可选节点预检
→ 解析目标 TalkSceneSO
→ 不修改运行时
ExecutePreparedRestore
→ Yarn variables
→ Scene
→ Providers
→ SceneReadiness
→ Anchor
→ Postflight
```
说明:显式 `<<save>>` 可以生成无节点 anchor 的新格式快照。这类快照仍预检章节和 YarnProject,但跳过节点存在性校验,并在恢复时只加载 YarnProject、不重进节点。
GameManager 管理命令锁、Session Phase、遮罩、旧会话表现清理、成功提交和失败决策。Orchestrator 不再查找或设置 GameManager 的当前章节。
恢复成功后,GameManager 提交 `RestoreResult.RestoredTalkScene`、解锁章节、进入 `Playing` 并发送 `SessionReady`。预检失败恢复发起前的稳定 Phase;执行失败停止后续 Anchor 并排队安全返回主界面。已接受请求的回调只调用一次。
存档文件统一经过当前快照反序列化和 schema 预检,不保留旧 StorageSystem 格式的专用识别、恢复或转写分支。
## 8. 核心流程
新游戏 / 选章:
```text
校验章节 → 获取命令锁 → Starting → SessionStarted → 遮黑
→ SceneLoader → SceneReadiness → 提交并解锁章节 → 启动 Yarn
→ Playing → SessionReady → 揭示画面
```
章节跳转:
```text
解析出口 → Transitioning → 遮黑 → SceneLoader → SceneReadiness
→ 提交并解锁章节 → 启动 Yarn → Playing → SessionReady → 揭示画面
```
读档:
```text
获取命令锁 → Restoring → PrepareRestore → SessionStarted → 遮黑
→ 清理旧会话表现 → ExecutePreparedRestore → 提交目标章节
→ Playing → SessionReady → 揭示画面
```
返回主界面:
```text
ReturningToMenu → SessionEnding → 恢复 timeScale / 清除暂停 → 遮黑
→ 等待对话停止 → 卸载场景 → 清理表现和相机
→ 清除章节与对话状态 → MainMenu → SessionEnded → 揭示主界面
```
## 9. 验证基线
静态验收应确认:
- 不存在 `GameLoopEnum``GameManager.state`、旧 GameManager API 或 `CleanSessionFirst`
- 输入只读取 `CanAdvanceDialogue`;暂停入口只读取 `CanPause`
- 开始、转场、恢复和 Yarn 场景命令都经过 GameManager 互斥。
- Snapshot 恢复不再通过 GameManager 查找或设置章节。
- 章节面板和存档恢复都只使用从首章可达的正式章节。
运行时冒烟仍需覆盖新游戏、选章、每类新格式存档、Gate 成功/超时、活动命令期间返回主界面,以及退出后场景、对话、暂停、音频、相机和表现 UI 清理。