ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

UE高级运动系统拆解:动画蓝图、距离匹配与运动匹配

UE高级运动系统拆解:动画蓝图、距离匹配与运动匹配 UE高级运动系统这个话题我前后拆过三个版本的工程最早是把社区里流传的那套 ALS 工程直接拖进项目里改数值中间踩过一次动画看着对、手感全是错的的坑后来在 UE5 上又用运动匹配Motion Matching重做了一遍才慢慢把里面那条数据链路理顺。这篇不是引擎文档的复述而是我把 UE高级运动系统从骨架到落地这一整条线拆开之后的理解包括每一层该放什么数据、哪些参数必须自己算、哪些地方看起来是动画问题其实是移动组件问题。适合已经写过动画蓝图、但一遇到急停漂移、原地转身抖、脚步悬空就不知道从哪下手的同学也适合准备把手头的胶囊体移动升级成有分量的角色控制的人。下面按我自己理解的顺序展开先定边界再拆数据流然后逐个攻起步、转向、落地这几个最容易翻车的环节最后讲性能与排查。1. 高级运动系统里的高级到底指什么1.1 传统胶囊体加混合空间为什么会露馅绝大多数人第一次做角色移动走的是同一条路CharacterMovementComponent 负责把输入积分成速度动画蓝图里挂一个二维混合空间横轴是速度、纵轴是方向再接一个状态机切走、跑、跳。这套东西在直线跑动的时候看起来完全没问题问题全出在速度突变的瞬间。角色从静止到全速只用两帧胶囊体的速度是阶跃上去的但起步动画有它自己的节奏——它需要 0.3 秒把重心从后脚压到前脚。动画还没起步位移已经出去了于是就出现了脚在地上蹭的滑步感。急停同理胶囊体在BrakingDecelerationWalking的作用下两帧内速度归零可急停动画的后半段还要往前收半步视觉上就变成了刹车但是身体继续飘。我早期做过一个很蠢的验证把MaxWalkSpeed调到 800然后逐个调混合空间的过渡时间试图用混合时间把滑步盖掉。结果是直线看起来勉强能接受一旦做 180 度回头整个角色像是被两根绳子往相反方向拽。那次之后我才明白这不是动画参数问题是位移和动画用了两条互不相干的时间轴。所谓高级核心就是把这两条时间轴重新对齐。1.2 高级运动系统的三层职责拆到最底层一个成熟的高级运动系统其实只干三件事剩下的都是这三件事的延伸。第一层是移动的权威性谁说了算。 capsule 的碰撞、贴墙、爬台阶、网络校正这些必须留在 CMC 或者它的替代品比如后来出现的 Mover 系列组件里因为这是唯一能保证所有客户端结果一致的地方。动画永远不能反过来决定胶囊体去哪一旦让根运动Root Motion直接驱动位移网络下的位置回滚就会变成灾难。第二层是动画的选择逻辑在当前速度、加速度、朝向差、姿态、手持物、受伤状态这些输入的组合下该播哪一帧。这一层在 ALS 那套实现里主要靠状态机和曲线驱动在 UE5 的运动匹配里则被换成了一个成本函数最小的搜索问题。第三层是姿态的修正动画选对了但脚落在了台阶外面、身体朝向和运动方向差了二十度、上下坡时骨盆穿模这些必须在运行时用 IK 和扭曲节点修回来。这三层的顺序不能乱。我见过有人先做第三层把 Foot IK 调得很漂亮然后发现整个角色的移动逻辑还是一片混乱脚确实贴地了但角色走路像螃蟹。先把第一层的权威性定死第二层的选择逻辑才有意义。提醒判断一个运动系统是不是高级有个很快的自检方法——把动画蓝图整个关掉只留胶囊体跑一圈。如果角色的移动手感依然成立说明移动层是独立的如果关掉动画之后你完全判断不出角色在干什么那说明位移严重依赖动画网络一抖动就会散架。2. 从输入到姿态数据流的四层管道设计2.1 输入层要把意图和状态分开存这一步听起来像概念游戏但它直接决定后面调试的时候能不能定位问题。我的做法是玩家的输入摇杆方向、是否按住冲刺、是否按下跳跃属于意图它只应该在本地存在用来驱动。而角色的当前姿态站立还是下蹲、步行还是冲刺、当前朝向偏移多少属于状态它必须能被复制到服务端和所有客户端。在 ALS 那套公开实现里有一个专门的动画状态组件Animation State Component来承载后者它复制的是一小撮标量姿态Stance、步态Gait、移动模式、朝向偏移YawOffset、是否处于转身中等。Pose 本身绝对不复制——动画是确定性的同样的输入在同样的版本上会算出同样的 Pose复制 Pose 是纯浪费带宽。我有一个项目早期就是复制了骨骼的旋转结果带宽账单很难看而且不同机器的浮点误差还会造成轻微的姿态不一致。改成复制标量加确定性的动画计算之后同样的动作在不同机器上完全对齐。2.2 移动组件要驯化而不是替换很多人一上手就想自己写一套移动组件我的建议是能不改就不改要改就改在加速和转向这两个函数上。原因很实际——CMC 里那些bJustTeleported、bClientUpdating、PendingLaunchVelocity之类的状态标志是踩了无数坑才有的你自己重写一遍大概率会漏掉几个然后在网络环境下出现莫名其妙的抽搐。我实际会动的地方主要有三处速度上限的切换不要用乘法去缩放MaxWalkSpeed而是直接设具体数值并且让动画层通过GetMaxSpeed拿到的值和实际一致。步态切换到冲刺时如果动画的播放速率还按 1.0 走就会出现腿速跟不上位移的滑步。旋转速率角色朝向和控制器朝向不一致的时段就是转身发生的地方。把RotationRate设成 0 再靠动画驱动朝向是很常见的做法但要注意控制器朝向本身也需要在某个时刻追上。加速度策略把起步过程从阶跃改成有节奏的加速让胶囊体的速度曲线和起步动画的重心转移曲线形状接近。这一步是消除滑步最有效的手段比调混合时间管用得多。下面这段是我常用的加速逻辑骨架思路是让速度的爬升跟动画节奏对齐// 在自定义 CMC 的 TickComponent 里做一次速度重映射 void UMyCharacterMovementComponent::UpdateAnimDrivenSpeed(float DeltaTime) { const float TargetMax GetMaxSpeed(); // 由步态决定 const float SpeedAlpha Velocity.Size2D() / FMath::Max(TargetMax, 1.f); // 起步阶段用曲线控制加速倍率让速度和动画重心转移同步 if (bIsStarting) { const float CurveValue StartAccelCurve-GetFloatValue(StartElapsed); MaxAcceleration BaseAcceleration * CurveValue; StartElapsed DeltaTime; if (SpeedAlpha 0.95f) { bIsStarting false; StartElapsed 0.f; } } }这段代码本身不复杂关键在StartAccelCurve这条曲线的形状——它不是随便画的而是从起步动画里量出来的找脚跟离地那一帧看位移占整个起步距离的比例把这个比例反过来做成加速倍率曲线。曲线做对了滑步会自动消失一大半。2.3 动画状态层该复制什么不该复制什么我把这一层的数据分成三个桶用表格说明更清楚。数据存放位置是否复制更新频率摇杆输入、镜头朝向本地控制器 / 输入组件否每帧步态、姿态、移动模式动画状态组件ActorComponent是可靠复制状态切换时朝向偏移、骨盆偏移动画状态组件是每帧或插值速度、加速度、是否落地CMC引擎自带复制每帧Pose、曲线值动画实例否每帧本地计算这里有一个容易被忽略的点朝向偏移YawOffset这类量在客户端和服务端必须用同一套插值逻辑。如果本地直接写值、远程走插值两边的转身动画就会不同步表现出来就是远程玩家转身时偶尔跳一下。我习惯把插值逻辑放在动画状态组件里统一处理本地和远程走同一个函数。3. 起步、急停与转向距离匹配和朝向扭曲3.1 滑步的本质是动画的位移预算和世界的位移需求不匹配起步动画在设计的时候美术脑子里有一个默认的位移距离比如这个起步动画从静止到全速角色实际上往前移动了 1.2 米。这个 1.2 米就是它的位移预算。如果游戏里角色的加速度设置导致 0.2 秒内就走完了 1.5 米动画还没播完位移已经超支了脚就必须在地上蹭。传统的做法是不用根运动纯靠混合空间动画里的位移信息完全丢掉只保留姿势。这样做的好处是网络简单代价就是预算信息丢失只能靠感觉调。而距离匹配Distance Matching做的事情是把位移预算重新捡回来用从动画里烘焙一条距离曲线记录每一帧累计走了多远运行时用当前实际速度反推出我现在应该播到动画的哪个位置。3.2 距离匹配的落地链路实现上有几个必须按顺序做的步骤顺序错了会白折腾。第一步是烘焙距离曲线。在动画序列上算每一帧相对上一帧的根骨骼水平位移累加起来做成一条 0 到总距离的曲线。这一步在引擎里有对应的工具需要注意的是采样频率——如果曲线采样太少起步和急停这种位移变化剧烈的片段会算不准我一般会把曲线关键帧密度提高一倍。第二步是运行时算应该播到哪。核心是一个积分把当前速度对时间积分累积出一个预期已走距离然后用这个距离去查曲线反解出对应的动画时间。这里有个细节积分要用实际速度而不是目标速度否则急停的时候会算出负距离。// 由速度积分反解动画时间核心思路 float UMyAnimInstance::CalcDistanceMatchedTime(float DeltaTime) { // 预测帧的移动距离 当前速度 * 帧长 const float PredictedDist CurrentSpeed2D * DeltaTime; DistanceAccumulator PredictedDist; // 在距离曲线上找到累计距离等于 DistanceAccumulator 的时间点 const float TotalDist DistanceCurve-GetFloatValue(DistanceCurve-GetPlayLength()); if (TotalDist KINDA_SMALL_NUMBER) { return 0.f; } const float TargetDist FMath::Clamp(DistanceAccumulator, 0.f, TotalDist); return DistanceCurve-GetTimeAtValue(TargetDist); }第三步是把结果接到播放速率上。这里有个取舍如果直接把动画跳到目标时间遇到速度抖动会一顿一顿的如果只改播放速率急停的时候动画会播得太快像加速播放。我通常用速率为主、时间修正为辅的混合策略速率限制在一个区间内比如 0.6 到 1.6超出的部分再用时间偏移补齐。3.3 朝向扭曲解决的是转身时脚步朝向不对角色在向前跑的时候突然向左推摇杆胶囊体会立刻开始往左偏但动画里的跑步姿势还是朝前的看上去就像角色侧着身子横移。朝向扭曲Orientation Warping做的事情是在转身的时间窗口内把整条动画的姿态主要是脊柱和腿往下一次稳定的朝向旋转旋转角度由运动方向和身体朝向的夹角决定。关键参数有三个我一般这么设参数作用我的常用取值说明扭曲角度上限单次姿态旋转的最大角度60 到 75 度超过这个值动画会明显变形宁可直接切换转向动画骨骼链起点从哪根骨骼开始往下转骨盆从骨盆开始能保证腿部跟着走脊柱单独再补一层扭曲窗口在动画的哪段时间内完成旋转对应动画的支撑脚落地区间一定要对齐脚步否则脚会在地上拧这里我要分享一个很容易踩的坑朝向扭曲和根骨骼旋转不能同时用。如果你既在动画里把根骨骼转了又开了朝向扭曲两边的旋转会叠加角色会转过量。我一般的做法是根骨骼只承担左右转向的倾斜感实际朝向全部交给扭曲节点和胶囊体旋转。还有一点朝向扭曲的窗口必须和脚步同步。我刚开始用的时候没管这个结果在脚落地的那一帧做旋转整个角色看起来像在冰面上碾脚。后来把窗口对齐到支撑脚完全踩实的区间问题就没了。4. 原地转身一条曲线撑起的完整闭环4.1 曲线驱动转身的原理原地转身Turn In Place是很多人的第一个坎。最常见的做法是在动画里给一个 90 度转身序列播放的时候让根骨骼真的转 90 度然后把胶囊体也转过去。问题在于时机如果胶囊体一开始就转动画还没播完视觉上身体朝向和碰撞朝向不一致会出现短暂的挤墙如果胶囊体一直不转控制器朝向又追不上玩家觉得转向迟滞。比较稳的做法是分三段预热段动画不转胶囊体也不转只是在动画状态里标记正在转身执行段动画通过一条曲线把根骨骼的旋转逐帧加大胶囊体同步用同样的曲线插值它的目标朝向收尾段曲线归零动画回到中性姿势胶囊体的朝向已经完全到位。整个过程动画和碰撞体的朝向用同一条曲线驱动所以永远同步。我实际会用的做法是给转身动画加一条叫YawOffset的曲线范围是 0 到 1在动画蓝图里用「按曲线旋转根骨骼」的节点把曲线值乘以目标角度应用到骨骼上。然后在动画状态组件里复制这个曲线值乘以角度胶囊体用同样的值更新朝向。本地和远程客户端只要拿到YawOffset和一个目标角度就能算出完全一致的结果。4.2 转身角度不足 90 度怎么办这是最实际的问题玩家只转了 30 度播完一整套 90 度转身动画太夸张。我的处理方式是做多种角度的转身资源然后按角度分档用最短的那个能覆盖当前角度的动画。经验值是 45 度、90 度、180 度三档超过 180 度直接走一个转身再跑的过渡不要硬撑。还有一个技巧如果当前角度落在两档之间比如 70 度播 90 度动画但把YawOffset曲线乘上一个 70/90 的系数同时加快播放速率。这样既能复用资源视觉上也不会觉得转多了。我试过在项目里用这个办法把转身资源从七套压到三套效果上只有极少数角度能看出来差异。4.3 网络环境下转身的抖动来源远程玩家转身抖绝大多数情况是这三件事之一朝向偏移没有插值本地写值、远程走瞬时赋值两边的骨骼角度会有跳变。胶囊体旋转和动画曲线不同步一个是引擎的平滑插值一个是你自己的曲线两者的时间常数不一样。转身被打断玩家转到一半松手你的状态机还在播转身但输入已经变成静止导致动画和实际朝向差一个固定角度。第三个问题的处理办法是给转身加一个最短持续时间一旦进入这个状态就必须播完或者反向播回去。不要允许转一半就切走那样一定会出现朝向误差累积。5. 脚步落地与 Foot IK 的三段式结构5.1 髋部偏移、脚部锁定、脚掌对齐Foot IK 不是改一下脚的位置这么简单它是一套三段式的调整。我按处理的先后顺序讲第一段是髋部偏移Pelvis Offset。当角色站在斜坡上两条腿的实际长度需求不一样如果不调整骨盆高度一条腿会一直悬空。做法是取左右脚各自的落地高度需求取其中的最小值也就是最陡的那只脚让骨盆往下走一点保证两只脚都能落地。这里要注意骨盆往下走会让上身整体下沉看起来像蹲着所以偏移量必须限制在一个小范围内我一般给 15 厘米封顶超出的部分宁可让脚悬空也不要把角色压扁。第二段是脚部锁定。在动画里脚掌踩实的那几帧脚不应该随着身体晃动而滑动。做法是给动画加左右两条曲线比如FootLock_L、FootLock_R在脚落地的区间值为 1抬起时为 0。运行时用一个记录节点把曲线为 1 时的脚部变换保存下来曲线为 0 时再释放。这个机制在急停和原地转身时作用特别明显——没有它转身的时候脚会在地上拧一圈。第三段才是脚掌对齐。让脚底贴合地面的法线方向上坡时脚掌前倾、下坡时后倾。这一步的旋转不要做满做 60% 到 80% 就够了做满的话脚踝会扭得很假。而且旋转要限制在脚踝的合理活动范围内超了要截断。5.2 曲线标记和 IK 的配合关系曲线标记和 IK 是两套独立的系统但它们必须对齐。我的经验是把曲线标记当作地面接触的真值IK 只是在这个真值的基础上做微调。具体说曲线标记告诉你这一帧脚应该踩在地面高度 hIK 负责把动画里因为骨盆偏移而下沉的脚重新拉回 h。有一次我把顺序搞反了先跑 IK 再读曲线锁定结果锁定的是已经被 IK 修正过的位置急停的时候脚会二次滑动。所以顺序一定是先根据曲线记录原始脚部变换再做 IK 修正最后如果需要把记录的位置作为 IK 的目标。5.3 特殊地形的处理思路地形问题处理方式斜坡某只脚悬空或穿模骨盆按双脚最低需求下移脚掌按地面法线旋转台阶前脚踩空抬脚高度加一个最低抬升量落地时用最短时间插值到位上下坡跑步幅和动画不匹配用步幅扭曲节点缩放腿部姿势别改播放速率半透明或镜面区域脚下的地面射线打不到正确表面收敛射线通道只对地形和可站立物体响应最后一条是我吃过亏的射线用了默认的通道结果角色走到玻璃地面上脚下打到的是一层特效碰撞体脚直接被拉到半空中。后来把所有 IK 射线收敛到一个专用通道只对地形、地板、可站立的静态物体响应问题就再没出现过。6. 状态机与分层混合的组织方式6.1 基础层加叠加层的分层策略我个人比较推荐的组织方式是两层基础层负责所有腿部的运动包含站立和蹲伏两个大状态叠加层负责上半身的姿态比如持枪、受伤、持物。两层用「按骨骼分层混合」节点接起来混合的起始骨骼设在脊柱中段附近。这样组织的最大好处是上半身的叠加状态不需要关心腿部在干什么切换持枪姿态的时候不会影响跑步循环。我早期把所有东西塞在一个状态机里每加一个持枪状态就要复制一遍所有的腿部转移连线后来改成两层之后状态机的连线数量直接少了一半。分层混合节点有几个参数容易调错我列一下混合深度控制混合的过渡范围。设太大上半身的姿态会渗透到腿部蹲伏的时候屁股会跟着动。网格空间旋转混合上身叠加动画通常需要勾上否则脊柱旋转在本地空间下会变形。混合配置文件如果两层的动画长度不一致用混合配置文件比用固定的混合时间更自然。6.2 混合空间里那个被忽视的朝向轴大部分人的移动混合空间只有一维速度。跑起来没问题一旦横向移动就露馅因为横向的腿部姿势和向前完全不同。正确做法是加一个方向轴一般用运动方向相对身体朝向的夹角的余弦和正弦组成一个圆形采样区域。这里有一个隐藏的坑方向轴的分辨率不需要太高。我见过有人放 16 个方向结果每一个方向分到的动画资源都很稀疏切换时会出现明显的跳姿势。我的经验是八个方向加一个中性的向前姿势就够了中间的方向交给混合插值。还有一点方向轴上的采样点必须在相同的步态相位上采样。如果八个方向的动画各自从不同的脚落地状态开始混合的时候脚会互相打架。做法是给这些动画设同一个同步组让它们按同步标记对齐。6.3 同步组和同步标记同步组解决的是循环动画之间的相位对齐问题比如走和跑之间切换时希望左脚落地时刻对齐左脚落地时刻。做法是把相关动画放进同一个同步组引擎会自动按标记对齐。同步标记则是你手动指定的关键相位点我一般标记左脚落地和右脚落地两个点就够了。这里我要提醒一个容易被忽略的细节参加同步的动画必须覆盖同样的相位数量。如果一段跑步循环的右脚落地标记丢了同步会退化成随机对齐表现就是跑走切换的时候偶尔跳一下。我每次加新动画之后都会顺手检查一遍同步标记这个习惯省了我很多排查时间。转身、急停这类一次性动画不要放进同步组它们有自己独立的进入时机。7. 从状态机走向运动匹配成本函数才是核心7.1 运动匹配解决的到底是什么状态机加混合空间这套方案的天花板在于它只能表达从预定义的少量状态中选一个。当角色需要做的动作越来越多——不同速度的转身、不同坡度的落地、被推挤时的踉跄——状态机的连线数量会指数爆炸而且每个过渡都得手动调。运动匹配换了一个思路不预定义状态把几百段动画的每一帧都放到一个数据库里运行时根据当前状态和期望的未来轨迹去数据库里找最匹配的那一帧。这本质是一个最近邻搜索问题匹配的度量就是成本函数。7.2 成本函数怎么理解和调成本函数一般由几项加权相加每一项的含义和调法都不太一样。我按重要性排一下成本项衡量什么权重调大的后果我常用的相对权重未来轨迹成本未来几帧的位置和朝向与期望的差距更严格地跟随预测路径转身会提前最高速度成本当前帧的速度和角色速度的差距更贴合当前速度急停会硬高姿态成本骨骼姿势的连续程度动作更平滑但响应变慢中位置成本当前帧相对原点的位移偏差减少漂移但可能选到不合理的一帧低到中延续成本偏置惩罚跳到不连续的帧调大能减少跳帧调小响应更快按需我调这套参数的经验是先调轨迹再调速度最后动姿态。轨迹决定了整体行为对不对速度决定了响应快不快姿态只在最后做平滑。如果一上来就调姿态成本很容易把系统调成很顺滑但不听话而且很难判断问题出在哪。还有一点成本函数的各项之间量纲不同所以权重不是凭感觉设的要按实际数值范围归一化。比如位置差可能是几十厘米速度差是几百如果不做归一化速度项的绝对值会压过其他所有项。我在调试的时候会把每一项成本单独打日志出来看范围再决定权重。7.3 轨迹预测才是真正的门槛运动匹配里最难搞的不是搜索算法是轨迹预测。因为你搜索的时候用的是未来期望位置这个未来位置必须提前算出来给他而算未来位置需要假设玩家接下来怎么走。常见的做法是用历史输入做外推记录过去零点几秒的输入方向变化如果方向变化率很小就假设继续直线如果变化率很大就假设正在转向并在轨迹里体现出一个圆弧。这里的时间窗口很关键——太短了来不及反应太长了转身会提前。我的经验是预测窗口在 0.5 到 1 秒之间比较舒服其中近端几帧权重高、远端权重低。转身和急停的检测也是从轨迹历史里看出来的如果过去一小段时间输入方向变化超过一个阈值就判定进入转向把轨迹的前几个采样点按圆弧生成。如果速度在短时间内掉到接近零就判定进入急停这时候要主动把轨迹截短否则系统会一直尝试用跑步帧去匹配一个静止的未来。8. 性能、线程与帧率排查8.1 先搞清楚动画的耗在哪动画的性能问题分两类一定要分开看动画图本身的计算成本和骨骼变换与蒙皮的成本。前者受节点数量、混合层数、IK 数量影响后者受骨骼数、顶点数、LOD 影响。混在一起查会浪费很多时间。我用的排查命令大致是这几个stat anim # 动画整体耗时 stat animblend # 混合节点耗时 stat animtick # 动画实例 Tick 耗时 ShowDebug Animation # 屏幕上的动画状态调试信息 p.AnimBlueprints 1 # 可视化动画蓝图调试还有一个很有效的办法是临时把 IK 和分层混合节点拔掉看帧率有没有明显变化。如果拔掉之后帧率回来了那问题就在节点计算如果没变就往骨骼数量、蒙皮、LOD 方向查。8.2 让动画实例跑在正确的线程上动画默认会在自己的线程上求值但前提是你的动画蓝图的更新逻辑是线程安全的。如果你的动画蓝图里在事件图上读取了 Actor 的位置、做了一次射线检测那这整个蓝图就被拉回主线程动画线程的优势全没了。我的处理原则是所有游戏逻辑数据在游戏线程上读取并缓存成标量动画线程只读缓存。具体做法是在动画实例里写一个线程安全的更新函数在里面只做数学计算不做射线检测、不访问世界。射线检测的结果比如地面高度、脚部目标在游戏线程算好作为一个普通变量传进来。这个改动带来的收益在我最近一个项目里大概是动画耗时降低 30% 到 40%而且帧率的抖动明显变小。8.3 更新频率与预算分配角色数量一多最有效的手段不是优化动画图而是降低更新频率。远处的角色每秒更新几次就够了屏幕上只有一个小点的时候谁看得出来它的 IK 有没有修正。动画预算分配器可以给每个动画实例分配一个预算超预算的自动降低更新频率。使用的时候有几个参数要调好预算的总额度、初始的更新频率、优先级分组。我的经验是把玩家角色和近处敌人放进高优先级组保证永远是全频率中距离的角色降频远处直接切 LOD 动画。这里有个容易忽略的细节降频的边界和 LOD 的边界要对齐否则会出现降频了但还是全骨骼的浪费情况。两套系统的切换距离设成一样的值最省心。8.4 我排查帧率问题的固定顺序踩过几次坑之后我形成了一套固定的排查顺序分享出来供参考先确认帧率下降是所有场景都有还是特定场景才有。如果只在某个场景掉多半是那个场景的角色数量或者地形问题。用stat unit区分是游戏线程、绘制线程还是 GPU 的问题。动画耗时只会体现在游戏线程上。确认是游戏线程之后用stat anim看动画的实际耗时。数字不大就别在动画上找了去查物理或者蓝图逻辑。如果动画耗时确实高先看角色的更新频率和 LOD再拔 IK 和分层混合最后才考虑重构动画图。别忘了检查有没有在动画通知里做重活。我遇到过一次帧率问题最后发现是脚部落地通知里每次都去查物理材质。最后这条特别值得说动画通知是很容易被滥用成每帧执行的小逻辑的地方实际上它的执行频率是每次通知触发绕着动画循环走就是每秒好几次在里面做查询或者生成 Actor 都要谨慎。9. 几个我反复踩到的坑坑一把播放速率当成速度调节器。步态切换的时候想用播放速率让动画追上位移结果速率调到 2.0整个跑步循环像快进。正确做法是让动画资源和速度档位一一对应播放速率只在很小范围内0.9 到 1.1微调。坑二用混合时间盖住逻辑错误。过渡时间调长确实能让切换看起来顺但那是把两个错误的姿态混在一起。判断方法很简单把过渡时间调到接近零看切换瞬间的姿态对不对。如果瞬间跳得很明显说明选中的两段动画的相位或朝向本身就对不齐应该去修资源不是去修时间。坑三Foot IK 的射线通道没有收敛。前面提过一次这里再强调一下因为这个问题在开放世界里出现的频率非常高。所有可站立的表面都要在一个专用通道里装饰性的碰撞体全部排除。坑四只在本地测试转身和急停。本地和服务端的插值路径往往不一样我在本地调好的一套参数上了网络之后远程玩家的转身会明显滞后。测试运动系统的时候一定要开两个窗口同时看或者至少在网络模拟延迟的情况下走一遍。坑五忘了动画状态组件的复制条件。有些状态量在服务端被改了但没标记为需要复制客户端的状态就和服务器分叉了表现是某些玩家的姿态卡在一个错误的状态上出不来。每次给动画状态组件加新字段我都会问自己一句这个值在服务端变化时客户端需要知道吗。这几个坑的共同点是它们都不表现为崩溃而是表现为手感不对。手感问题最难查因为没有报错信息只能靠假设、验证、排除。我的建议是每加一个新特性都在固定的几条路径上走一遍直线跑、急停、90 度转身、上坡、下台阶把这几条路径当成回归测试能省下大量后期返工的时间。真正让我对 UE 高级运动系统产生理解的不是某一个具体的节点或者参数而是意识到动画和位移是两套时间轴这件事之后整个问题空间突然就清晰了。起步、急停、转身、落地这些看起来互不相关的现象本质都是两条时间轴在某处对不上。想清楚这一点再去翻那些曲线、扭曲节点、IK 结构就都能找到它们各自的定位了。
返回列表