UE5.8 Chooser 喂饭级专题

UE5.8 Chooser 专题(四):列、过滤、评分、随机和输出,表格怎么真正选中一行

拆解 Bool、Enum、Name、Object、ObjectClass、FloatRange、FloatDistance、GameplayTag、GameplayTagQuery、Randomize 和 Output 列的使用场景。

总览

Chooser 表里的列不是装饰。运行时会从全部未禁用行开始,把候选索引放进 FChooserIndexArray,然后每个过滤列拿输入索引、输出新索引。最后剩下的行按成本或随机权重处理,命中后再执行输出列。

UE5.8 Chooser 专题(四):列、过滤、评分、随机和输出,表格怎么真正选中一行 配图
每一列都是一次候选行处理:过滤列筛掉,评分列排序,输出列把结果参数写回去。

列的三种职责

FChooserColumnBase 里有三类关键函数:

函数含义
Filter根据上下文筛选候选行
HasCosts这一列会给行加成本或排序依据
SetOutputs命中某行后,把该行输出写回 Context

所以一张表里可以同时有条件列、评分列、输出列。

常用过滤列

适合
Bool是否在空中、是否锁定目标、是否可连段
Enum / Enum(Or)姿态、武器类型、AI 警戒等级
Name轻量名称匹配,如 SlotName、SectionName
Object当前武器对象、目标对象是否等于某对象
Object Class目标是否是某类敌人、武器是否继承某类
Float Range距离、速度、角度、资源值范围
Gameplay Tag输入标签、武器标签、技能标签
Gameplay Tag Query“包含 A 且不包含 B”这类组合条件

技能连招里最常用的是 GameplayTag、GameplayTagQuery、FloatRange 和 Bool。

Float Range vs Float Difference

Float Range 是硬过滤:距离不在范围内就淘汰。比如 0-300 才能近战。

Float Difference 是评分:多个候选都能用时,谁的目标值更接近输入值,谁成本更低。比如按角色速度选择 Start/Stop/Turn 动画,Speed=430 时更接近 Jog 而不是 Walk。

Randomize 列

Randomize 列会在剩余候选里按权重随机。它不是先随机再检查条件,而是在过滤之后工作,所以很适合:

  • 同一段轻攻击有 3 个等价表现变体。
  • AI 近战命中后随机选吼叫、挥砍、后跳。
  • 命中特效或音效做轻微变化。

如果绑定了 RandomizationContext,还可以降低重复选中同一项的概率。动作游戏里这能避免连续播放同一个受击动画。

Output 列

Output 列命中后写值,不一定改变 Result。常见输出:

输出用途
Output NameMontage Section、SocketName
Output Float消耗倍率、播放速度、镜头强度
Output Bool是否需要 Root Motion、是否允许取消
Output GameplayTagCamera Cue、SFX Cue、Combo Step Tag
Output Object附带相机 Rig、特效资产、子 Chooser
Output Struct一次写一组复杂参数

推荐把技能表现参数整理到 FComboChooserOutput,用 Output Struct 写入。这样行越多,表越整齐。

列顺序建议

从便宜、稳定、强约束到复杂、表现型:

InputTag -> WeaponTag -> PreviousSkillTag -> 状态 Bool -> TagQuery -> FloatRange -> Scoring/Randomize -> Output

虽然运行时都会逐列执行,但这个顺序更符合读表习惯。把 Output 放后面也符合“先选中,再写出”的心智模型。

使用案例:轻攻击连段列设计

绑定目的
GameplayTagInputTag只响应 Light
GameplayTagWeaponTag区分 Sword/Spear/Dagger
GameplayTagPreviousSkillTag决定第几段
GameplayTagQueryPlayerTags排除 Stunned、Silenced、Rooted
FloatRangeTargetDistance近战、冲刺、远距派生
BoolbIsInAir地面/空中分支
RandomizeRandomContext同条件变体
OutputStructComboOutput写 Section、Cost、Cue

架构分析

列设计是把复杂判断拆成可读步骤。每一列都应该回答一个明确问题:输入对吗?武器对吗?上一段对吗?距离合适吗?状态允许吗?如果一列需要解释半天,说明 Context 或表拆分可能有问题。

常见坑

  • 第一列就用复杂 TagQuery,后面读表的人不知道基础输入是什么。
  • MatchAny 用太多,导致表看起来命中但规则实际很松。
  • 同一张表混用技能、动画、音效三类选择,输出列爆炸。
  • Randomize 权重默认都一样,却以为某行会更容易中。
  • Output 写了参数,但执行技能时没读取这些输出。

源码依据

FChooserColumnBase 定义 FilterHasCostsHasOutputsSetOutputsUChooserTable::EvaluateChooser 先构造所有候选行,再按列过滤,若候选有成本则排序,命中行后先执行所有列的 SetOutputs,再调用 Result 的 ChooseMulti。Fallback 命中时也会触发输出列的 fallback/default 输出。

源码路径索引

  • Engine/Plugins/Chooser/Source/Chooser/Public/IChooserColumn.h
  • Engine/Plugins/Chooser/Source/Chooser/Private/Chooser.cpp
  • Engine/Plugins/Chooser/Source/Chooser/Internal/BoolColumn.h
  • Engine/Plugins/Chooser/Source/Chooser/Internal/FloatRangeColumn.h
  • Engine/Plugins/Chooser/Source/Chooser/Internal/FloatDistanceColumn.h
  • Engine/Plugins/Chooser/Source/Chooser/Internal/GameplayTagColumn.h
  • Engine/Plugins/Chooser/Source/Chooser/Internal/GameplayTagQueryColumn.h
  • Engine/Plugins/Chooser/Source/Chooser/Internal/RandomizeColumn.h
  • Engine/Plugins/Chooser/Source/Chooser/Internal/OutputStructColumn.h