总览
Chooser 表越多,越要建立规范。生产级 Chooser 不只是“能选出东西”,还要能回答:为什么选了这一行?为什么没选?Cook 后数据还在吗?玩家旧档案会不会导致空结果?多人下客户端和服务器会不会选出不同动作?
命名规范
推荐:
CHT_Combo_NextMove_Sword
CHT_Combo_EditCandidates_Sword
CHT_Anim_Locomotion_Start
CHT_AI_MeleeAttack_Select
PXA_Move_Light02_Montage
PT_Skin_Samurai_ComboAssets
前缀区分资产类型,后缀表达用途。不要叫 NewChooserTable_12,调试 Trace 时会很难受。
表拆分规范
一张表只解决一个问题:
| 表 | 职责 |
|---|---|
| NextMove | 按战斗上下文选下一段技能 |
| EditCandidates | 给 UI 列出可编辑候选 |
| AnimationVariant | 为已确定 Move 选动画变体 |
| CameraCue | 为已确定动作选相机参数 |
如果一张表里同时决定技能、动画、镜头、音效、掉落,后期会很难读。
Debug Target 和选中行
UChooserTable 编辑器期有 Debug Target、Recent Context Objects、Debug Selected Rows 等字段。运行时 Evaluate 时,表会更新调试信息,编辑器能显示哪些行通过或失败。
调试顺序建议:
- 确认 Context Object 是否传对。
- 确认结构体参数是否出现在 Parameters。
- 打开测试值,看每列是否按预期通过。
- 看最终 Selected Row。
- 如果走 Fallback,逐列缩小是哪一列筛掉了所有行。
Trace 和 Rewind Debugger
源码里有 ChooserTrace.cpp、RewindDebuggerChooserRuntime.cpp 和编辑器侧 Rewind Debugger 支持。项目调试时可以把 Chooser 命中看作时间线事件:某一帧、某个角色、某张表、命中哪一行。动作游戏调连招时,这比只看日志舒服很多。
Cook 行为
UChooserTable 编辑器期有 ResultsStructs、DisabledRows、NestedObjects。Cook 时会调用 PopulateCookedData,把未禁用结果写到 CookedResults,并移除禁用行和无效列。运行时在 Cooked 包里用 CookedResults。
这意味着:
- 禁用行不会成为运行时逻辑的一部分。
- 编辑器里测试和 Cook 后测试都要做。
- 动态引用的资产仍然要进入正确 Cook 边界。
性能预算
Chooser 通常不贵,但别乱用:
| 场景 | 建议 |
|---|---|
| 玩家按键解析 | 可以按输入触发 Evaluate |
| 动画 OnBecomeRelevant | 通常没问题 |
| 动画 OnUpdate 每帧 | 表要小,Context 要轻 |
| Mass 大批实体 | 谨慎,优先批处理/缓存分类 |
| UI 候选列表 | 打开面板时算,别每帧刷 |
不要在列绑定里触发昂贵函数或同步加载。Context 应该提前准备好轻量字段。
多人一致性
玩家编辑连招项目里,服务器和客户端都可能需要解析。要注意:
- 玩家档案由服务器校验和同步。
- 随机选择要么只在服务器做,要么使用可复现 RandomStream。
- 客户端预测可以先选,但服务器拒绝时要回滚。
- Ability 最终仍由 GAS 权威判断。
- 动画变体可以客户端表现不同,但不能影响伤害判定。
上线检查清单
- 每张表都有 Fallback 或明确的空结果处理。
- Context Struct 有版本意识,字段改名有迁移方案。
- 玩家 SaveGame 只存稳定 ID/Tag,不存易变对象路径。
- 编辑候选表和战斗解析表规则一致或有明确差异。
- Result 类型和调用侧类型检查一致。
- Cook 包里资源存在,禁用行符合预期。
- Trace 或日志能定位命中行。
- ProxyTable 停用后没有强引用残留。
- GAS 冷却、消耗、Tag 阻塞仍是最终权威。
使用案例:排查玩家“第二段不出招”
先看输入缓冲是否产生 Input.Attack.Light。再看玩家档案里第二槽 MoveTag 是否存在。然后用 Debug Target 选中该玩家,Evaluate CHT_Combo_NextMove,看 PreviousMoveTag 是否是第一段,TargetDistance 是否落在范围里,PlayerTags 是否包含 Stunned。若全部通过但 GAS 拒绝,再看冷却、Cost、ActivationBlockedTags。这样排查路径很清晰,不会在表、输入、动画、GAS 之间来回猜。
架构分析
Chooser 的可维护性来自三件事:小 Context、小表、清楚的执行边界。Context 太大,表会脆;表太大,调试会慢;边界不清,GAS、动画、UI 会互相推锅。生产规范的目的不是让表变保守,而是让它能被多人长期修改。
常见坑
- 只在 Editor PIE 测试,Cook 后
CookedResults或资源引用出问题。 - 没 Fallback,玩家旧档案一升级就空结果。
- 每帧 Evaluate 大表,并且绑定深层对象属性。
- 随机结果客户端和服务器不一致。
- ProxyTable 继承链和 Nested Chooser 太深,调试选中行无法解释。
源码依据
UChooserTable::PopulateCookedData 会在 Cook 时移除禁用数据并填充 CookedResults。EvaluateChooser 在编辑器下会更新 Debugging,并记录 Selected Rows。ChooserTrace 和 Rewind Debugger 相关源码提供运行时追踪支持。CHOOSER_TRACE_ENABLED 在非 Shipping/Test 且启用 Trace 时打开,Shipping 构建不要依赖调试信息。
源码路径索引
Engine/Plugins/Chooser/Source/Chooser/Public/Chooser.hEngine/Plugins/Chooser/Source/Chooser/Private/Chooser.cppEngine/Plugins/Chooser/Source/Chooser/Private/ChooserTrace.cppEngine/Plugins/Chooser/Source/Chooser/Private/RewindDebuggerChooserRuntime.cppEngine/Plugins/Chooser/Source/ChooserEditor/Private/RewindDebuggerChooser.cppEngine/Plugins/Chooser/Source/ChooserEditor/Private/ChooserAnalyzer.cppEngine/Plugins/Chooser/Source/Chooser/Public/IObjectChooser.h