Skip to content

10 · 存档与回溯 ​

本章讲解存档系统和回溯机制——让玩家随时保存进度、回看历史。

存档系统 ​

DSL 存档命令 ​

dsl
save "slot1"       // 保存到指定槽位
load "slot1"       // 读取指定槽位
auto_save          // 自动存档(存到自动存档槽)
save_delete "slot1" // 删除指定存档

C# API ​

csharp
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 禁止回溯 ​

dsl
block_rollback

在当前位置设置屏障,玩家无法回溯到此点之前。适用于:

  • 已观看的过场动画后
  • 重要选择确认后
  • 防止通过回溯作弊

fix_rollback 修复回溯 ​

dsl
fix_rollback

与 block_rollback 类似,但更温和——允许回溯查看,但不允许改变选择。

回溯与 C# 场景 ​

C# 场景通过 CreateSceneCheckpoint() 创建检查点:

csharp
public class MyScene : StoryScript
{
    public override async Task RunAsync()
    {
        await SayAsync("第一句");
        CreateSceneCheckpoint();  // 手动创建检查点
        await SayAsync("第二句");
    }
}

C# 场景的回溯

C# 场景通过异常机制实现回溯——回溯时抛出 CSharpSceneReplayCancelledException,终止旧的 Runner,从检查点重新执行。

自动存档 ​

引擎在以下时机自动存档:

  • 每章开始时
  • 玩家选择后
  • 场景切换时(可配置)

历史记录 ​

引擎自动记录对话历史,玩家可通过工具栏的「历史」按钮查看:

dsl
// 历史记录自动生成,无需手动操作
say "你好。" speaker="老张"
say "最近怎么样?" speaker="老张"
// 历史列表:[{speaker: "老张", text: "你好。"}, {speaker: "老张", text: "最近怎么样?"}]

历史上限 ​

dsl
set "__history_max" 100   // 最多保留 100 条

存档 UI ​

灵泛引擎不强制特定的存档 UI。你可以用 call_screen 调用自定义的存档面板:

dsl
label save_menu:
  call_screen "save_panel" store="result"
  if {result == "saved"}
    say "存档成功!" speaker="系统"
  navigate "main"

Demo 项目中包含一个完整的 SaveLoadPanel 实现可供参考。

动手练习 ​

体验存档和回溯:

dsl
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="系统"

下一步 ​

存档回溯掌握了!下一章学习时间事件系统——让游戏世界随时间运转。

基于 .NET 10 + Avalonia 12 · Apache-2.0 License