docs: GameManager大重构计划,需要重新审视

This commit is contained in:
2026-07-16 23:07:24 +08:00
parent 3959561686
commit 00a43fcad3
+331
View File
@@ -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<RestoreResult> completed = null);
public bool TryRestoreFile(
string path,
RestoreOptions options,
Action<RestoreResult> 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<SceneInstance>`
- 校验空 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,并以本文件记录的待决问题为准,避免直接在现有实现上继续叠加。