From 00a43fcad3f6ce455becbaab49db5f9ad0f09391 Mon Sep 17 00:00:00 2001 From: Ding Yuntian <1491671119@qq.com> Date: Thu, 16 Jul 2026 23:07:24 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20GameManager=E5=A4=A7=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E8=AE=A1=E5=88=92=EF=BC=8C=E9=9C=80=E8=A6=81=E9=87=8D=E6=96=B0?= =?UTF-8?q?=E5=AE=A1=E8=A7=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Docs/GameManagerRefactorPlan.md | 331 ++++++++++++++++++++++++++++++++ 1 file changed, 331 insertions(+) create mode 100644 Docs/GameManagerRefactorPlan.md diff --git a/Docs/GameManagerRefactorPlan.md b/Docs/GameManagerRefactorPlan.md new file mode 100644 index 000000000..e64c5b7fc --- /dev/null +++ b/Docs/GameManagerRefactorPlan.md @@ -0,0 +1,331 @@ +# 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,并以本文件记录的待决问题为准,避免直接在现有实现上继续叠加。