打断传播语义(已定稿,M5)¶
1. 为什么要把"打断"显式化¶
多段技能被打断时,"先停哪个、哪些必须做完"直接决定两件事: - 表现是否突兀(特效还在放、位移突然截断) - 关键逻辑会不会丢(收尾 GE 没落地、资源没回收)
Able 的中断是"遍历活跃列表逐个结束",顺序由遍历决定——不可控。 UAK 把它显式化为三条规则。
2. 规则一:结束顺序(EAKInterruptOrder)¶
| 值 | 语义 | 适用 |
|---|---|---|
ReverseStartOrder(默认) |
后启动的先结束(栈式回退) | 大多数技能:回退到"打断前那一刻" |
StartOrder |
按启动顺序结束 | 需要保证前段先收尾的链式技能 |
TrackPriority |
先停表现,再停逻辑,最后停主时序 | 表现丰富的技能,避免特效残留 |
TrackPriority 的顺序:
Presentation(0) → Motion(1) → Query(2) → Effect(3) → Custom(4) → Rule(5) → Main(6)
实现上依赖 FAKActiveTask::StartOrder,因此 EndDueTasks 用 RemoveAt 而非 RemoveAtSwap
——保序是中断顺序正确性的前提(这是一处容易被忽略的坑)。
3. 规则二:轨道被停用 = 取消,不是中断¶
Branch 把某条轨道停用后,该轨道上已经在跑的任务会被立即结束,且带 bInterrupted = true:
- 语义上是"这个分支被取消了",而不是"整个技能被打断";
- 由
EndTasksOnDisabledTracks()每帧检查,因此分支生效的下一帧就会收敛; - 会在 Trace 上记一条"所属轨道被 Branch 停用而取消",调试时能区分两种结束原因。
注意 DisabledTracks 只影响尚未启动的任务(在 StartDueTasks 里过滤)与正在运行的任务(上面这条),
已完成的单帧任务不会被回溯。
4. 规则三:保证执行(bAlwaysExecute)¶
任务可标记"即使被中断也保证至少执行一次":
| 情况 | 行为 |
|---|---|
| 中断时正在运行 | 以 bInterrupted = false 结束(正常收尾,不突兀) |
| 中断时还没轮到 | 补执行一次 OnTaskStart → OnTaskEnd(false) |
| 所属轨道已被 Branch 停用 | 跳过(分支明确关闭了它,不违背分支语义) |
| 本端不该执行(NetMode) | 跳过 |
典型用途:必中的收尾 GE、资源回收、必须发出的关键 Cue。
Trace 上会记"技能被中断,但标记为保证执行,补执行一次",避免事后误判为漏配。
5. 门控与中断¶
WaitUntil 这类门控任务在中断时必须解除门控,否则时间轴会永久冻结。
由于 Finish() 会清空 ActiveTasks,门控必然随之解除——
但这一点容易在后续改动中被破坏,因此实现上把"清空活跃列表"放在 Finish 的第一步之后立即执行。
6. 循环重启不是中断¶
RestartLoop() 结束存活任务时传 bInterrupted = false(正常结束),
因为循环是技能自身的正常流程,不是被打断。它同样遵守 InterruptOrder。
7. 中断来源¶
| 来源 | 路径 |
|---|---|
| 技能被取消 / 打断 | GAS CancelAbility → EndAbility(bWasCancelled=true) → Finish(true) |
| 技能正常结束 | EndAbility → Finish(bWasCancelled) |
| 时间轴跑完 | Tick → Finish(false) |
| 分支停用轨道 | EndTasksOnDisabledTracks() → 单任务 OnTaskEnd(true) |
8. 已知限制¶
- 中断不做"回滚":已经施加的 GE 不会撤销(GAS 的回滚由
FPredictionKey负责,UAK 不介入)。 bAlwaysExecute的任务若自身又依赖前序结果(例如依赖 Query 的目标),补执行时可能拿不到目标—— 建议这类任务自带兜底(如ApplyGameplayEffect的bApplyToSelfIfNoTarget)。- 复合轨道被打断时,Repeat 剩余轮次直接丢弃(不补跑)。