1613 字
8 分钟
UE 动画系统——从 AnimBlueprint 到 Root Motion

一、AnimBlueprint 的两层结构#

AnimBlueprint 不是一个大的蓝图文件——它有两个完全独立的图:

AnimBlueprint
├── EventGraph ← 更新动画参数(每帧 Tick)
│ └── BlueprintUpdateAnimation → 从 Character 读速度、方向、是否在空中
│ → 写入 AnimInstance 的变量
└── AnimGraph ← 根据参数混合动画(输出每帧 Pose)
├── State Machine → 决定播 Idle / Walk / Run / Jump
├── BlendSpace → 根据参数连续混合动画
├── IK / Bone Transform → 修正骨骼位置
└── Output Pose → 送到渲染管线

关键:EventGraph 每帧跑在 Game Thread 上。它从 APawn 读数据(速度、是否按下跳跃键),写到 UAnimInstance 的成员变量。AnimGraph 在 Worker Thread 上消费这些变量做动画混合。两者完全解耦。


二、State Machine:动画状态的”交通灯”#

1 状态定义#

每个 State 绑定一个动画(或 BlendSpace):

[Idle/Rifle/Hip] → 播放持枪待机动画
[Walk/Rifle] → 播放持枪走动画
[Run/Rifle] → 播放持枪跑动画
[Jump/Start] → 起跳动画
[Jump/Loop] → 空中循环
[Jump/Land] → 落地动画

2 Transition 规则#

Idle → Walk: Speed > 0.1(开始移动)
Walk → Idle: Speed < 0.05(停下)
Walk → Run: Speed > 400(加速到跑)
Run → Walk: Speed < 300(减速到走)
条件由 C++ 侧每帧更新到 AnimInstance 的变量驱动:
Speed, Direction, bIsInAir, bIsCrouching...

Transition 不是瞬间切换——每个 Transition 有 Blend Duration(如 0.15 秒)。0.15 秒内 Weight 从 0 到 1,两个动画平滑过渡。

3 BlendSpace:连续参数空间#

不是简单的 if Speed > 400——用 BlendSpace 把走和跑之间的速度映射为连续的混合比例:

BlendSpace_Locomotion:
横轴 = Speed (0 → 600)
纵轴 = Direction (-180° → 180°,向前/向后/向左/向右)
Speed=300, Dir=0 → WalkForward(60%) + JogForward(40%)
Speed=300, Dir=90 → WalkForward(30%) + WalkRight(70%)
Speed=600, Dir=0 → SprintForward(100%)

策划可以在 BlendSpace 编辑器里拖动采样点观察混合效果——不需要程序员改代码。


三、AnimNotify / AnimNotifyState#

动画不只是”播放”——需要在特定帧触发事件。比如:

Attack Animation(1 秒,30 帧):
Frame 10 → AnimNotify: EnableWeaponCollision (开始检测武器碰撞)
Frame 20 → AnimNotify: DisableWeaponCollision (关闭碰撞检测)
Frame 15 → AnimNotify: SpawnSlashEffect (生成刀光特效)

Notify 的 C++ 侧#

// AnimNotify 在动画资源里配置,触发时调用这个函数
void UAnimNotify_EnableWeaponCollision::Notify(
USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation) {
ACharacter* Owner = Cast<ACharacter>(MeshComp->GetOwner());
if (Owner && Owner->HasAuthority()) { // 只在服务端处理碰撞
// 给角色的武器 Component 开启碰撞
UWeaponComponent* Weapon = Owner->FindComponentByClass<UWeaponComponent>();
Weapon->EnableCollision();
}
}

Notify 和网络:伤害检测通常在服务端——Notify 触发时要检查 HasAuthority()。客户端的 Notify 只播放 VFX/SFX(特效音效),不管碰撞。

AnimNotifyState:有持续时间的 Notify#

// 不是一瞬间——有 Begin / Tick / End
class UAnimNotifyState_WeaponTrail : public UAnimNotifyState {
void NotifyBegin(...) { StartTrailEffect(); }
void NotifyTick(...) { UpdateTrailPosition(); }
void NotifyEnd(...) { StopTrailEffect(); }
};

四、Root Motion——用动画驱动移动#

1 问题#

角色播放攻击动画时,如果玩家按了 W 键,角色该不该往前走?

传统方案:移动 和 动画 完全分离——CharacterMovement 控制位置,动画只是播放视觉效果。缺点是”脚底打滑”——播放 Walk 动画但角色移动速度不匹配。

