总览
UAFAnimGraph 是 UAF 最核心也最容易误解的一层。它不是简单复制 AnimBlueprint 节点系统,而是用 Trait 栈描述动画图节点的能力组合:Update 负责推进状态,Evaluate 负责生成 EvaluationProgram,EvaluationVM 再执行任务产出姿态、曲线、属性、通知和 Root Motion。
源码依据
重点文件:
UAFAnimGraph/Public/Graph/AnimNextAnimationGraph.hUAFAnimGraph/Public/Graph/AnimNextAnimGraph.hUAFAnimGraph/Public/Graph/AnimNextGraphInstance.hUAFAnimGraph/Public/Graph/UAFGraphInstanceComponent.hUAFAnimGraph/Public/TraitCore/Trait.hUAFAnimGraph/Public/TraitCore/TraitSharedData.hUAFAnimGraph/Public/TraitInterfaces/IUpdate.hUAFAnimGraph/Public/TraitInterfaces/IEvaluate.hUAFAnimGraph/Internal/Graph/RigUnit_AnimNextTraitStack.hUAFAnimGraph/Public/EvaluationVM/EvaluationProgram.hUAFAnimGraph/Public/EvaluationVM/EvaluationVM.h
UUAFAnimGraph 继承 UUAFSharedVariables。它可以 PreAllocateInstance 或 AllocateInstance,返回 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:每个图实例自己的运行时状态。- 接口实现:例如
IUpdate、IEvaluate、IHierarchy、IGarbageCollection。 - 事件处理:通过
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:遍历上下文,可以RaiseInputTraitEvent和RaiseOutputTraitEvent。
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 设计建议:
- 会改变拓扑或状态的逻辑放
IUpdate。 - 只追加评估任务的逻辑放
IEvaluate。 - 有子节点的 Trait 实现
IHierarchy。 - 持有 UObject 引用的 Trait 实现
IGarbageCollection。 - 图上可调参数放
FSharedData,每实例状态放FInstanceData。 - Latent property 只用于确实需要运行时求值的 pins,不要滥用。
简化流程图:
UUAFAnimGraph
-> AllocateInstance()
-> FAnimNextGraphInstance
-> UpdateGraph(FUpdateGraphContext)
-> EvaluateGraph(FEvaluateGraphContext)
-> FEvaluationProgram
-> FEvaluationVM
-> FAnimNextGraphLODPose / ValueBundle / Notifies
使用案例:搭一个 LocomotionGraph
如果从零验证 UAF AnimGraph,推荐先搭一个 AG_Hero_Locomotion_UAF,只解决地面移动,不碰攻击、受击、剧情:
- Root EntryPoint 输出一个 BlendStack。
- BlendStack 的子图先放 Idle、Walk、Run 三个 Sequence Player,后续再替换为 Motion Matching。
- 用 Input Value Trait 读取
GroundSpeed、bIsInAir、FacingYawDelta。 - 用 Chooser Player 根据
GroundSpeed和bIsInAir选择地面/空中子图。 - Evaluate 阶段只输出 pose,不在 trait 里直接访问 MovementComponent。
- 需要脚部修正时,先在图末尾接一个可开关的 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
验收标准不是“图能跑”,而是这些行为可观测:
- 从 Idle 到 Run,BlendStack 是否只切需要切的子图。
GroundSpeed抖动时,Chooser 是否频繁重选大图。- 关闭 Control Rig 后,基础 locomotion 是否仍可用。
- 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。推荐顺序:
- 先用内置 Sequence、BlendStack、Chooser、Motion Matching 跑通项目变量。
- 确认内置能力解决不了,再写只实现
IEvaluate的最小 Trait。 - 如果需要推进时间、处理事件或维护状态,再加
IUpdate和FInstanceData。 - 如果有子节点,再实现
IHierarchy,并把子节点权重传播写清楚。 - 如果保存 UObject 引用,再实现 GC 接口。
- 每个自定义 Trait 都做一个能开关的 debug path,方便和旧 AnimBP 输出对比。
常见坑
- 不要把 Trait 当 UObject 组件写。Trait 是紧凑内存布局,生命周期由图实例管理。
FSharedData不是每实例状态;在里面放“当前播放时间”这类字段会错。IUpdate的事件分输入和输出方向,传播方向错误会导致父子图收不到事件。- 评估阶段要尽量生成任务,避免把 Update 阶段的数据访问偷偷延迟到 Evaluate。
- Graph EntryPoint 默认名是
Root,但复杂图要明确入口名和引用关系。
源码路径索引
UAFAnimGraph/Source/UAFAnimGraph/Public/Graph/AnimNextAnimationGraph.hUAFAnimGraph/Source/UAFAnimGraph/Public/Graph/AnimNextGraphInstance.hUAFAnimGraph/Source/UAFAnimGraph/Public/TraitCore/Trait.hUAFAnimGraph/Source/UAFAnimGraph/Public/TraitInterfaces/IUpdate.hUAFAnimGraph/Source/UAFAnimGraph/Public/TraitInterfaces/IEvaluate.h