# 录像指令 录像验证要能重放出和原来一模一样的结果,前提是:把该记下来的操作都记下来。录像系统(`state.replaySystem`)做的正是这件事——把玩家在游戏中的操作记录成一条条**录像步**,之后可以重新播放。它的主要用途是**录像验证**:只运行数据端、重放录像,就能确认同样的操作是否得到同样的结果(原理见[数据端与渲染端](./data-vs-render.md))。 ## 核心原则 :::info 核心原则 **玩家直接产生、会影响最终成绩的行为,必须计入录像。** ::: 这句话包含两个条件: - **玩家直接产生**:是玩家亲手做出的操作,例如移动、使用道具、装备道具。由代码或时间自动发生的变化不算; - **会影响最终成绩**:这个操作会改变游戏结果。只改渲染表现、不进数据端的操作不算。 满足两个条件的操作,都必须作为一条录像步记录下来。否则重放这条录像时,玩家的操作没有被重新执行,结果就会与原始游玩对不上。 ## 主动录像与被动录像 录像步分两类,由 `ReplayCommandType` 表示: - **主动录像(`Active`)**:指令自己就会执行对应的操作。引擎内置的移动、瞬移、使用道具(见[勇士道具](./hero-items.md))、装备(见[勇士装备](./hero-equipment.md))都是主动录像; - **被动录像(`Passive`)**:指令不自己执行,而是让外部通过录像沙盒的 `getPassive()` 取到这一步的参数,再由外部决定怎么处理。 ## 新增自定义录像指令 录像指令属于**引擎内容**,需要改动 `packages-user` 下的包(这个目录是什么、和 `src` 是什么关系,见 [src 导览](./src-tour.md) 的「包从哪来」),而不是只在 `src` 里写几行代码。新增一条自定义录像指令大致分三步: 1. 在录像指令码枚举 `ReplayCode` 中分配一个新码。该枚举属于录像格式的一部分,遵循「只增不改」——新语义必须用新码,不能复用旧码; 2. 实现 `IReplayCommand`:`type` 指明是主动还是被动录像,`execute(step)` 执行这一步对应的操作并返回 `ReplayCommandResult`;若需要「连续步骤的后处理」,再实现可选的 `finalize()`; 3. 用 `state.replaySystem.registerCommand(code, command)` 把指令注册进录像系统。引擎内置指令的注册代码在 `src/data/replay/initialize.ts` 中。 `execute` 的返回值 `ReplayCommandResult` 有三种:`Success`(成功)、`Failed`(失败)、`Ignored`(失败但可忽略,继续执行后续录像,并会在控制台输出信息)。 ## 录像安全检测:@shouldReplay 与 @ignoreReplay `@shouldReplay(message)` 与 `@ignoreReplay(message)` 写在一个方法的上方,形如 `@名字(...)`。这种以 `@` 开头的写法叫**装饰器(decorator)**:它不改变方法本身的代码,只给这个方法附加一条额外说明(在这里,就是给录像系统打一个标记)。装饰器的基础写法见[代码基础](./code-basics.md)。 录像系统会检查「一步录像执行后,游戏是否真的记录下了新的录像步」。为了让这个检查覆盖到正确的方法,可以用两个方法装饰器给方法打标: - `@shouldReplay(message)`:把方法标记为**应该计入录像**。它会为这次检查登记一段区间,并把 `message` 作为信息记录;一旦检查发现问题,就用这些信息告诉你哪些调用路径没有被计入录像; - `@ignoreReplay(message)`:把方法标记为**忽略本次录像检查**,一般用于异步内容等不会立刻修改状态的方法。 检测区间的开始与结束由录像系统在每步录像执行前后统一控制:执行前开始收集,执行后结束并核对区间内是否产生了新的录像步。若没有,就在控制台输出告警,并列出登记过却未被计入的 `@shouldReplay` 路径。 :::warning 不要用 `@ignoreReplay` 来掩盖控制台的录像告警。它只应用于「确实不该计入录像」的方法,滥用很可能导致录像出错。 ::: > 录像指令的更多细节(内置指令各自怎么实现、录像格式与沙盒机制等)会在**高级教程**中讲解(预留标记:`高级教程 · 录像指令`);待高级教程就位后,此处会替换为链接。