UE5.8 Chooser 喂饭级专题

UE5.8 Chooser 专题(一):先做一张能选择下一段技能的表

从启用 Chooser 插件、创建 Chooser Table、设计 Combo Context、添加 GameplayTag/Float/Bool 列、返回 Ability Class 或 Montage,到用蓝图跑通第一条连招。

总览

第一篇先不追源码,目标是跑通一个最小闭环:玩家按轻攻击,系统根据当前武器、上一段技能、目标距离和是否在空中,选出下一段 Ability 或 Montage。

UE5.8 Chooser 专题(一):先做一张能选择下一段技能的表 配图
第一张表只做一件事:玩家按下攻击键后,根据上下文选出下一段技能。

第一步:启用插件

编辑器里启用 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 TypeSubClass OfObject Of Type
Result ClassUGameplayAbilityUAnimMontage 或你的 UComboMoveData
Parameters添加 FComboChooserContext,需要输出时再添加 FComboChooserOutput

如果 Result 是 Ability Class,就选 SubClass Of,Result Class 填你的 UGameplayAbility 基类。如果 Result 是数据资产,就选 Object Of Type

第四步:加列

第一张表建议这样配:

绑定示例格子
Gameplay TagContext.InputTagInput.Attack.Light
Gameplay TagContext.PreviousSkillTagSkill.Combo.Light.01
Gameplay TagContext.WeaponTagWeapon.Sword
Float RangeContext.TargetDistance0 到 350
BoolContext.bIsInAirMatchFalse / MatchTrue
Gameplay Tag QueryContext.OwnerTags不包含 State.Stunned
Output StructFComboChooserOutputSection_Light_02、Cost=1.1

行可以这样填:

InputPreviousWeaponDistanceInAirResult
LightNoneSword0-300falseGA_SwordLight01
LightLight01Sword0-300falseGA_SwordLight02
LightLight02Sword0-300falseGA_SwordLight03
LightAnySword300-700falseGA_SwordDashSlash
LightAnySwordAnytrueGA_SwordAirSlash

第五步:蓝图里先跑通

蓝图侧最简单流程:

  1. Make Chooser Evaluation Context。
  2. Add Chooser Object Input,传角色或 AnimInstance。
  3. Add Chooser Struct Input,传 FComboChooserContext
  4. Add Chooser Struct Input Output,传 FComboChooserOutput
  5. Make Evaluate Chooser。
  6. Evaluate Object Chooser Base,ObjectClass 选 Ability 或数据资产类。
  7. 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 提供 MakeChooserEvaluationContextAddChooserObjectInputAddChooserStructInputAddChooserStructInputOutputEvaluateObjectChooserBase 和输出读取函数。UChooserTable::EvaluateChooser 接收 FChooserEvaluationContext 和回调。GameplayTag、FloatRange、Bool、OutputStruct 都是 FChooserColumnBase 的具体列实现。

源码路径索引

  • Engine/Plugins/Chooser/Chooser.uplugin
  • Engine/Plugins/Chooser/Source/Chooser/Public/Chooser.h
  • Engine/Plugins/Chooser/Source/Chooser/Public/ChooserFunctionLibrary.h
  • Engine/Plugins/Chooser/Source/Chooser/Internal/GameplayTagColumn.h
  • Engine/Plugins/Chooser/Source/Chooser/Internal/FloatRangeColumn.h
  • Engine/Plugins/Chooser/Source/Chooser/Internal/BoolColumn.h
  • Engine/Plugins/Chooser/Source/Chooser/Internal/OutputStructColumn.h