# GameManager 重构计划(待重新评审) > 状态:已暂存方案,尚未获得最终认可,后续继续讨论前不要据此扩大实现。 > > 记录日期:2026-07-16 ## 当前结论 目标是把 `GameManager` 收敛为游戏会话协调器,但本轮方案和实现涉及范围明显大于最初预期。目前不应把现有实现视为最终架构,应先逐项重新评审,再决定保留、简化或撤回哪些改动。 已明确需要重新讨论的问题: 1. `NarrativeFlowController`、`TalkSceneCatalog`、`ChapterController` 的职责是否拆分过度。 2. `NarrativeFlowController` 与 `ChapterController` 当前存在双向依赖,边界没有真正理顺。 3. `TalkSceneCatalog` 与现有 Chapter Graph Editor 只做到数据层兼容,没有做到创作工作流兼容。 4. Chapter Graph Editor 新建、删除章节时不会同步 Catalog;Catalog 的首章、角色、显示状态和顺序还需要单独维护。 5. 在决定保留 Catalog 前,需要先确认章节编辑器是否应继续作为唯一内容创作入口。 ## 原始目标 将 `GameManager` 收敛为唯一的游戏会话协调器: - 只管理会话状态、命令互斥、开始、读档、转场、暂停和退出。 - 不再持有章节目录、显示设置、应用启动和 Fix 内部清理职责。 - 一次性迁移旧调用方,不长期保留旧 API 或兼容转发层。 - 保留 `GameStart` 的早期语义,增加 `SessionReady` 表示场景和交互真正就绪。 - 新格式存档结构及 `sceneSoName` 身份保持不变。 - 旧格式存档停止运行时恢复。 ## 候选组件边界 | 组件 | 候选职责 | 重新评审重点 | |---|---|---| | `AppBootstrapper` | 设置、SnapshotRegistry、本地化、`AppStart` 和启动遮罩 | 是否值得从 GameManager 单独拆出 | | `GameManager` | 会话状态、互斥、开始、读档、暂停、返回主界面 | 保留为核心目标 | | `NarrativeFlowController` | 当前章节、进章和出口跳转 | 是否可以进一步简化,是否与 ChapterController 重叠 | | `TalkSceneCatalog` | 正式章节、恢复兼容章节和显示顺序目录 | 是否保留;若保留,必须与章节编辑器整合 | | `SceneLoader` | 串行、可验证的 Addressables 场景加载与卸载 | 可以独立保留 | | `SceneReadinessService` | 收集当前场景 Gate 并等待业务就绪 | 是否需要独立类型或可并入场景流程 | | `SaveRestoreOrchestrator` | 快照预检、Provider Barrier、Anchor 恢复和结构化结果 | 与 GameManager 的编排边界需复核 | | `ChapterController` | 解锁数据和章节面板数据 | 应重命名、收紧职责或合并 | | `DisplaySettingsController` | 窗口模式和分辨率应用 | 是否值得独立拆出 | ## 候选会话状态 ```csharp public enum GameSessionPhase { MainMenu, StartingSession, Transitioning, Restoring, Playing, ReturningToMenu } ``` 会话只读状态候选字段: - `Phase` - `IsPaused` - `IsDialogueActive` - `IsInGame` - `IsTransitioning` - `CanPause` - `IsInputAvailable` 需要重新确认:这些派生状态是否与现有输入、UI 和存档判断相比确实降低复杂度,而不是单纯扩大迁移范围。 ## GameManager 候选公共 API ```csharp public GameSessionState State { get; } public bool TryStartNewGame(); public bool TryStartChapter(TalkSceneSO chapter); public bool TryRestoreSlot(int slotIndex, Action completed = null); public bool TryRestoreFile( string path, RestoreOptions options, Action completed = null); public void PauseSession(); public void ResumeSession(); public void ReturnToMainMenu(); public void QuitApp(); ``` Yarn 内部候选入口: ```csharp internal IEnumerator AdvanceChapterRoutine(string exitName); internal IEnumerator LoadNarrativeSceneRoutine(string sceneName); internal IEnumerator UnloadNarrativeSceneRoutine(); ``` 旧 API 的删除范围需要在重新评审后逐项确定,避免为了接口整洁产生不必要的大规模迁移。 ## 事件语义候选 保留: - `AppStart` - `GameStart` - `GameQuit` - `PauseGame` - `UnPauseGame` 候选新增: - `SessionReady` - `SessionTeardownStarted` - `SessionFailed` 核心规则: 1. `GameStart` 表示开始进入正式游戏会话。 2. 切章和 Yarn 内部换场景不重复发送 `GameStart`。 3. 场景、Gate、对话或 Anchor 全部成功后发送 `SessionReady`。 4. GameManager 先更新自身状态,再广播事件。 5. `DialogStart/DialogEnd` 只更新对话活动状态。 6. 新会话、失败和退出时原子清理暂停及对话状态。 ## 转场互斥和取消候选规则 - GameManager 只允许一个活动命令。 - 开始、切章、读档进行中,普通请求直接拒绝,不排队。 - 返回主界面允许请求取消当前流程。 - 已经开始的 Addressables 操作不强行中断;底层操作完成后停止后续 Gate、对话或 Anchor。 - Provider 和 SceneLoader 的不可中断单步允许完成,但阶段之间检查取消。 - 所有活动命令必须通过 `try/finally` 释放。 ## 章节系统待决方案 ### 原 Catalog 设想 Catalog 显式登记: - `FirstScene` - Runtime 章节 - RestoreOnly 章节 - 章节面板显示状态 - 章节面板顺序 - 以 `TalkSceneSO.name` 为键的恢复查询 原校验规则包括: - 引用和资产名唯一。 - FirstScene 必须属于 Runtime。 - Runtime 出口目标必须在 Runtime Catalog。 - RestoreOnly 不能显示在章节面板。 - Runtime 必须具有 YarnProject 和场景 Key。 ### 当前暴露的问题 现有 Chapter Graph Editor 以指定文件夹中的 `TalkSceneSO` 为数据源,并直接编辑 `exits`。Catalog 是另一份显式目录,因此形成两个需要同步的创作入口。 如果继续保留 Catalog,至少需要做到: - Chapter Graph Editor 新建章节时同步新增 Runtime 条目。 - 删除章节时同步移除 Catalog 引用,并处理其他节点出口。 - 在章节编辑器内设置 FirstScene。 - 在章节编辑器内维护 Runtime/RestoreOnly、是否显示和章节顺序。 - RestoreOnly 使用独立区域,不混入正式剧情图。 - 加载和校验时区分“当前文件夹过滤”与“全局 Catalog 缺失”,避免误报。 - 所有 Catalog 修改支持 Undo。 重新评审时需要在以下方向中选择: 1. 保留 Catalog,并让 Chapter Graph Editor 成为唯一维护入口。 2. 取消显式 Catalog,继续由首章链和少量恢复补充目录组成运行时数据。 3. 由章节编辑器生成只供运行时使用的 Catalog,不要求人工维护 Catalog Inspector。 在该选择确定之前,不继续扩大章节系统改动。 ## 场景加载与 Gate 候选规则 SceneLoader 候选约束: - 真正等待正在执行的场景操作完成。 - 使用强类型 `AsyncOperationHandle`。 - 校验空 Key、Addressables 状态和 SceneInstance。 - 只有加载成功后更新当前场景名。 - `try/finally` 复位状态和 Loading UI。 - 加载失败回滚 ResourceSystem 场景生命周期。 - 卸载失败保留真实句柄和场景名。 - 移除 fire-and-forget 场景入口。 Gate 候选规则: - 场景加载后从对应 Scene 根对象收集 `ISceneDialogueGate`。 - 不维护跨场景静态注册表。 - 使用 unscaled time,超时 15 秒。 - 无 Gate 时等待稳定一帧后成功。 - Gate 销毁、取消或超时都返回结构化失败。 - 普通进章在加载后等待。 - 读档在 Provider Barrier 后、Anchor 前等待。 - 超时不得强行启动 Yarn。 ## 存档恢复候选流程 恢复拆成: ```text PrepareRestore:读盘、格式识别、章节/Yarn/节点预检,不修改运行时 ExecuteRestore:变量、场景、章节、Provider、Readiness、Anchor ``` 候选恢复顺序: ```text 获取恢复锁 → PrepareRestore → GameStart(从主界面恢复时) → Phase=Restoring → 遮黑 → 清理现有会话(如有) → Yarn variables → Scene → 设置当前章节 → Providers → SceneReadiness → RestoreAnchor → Postflight → Phase=Playing → SessionReady → 揭示画面 ``` 恢复失败时不得继续 Anchor,应安全清理并回到主界面。旧格式运行时恢复应明确返回不支持,不修改 Yarn 变量或自动档。 需要重新评审:恢复编排究竟应由 GameManager 掌握到什么粒度,以及哪些失败清理可以继续留在 Orchestrator 内部。 ## 暂停和返回主界面候选规则 暂停: ```text 更新暂停状态 → Time.timeScale = 0 → PauseGame ``` 恢复: ```text Time.timeScale = 1 → 更新暂停状态 → UnPauseGame ``` 返回主界面: ```text 取消当前转场 → 遮黑 → SessionTeardownStarted → 等待对话停止 → 卸载场景 → State=MainMenu → GameQuit → 错误提示(如有) → 揭示主界面 ``` 暂停和清理副作用是否全部下沉到 Audio、Dialog UI、Screen Effect、Camera 和 UIManager,需要逐个确认现有监听器是否因此变得更复杂。 ## 应用启动与显示设置候选 候选 `AppBootstrapper` 职责: - `SettingLoader.Init` - `SnapshotRegistry.EnsureInitialized` - 启动遮罩 - 等待本地化 - 发送 `AppStart` - UI 本地化刷新 - 显示主界面 候选 `DisplaySettingsController` 职责: - 应用窗口模式、分辨率和全屏状态。 - 等待实际分辨率变化。 - 发送独立显示事件。 - CameraAspectAdapter 改监听显示事件。 这两项都需要按“是否确实降低 GameManager 复杂度”重新判断,不以拆分类数量为目标。 ## 调用方迁移候选范围 - Yarn `next`、`load_scene`、`unload_scene`。 - `LineAdvanceInput`。 - `UIManager`、`CursorManager` 和游戏内 Terminal。 - `SavePointEvaluator`。 - Snapshot 捕获和恢复。 - `ChapterController` 和章节选择 UI。 - 所有退出主界面入口。 - 开发存档跳转工具。 重新实施时应分批迁移并保持每批可编译,不再默认一次性扩大所有调用方改动。 ## 候选验收矩阵 1. Unity batchmode 编译无错误。 2. 章节目录和 Chapter Graph 校验无错误。 3. 新格式开发存档逐档冒烟成功。 4. 连续点击两次新游戏只接受一次。 5. 场景加载中返回主界面不会启动对话,并最终安全卸载。 6. 暂停后返回主界面不会残留 `timeScale`、暂停或对话状态。 7. 默认出口和命名出口进入正确章节。 8. 非法出口、非法章节和场景加载失败安全回主界面。 9. Fix、Subway、Outside 在 Gate Ready 后启动对话。 10. Gate 永久不 Ready 时超时失败,不启动 Yarn。 11. 读档 Provider 完成后经过 Gate,再恢复 Anchor。 12. 读档预检失败不加载目标场景。 13. 旧格式存档提示不兼容且不修改运行时。 14. Yarn 场景命令不绕过互斥。 15. 返回主界面和切场景后没有资源、对话、相机和表现 UI 残留。 ## 下次继续时的建议顺序 1. 先决定是否保留 `TalkSceneCatalog`,以及章节编辑器谁是唯一数据维护入口。 2. 再确定 `NarrativeFlowController` 与 `ChapterController` 是保留、重命名还是合并。 3. 画出最小调用链,限定 GameManager 必须负责和明确不负责的部分。 4. 对现有未提交实现逐文件分类:保留、简化、撤回。 5. 先处理章节与恢复主链,再考虑 AppBootstrapper、DisplaySettings 和错误 UI 等外围拆分。 6. 每批修改后单独编译和冒烟,不再把整份方案视作必须一次完成的整体。 ## 当前工作区说明 当前工作区已经存在一版按原方案实施的未提交改动。该实现通过了当时的 Unity 编译及现有测试,但这不代表架构已获认可。下次继续时应先检查当前 diff,并以本文件记录的待决问题为准,避免直接在现有实现上继续叠加。