总览
第一篇先不追源码,目标是跑通一个最小闭环:玩家按轻攻击,系统根据当前武器、上一段技能、目标距离和是否在空中,选出下一段 Ability 或 Montage。
第一步:启用插件
编辑器里启用 Chooser。如果你要跟玩家连招、技能和输入系统集成,通常还会启用:
- Gameplay Abilities
- GameplayTags
- Enhanced Input
- Common UI,如果要做玩家编辑连招界面
C++ 模块最小依赖:
PrivateDependencyModuleNames.AddRange(new[]
{
"Chooser",
"GameplayTags",
"GameplayAbilities"
});
如果要使用 ProxyTable,再加 ProxyTable。
第二步:设计最小 Context
不要一上来把整个 Character 所有字段都暴露给 Chooser。先建一个小结构体,只放这张表需要的东西:
USTRUCT(BlueprintType)
struct FComboChooserContext
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FGameplayTag InputTag;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FGameplayTag PreviousSkillTag;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FGameplayTag WeaponTag;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FGameplayTagContainer OwnerTags;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float TargetDistance = 0.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
bool bIsInAir = false;
};
再准备一个输出结构体:
USTRUCT(BlueprintType)
struct FComboChooserOutput
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FName MontageSection = NAME_None;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float CostMultiplier = 1.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FGameplayTag CameraCueTag;
};
Result 可以先返回 UGameplayAbility 子类,也可以返回一个 UComboMoveData 数据资产。新手更推荐返回数据资产,里面再引用 Ability、Montage、Cue、说明文本,后期扩展更稳。
第三步:创建 Chooser Table
内容浏览器里创建 Chooser Table,例如 CHT_Combo_NextMove。设置:
| 字段 | 建议 |
|---|---|
| Result Type | SubClass Of 或 Object Of Type |
| Result Class | UGameplayAbility、UAnimMontage 或你的 UComboMoveData |
| Parameters | 添加 FComboChooserContext,需要输出时再添加 FComboChooserOutput |
如果 Result 是 Ability Class,就选 SubClass Of,Result Class 填你的 UGameplayAbility 基类。如果 Result 是数据资产,就选 Object Of Type。
第四步:加列
第一张表建议这样配:
| 列 | 绑定 | 示例格子 |
|---|---|---|
| Gameplay Tag | Context.InputTag | Input.Attack.Light |
| Gameplay Tag | Context.PreviousSkillTag | Skill.Combo.Light.01 |
| Gameplay Tag | Context.WeaponTag | Weapon.Sword |
| Float Range | Context.TargetDistance | 0 到 350 |
| Bool | Context.bIsInAir | MatchFalse / MatchTrue |
| Gameplay Tag Query | Context.OwnerTags | 不包含 State.Stunned |
| Output Struct | FComboChooserOutput | Section_Light_02、Cost=1.1 |
行可以这样填:
| Input | Previous | Weapon | Distance | InAir | Result |
|---|---|---|---|---|---|
| Light | None | Sword | 0-300 | false | GA_SwordLight01 |
| Light | Light01 | Sword | 0-300 | false | GA_SwordLight02 |
| Light | Light02 | Sword | 0-300 | false | GA_SwordLight03 |
| Light | Any | Sword | 300-700 | false | GA_SwordDashSlash |
| Light | Any | Sword | Any | true | GA_SwordAirSlash |
第五步:蓝图里先跑通
蓝图侧最简单流程:
- Make Chooser Evaluation Context。
- Add Chooser Object Input,传角色或 AnimInstance。
- Add Chooser Struct Input,传
FComboChooserContext。 - Add Chooser Struct Input Output,传
FComboChooserOutput。 - Make Evaluate Chooser。
- Evaluate Object Chooser Base,ObjectClass 选 Ability 或数据资产类。
- Get Chooser Struct Output,拿到输出参数。
如果你只传一个 ContextObject,也可以用 EvaluateChooser(ContextObject, ChooserTable, ObjectClass) 这种简单节点;但技能连招通常需要结构体输入和输出,所以建议从一开始就学 Evaluation Context 版本。
C++ 最小调用
FComboChooserContext ComboContext;
ComboContext.InputTag = TAG_Input_Attack_Light;
ComboContext.PreviousSkillTag = CurrentComboStepTag;
ComboContext.WeaponTag = CurrentWeaponTag;
ComboContext.OwnerTags = AbilitySystem->GetOwnedGameplayTags();
ComboContext.TargetDistance = DistanceToTarget;
ComboContext.bIsInAir = Movement->IsFalling();
FComboChooserOutput ComboOutput;
FChooserEvaluationContext EvalContext;
EvalContext.AddObjectParam(AvatarActor);
EvalContext.AddStructParam(ComboContext);
EvalContext.AddStructParam(ComboOutput);
UClass* SelectedAbilityClass = nullptr;
UChooserTable::EvaluateChooser(
EvalContext,
ComboChooser,
FObjectChooserBase::FObjectChooserIteratorCallback::CreateLambda(
[&SelectedAbilityClass](UObject* Result)
{
SelectedAbilityClass = Cast<UClass>(Result);
return SelectedAbilityClass
? FObjectChooserBase::EIteratorStatus::Stop
: FObjectChooserBase::EIteratorStatus::Failed;
}));
项目里你还要做空指针检查、类型检查、GAS 冷却检查和日志,这里先把主线看清楚。
使用案例:三段轻攻击
玩家第一次按 Light,PreviousSkillTag=None,表命中 GA_SwordLight01。技能激活成功后,把当前连招状态记成 Skill.Combo.Light.01。第二次按 Light,表命中 GA_SwordLight02。如果第二次按键时目标距离变成 500,表可以改命中 GA_SwordDashSlash。这就是 Chooser 的价值:不是写死“第二段一定是 02”,而是把“第二段在什么条件下是什么”放到可调表里。
架构分析
第一张表不要追求完整。你先证明四件事:Context 能正确传进来,列能过滤,Result 能返回,输出参数能写出来。之后再把输入缓冲、玩家自定义、GAS 激活、动画播放接进来。这样排错会简单很多。
常见坑
- Result Type 选错:想返回 Ability Class 却用 Object Result。
- Context 结构体没加入 Parameters,列绑定不到字段。
- Output Struct 只 Add Struct Input,没有可写绑定或没有读回输出。
- 第一行 MatchAny 太宽,导致后面的具体行永远没机会。
- Chooser 返回结果后直接激活技能,没让 GAS 再做冷却、资源和权限检查。
源码依据
UChooserFunctionLibrary 提供 MakeChooserEvaluationContext、AddChooserObjectInput、AddChooserStructInput、AddChooserStructInputOutput、EvaluateObjectChooserBase 和输出读取函数。UChooserTable::EvaluateChooser 接收 FChooserEvaluationContext 和回调。GameplayTag、FloatRange、Bool、OutputStruct 都是 FChooserColumnBase 的具体列实现。
源码路径索引
Engine/Plugins/Chooser/Chooser.upluginEngine/Plugins/Chooser/Source/Chooser/Public/Chooser.hEngine/Plugins/Chooser/Source/Chooser/Public/ChooserFunctionLibrary.hEngine/Plugins/Chooser/Source/Chooser/Internal/GameplayTagColumn.hEngine/Plugins/Chooser/Source/Chooser/Internal/FloatRangeColumn.hEngine/Plugins/Chooser/Source/Chooser/Internal/BoolColumn.hEngine/Plugins/Chooser/Source/Chooser/Internal/OutputStructColumn.h