UE5.8 Unreal Animation Framework 专题系列

UE5.8 UAF 专题(七):StateTree 决策、图切换与变量任务

分析 UAnimNextStateTree、FStateTreeTrait、UStateTreeAnimNextSchema、FUAFStateTreeNode、Set Variable Task 以及用 StateTree 驱动动画图。

总览

UAFStateTree 的价值在于把动画决策从图里拆出来。复杂角色很容易把“能不能跑、拿什么武器、是否空中、是否受击、是否瞄准、是否剧情锁定”堆成一团布尔分支。StateTree 更适合承载这些决策,再由 UAF 执行对应动画图或写变量。

UE5.8 UAF 专题(七):StateTree 决策、图切换与变量任务 配图
StateTree 适合承载动画决策和图切换,UAF 把它包装成 AnimGraph 资产和 AnimNode 数据。

源码依据

重点文件:

  • UAFStateTree/Internal/AnimNextStateTree.h
  • UAFStateTree/Internal/AnimStateTreeTrait.h
  • UAFStateTree/Internal/AnimNextStateTreeSchema.h
  • UAFStateTree/Internal/AnimNode/UAFStateTreeNode.h
  • UAFStateTree/Internal/Tasks/UAFStateTreeSetVariableTask.h
  • UAFStateTree/Internal/Tasks/AnimNextStateTreeGraphInstanceTask.h

UAnimNextStateTree 继承 UUAFAnimGraph,内部持有 UStateTree* StateTree。这意味着它在资产层面仍是一个 UAF AnimGraph,但决策内容由 StateTree 负责。

StateTree Trait

FAnimNextStateTreeTraitSharedData 保存:

  • FStateTreeReference StateTreeReference
  • FStateTreeReferenceOverrides LinkedStateTreeOverrides

FStateTreeTrait 继承 FAdditiveTrait,实现 IUpdateIGarbageCollection。实例数据里有:

  • TObjectPtr<const UStateTree> StateTree
  • FStateTreeInstanceData InstanceData
  • FStateTreeExecutionContext::FExternalGlobalParameters StateTreeExternalParameters

这说明 StateTree 的执行状态是每图实例独立的,适合做动画决策,但不要把全局 gameplay 状态塞进去当单例用。

Schema 限制

UStateTreeAnimNextSchema 继承 UStateTreeSchema。源码里它覆盖:

  • IsStructAllowed
  • IsClassAllowed
  • IsExternalItemAllowed
  • GetContextDataDescs
  • GetGlobalParameterDataType

编辑器里还禁用了 Utility Considerations、Evaluators 和 Queued Compilation。这说明 UAF StateTree 是一个受控子集,不是完整开放的通用 StateTree 工作区。这个限制对生产反而是好事:动画决策应该保持可预测、可编译、可回放。

StateTree AnimNode

FUAFStateTreeNode 继承 FUAFBlendStack。它持有 UStateTreeFStateTreeInstanceData 和 external parameters,并提供:

  • PreUpdate
  • BlendTo(Context, TConstStructView<FUAFGraphFactoryAsset> AssetData, FUAFTransitionNodeData* TransitionBlend)
  • AddReferencedObjects

FUAFStateTreeNodeData 则是 FUAFAnimNodeData,可以引用 UAnimNextStateTree。这条路径让 StateTree 既能作为 Trait,也能作为 AnimNode 风格的图切换节点。

Set Variable Task

FUAFStateTreeSetVariableTask 是非常实用的桥。它的实例数据:

USTRUCT()
struct FUAFStateTreeSetVariableTaskInstanceData
{
    GENERATED_BODY()

    UPROPERTY(EditAnywhere, Category = Variable)
    FAnimNextVariableReference Variable;

    UPROPERTY(EditAnywhere, Category = Variable, Meta = (FixedLayout, ShowOnlyInnerProperties))
    FInstancedPropertyBag Value;
};

注释写得很清楚:这个任务在 state entry 时写一个 UAF shared variable;如果要写多个变量,就在一个 state 中叠多个 task。它适合做“进入 Aiming 状态时设置 AimAlphaTarget”、“进入 Injured 状态时设置 LayerWeight”、“进入 Weapon_Rifle 状态时设置 StanceName”这类决策输出。

项目落地

