4.4 动画的网络同步与平滑

远程玩家的动画从哪来

本地玩家的动画由本地逻辑驱动(输入 → 状态机 → 动画)。远程玩家的动画不逐帧同步(带宽爆炸),而是同步「驱动动画的状态」:

  • 复制属性:速度(Idle/Run 混合)、朝向、是否在空中、Montage 播放请求
  • CMC 自带的 MovementMode 复制:走路/跑/跳/掉落的状态直接复制,客户端的状态机据此自然过渡
  • RootMotion 的特殊处理:RootMotion 的位置位移由服务器权威推导,客户端用 replicated 位移

这就是为什么远程玩家的动画一般不需要专门写网络代码——动画实例(AnimBP)读的变量(Speed、bIsInAir)在模拟代理上由复制状态自动填充。

Montage 的网络播放

播放一个受击/攻击 Montage 有三条路:

方式 做法 适用
属性 + OnRep 复制一个「当前播放的 Montage 标识」,OnRep 里 PlayMontage 简单、可靠
Client RPC 服务器给拥有者发 RPC 播 只给某人看
Multicast 服务器广播播放 全场事件

坑点:

  • 同步到位但开始时间不同:服务器播了 0.3 秒才同步到客户端,客户端从头播 → 远程玩家的动画永远「慢半拍」。解法:OnRep 时把 StartTime 设为「服务器已播放时长」(Montage 支持 JumpToPosition),或在数据包里带 TimeStamp 参数。
  • Montage 复制用「播放计数」而非布尔:uint32 PlayCount 每次播放 +1,OnRep 里比较本地缓存的次数 → 触发重播。用 bool bPlaying 会在「中断→再播同 montage」时漏掉。
  • RootMotion 的权威性:带 RootMotion 的 Montage 在模拟代理上播放时,位移不由动画驱动(避免两端漂移),位置仍由复制位置主导——CMC 的 RootMotionSource 系统专门处理这块。

动画降噪:远程角色不需要全覆盖

模拟代理上的 AnimBP 可以用「只放基础层、不 IK、不面部细节」的轻量模式:

// 动画实例里按 IsLocallyControlled 拆分逻辑图表
if (!TryGetPawnOwner()->IsLocallyControlled())
{
    // 远程:只用 Speed + MovementMode 驱动基础混合
    // 关闭 IK、面部、高细节图层
}

这是「远程玩家省性能」的标准手段,顺带省不少 CPU:几十个远程角色同屏时差距明显。

动作融合(Motion Matching / 战术同步)

对动作游戏,远程角色的攻击命中判定依赖动画时机。两条路线:

  • 服务器做动画时机判定:服务器播 Montage(或逻辑计时),到「判定帧」时做命中检查——不看客户端动画。最权威,推荐。
  • 动画通知(AnimNotify)上 RPC:客户端动画播放到 notify 时发 Server RPC 报告——有延迟,且依赖客户端表现,不推荐用于权威判定。

铁律:游戏逻辑永远不依赖远程客户端的动画帧。 动画是表现,判定是逻辑,两者解耦。

平滑与动画的配合

上一节的位置平滑会让远程角色「位置滑过去」,而动画状态(速度)是离散到的——可能出现「动画已经 stopped 但位置还在滑」的滑步。解法:

  • 平滑时间别调过长(0.1s 级别),让位置收敛速度跟得上状态切换
  • 远程角色的动画速度参数用「平滑后的速度」而非真实速度(从平滑位移差分算),动画与位置就同步了

心得:远程角色的「真实感」= 状态复制到位 + 平滑参数合理 + 动画轻量化。三块都不是什么高深技术,但拼起来决定了玩家对「这游戏联机做得好」的第一印象。