10 · 存档与回溯
本章讲解存档系统和回溯机制——让玩家随时保存进度、回看历史。
存档系统
DSL 存档命令
save "slot1" // 保存到指定槽位
load "slot1" // 读取指定槽位
auto_save // 自动存档(存到自动存档槽)
save_delete "slot1" // 删除指定存档C# API
await gameController.SaveAsync("slot1");
await gameController.LoadAsync("slot1");
// 自动存档:DSL 中写 auto_save true;C# 中通过状态键开启:
gameController.Set(StateKeys.PlaybackControl.AutoSave, true);存档内容
存档包含:
- 全量用户状态(所有变量)
- 场景堆栈快照
- DSL 执行位置(当前场景 + 命令索引)
- 缩略图(截图)
- 时间事件状态
只有 game 场景可存档
type=menu 和 type=ui 的场景不会创建存档点。只有 type=game 的场景可以存档。这是为了防止在菜单界面存档导致状态混乱。
存档目录
存档默认保存在 Saves/ 目录:
Saves/
├── slot1/
│ ├── save.json # 存档数据
│ └── thumbnail.jpg # 缩略图
├── auto/
│ ├── save.json
│ └── thumbnail.jpg
└── .meta/ # 轻量索引(快速加载列表)回溯系统
回溯(Rollback)让玩家回到之前的对话,而不需要读档。
检查点
引擎在以下位置自动创建回溯检查点:
| 操作 | 创建检查点 |
|---|---|
say | ✅ |
menu | ✅ |
input | ✅ |
wait(含 wait skipable) | ✅ |
pause | ✅ |
nvl clear | ✅ |
call_screen | ✅ |
navigate | ✅ |
scene_idle | ✅ |
每个检查点保存完整的状态快照。
NVL 模式下的检查点
在 NVL 模式中,nvl clear(清空累积文本)会建立一个独立检查点,因此回溯能逐级经过「已清空」状态,而不会跨过它直接跳回清空前的文本。此外,NVL 模式下 scene_idle 检查点会被抑制以避免冗余(末句 say 已完整呈现累积文本)。
滚轮回溯
玩家可以用鼠标滚轮回溯:
- 滚轮向上——回退一步
- 滚轮向下——前进一步
回退后,之前的对话会重新展示(打字机效果),但不会重复记录到历史中。
回溯 vs 读档
- 回溯——逐句回退,保留回溯前沿(可以前进回来),快速
- 读档——跳转到指定存档点,丢弃当前进度
回溯适合"我想再看一遍刚才那句",读档适合"我想回到某个存档点"。
回溯上限
引擎配置 MaxRollbackCheckpoints(默认 100)限制检查点数量。超过上限时,最旧的检查点被丢弃。
block_rollback 禁止回溯
block_rollback在当前位置设置屏障,玩家无法回溯到此点之前。适用于:
- 已观看的过场动画后
- 重要选择确认后
- 防止通过回溯作弊
fix_rollback 修复回溯
fix_rollback与 block_rollback 类似,但更温和——允许回溯查看,但不允许改变选择。
回溯与 C# 场景
C# 场景通过 CreateSceneCheckpoint() 创建检查点:
public class MyScene : StoryScript
{
public override async Task RunAsync()
{
await SayAsync("第一句");
CreateSceneCheckpoint(); // 手动创建检查点
await SayAsync("第二句");
}
}C# 场景的回溯
C# 场景通过异常机制实现回溯——回溯时抛出 CSharpSceneReplayCancelledException,终止旧的 Runner,从检查点重新执行。
自动存档
引擎在以下时机自动存档:
- 每章开始时
- 玩家选择后
- 场景切换时(可配置)
历史记录
引擎自动记录对话历史,玩家可通过工具栏的「历史」按钮查看:
// 历史记录自动生成,无需手动操作
say "你好。" speaker="老张"
say "最近怎么样?" speaker="老张"
// 历史列表:[{speaker: "老张", text: "你好。"}, {speaker: "老张", text: "最近怎么样?"}]历史上限
set "__history_max" 100 // 最多保留 100 条存档 UI
灵泛引擎不强制特定的存档 UI。你可以用 call_screen 调用自定义的存档面板:
label save_menu:
call_screen "save_panel" store="result"
if {result == "saved"}
say "存档成功!" speaker="系统"
navigate "main"Demo 项目中包含一个完整的 SaveLoadPanel 实现可供参考。
动手练习
体验存档和回溯:
label save_test:
say "这是第一句。" speaker="旁白"
say "这是第二句。" speaker="旁白"
say "这是第三句。" speaker="旁白"
say "现在试着用滚轮回退。" speaker="系统"
say "然后再滚轮前进。" speaker="系统"
save "test_slot"
say "已存档到 test_slot。" speaker="系统"
say "继续剧情..." speaker="旁白"
load "test_slot"
say "读档回来了!" speaker="系统"下一步
存档回溯掌握了!下一章学习时间事件系统——让游戏世界随时间运转。