2 Root Motion 的做法#

让动画本身告诉引擎”这一帧我移动了多少”:

动画里,Root 骨骼有位移数据(制作时自然就有——Walk 动画里 Root 骨自然会前移)
UE 在 AnimBlueprint 里勾选 EnableRootMotion
每帧,AnimInstance 计算 Root 骨的 ΔTransform
传给 CharacterMovement → 驱动角色实际移动
结果:动画和移动天然同步——走的动画 = 走的速度,脚底不会打滑

3 什么时候用 Root Motion#

适用不适用
近战攻击连招(动画驱动位移)玩家自由移动(WASD,不能用 Root Motion 锁定)
翻滚/闪避(固定距离的位移)平台跳跃(需要精确控制距离)
处决动画(完全接管角色控制)竞速游戏

Root Motion 的问题是”玩家失去了控制权”——动画期间不能移动、不能转向。这对动作游戏是可以接受的(出招时本来就不该移动),但对于开放世界跑图是灾难。


五、Layered Blend——上半身和下半身分离#

角色移动时可以做攻击——上半身播攻击动画,下半身继续 Walk/Idle:

AnimGraph:
Base Pose = BlendSpace_Locomotion(Speed, Direction) ← 全身移动
Upper Body = Animation_FireWeapon ← 上半身攻击
Final Pose = LayeredBlendPerBone(BasePose, UpperBody,
Spine_01, // 从这根骨头往上,全用 UpperBody
1.0) // 混合权重

Layered Blend Per Bone 的核心思想:不同的骨头跑不同的动画。下半身走移动 BlendSpace,上半身播攻击动画。中间用 Spine 骨头做平滑过渡(不然腰那里会有断痕)。


六、IK(Inverse Kinematics)——让脚贴地#

IK 解决的问题:角色站在楼梯/斜坡上时,“Walk 动画假设脚在平地上”就不对了——脚会浮空或陷入地面。

AnimGraph 最后一步:
→ Two Bone IK (脚部)
→ 目标位置 = 地面检测结果(从 IK Trace 来的)
→ 修正 Foot 骨骼的 Transform,让 IK 目标贴合地面
→ 结果:楼梯上每一步脚都贴在台阶上

Control Rig(UE5 新功能): 比 AnimBlueprint 里手搭 IK 节点更灵活——Control Rig 是一个独立的 Rig 资产,可以做更复杂的程序化动画(爬墙、牵手、武器 IK)。它在 AnimBlueprint 的 AnimGraph 最后一步执行。


七、C++ 与 AnimBlueprint 的数据流#

// C++ 侧:每帧更新动画参数
void UMyAnimInstance::NativeUpdateAnimation(float DeltaSeconds) {
Super::NativeUpdateAnimation(DeltaSeconds);
AActor* Owner = GetOwningActor();
if (!Owner) return;
// 从 Character 拿数据
Speed = Owner->GetVelocity().Size2D();
Direction = CalculateDirection(Owner->GetVelocity(), Owner->GetActorRotation());
bIsInAir = Owner->GetCharacterMovement()->IsFalling();
}

AnimInstance 是 C++ class——不是纯蓝图对象。你可以在 C++ 里覆盖 NativeUpdateAnimation 做复杂的每帧数据准备。AnimBlueprint 只是 UI 展示层。


八、总结#

概念一句话
EventGraph每帧更新动画参数(C++ 或 BP)——速度、方向、是否空中
AnimGraph消费参数 → 状态机选动画 → BlendSpace 混合 → IK 修正 → Output Pose
Transition状态之间平滑过渡——Blend Duration 决定过渡时间
AnimNotify动画特定帧触发事件——武器碰撞、音效、特效
Root Motion动画里的位移驱动角色实际移动——脚不滑但不灵活
Layered Blend Per Bone不同骨头不同动画——腰以上攻击、腰以下走路
IK修正脚/手的位置贴合环境——Control Rig 是更强大的 IK 系统

动画系统是 UE 里唯一一个”一半在 C++、一半在蓝图、一半在工具里”的模块——搞清楚 EventGraph vs AnimGraph vs C++ NativeUpdateAnimation 的数据流向,就不会迷失在动画 bug 的排查里。

UE 动画系统——从 AnimBlueprint 到 Root Motion
https://www.m4doka.xyz/posts/ue/ue-2-animation-system/
作者
m4doka
发布于
2026-08-04
许可协议
CC BY-NC-SA 4.0