UE5动画系统实战:从Lyra拆解到自定义角色迁移
1. 从Lyra的动画系统里偷师之前先搞清楚它到底强在哪如果你正在用UE5做游戏尤其是做那种带射击、跑动、瞄准的第三人称或第一人称项目那你大概率听说过Lyra。这个官方示例项目里最值钱的东西之一就是它那套动画系统。很多人第一次打开Lyra看到角色跑起来、停下来、瞄准、开火那一整套动作丝滑得不行第一反应就是我想把这个直接搬到我自己的项目里。但真动手的时候就会发现事情没那么简单——动画蓝图里一堆状态机、一堆变量、一堆看起来像是引擎内部专用的节点直接复制粘贴要么报错要么跑起来角色像得了帕金森。我自己在这个事情上折腾了挺长时间从最开始想整包搬到后来老老实实拆解、逐层剥离中间踩的坑足够写一篇长文。这篇内容就是把我整个调整和迁移的过程完整讲一遍包括Lyra动画系统的核心设计思路、哪些部分可以拿来直接用、哪些必须改、怎么改以及改完之后怎么在自己的角色上跑通。不管你是刚接触UE5动画的新手还是已经用过动画蓝图但没深入过Lyra的老手应该都能从里面找到能直接抄作业的东西。先说清楚一个前提Lyra的动画系统不是一个动画蓝图那么简单它是一整套从数据到逻辑到表现的完整链路。你如果只盯着那个ABP_Lyra看会漏掉很多东西。它至少包含这几层动画实例AnimInstance里的变量和逻辑、动画蓝图里的状态机和BlendSpace、动画层接口Anim Layer Interface用来做上下半身分离、以及一堆动画修改器Anim Modifier和通知Notify来驱动特效和音效。这几层是咬合在一起的你单独抽一层出来它就跑不起来。所以我的建议是别想着整体搬迁而是按需拆解。你先想清楚自己的项目需要Lyra动画系统里的哪些能力然后只把那部分逻辑和资源拿过来重新接到你自己的角色上。下面我就按这个思路一层一层拆给你看。2. Lyra动画系统的四层结构以及每层你能拿走什么2.1 动画实例层变量和逻辑的集散地Lyra的动画实例比如ULyraAnimInstance继承自UAnimInstance里面定义了大量和角色运动状态相关的变量。比如角色的速度、是否在瞄准、是否在开火、是否在换弹、移动方向、是否落地等等。这些变量不是随便定义的它们对应着动画蓝图里状态机切换的条件。你打开Lyra的动画蓝图会看到很多变量前面带BlueprintReadOnly或者BlueprintReadWrite这些就是动画实例里暴露出来的。动画蓝图本身不计算这些值它只是读。真正的计算逻辑在动画实例的NativeUpdateAnimation或者BlueprintUpdateAnimation里。Lyra用的是C和蓝图混合的方式核心逻辑在C里蓝图里做微调。这一层你能拿走什么如果你自己的项目已经有了一套角色运动组件那你可以参考Lyra的变量命名和分类方式把你自己需要的变量补进去。但不要直接把Lyra的动画实例类整个继承过来因为它依赖Lyra的角色类ALyraCharacter和角色运动组件ULyraCharacterMovementComponent。你继承过来编译都过不了。我的做法是新建一个自己的动画实例类继承自UAnimInstance然后把Lyra动画实例里那些纯计算的部分抄过来。什么叫纯计算就是那些只依赖角色自身状态、不依赖Lyra特有组件的逻辑。比如根据速度算移动方向、根据加速度算倾斜角度、根据是否落地算落地状态。这些逻辑你抄过来改改变量名就能用。2.2 动画蓝图层状态机和BlendSpace的编排Lyra的动画蓝图ABP_Lyra是整个系统的表现层。它里面有几个大的状态机地面移动、跳跃、落地、瞄准、开火、换弹、死亡等等。每个状态机里又有若干状态状态之间通过过渡规则切换。地面移动状态机是核心。它里面用了一个BlendSpace来做八向移动的混合。这个BlendSpace的输入是移动速度和移动方向输出是不同方向的跑步动画。Lyra用的是BS_LyraMovement之类的命名里面放了前、后、左、右、以及四个斜向的跑步动画。这里有个细节值得说Lyra的移动BlendSpace不是简单的二维混合它用了速度和方向两个轴但方向轴的处理方式比较讲究。它不是直接用角色的朝向和速度方向的夹角而是用了一个叫CalculateDirection的节点这个节点会返回一个-180到180的角度值然后把这个值映射到BlendSpace的方向轴上。这样做的好处是不管角色怎么转动画都能正确反映移动方向。你要把这个搬到自己的项目里需要做几件事第一准备你自己的八向跑步动画如果没有可以用Mixamo的动画或者UE5自带的Mannequin动画先顶着第二新建一个BlendSpace把动画按方向摆好第三在动画蓝图里用CalculateDirection算出方向连到BlendSpace的方向轴上第四用角色的速度大小连到速度轴上。听起来简单但实际做的时候动画的朝向和角色的朝向经常对不上。Lyra的动画是专门调整过的动画里的角色朝向和胶囊体朝向是一致的。你如果从Mixamo下载动画它的朝向可能和UE5的坐标系不一致需要你在导入的时候调整旋转或者在BlendSpace里给每个动画加一个旋转偏移。这个坑我踩过角色跑起来是横着的调了半天才发现是动画导入时的朝向问题。2.3 动画层接口上下半身分离的关键Lyra的动画系统里有一个很重要的设计就是动画层接口Anim Layer Interface。这个东西允许你把动画蓝图拆成多个层比如基础层下半身移动和叠加层上半身瞄准、开火。然后通过接口把这些层组合起来。为什么要这么做因为角色在跑动的时候上半身可能在做完全不同的动作比如瞄准、换弹、扔手雷。如果把这些都塞进一个状态机里状态数量会爆炸过渡规则会复杂到没法维护。用动画层接口你可以让下半身的状态机独立运行上半身的状态机也独立运行最后在动画蓝图的Layered blend per bone节点里按骨骼混合。Lyra的动画层接口定义了几个函数比如AnimGraph、Layers之类的。具体实现是在动画蓝图里通过Layered blend per bone节点把上半身的骨骼比如spine_01到hand_r的权重设为1下半身的权重设为0这样上半身播上半身的动画下半身播下半身的动画。你要用这个机制需要在自己的动画蓝图里也定义一个动画层接口然后把上下半身的逻辑分开。这里有个关键点骨骼的混合权重不是随便设的你要根据自己角色的骨骼结构来调。Lyra用的是UE5的Mannequin骨骼如果你用的也是这套骨骼那可以直接抄它的权重设置。如果你用的是自定义骨骼那就要自己试一般是从脊柱的某一节开始往上算上半身。2.4 动画修改器和通知驱动特效和音效Lyra的动画里埋了很多动画通知Anim Notify比如脚步落地时触发音效、开火时触发枪口火焰、换弹时触发弹匣掉落音效。这些通知在动画蓝图的动画序列里你打开动画序列就能看到。另外还有动画修改器Anim Modifier比如AnimModifier_Lyra之类的用来在运行时动态调整动画的播放速率或者叠加偏移。这些修改器一般是C写的挂在动画序列或者动画蓝图上。这一层你能拿走的是思路而不是具体的资源。因为音效和特效资源是Lyra项目专用的你拿过来也没有对应的音效文件。但你可以参考它的做法在动画序列的特定帧上埋通知然后在角色蓝图或者动画蓝图里接收这些通知触发你自己的音效和特效。我自己的做法是先不管音效和特效把动画逻辑跑通角色能正常跑、跳、瞄准。然后再回头在动画序列里补通知一个一个接音效。这样不会一开始就被一堆资源依赖卡住。3. 把Lyra的动画逻辑接到自己角色上的完整操作流程3.1 准备工作确认骨骼兼容性和动画资源在动手之前先确认一件事你的角色骨骼和Lyra的骨骼是不是同一套。Lyra用的是UE5的SK_Mannequin骨骼也就是UE5自带的那个小白人骨骼。如果你的角色也是用这套骨骼做的那恭喜你动画资源可以直接复用。如果不是那你需要做骨骼重定向Retargeting。骨骼重定向在UE5里已经比较成熟了。你可以在动画资源上右键选择Retarget Animations然后选择源骨骼和目标骨骼UE5会自动帮你生成重定向后的动画。但自动重定向的结果不一定完美尤其是手指、肩膀这些关节可能会有扭曲。你需要手动调整重定向链Retarget Chain的设置或者对个别动画做微调。我自己的项目用的是自定义骨骼和Mannequin的骨骼比例不太一样。我的做法是先用自动重定向生成一套动画然后在BlendSpace里看效果哪个动画扭曲得厉害就单独打开那个动画调整重定向链的旋转偏移直到看起来正常。这个过程比较耗时间但一次调好之后后面就省事了。另外Lyra的动画资源是打包在Lyra项目里的你不能直接复制到自己的项目里因为会丢失引用。正确的做法是在Lyra项目里找到动画资源右键选择Asset Actions-Migrate然后选择目标项目的Content文件夹。UE5会自动把动画资源以及它依赖的骨骼、物理资产等一起迁移过去。3.2 新建动画实例类并移植核心变量接下来新建一个C类继承自UAnimInstance命名为你自己的动画实例类比如UMyAnimInstance。然后打开Lyra的动画实例类如果你有Lyra的C源码把里面和运动状态相关的变量抄过来。Lyra的动画实例里大概有这些变量GroundSpeed地面速度、bIsMoving是否在移动、bIsFalling是否在下落、bIsCrouching是否在蹲伏、bIsAiming是否在瞄准、AimPitch瞄准俯仰角、AimYaw瞄准偏航角、bIsFiring是否在开火、bIsReloading是否在换弹等等。抄的时候注意Lyra的这些变量有些是UPROPERTY(BlueprintReadOnly)有些是BlueprintReadWrite。你根据自己的需要设置。一般来说动画蓝图里只读的变量设为BlueprintReadOnly需要在动画蓝图里修改的设为BlueprintReadWrite。然后在NativeUpdateAnimation函数里写更新这些变量的逻辑。比如void UMyAnimInstance::NativeUpdateAnimation(float DeltaSeconds) { Super::NativeUpdateAnimation(DeltaSeconds); if (APawn* Pawn TryGetPawnOwner()) { if (ACharacter* Character CastACharacter(Pawn)) { FVector Velocity Character-GetVelocity(); GroundSpeed Velocity.Size2D(); bIsMoving GroundSpeed 3.0f; bIsFalling Character-GetCharacterMovement()-IsFalling(); bIsCrouching Character-bIsCrouched; } } }这段代码是纯逻辑不依赖Lyra的任何组件可以直接用。瞄准相关的变量需要你从自己的角色类里获取比如你的角色有一个bIsAiming的布尔值那就直接读过来。3.3 搭建动画蓝图的状态机骨架动画实例类准备好之后新建一个动画蓝图父类选择你刚建的UMyAnimInstance。然后打开动画蓝图开始搭状态机。先搭最基础的地面移动状态机。在AnimGraph里新建一个状态机命名为Locomotion。然后在状态机里新建一个状态命名为GroundMove。在GroundMove状态里放一个BlendSpace播放器选择你准备好的移动BlendSpace。然后设置过渡规则从GroundMove到Jump的过渡条件是bIsFalling为true从Jump到Land的过渡条件是bIsFalling为false且GroundSpeed小于某个阈值从Land到GroundMove的过渡条件是动画播放完成。这些过渡规则和Lyra的基本一致你可以直接参考。但要注意Lyra的状态机里还有一些额外的状态比如Crouch、Aim、Fire等。你可以先不加等基础移动跑通了再加。3.4 用动画层接口做上下半身分离基础移动跑通之后接下来做上下半身分离。在动画蓝图里新建一个动画层接口命名为MyAnimLayerInterface。在接口里定义一个函数比如UpperBody返回类型是PoseLink。然后在动画蓝图里实现这个接口。在AnimGraph里用Layered blend per bone节点把基础层的Pose和上半身层的Pose混合起来。基础层就是刚才搭的Locomotion状态机上半身层是一个单独的状态机里面放瞄准、开火、换弹等状态。Layered blend per bone节点的设置里有一个Layer Setup数组你需要添加一个元素设置Bone Name为spine_01或者你角色骨骼的对应骨骼Blend Depth为1Blend Weight为1。这样从spine_01往上的骨骼都会用上半身层的动画往下的用基础层的动画。这个设置不是固定的你要根据自己角色的骨骼结构调整。如果混合之后发现脖子或者头部有扭曲可能是Blend Depth设得不对试着调大或调小。3.5 在角色蓝图里挂接动画蓝图最后一步打开你的角色蓝图在Mesh组件的Anim Class里选择你刚建的动画蓝图。然后运行游戏看角色是否能正常播放动画。如果角色不动或者动画播放不正常先检查动画实例类里的变量是否被正确更新。你可以在动画蓝图里加一个Print String节点把GroundSpeed打印出来看是不是0。如果是0说明角色蓝图里的移动逻辑没有正确驱动角色或者动画实例类没有正确获取到角色。如果角色动了但动画不对比如跑起来是滑步那可能是动画的播放速率和角色的移动速度不匹配。你可以在BlendSpace里调整动画的播放速率或者在动画蓝图里用Play Rate节点根据GroundSpeed动态调整播放速率。4. 调整过程中最容易翻车的几个地方4.1 动画朝向和角色朝向不一致这个问题我前面提过但值得单独拿出来说。Lyra的动画是专门调整过的动画里的角色朝向和胶囊体朝向一致。但你从Mixamo或者其他地方下载的动画很可能朝向不一致。表现就是角色跑起来是横着的或者倒着的。解决办法有两个一是在导入动画的时候调整Import Rotation把动画旋转到正确的朝向二是在BlendSpace里给每个动画设置Rotation Offset。我一般用第一种因为一次调好后面所有动画都对了。具体操作是在导入FBX的时候在Mesh设置里把Import Rotation的Z轴设为-90或者90看哪个方向对。4.2 状态机过渡条件写错导致动画卡死状态机的过渡条件如果写得太严格或者有逻辑漏洞会导致动画卡在某个状态出不来。比如从Jump到Land的过渡条件如果你只写了bIsFalling为false那角色落地后如果bIsFalling因为某种原因没有及时更新就会一直卡在Jump状态。我的经验是过渡条件要尽量宽松并且加一个超时机制。比如从Jump到Land除了bIsFalling为false还可以加一个Time Remaining小于某个值强制切换。另外在状态机里加一个Any State到Land的过渡条件也是bIsFalling为false这样不管在哪个状态只要落地了就切到Land。4.3 动画层接口的混合权重设错导致骨骼扭曲Layered blend per bone节点的混合权重如果设得不对角色的上半身和下半身会打架表现就是骨骼扭曲、穿模。常见的原因是Blend Depth设得太大或太小导致混合的骨骼范围不对。我的做法是先在Layered blend per bone节点里只设一个骨骼比如spine_01Blend Depth设为1。然后运行游戏看上半身是否正常。如果正常再逐步往上加骨骼比如spine_02、spine_03直到上半身完全受上半身层控制。如果发现某个骨骼开始扭曲就把它从混合列表里去掉或者调整Blend Depth。4.4 动画通知没有正确触发动画通知是在动画序列里埋的如果你迁移动画的时候只迁移了动画蓝图没有迁移动画序列那通知就不会触发。或者你迁移了动画序列但通知绑定的函数在动画蓝图里没有实现也不会触发。解决办法是打开动画序列检查通知是否还在。如果不在手动加回去。然后在动画蓝图里右键添加通知事件确保每个通知都有对应的处理逻辑。如果通知是用来触发音效的你还需要在角色蓝图里实现对应的音效播放逻辑。5. 跑通之后还能怎么优化5.1 用动画修改器做动态调整Lyra的动画系统里用了动画修改器来做一些动态调整比如根据角色的速度动态调整跑步动画的播放速率或者根据瞄准角度动态调整上半身的倾斜。这些修改器一般是C写的挂在动画蓝图上。你可以参考这个思路写自己的动画修改器。比如写一个修改器根据GroundSpeed动态调整BlendSpace的播放速率让角色的脚步和移动速度匹配。或者写一个修改器根据AimPitch调整上半身的俯仰角度让瞄准看起来更自然。5.2 用动画共享Animation Sharing优化性能如果你的游戏里有很多角色每个角色都跑一套完整的动画蓝图性能开销会很大。Lyra用了动画共享Animation Sharing来优化让多个角色共享同一套动画更新逻辑。动画共享在UE5里是一个插件叫Animation Sharing。你可以在插件设置里启用它然后配置哪些动画蓝图可以共享。配置好之后多个角色可以共享同一个动画实例的更新结果大大降低CPU开销。这个功能在Lyra里是默认开启的你可以参考它的配置。5.3 用Control Rig做程序化动画Lyra的动画系统里还用了Control Rig来做一些程序化动画比如根据地形调整脚部的位置或者根据瞄准方向调整手臂的角度。Control Rig是UE5的一个功能允许你在运行时用蓝图或者C控制骨骼。你可以参考Lyra的做法给自己的角色加一个Control Rig然后在动画蓝图里用Control Rig节点根据角色的状态动态调整骨骼。比如角色站在斜坡上时用Control Rig调整脚部骨骼让脚贴合地面。这个功能做出来之后角色的动画会自然很多。6. 我自己的实操心得和几个小技巧先说一个我踩过的坑一开始我想直接把Lyra的动画蓝图整个复制到我的项目里结果打开就报了一堆错因为引用了Lyra项目里不存在的类。后来我改成只抄逻辑不抄资源把Lyra动画蓝图里的状态机和过渡规则用截图记下来然后在自己新建的动画蓝图里重新搭一遍。虽然麻烦但一次搭好之后后面改起来很顺手。另一个心得是不要一开始就追求完美。我刚开始做的时候总想把Lyra的所有功能都搬过来结果做了两周还没跑通基础移动。后来我改变策略先只做地面移动跑通了再加跳跃再加瞄准再加开火。每加一个功能都确保它能独立工作再往下做。这样虽然看起来慢但实际上更快因为出问题的时候容易定位。还有一个小技巧在动画蓝图里加一个调试用的Print String节点把关键变量打印出来。比如GroundSpeed、bIsFalling、bIsAiming。运行游戏的时候看这些值是否和预期一致。如果不一致说明动画实例类的更新逻辑有问题或者角色蓝图里的状态没有正确同步。这个技巧帮我省了很多排查时间。最后说一个关于动画资源的建议如果你没有自己的动画资源可以先用UE5自带的Mannequin动画顶着。UE5的初学者内容包里有全套的移动、跳跃、瞄准动画虽然质量一般但足够你把逻辑跑通。等逻辑跑通了再换成你自己的动画资源只需要替换BlendSpace里的动画引用不用改逻辑。整个流程走下来我的感受是Lyra的动画系统确实设计得很好但它不是拿来就能用的。你需要理解它的分层设计然后按需拆解一层一层接到自己的项目里。这个过程需要耐心但一旦跑通你对UE5动画系统的理解会上一个台阶。后面再做其他动画相关的功能比如攀爬、翻越、处决都会轻松很多。