# 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 completed = null); public bool TryPause(); public bool TryResume(); public bool TryReturnToMainMenu(); public void QuitApplication(); public IReadOnlyList 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`,加载和卸载均返回结构化 `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 ``` 说明:显式 `<>` 可以生成无节点 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 清理。