UE5.8 Unreal Animation Framework 专题系列

UE5.8 UAF 专题(三):AnimGraph、Trait 栈与 EvaluationVM

深入 UUAFAnimGraph、FAnimNextGraphInstance、Trait SharedData/InstanceData、IUpdate、IEvaluate、IHierarchy、EvaluationProgram 和 EvaluationVM。

总览

UAFAnimGraph 是 UAF 最核心也最容易误解的一层。它不是简单复制 AnimBlueprint 节点系统,而是用 Trait 栈描述动画图节点的能力组合:Update 负责推进状态,Evaluate 负责生成 EvaluationProgram,EvaluationVM 再执行任务产出姿态、曲线、属性、通知和 Root Motion。

UE5.8 UAF 专题(三):AnimGraph、Trait 栈与 EvaluationVM 配图
Trait 栈在更新遍历里推进状态,在评估遍历里生成 EvaluationProgram,再交给 VM 产出姿态和属性。

源码依据

重点文件:

  • UAFAnimGraph/Public/Graph/AnimNextAnimationGraph.h
  • UAFAnimGraph/Public/Graph/AnimNextAnimGraph.h
  • UAFAnimGraph/Public/Graph/AnimNextGraphInstance.h
  • UAFAnimGraph/Public/Graph/UAFGraphInstanceComponent.h
  • UAFAnimGraph/Public/TraitCore/Trait.h
  • UAFAnimGraph/Public/TraitCore/TraitSharedData.h
  • UAFAnimGraph/Public/TraitInterfaces/IUpdate.h
  • UAFAnimGraph/Public/TraitInterfaces/IEvaluate.h
  • UAFAnimGraph/Internal/Graph/RigUnit_AnimNextTraitStack.h
  • UAFAnimGraph/Public/EvaluationVM/EvaluationProgram.h
  • UAFAnimGraph/Public/EvaluationVM/EvaluationVM.h

UUAFAnimGraph 继承 UUAFSharedVariables。它可以 PreAllocateInstanceAllocateInstance,返回 TSharedPtr<FAnimNextGraphInstance>。图资产内部保存 EntryPoints、默认 EntryPoint、Resolved Root Trait Handle、SharedDataBuffer、GraphReferencedObjects 和 DefaultState。

Trait 栈是什么

源码里的 FRigUnit_AnimNextTraitStack 注释很直接:它是编辑器里的合成节点,编译时会从 RigVM 图里断开,替换成 cooked trait metadata。也就是说,Trait Stack 是作者ing形态;运行时吃的是编译后的 trait 描述。

一个 Trait 通常包含:

  • FSharedData:图上这个 trait 的静态数据,通常是 USTRUCT,可含 latent property。
  • FInstanceData:每个图实例自己的运行时状态。
  • 接口实现:例如 IUpdateIEvaluateIHierarchyIGarbageCollection
  • 事件处理:通过 OnTraitEvent 传播输入/输出事件。

DECLARE_ANIM_TRAIT 宏会声明 Trait UID、名称、内存布局、接口支持、事件支持和 latent property 支持。这个宏体系看着重,但目的很明确:让 UAF 能把 Trait 以紧凑内存布局放进图实例,而不是每个节点都 new UObject。

Update 遍历

IUpdate 定义了更新阶段。关键数据结构是:

  • FUpdateGraphContext:持有 FAnimNextGraphInstance、DeltaTime、输入事件列表、输出事件列表。
  • FTraitUpdateState:16 字节以内,保存 DeltaTime、TotalWeight、TrajectoryWeight、bIsBlendingOut、bIsNewlyRelevant。
  • FUpdateTraversalContext:遍历上下文,可以 RaiseInputTraitEventRaiseOutputTraitEvent

Update 阶段适合:

  • 推进播放时间。
  • 根据权重决定是否更新子图。
  • 处理输入事件和输出事件。
  • 更新 Motion Matching 搜索状态。
  • 缓存 Evaluation 阶段需要但不能再读取变量的数据。

Evaluate 遍历

IEvaluate 定义评估阶段。源码注释说明每个节点会先调用 PreEvaluate,然后通过 IHierarchy 查询孩子,递归评估孩子,最后调用 PostEvaluate。评估结果不是直接写 pose,而是生成 FEvaluationProgram

这点很重要:Trait 的 Evaluate 阶段应该追加任务,而不是到处直接修改最终姿态。

struct FProjectPoseOffsetTrait : FAdditiveTrait, IEvaluate
{
    DECLARE_ANIM_TRAIT(FProjectPoseOffsetTrait, FAdditiveTrait)

    using FSharedData = FProjectPoseOffsetTraitSharedData;

    virtual void PostEvaluate(
        FEvaluateTraversalContext& Context,
        const TTraitBinding<IEvaluate>& Binding) const override
    {
        Context.AppendTask(FProjectPoseOffsetTask::Make(...));
    }
};

这是示意代码,真实项目需要补齐 SharedData、InstanceData、宏实现和任务参数。

GraphInstance 的生命周期

FAnimNextGraphInstance 继承 FUAFAssetInstance,并用 TSharedFromThis 管共享生命周期。它有几种分配状态:

状态 含义
Empty 默认空实例
PreAllocated 有图引用,但未分配变量和内部结构
VariablesAllocated 变量和组件已分配
Allocated 图根 Trait 等内部结构完整

它还保存 EntryPoint、GraphInstancePtr、ModuleInstance、RootGraphInstance、GCHandler 和 bHasUpdatedOnce。编辑器编译时会 freeze/thaw live instances,这也是为什么项目里要避免在外部长期缓存内部 Trait 指针。

GraphInstanceComponent

