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 级别),让位置收敛速度跟得上状态切换
- 远程角色的动画速度参数用「平滑后的速度」而非真实速度(从平滑位移差分算),动画与位置就同步了
心得:远程角色的「真实感」= 状态复制到位 + 平滑参数合理 + 动画轻量化。三块都不是什么高深技术,但拼起来决定了玩家对「这游戏联机做得好」的第一印象。
