将游戏流程接入 P2 槽位层: - SaveRestoreOrchestrator:SaveToAutoSlot / CreateManualSlot / RestoreFromSlot - GameManager:<<auto_save>> 调用新入口,新增 StartWithSlot 读档入口 保留 RestoreFromFile 旧档兼容路径。
SaveSystem 存档快照层
P1 实现:纯数据快照 + 逐项还原。设计文档见
Docs/存档系统设计方案.md(§4.1 / D6 描述读档编排终态)。
职责边界
| 模块 | 位置 | 做什么 |
|---|---|---|
| Yarn 变量 | Game Loop/YarnVariableStorage.cs |
运行时 $ / $global_ 读写 |
| 本目录 | SaveSystem/ |
快照结构、捕获/还原、落盘、流程编排 |
| 深度维修(旧) | DeepRepairDataRegistry.cs |
FixSystem 等 IData,P5 前临时保留 |
| 槽位 / 截图 | P2 待做 | 接管 SnapshotPersistence 与目录布局 |
不要再往 YarnVariableStorage 上堆存档逻辑。
对外入口
// 存盘(<<auto_save>>、调试)
SaveRestoreOrchestrator.SaveToTestPath();
// 读盘
yield return SaveRestoreOrchestrator.RestoreFromFile(path);
// 仅内存快照(P2 手动档固化等)
var snap = SnapshotService.Capture();
yield return SnapshotService.Restore(snap);
// Yarn 变量(与存档无关)
YarnVariableStorage.Instance.SetValue("$foo", 1f);
P1 测试落盘路径:persistentDataPath/AllOurBrokenParts/snapshot_test/latest_snapshot.json
目录结构
SaveSystem/
├── README.md ← 本文件
├── ISnapshotProvider.cs 契约:Capture / Restore(P1 临时形态,见下)
├── SaveSnapshot.cs 快照根对象 + 各 section DTO
├── SnapshotProviderIds.cs section 稳定 id("actor" 等)
├── SnapshotRegistry.cs provider 注册表
├── SnapshotBootstrap.cs 启动时注册 6 个 provider
├── SnapshotCapture.cs 组装 SaveSnapshot(scene / anchor 由框架直管)
├── SnapshotRestore.cs 写回运行时(scene / anchor 由框架直管)
├── SnapshotService.cs Capture / Restore 对外 API
├── SnapshotPersistence.cs 文件读写、旧档检测
├── SnapshotSerializer.cs JSON + schemaVersion
├── SnapshotSectionDeserializer.cs JObject → 强类型 DTO
├── SaveRestoreOrchestrator.cs UI + 落盘 + 读盘编排
├── DeepRepairSnapshot.cs 深度维修占位(P5)
├── DeepRepairDataRegistry.cs 旧 IData 容器(P5 前)
├── ScreenSnapshotHelper.cs screen section 实现细节
└── Providers/ 表现层子系统薄适配层
├── EnvironmentSnapshotProvider.cs order 20
├── ActorSnapshotProvider.cs order 40
├── AudioSnapshotProvider.cs order 50
├── TimelineSnapshotProvider.cs order 60
├── FixSnapshotProvider.cs order 65
└── ScreenSnapshotProvider.cs order 70
存盘 / 读盘流程
存盘:
SaveRestoreOrchestrator
→ SnapshotService.Capture()
→ SnapshotCapture(场景 + 锚点 + Yarn 变量 + 各 Provider)
→ SnapshotPersistence.Save()
读盘:
SaveRestoreOrchestrator
→ SnapshotPersistence.Load() (或 legacy 分支)
→ SnapshotService.Restore()
→ SnapshotRestore(见「读档还原编排」)
读档还原编排(终态 vs P1 代码)
勿将当前代码当作终态。 P1 中
ISnapshotProvider.Restore统一返回IEnumerator,SnapshotRestore对每个 Provider 做yield return——这是受旧IData.Load()影响的临时 scaffolding。终态见设计文档 §4.1 / D6;P4 实施前应 refactor。
终态:Phase + Barrier
读档分阶段推进,仅在必须等待的边界上 yield:
Phase 0 Yarn 变量(sync)
↓
Phase 1 场景加载(Barrier:LoadSceneAsync)← 框架直管,不走 Provider
↓
Phase 1.5 设置章节 SO(sync)← 框架直管,从 anchor.sceneSoName 读取
↓
Phase 2 env / actor / audio / timeline / fix / screen(Provider 按 RestoreOrder)
↓
Phase 2′ 可选 Barrier(如 Timeline Addressable 须显式等待)
↓
Phase 3 加载对话工程 + RestoreAnchor(Barrier:StartDialogue)← 框架直管
↓
Phase 4 P4:淡入淡出等演出时序
- 核心层(
scene、anchor、yarnVariables)由SnapshotRestore框架直管,不通过 Provider 注册。 - 表现层(
sections内各子系统)通过ISnapshotProvider扩展;RestoreOrder表示同 Phase 内的建议顺序或软依赖。 - **逐项还原(D1)**指各子系统各自写回状态,不是 Provider 之间逐步
yield return。
P1 临时形态 vs P4 目标
| P1(当前) | P4 目标 | |
|---|---|---|
| 核心层还原 | 硬编码在 SnapshotRestore |
保持框架直管 |
| Provider 还原 | 全部 IEnumerator Restore |
默认 void Restore;仅需 async 加载的实现显式协程 |
| 编排 | foreach 逐步 yield return |
Phase 编排,仅 Barrier 步骤 yield |
| Manager | 部分 IEnumerator RestoreSnapshot 仅 yield break |
默认 void;真有 async 才保留协程 |
已知偏离(tech debt)
DirectorHandler在 Addressable Timeline 路径下内部StartCoroutine,Provider 已返回,编排层无法感知——P4 应改为可等待路径。- P2/P3 新增代码不要再复制「全 Provider 协程链」模式。
快照 JSON 结构(schemaVersion = 1)
| 字段 | 含义 |
|---|---|
scene |
当前场景名(Addressable key),读档时最先加载 |
anchor |
恢复锚点:sceneSoName + yarnProjectId + nodeName;最后阶段加载对话并重进节点 |
yarnVariables |
floats / strings / bools |
sections |
key 为 SnapshotProviderIds,值为各 DTO;表现层状态(env / actor / audio / timeline / fix / screen) |
deepRepair |
P5 深度维修(BlockPuzzle / Cutting / AnalysisMode 等)。P1 阶段已预留 DeepRepairSnapshotDto,但详细子系统状态暂不捕获;待存档系统主干稳定后再补充并测试 |
宏观信息(场景、章节、节点)直接位于
SaveSnapshot根对象,不再冗余到sections;槽位 / 「继续游戏」UI 需要展示时,从scene/anchor直接读取。
新增一个可存子系统(表现层)
表现层子系统通过 Provider 注册到 sections:
- 在
SaveSnapshot.cs增加 DTO 类。 - 在
SnapshotProviderIds增加稳定 id,并加入RequiredForCapture(若 P1 必须存)。 - 在对应 Manager 实现
CaptureSnapshot/RestoreSnapshot(默认 syncvoid;仅确有 async 加载/状态切换时用IEnumerator并向编排层上报)。 - 新建
Providers/XxxSnapshotProvider.cs实现ISnapshotProvider(P4 后:sync 默认 + 显式 async 接口)。 - 在
SnapshotBootstrap.EnsureInitialized中Register。 - 在
SnapshotSectionDeserializer.Coerce增加 JObject 转换分支。
禁止在 Provider 内私自 StartCoroutine 而不纳入编排 Barrier。
例外:
scene(场景加载)与anchor(章节+节点重进)属于核心叙事坐标,由SnapshotCapture/SnapshotRestore框架直管,不走 Provider 注册。新增「核心层」字段需修改SaveSnapshot根对象与对应编排方法。
Fix 场景快照(P1 接口层完成)
Fix 场景(维修场景)的快照存储在 sections["fix"],由 FixSnapshotProvider(order 65)管理。
为什么 FixStateMachine 放在 sections 而不是 deepRepair?
FixStateMachine 不仅管理深度维修(Eye、Memory、EmoWave 等),还管理 Clinic(诊所) 和 BodyModule(插线维修) 等非深度维修状态。把它放在 sections 中作为表现层子系统之一,可以避免概念错位。
双入口设计:Enter vs EnterImmediate
Fix 场景各 State 的 Enter() 通常包含相机过渡、Timeline 播放、Fade 等动画。读档时需要跳过这些动画直接到达终态,因此为 IFixState 增加了 EnterImmediate() 入口:
- 正常游戏流程 →
Enter()(完整动画) - 读档恢复 →
SwitchStateImmediate()→EnterImmediate()(跳过动画)
读档时 SwitchStateImmediate 不调用前一个状态的 Exit(),因为读档本质是覆盖当前状态,不需要清理。
P1 已落地的接口
FixSnapshotDto:state、args、moduleState、eyeColorState、isSystemOn、currentRepairSystemTypeIFixState接口 +EnterImmediate()默认实现FixStateMachine.SwitchStateImmediate()FixSystemCenter.CaptureSnapshot()/RestoreSnapshot()FixSnapshotProvider注册到SnapshotBootstrap
尚未实现(P4/P5)
- 各 State 的
EnterImmediate()具体逻辑(当前默认yield break) - 深度维修子系统详细状态(BlockPuzzle grid、Cutting 进度等)→ P5
deepRepair - Fix 场景读档的 Fade 时序 → P4 编排层
相关阶段(未在本目录完整实现)
| 阶段 | 内容 |
|---|---|
| P2 | 槽位、原子写、截图 sidecar → 扩展 SnapshotPersistence |
| P3 | 可存点判定、写盘门控 |
| P4 | 按 §4.1 重写 SnapshotRestore(Phase + Barrier);refactor Provider 契约;SaveRestoreOrchestrator 接入淡入淡出 |
| P5 | deepRepair 段、维修模块清单;还原模型单独定案,不默认套用 P1 Provider 协程链 |