FUAFGraphInstanceComponent 继承 FUAFAssetInstanceComponent,但容器类型是 FAnimNextGraphInstance。它提供三个钩子:

  • PreUpdate(FExecutionContext&)
  • PostUpdate(FExecutionContext&)
  • OnTraitEvent(FExecutionContext&, FAnimNextTraitEvent&)

如果团队需要给所有图实例加调试、统计、事件观察或缓存,可以优先考虑 GraphInstanceComponent,而不是侵入每个 Trait。

项目落地

Trait 设计建议:

  1. 会改变拓扑或状态的逻辑放 IUpdate
  2. 只追加评估任务的逻辑放 IEvaluate
  3. 有子节点的 Trait 实现 IHierarchy
  4. 持有 UObject 引用的 Trait 实现 IGarbageCollection
  5. 图上可调参数放 FSharedData,每实例状态放 FInstanceData
  6. Latent property 只用于确实需要运行时求值的 pins,不要滥用。

简化流程图:

UUAFAnimGraph
  -> AllocateInstance()
  -> FAnimNextGraphInstance
  -> UpdateGraph(FUpdateGraphContext)
  -> EvaluateGraph(FEvaluateGraphContext)
  -> FEvaluationProgram
  -> FEvaluationVM
  -> FAnimNextGraphLODPose / ValueBundle / Notifies

使用案例:搭一个 LocomotionGraph

如果从零验证 UAF AnimGraph,推荐先搭一个 AG_Hero_Locomotion_UAF,只解决地面移动,不碰攻击、受击、剧情:

  1. Root EntryPoint 输出一个 BlendStack。
  2. BlendStack 的子图先放 Idle、Walk、Run 三个 Sequence Player,后续再替换为 Motion Matching。
  3. 用 Input Value Trait 读取 GroundSpeedbIsInAirFacingYawDelta
  4. 用 Chooser Player 根据 GroundSpeedbIsInAir 选择地面/空中子图。
  5. Evaluate 阶段只输出 pose,不在 trait 里直接访问 MovementComponent。
  6. 需要脚部修正时,先在图末尾接一个可开关的 Control Rig Trait。

一个可维护的图层次可以写成这样:

Root
  Blend Stack Core
  Chooser Player: CH_Hero_Locomotion
    Graph: AG_Hero_Grounded
      Sequence/BlendSpace or Motion Matching
    Graph: AG_Hero_InAir
      JumpLoop / FallLoop
  Optional: Control Rig FootIK
  Optional: Offset Root Bone

验收标准不是“图能跑”,而是这些行为可观测:

  1. 从 Idle 到 Run,BlendStack 是否只切需要切的子图。
  2. GroundSpeed 抖动时,Chooser 是否频繁重选大图。
  3. 关闭 Control Rig 后,基础 locomotion 是否仍可用。
  4. Rewind/日志里能看到当前 EntryPoint、当前图和关键变量。

架构分析:Trait、AnimNode、AnimBP 怎么分工

任务 更适合用 原因
图上静态能力组合,例如输出 pose、追加 task、子图层级 Trait 编译后 shared data 紧凑,EvaluationVM 能统一执行
树状节点、过渡、Sequence/BlendStack、轻量自定义播放逻辑 UAF AnimNode Data + Node + AnimOp 更接近传统节点思维
稳定生产主干、复杂编辑器工作流、已有资产体系 AnimBlueprint UE 生产路径成熟,团队熟悉
动画状态选择、武器/姿态表格、角色体型差异 Chooser / StateTree 让决策数据化,减少图里硬分支

项目里可以先让 AnimBP 仍是主干,UAF 只产出一个子 pose 或上半身层。等 UAF 图调试和 Cook 稳定后,再扩大覆盖范围。这样读源码得到的结论才会转化为工程决策:Trait 不是替代所有节点,而是提供一条更明确的 Update/Evaluate/Task 边界。

自定义 Trait 的落地顺序

不要第一天就写复杂 Trait。推荐顺序:

  1. 先用内置 Sequence、BlendStack、Chooser、Motion Matching 跑通项目变量。
  2. 确认内置能力解决不了,再写只实现 IEvaluate 的最小 Trait。
  3. 如果需要推进时间、处理事件或维护状态,再加 IUpdateFInstanceData
  4. 如果有子节点,再实现 IHierarchy,并把子节点权重传播写清楚。
  5. 如果保存 UObject 引用,再实现 GC 接口。
  6. 每个自定义 Trait 都做一个能开关的 debug path,方便和旧 AnimBP 输出对比。

常见坑

  • 不要把 Trait 当 UObject 组件写。Trait 是紧凑内存布局,生命周期由图实例管理。
  • FSharedData 不是每实例状态;在里面放“当前播放时间”这类字段会错。
  • IUpdate 的事件分输入和输出方向,传播方向错误会导致父子图收不到事件。
  • 评估阶段要尽量生成任务,避免把 Update 阶段的数据访问偷偷延迟到 Evaluate。
  • Graph EntryPoint 默认名是 Root,但复杂图要明确入口名和引用关系。

源码路径索引

  • UAFAnimGraph/Source/UAFAnimGraph/Public/Graph/AnimNextAnimationGraph.h
  • UAFAnimGraph/Source/UAFAnimGraph/Public/Graph/AnimNextGraphInstance.h
  • UAFAnimGraph/Source/UAFAnimGraph/Public/TraitCore/Trait.h
  • UAFAnimGraph/Source/UAFAnimGraph/Public/TraitInterfaces/IUpdate.h
  • UAFAnimGraph/Source/UAFAnimGraph/Public/TraitInterfaces/IEvaluate.h