总览
这一篇只看 UAF 核心运行时。源码入口主要是 UAF.uplugin、Component/AnimNextComponent.h、UAFAssetInstance.h、UAFAssetInstanceComponent.h、Factory/UAFSystemFactoryParams.h 和 DataRegistry.h。先把“系统跑在哪里”弄清楚,后面的 Trait、AnimNode、Chooser、Motion Matching 才不会飘。
源码依据
UAF.uplugin 明确把插件标为 Experimental,默认不启用。运行时模块 UAF 在 PreDefault 阶段加载,编辑器能力拆到 UAFEditor 和 UAFUncookedOnly。依赖里有 RigVM、Workspace、HierarchyTable、GameplayInsights、PropertyBindingUtils 等,这已经说明 UAF 不是一个单纯播放动画的插件,而是带资产编辑、变量绑定、调试和图编译的一整套框架。
核心类的职责:
| 类型 | 职责 |
|---|---|
UUAFComponent |
ActorComponent,注册/注销 UAF 系统,读写变量,管理输入输出,决定是否写回 SkeletalMeshComponent |
FUAFAssetInstance |
UAF 资产运行时实例,持有变量、默认组件、宿主关系、调试 ID |
FUAFAssetInstanceComponent |
挂在实例上的扩展组件,派生类型用 DECLARE_UAF_ASSET_INSTANCE_COMPONENT() 提供 RTTI |
UUAFRigVMAsset |
可承载 RigVM 逻辑的资产基类,持有 RigVM、变量默认值、函数数据、引用变量资产和默认组件 |
FUAFSystemFactoryParams |
程序化生成系统实例的参数集合,可加公共变量、组件和初始化任务 |
FDataRegistry |
全局动画数据注册表,管理 reference pose、命名数据、引用计数数据块 |
UAFComponent 在 Actor 上做什么
UUAFComponent 是 UAF 和游戏对象之间的主连接点。它是 UActorComponent,也实现了 UE::UAF::ISystemOutputAdapter。源码里能看到它覆盖 OnRegister、OnUnregister、BeginPlay、EndPlay、Activate、Deactivate,并在注册时设置输入输出、分配系统、缓存 Skeletal Mesh。
它暴露的关键 API 包括:
SetVariable(const FAnimNextVariableReference&, const ValueType&)GetVariable(const FAnimNextVariableReference&, ValueType&)WriteVariable(...)BlueprintSetVariableReference(...)BlueprintGetVariableReference(...)BlueprintSetInputBinding(...)QueueTask(FName ModuleEventName, TUniqueFunction<void(const FModuleTaskContext&)>, ETaskRunLocation)QueueInputTraitEvent(FAnimNextTraitEventPtr)GetSystemReference()AddComponentPrerequisite/AddComponentSubsequentAddModuleEventPrerequisite/AddModuleEventSubsequent
UUAFComponent 的输出配置也很重要。EUAFSkeletalMeshComponentOutputMode 有两个值:WriteToSkeletalMeshComponentPose 和 SkipWriteToSkeletalMeshComponentPose。前者直接驱动 Mesh,后者适合做中间 UAF 输出,比如上游生成 ValueBundle,下游另一个 UAF 系统再消费。
实例、变量和组件
FUAFAssetInstance 是运行时状态容器。它保存:
TArray<TInstancedStruct<FUAFAssetInstanceComponent>> ComponentsFUAFInstanceVariableData Variables- 资产硬引用
Asset - 实例脚本结构
ScriptStruct - 宿主实例弱引用
HostInstance
变量访问不是裸 float 或 FName 查表,而是通过 FAnimNextVariableReference 和 FAnimNextParamType 做强类型读写:
float Speed = 0.0f;
if (UAFComponent->GetVariable(SpeedVariable, Speed))
{
Speed = FMath::Max(Speed, 0.0f);
}
UAFComponent->SetVariable(SpeedVariable, Speed);
实例组件是扩展点。FUAFAssetInstance::GetOrAddComponent<T>() 会懒创建组件并调用 OnBindToInstance()。Graph、Module、Layering、Injection 等功能都可以用组件把状态挂在实例上,而不是塞到一个超大对象里。
USTRUCT()
struct FProjectAnimBudgetComponent : public FUAFAssetInstanceComponent
{
GENERATED_BODY()
DECLARE_UAF_ASSET_INSTANCE_COMPONENT()
int32 MaxSearchesPerFrame = 2;
private:
virtual void OnBindToInstance() override
{
// 在这里缓存实例相关信息,避免每帧从外部找。
}
};
系统工厂和初始化任务
FUAFSystemFactoryParams 用来描述如何生成和初始化一个系统。它内部持有 FUAFSimpleSystemBuilder,可以添加公共变量结构、变量资产、RigVM 资产和默认组件;也可以添加初始化任务。
FUAFSystemFactoryParams Params;
Params.AddPublicVariablesStruct<FProjectAnimVariables>();
Params.AddComponent<FProjectAnimBudgetComponent>();
Params.AddInitializeTask(
[](const UE::UAF::FInstanceTaskContext& Context)
{
Context.AccessVariablesStruct<FProjectAnimVariables>(
[](FProjectAnimVariables& Vars)
{
Vars.Speed = 0.0f;
Vars.bIsInCombat = false;
});
});
FInstanceTaskContext 的 API 很克制:SetVariable、AccessVariable、AccessVariablesStruct 和 GetAssetInstance()。这说明初始化任务应该只做实例初始化,不应该在里面偷跑复杂 gameplay 逻辑。
Reference Pose 和 DataRegistry
动画系统最怕“每个节点都自己算骨架映射”。UAF 用 FDataRegistry 缓存全局数据,尤其是 reference pose。RegisterReferencePose / GetOrGenerateReferencePose 可以按 USkeletalMeshComponent 或 USkeletalMesh 生成并返回 FDataHandle。FDataHandle 是引用计数句柄,底层 FAllocatedBlock 保存内存、元素数量和 FAnimNextParamType。
FReferencePose 里有 LOD 排序骨骼、mesh/skeleton/LOD 索引映射、父骨骼映射、曲线标记和 fast path 标记。FLODPose 再基于它保存当前 LOD 的 local transforms,并提供 LOD 间 copy、ref pose、identity、bone name 查找等能力。
这套设计的工程意义是:复杂节点、Pose Search、Warping、Mirroring 都应该复用同一个 reference pose 和 LOD 映射,不要各自手搓。
项目落地
最小接入建议:
- 在测试角色上添加
UUAFComponent,先绑定一个简单 UAF 系统资产。 - 显式设置
OutputComponent,避免 Actor 上有多个 SkeletalMeshComponent 时选错。 - 把输入拆成少量公共变量,例如速度、加速度、朝向、移动模式、战斗状态。
- 用
SetVariable/GetVariable做 C++ 边界,蓝图只负责调试和快速试验。 - 使用
AddComponentPrerequisite或模块事件依赖确定 Tick 顺序,避免动画读取上一帧 movement 数据。 - 不写回 Mesh 的中间 UAF 系统,明确设为
SkipWriteToSkeletalMeshComponentPose。
使用案例:第三人称角色旁路验证
第一阶段不要直接替换角色 AnimBP。建议把 UAF 当成旁路系统,先只验证输入、图运行和输出质量:
- 角色保留现有
USkeletalMeshComponent、AnimBP 和 Montage。 - 额外添加一个
UUAFComponent,显式指定它要读取或写入的 Mesh。 OutputMode先使用SkipWriteToSkeletalMeshComponentPose,让它生成中间输出但不影响玩家看到的动画。- 每帧从 Movement、Ability、Inventory 汇总少量变量写入 UAF。
- 在调试 UI 中显示 UAF 是否激活、当前图、当前 layer、最近一次变量写入时间。
- 通过项目开关把某个测试角色切到
WriteToSkeletalMeshComponentPose,只对 QA 地图开放。
项目代码通常不应该到处保存 UUAFComponent 指针并随手写变量,而是做一个桥接组件:
UCLASS(ClassGroup = Animation, meta = (BlueprintSpawnableComponent))
class UProjectUAFBridgeComponent : public UActorComponent
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, Category = "UAF")
TObjectPtr<UUAFComponent> UAFComponent = nullptr;
UPROPERTY(EditAnywhere, Category = "UAF|Variables")
FAnimNextVariableReference SpeedVariable;
UPROPERTY(EditAnywhere, Category = "UAF|Variables")
FAnimNextVariableReference WeaponStanceVariable;
virtual void TickComponent(
float DeltaTime,
ELevelTick TickType,
FActorComponentTickFunction* ThisTickFunction) override
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
if (!UAFComponent)
{
return;
}
const float Speed = CachedMovementSpeed();
const FName WeaponStance = CachedWeaponStance();
UAFComponent->SetVariable(SpeedVariable, Speed);
UAFComponent->SetVariable(WeaponStanceVariable, WeaponStance);
}
};
这样做的好处是:UAF 仍然是动画实现细节,gameplay 系统只负责提供摘要状态;如果试点失败,删掉桥接组件或关掉开关,不会污染角色主逻辑。
架构分析:组件边界怎么划
UUAFComponent 是 UAF runtime 的入口,但它不应该承担项目动画架构里的全部职责。推荐这样拆:
| 职责 | 放在哪里 | 原因 |
|---|---|---|
| 从 Movement/Ability/Inventory 汇总状态 | 项目桥接组件 | gameplay API 经常变,不要让 UAF 图直接依赖 |
| 保存 UAF 变量引用 | 项目桥接组件或数据资产 | 避免在蓝图里用字符串找变量 |
| 图选择、层权重、动画状态 | UAF Chooser/StateTree/Layering | 数据化、可调试、可交给动画技术美术维护 |
| 姿态生成和修正 | UAF AnimGraph/Trait/AnimNode | 由 EvaluationVM 管理执行顺序 |
| 是否启用 UAF、是否回退 | 项目设置、控制台变量、角色配置 | Experimental 插件必须有退出路线 |
验收标准
做完最小接入后,不要只看“有没有动”。至少验证这些点:
- 角色身上有多个 Mesh 时,UAF 输出目标没有选错。
SetVariable返回成功;失败时能打印变量名、类型和资产路径。- Movement tick 先于 UAF 更新,速度和空中状态不是上一帧。
- 关闭 UAF 开关后旧 AnimBP 仍正常工作。
- PIE 结束、角色销毁、关卡切换时没有持有失效的系统引用。
- 打包构建不依赖
UAFEditor或UAFUncookedOnly头文件。
常见坑
SetAsset注释写着只能在组件未注册时调用;运行中换资产应走图注入、Chooser 或专门的系统切换路径。BlueprintSetVariable(FName, int32)已 deprecated,应使用变量引用版本。FAnimNextVariableReference指向资产和变量名,重命名变量后要走编辑器迁移和验证。FUAFAssetInstanceComponent是USTRUCT不是UObject,不要用 UObject 生命周期假设管理它。- Reference pose 与 LOD 映射必须来自当前输出 Mesh,否则 Warping/Mirroring 很容易在骨骼索引上出错。
源码路径索引
UAF/Source/UAF/Public/Component/AnimNextComponent.hUAF/Source/UAF/Public/UAFAssetInstance.hUAF/Source/UAF/Public/UAFAssetInstanceComponent.hUAF/Source/UAF/Public/Factory/UAFSystemFactoryParams.hUAF/Source/UAF/Public/DataRegistry.h