推荐拆分:

  1. Gameplay 状态仍由 gameplay 系统权威维护。
  2. UAF 公共变量接收 gameplay 的摘要数据。
  3. StateTree 根据摘要数据做动画状态决策。
  4. StateTree 进入状态时通过 Set Variable Task 写动画变量。
  5. 图切换通过 UAF Graph Factory Asset 和 BlendStack 完成。

示意结构:

Gameplay / Movement
  -> UAF public variables
  -> UAnimNextStateTree
  -> State entry tasks
  -> UAF graph variables / BlendTo graph
  -> AnimGraph evaluation

使用案例:武器和瞄准动画决策

假设角色有空手、步枪、大剑三种武器,还有瞄准、换弹、受击三个覆盖状态。不要把这些分支全放进 AnimGraph。可以建一个 ST_Hero_AnimDecision_UAF

Root
  Locomotion
    Grounded
    InAir
  Weapon
    Unarmed
    Rifle
      RifleHip
      RifleAim
      RifleReload
    GreatSword
  Overlay
    None
    HitReact
    CinematicLock

每个状态进入时只写动画变量:

State Task 写入值
RifleHip UAF Set Variable WeaponStance = RifleOverlayState = None
RifleAim UAF Set Variable WeaponStance = RifleAimAlphaTarget = 1
RifleReload UAF Set Variable OverlayState = ReloadUpperBodyWeight = 1
HitReact UAF Set Variable OverlayState = HitReactHitLayerWeight = 1

AnimGraph 之后读取这些变量:Chooser 根据 WeaponStance 选上半身图,Layering 根据 OverlayState 和 layer weight 播放覆盖层。这样 StateTree 管“现在应该表现成什么状态”,AnimGraph 管“姿态如何混合”。

操作步骤

  1. 创建 UAF State Tree 资产。源码里的资产定义显示它在 Animation / Animation Framework 分类下,显示名是 UAF State Tree
  2. 在 StateTree schema 可用的上下文里接入 UAF 变量上下文。
  3. 为每个离散动画状态建 state,不要把连续速度、方向做成 state。
  4. 每个 state 进入时添加一个或多个 UAF Set Variable task。
  5. 过渡条件读取 gameplay 摘要变量,例如 bIsInAirWeaponStancebWantsAim
  6. 图切换由 UAF Graph Factory Asset 或下游 Chooser 完成,StateTree 不直接处理 pose。
  7. 给每个状态配置明确的 fallback,例如武器状态未知时回到 Unarmed

架构分析:StateTree 和 Chooser 谁先谁后

两种组合都能用,但用途不同:

组合 适合场景 示例
StateTree 先决策,Chooser 后选资产 状态生命周期重要,需要 entry task 瞄准进入时写 AimAlphaTarget,再由 Chooser 选 RifleAim 图
Chooser 先选大类图,StateTree 在子图内决策 条件主要是资产差异 角色体型或武器先选子图,子图内再管 reload/aim
两者并行,分别管不同层 大型角色系统 locomotion Chooser,upper body StateTree

判断标准:如果你关心“进入状态时做一次事”,用 StateTree;如果你关心“根据一组条件选一个结果”,用 Chooser。

使用建议

  • StateTree 负责“选择什么”,AnimGraph 负责“怎么混合和评估”。
  • 变量 task 负责状态入口的离散写入,不适合每帧连续驱动。
  • 连续值如速度、方向、坡度仍应由 movement 每帧写入公共变量。
  • 图切换时给 transition data,避免状态切换变硬切。
  • 每个 StateTree 资产只负责一个清晰层级,例如 locomotion、weapon stance、hit reaction,不要一个表管全角色。

常见坑

  • StateTree 不是 gameplay authority。不要让动画 StateTree 决定角色是否真的能攻击。
  • UStateTreeAnimNextSchema 有限制,不要照搬通用 StateTree 用法。
  • Set Variable Task 的 FInstancedPropertyBag 要和变量类型匹配;变量类型变化后要重新检查任务值。
  • Graph switch 和 variable write 同时发生时,要明确顺序和过渡策略。
  • StateTree 实例状态不能跨角色共享,调试时要看具体 UAF 图实例。

源码路径索引

  • UAFStateTree/Source/UAFStateTree/Internal/AnimNextStateTree.h
  • UAFStateTree/Source/UAFStateTree/Internal/AnimStateTreeTrait.h
  • UAFStateTree/Source/UAFStateTree/Internal/AnimNextStateTreeSchema.h
  • UAFStateTree/Source/UAFStateTree/Internal/Tasks/UAFStateTreeSetVariableTask.h