总览
UAFAnimNode 给 UAF 提供了一套更接近传统节点树的运行时层。它不是 FAnimNode_Base,而是 FUAFAnimNode、FUAFAnimNodeData 和 FUAFAnimOp 三件套:节点在 Update 阶段维护拓扑、时间和状态,AnimOp 在 Evaluate 阶段真正产生或修改动画值。
源码依据
重点文件:
UAFAnimNode/Public/UAF/AnimNodeCore/UAFAnimNode.hUAFAnimNode/Public/UAF/AnimNodeCore/UAFAnimNodeData.hUAFAnimNode/Public/UAF/AnimNodeCore/UAFAnimNodeUpdate.hUAFAnimNode/Public/UAF/AnimOpCore/UAFAnimOp.hUAFAnimNode/Public/UAF/AnimNodes/UAFSequencePlayer.hUAFAnimNode/Public/UAF/AnimNodes/UAFBlendStack.hUAFAnimNode/Public/UAF/AnimNodes/UAFBlendByBoolNode.hUAFAnimNode/Public/UAF/AnimNodes/UAFSimpleTransition.hUAFAnimNode/Public/UAF/AnimNodes/InputValueAnimNode.h
AnimNode 和 AnimNodeData
FUAFAnimNodeData 是可序列化的共享数据,核心虚函数是 CreateInstance(FUAFAnimGraphUpdateContext&)。FUAFAnimNode 是运行时实例,用 TRefCountPtr 管生命周期,禁止复制移动。
FUAFAnimNode 管:
- Parent 指针。
- Children 数组,内联容量为 2。
- TotalWeight、bIsBlendingOut、bIsNewlyRelevant。
- PreAnimOp 和 PostAnimOp。
PreUpdate/PostUpdate。- 接口查询和 GC 引用收集。
这让节点可以很轻地组合。比如单子节点节点可以自动传播权重,多子节点节点可以自己控制每个 child 的权重与过渡。
UpdateContext
FUAFAnimGraphUpdateContext 保存 HostObject、VariablesOwner、GCReferences、DeltaTime、PlayRate 栈和待销毁节点。节点更新时可以:
GetVariablesOwner()读写 UAF 实例变量。GetHostObject()找宿主对象。PushPlayRate()/PopPlayRate()临时缩放时间。- 创建或销毁子节点。
- 设置 Pre/Post AnimOp。
源码里有一个很好的边界例子:FUAFInputValueAnimNode 在 PreUpdate 读取变量,把 ValueBundle 或 LODPose 缓存在 FUAFInputValueAnimOp 上,因为变量访问只在 Update 阶段安全,Evaluation 阶段只消费缓存。
AnimOp 是评估阶段的命令
FUAFAnimOp 是 USTRUCT,用于产出或修改 animated values、notifies、sync contributors。它有三个入口:
EvaluateValues(FUAFAnimOpValueEvaluator&)EvaluateNotifies(FUAFAnimOpNotifyEvaluator&)EvaluateSynchronization(FUAFAnimOpSyncEvaluator&)
InitializeAs<T>() 会用函数指针比较判断派生类型实现了哪些评估函数。每个 AnimOp 还声明自己消费多少输入:
USTRUCT()
struct FProjectClampAnimOp : public UE::UAF::FUAFAnimOp
{
GENERATED_BODY()
UAF_DECLARE_ANIMOP(FProjectClampAnimOp)
FProjectClampAnimOp()
: FUAFAnimOp(1)
{
InitializeAs<FProjectClampAnimOp>();
}
virtual void EvaluateValues(FUAFAnimOpValueEvaluator& Evaluator) override
{
// 从 Evaluator 取输入 pose/value,做修正,再 push 回栈。
}
};
内置节点
| 节点 | 数据结构 | 说明 |
|---|---|---|
| Sequence Player | FUAFSequencePlayerData / FUAFSequencePlayer |
播放 UAnimSequence,支持 LoopMode、StartTime、Timeline、RootMotion |
| Input Value | FUAFInputValueAnimNodeData |
从 UAF 变量读 FUAFValueBundle |
| BlendStack | FUAFBlendStack |
管一个当前 child 和过渡,过渡完成后裁剪节点 |
| Blend By Bool | FUAFBlendByBoolNodeData |
用 FBindableBool 在 TrueNode/FalseNode 间切换 |
| Simple Transition | FUAFSimpleTransitionData |
Duration + BlendOption,内部用 FUAFBlendTwoAnimOp |
| Timed Transition | FUAFTimedTransition |
管 Source/Target、TimeRemaining 和完成通知 |
| Apply Additive | FUAFApplyAdditiveData |
Additive 应用 |
| Make Dynamic Additive | FUAFMakeDynamicAdditiveData |
动态 additive |
| Offset Root Bone / Steering | 对应 Warping/AnimOp | 根骨和朝向修正 |
自定义节点示例
一个项目节点通常由 Data + Instance + AnimOp 组成:
USTRUCT(DisplayName = "Project Speed Gate")
struct FProjectSpeedGateData : public FUAFAnimNodeData
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, Category = "Gate")
FBindableFloat Speed;
UPROPERTY(EditAnywhere, Category = "Gate")
FUAFAnimNodeDataEx MovingNode;
UPROPERTY(EditAnywhere, Category = "Gate")
FUAFAnimNodeDataEx IdleNode;
virtual FUAFAnimNodePtr CreateInstance(FUAFAnimGraphUpdateContext& Context) const override;
};
struct FProjectSpeedGateNode : public FUAFBlendStack
{
FProjectSpeedGateNode(FUAFAnimGraphUpdateContext& Context, const FProjectSpeedGateData& InData)
: FUAFBlendStack(Context)
, Data(&InData)
{
}
virtual void PreUpdate(FUAFAnimGraphUpdateContext& Context) override
{
const bool bShouldMove = ResolveSpeed(Context) > 3.0f;
// 根据 bShouldMove 创建目标 child,并通过 TransitionTo 切换。
FUAFBlendStack::PreUpdate(Context);
}
const FProjectSpeedGateData* Data = nullptr;
};
这是结构示例,不是可以直接复制编译的完整插件代码;真实实现还需要绑定解析、节点创建和 GC 引用处理。
项目落地
使用 AnimNode 层的建议:
- 想复用“树状组合 + 过渡”时用 AnimNode。
- 想在图评估里追加底层任务时用 Trait。
- 叶子节点必须设置 PostAnimOp,否则没有输出。
- Modifier 节点通常消费一个输入,适合用
FUAFModifierAnimNode。 - 需要持有 UObject 的节点实现
AddReferencedObjects,不要只靠裸指针。
使用案例:武器持握过渡节点
假设项目有单手剑、双手剑、步枪三种持握方式。旧 AnimBP 里经常会出现一堆 bool:bUseRiflePose、bUseTwoHandedPose、bIsRelaxed。在 UAF AnimNode 层,可以把它变成一个明确的持握过渡节点:
Input variables
WeaponStance: Unarmed / Sword1H / Sword2H / Rifle
bIsAiming
UpperBodyWeight
FWeaponStanceNode
-> Current child graph
-> Target child graph
-> FUAFTimedTransition
-> FUAFBlendTwoAnimOp
落地步骤:
- Data 里暴露
FBindableName WeaponStance,让它绑定到 UAF 公共变量。 - Data 里保存各个 stance 对应的
FUAFAnimNodeDataEx或图工厂资产。 - Node 的
PreUpdate读取绑定值,只在 stance 变化时创建目标 child。 - 切换时使用
FUAFTimedTransition,不要每帧重建子节点。 PostAnimOp输出当前 stance 混合后的 pose。- 节点加
AddReferencedObjects,确保引用的动画资产或图资产不会被 GC 漏掉。
这类节点适合解决“少量离散状态 + 有过渡 + 需要复用”的问题。它比把所有分支写在 C++ Tick 里更可调,也比在 Chooser 表里塞复杂过渡更清楚。
使用案例:输入缓存节点
源码里的 FUAFInputValueAnimNode 给了一个很实用的模式:Update 阶段读取变量,Evaluate 阶段只消费缓存。如果你要做“上一帧目标点”“本帧命中方向”“脚 IK 目标”这类输入,不要在 AnimOp 里回头读变量。
struct FProjectCachedTargetNode : public FUAFAnimNode
{
FTransform CachedTarget = FTransform::Identity;
virtual void PreUpdate(FUAFAnimGraphUpdateContext& Context) override
{
Context.GetVariablesOwner()->GetVariable(TargetVariable, CachedTarget);
SetPostAnimOp(FProjectTargetWarpOp(CachedTarget));
}
};
原因很简单:Update 阶段能安全访问变量、处理事件、维护节点树;Evaluate 阶段应该像命令执行器,消费已经准备好的数据并产出 pose/value/notify。这个边界能让 EvaluationVM 更稳定,也让调试更容易复现。
架构分析:何时写自定义 AnimNode
| 需求 | 写自定义 AnimNode 是否合适 | 推荐做法 |
|---|---|---|
| 根据变量在几个子图间切换,并保留过渡状态 | 合适 | Data 保存子节点,Node 保存当前/目标 child |
| 只是在最终 pose 上做一个数学修正 | 不一定 | 优先写 Trait/Evaluation Task 或 Modifier AnimOp |
| 播放一个普通 Sequence | 不合适 | 直接用内置 Sequence Player |
| 做复杂 asset selection | 不合适 | Chooser 表更适合维护 |
| 每帧读取大量 gameplay 对象 | 不合适 | 桥接组件先写 UAF 变量,Node 只读变量 |
如果一个自定义节点需要知道太多 gameplay 细节,通常说明边界错了。把 gameplay 摘要前置成公共变量,再让节点只关心动画选择和混合。
常见坑
PreAnimOp会在孩子之前执行,PostAnimOp在孩子之后执行;叶子生产输出优先用 PostAnimOp。PostUpdate里已经晚于 PreAnimOp 入队,不能再依赖它改变 PreAnimOp。- 节点重用前必须
Reset(),否则bIsNewlyRelevant、权重和父子关系可能残留。 FUAFBlendStack::TransitionTo需要 transition data,否则体验可能是硬切或默认过渡。- Evaluation 阶段不要访问只在 UpdateContext 安全的数据。
源码路径索引
UAFAnimNode/Source/UAFAnimNode/Public/UAF/AnimNodeCore/UAFAnimNode.hUAFAnimNode/Source/UAFAnimNode/Public/UAF/AnimNodeCore/UAFAnimNodeUpdate.hUAFAnimNode/Source/UAFAnimNode/Public/UAF/AnimOpCore/UAFAnimOp.hUAFAnimNode/Source/UAFAnimNode/Public/UAF/AnimNodes/UAFSequencePlayer.hUAFAnimNode/Source/UAFAnimNode/Public/UAF/AnimNodes/UAFBlendStack.h