Files
aibis-dream/Docs/GameManagerRefactorPlan.md
T

12 KiB

GameManager 重构计划(待重新评审)

状态:已暂存方案,尚未获得最终认可,后续继续讨论前不要据此扩大实现。

记录日期:2026-07-16

当前结论

目标是把 GameManager 收敛为游戏会话协调器,但本轮方案和实现涉及范围明显大于最初预期。目前不应把现有实现视为最终架构,应先逐项重新评审,再决定保留、简化或撤回哪些改动。

已明确需要重新讨论的问题:

  1. NarrativeFlowControllerTalkSceneCatalogChapterController 的职责是否拆分过度。
  2. NarrativeFlowControllerChapterController 当前存在双向依赖,边界没有真正理顺。
  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 窗口模式和分辨率应用 是否值得独立拆出

候选会话状态

public enum GameSessionPhase
{
    MainMenu,
    StartingSession,
    Transitioning,
    Restoring,
    Playing,
    ReturningToMenu
}

会话只读状态候选字段:

  • Phase
  • IsPaused
  • IsDialogueActive
  • IsInGame
  • IsTransitioning
  • CanPause
  • IsInputAvailable

需要重新确认:这些派生状态是否与现有输入、UI 和存档判断相比确实降低复杂度,而不是单纯扩大迁移范围。

GameManager 候选公共 API

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 内部候选入口:

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。

存档恢复候选流程

恢复拆成:

PrepareRestore:读盘、格式识别、章节/Yarn/节点预检,不修改运行时
ExecuteRestore:变量、场景、章节、Provider、Readiness、Anchor

候选恢复顺序:

获取恢复锁
→ PrepareRestore
→ GameStart(从主界面恢复时)
→ Phase=Restoring
→ 遮黑
→ 清理现有会话(如有)
→ Yarn variables
→ Scene
→ 设置当前章节
→ Providers
→ SceneReadiness
→ RestoreAnchor
→ Postflight
→ Phase=Playing
→ SessionReady
→ 揭示画面

恢复失败时不得继续 Anchor,应安全清理并回到主界面。旧格式运行时恢复应明确返回不支持,不修改 Yarn 变量或自动档。

需要重新评审:恢复编排究竟应由 GameManager 掌握到什么粒度,以及哪些失败清理可以继续留在 Orchestrator 内部。

暂停和返回主界面候选规则

暂停:

更新暂停状态
→ Time.timeScale = 0
→ PauseGame

恢复:

Time.timeScale = 1
→ 更新暂停状态
→ UnPauseGame

返回主界面:

取消当前转场
→ 遮黑
→ SessionTeardownStarted
→ 等待对话停止
→ 卸载场景
→ State=MainMenu
→ GameQuit
→ 错误提示(如有)
→ 揭示主界面

暂停和清理副作用是否全部下沉到 Audio、Dialog UI、Screen Effect、Camera 和 UIManager,需要逐个确认现有监听器是否因此变得更复杂。

应用启动与显示设置候选

候选 AppBootstrapper 职责:

  • SettingLoader.Init
  • SnapshotRegistry.EnsureInitialized
  • 启动遮罩
  • 等待本地化
  • 发送 AppStart
  • UI 本地化刷新
  • 显示主界面

候选 DisplaySettingsController 职责:

  • 应用窗口模式、分辨率和全屏状态。
  • 等待实际分辨率变化。
  • 发送独立显示事件。
  • CameraAspectAdapter 改监听显示事件。

这两项都需要按“是否确实降低 GameManager 复杂度”重新判断,不以拆分类数量为目标。

调用方迁移候选范围

  • Yarn nextload_sceneunload_scene
  • LineAdvanceInput
  • UIManagerCursorManager 和游戏内 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. 再确定 NarrativeFlowControllerChapterController 是保留、重命名还是合并。
  3. 画出最小调用链,限定 GameManager 必须负责和明确不负责的部分。
  4. 对现有未提交实现逐文件分类:保留、简化、撤回。
  5. 先处理章节与恢复主链,再考虑 AppBootstrapper、DisplaySettings 和错误 UI 等外围拆分。
  6. 每批修改后单独编译和冒烟,不再把整份方案视作必须一次完成的整体。

当前工作区说明

当前工作区已经存在一版按原方案实施的未提交改动。该实现通过了当时的 Unity 编译及现有测试,但这不代表架构已获认可。下次继续时应先检查当前 diff,并以本文件记录的待决问题为准,避免直接在现有实现上继续叠加。