UE5.8 Chooser 喂饭级专题

UE5.8 Chooser 专题(六):蓝图与 C++ 运行时调用和缓存

从简单 EvaluateChooser 到 FChooserEvaluationContext、AddObjectParam、AddStructParam、EvaluateObjectChooserBase、EvaluateChooserMulti、输出读取、缓存与线程注意事项。

总览

Chooser 资产不会自己运行。你需要在技能组件、动画实例、AI Task 或 UI 页面里构造 Context,调用表,检查结果,再把结果交给 GAS、动画或 UI。

UE5.8 Chooser 专题(六):蓝图与 C++ 运行时调用和缓存 配图
表格只是资产;真正稳定落地靠运行时代码把 Context 构造、调用、校验和执行串起来。

蓝图调用有两条路线

简单路线:

Evaluate Chooser(ContextObject, ChooserTable, ObjectClass)

适合只需要一个对象输入的表,比如 AnimInstance 里根据自身属性选动画。

完整路线:

Make Chooser Evaluation Context
Add Chooser Object Input
Add Chooser Struct Input
Add Chooser Struct Input Output
Make Evaluate Chooser
Evaluate Object Chooser Base
Get Chooser Struct Output

技能连招、玩家配置、输出参数、多结果都应该用完整路线。

C++ 单结果模板

UComboMoveData* UComboResolverComponent::ResolveNextMove(
    const FComboChooserContext& Input,
    FComboChooserOutput& Output) const
{
    if (!ComboChooser)
    {
        return nullptr;
    }

    FComboChooserContext InputCopy = Input;
    FChooserEvaluationContext Context;
    Context.AddObjectParam(GetOwner());
    Context.AddStructParam(InputCopy);
    Context.AddStructParam(Output);

    UComboMoveData* ResultMove = nullptr;
    UChooserTable::EvaluateChooser(
        Context,
        ComboChooser,
        FObjectChooserBase::FObjectChooserIteratorCallback::CreateLambda(
            [&ResultMove](UObject* Result)
            {
                ResultMove = Cast<UComboMoveData>(Result);
                return ResultMove
                    ? FObjectChooserBase::EIteratorStatus::Stop
                    : FObjectChooserBase::EIteratorStatus::Failed;
            }));

    return ResultMove;
}

注意 AddStructParam 持有的是引用视图,结构体生命周期必须长于本次 Evaluate。不要传临时右值。

C++ 多结果模板

TArray<UComboMoveData*> Results;

UChooserTable::EvaluateChooser(
    Context,
    ComboChooser,
    FObjectChooserBase::FObjectChooserIteratorCallback::CreateLambda(
        [&Results](UObject* Result)
        {
            if (UComboMoveData* Move = Cast<UComboMoveData>(Result))
            {
                Results.Add(Move);
                return FObjectChooserBase::EIteratorStatus::Continue;
            }
            return FObjectChooserBase::EIteratorStatus::Failed;
        }));

返回 Continue 会继续找后面的结果,返回 Stop 会停止,返回 Failed 会告诉 Chooser 这行没有成功。

用 Function Library 包装 ObjectChooserBase

如果你把 Chooser 表包装成 FInstancedStruct,可以用:

FInstancedStruct EvalChooser = UChooserFunctionLibrary::MakeEvaluateChooser(ComboChooser);
UObject* Result = UChooserFunctionLibrary::EvaluateObjectChooserBase(
    Context,
    EvalChooser,
    UComboMoveData::StaticClass(),
    false);

这个路线和蓝图里的 Make Evaluate Chooser 更接近,适合你要在同一接口里支持 EvaluateChooser、LookupProxy、Nested 等多种 ObjectChooserBase。

缓存怎么做

可以缓存:

  • ChooserTable 软引用加载后的指针。
  • 常用 GameplayTag。
  • Context Struct 的字段填充逻辑。
  • 玩家连招配置的运行时快照。

不要缓存:

  • FChooserEvaluationContext 本身。
  • 表里返回结果的“最终决定”,除非上下文完全不变。
  • 指向临时输出结构体的引用。

Chooser 每次 Evaluate 依赖当下上下文。技能连招、目标距离、玩家 Tag、冷却状态都可能帧间变化。

使用案例:Ability 中调用

UGameplayAbility 里可以在 Commit 前解析:

FComboChooserOutput Output;
UComboMoveData* Move = ComboResolver->ResolveNextMove(Context, Output);
if (!Move || !CanActivateMove(Move))
{
    EndAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, true, true);
    return;
}

CommitAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo);
PlayMontageSection(Output.MontageSection);

更推荐把解析放在角色的 ComboResolverComponent 或 CombatSubsystem 里,Ability 只消费解析结果。这样 UI、AI、预测和测试也能复用。

架构分析

运行时代码应该是一道“防火墙”。表作者可以填错,玩家配置可能过期,资产可能没加载,Result 类型可能不匹配。调用层必须做空值、类型、Fallback、日志、GAS 合法性和失败提示。Chooser 表越可调,运行时代码越要稳。

常见坑

  • FChooserEvaluationContext 存成成员变量,内部 FStructView 指向已经失效的栈内存。
  • 多结果回调返回 Stop,实际只拿到第一个。
  • 忘记检查 Result 类型,UClass 和 UObject 混用。
  • 输出结构体被拷贝了,读回的是旧值。
  • 动画线程/AnyThread 场景调用了非线程安全的项目对象。

源码依据

UChooserFunctionLibrary::EvaluateChooser 是单对象输入的便捷函数。AddChooserStructInput 蓝图 thunk 把结构体参数作为 FStructView 加入 Context。AddChooserStructInputOutput 还会记录输出数组来源。EvaluateObjectChooserBase 会调用 FObjectChooserBase::ChooseMulti 并按 ObjectClassbResultIsClass 做类型过滤。

源码路径索引

  • Engine/Plugins/Chooser/Source/Chooser/Public/ChooserFunctionLibrary.h
  • Engine/Plugins/Chooser/Source/Chooser/Private/ChooserFunctionLibrary.cpp
  • Engine/Plugins/Chooser/Source/Chooser/Public/Chooser.h
  • Engine/Plugins/Chooser/Source/Chooser/Public/IObjectChooser.h
  • Engine/Plugins/Chooser/Source/Chooser/Private/Chooser.cpp