4.3 模拟代理的插值与平滑

为什么远程角色需要平滑

模拟代理(SimulatedProxy)的位置以 NetUpdateFrequency(如 30Hz)离散到达,如果直接渲染,就是每秒 30 次的跳变。CMC 提供网络平滑(Network Smoothing):维护一个「目标位置+当前渲染位置」,每帧向目标插值靠近,把 30Hz 的离散补成连续运动。

平滑带来的「人为延迟」

平滑的代价是增加渲染延迟:渲染位置始终「落后于」最新真实位置一个平滑窗口。这是不可调和的三角:零延迟 = 不平滑(跳变),平滑 = 有一点延迟。好在人眼对「别人的轻微延迟」远没有对「自己操作的延迟」敏感。

关键参数(以 CharacterMovementComponent 为例)

// 位置平滑
NetworkSimulatedSmoothLocationTime = 0.10f;   // 平滑时长(秒),0.1 是较常用值
NetworkSimulatedSmoothRotationTime = 0.05f;   // 旋转平滑(通常更短)
NetworkMaxSmoothUpdateDistance = 256.0f;      // 超过此距离仍平滑(可能拉丝)
NetworkNoSmoothUpdateDistance = 512.0f;       // 超过此距离直接瞬移(传送时)

调参要点:

症状 调整
远程角色有「拖影/拉丝」感 平滑时间过长 → 调小 LocationTime
远程角色动作「一跳一跳」 推送频率太低 / 平滑时间太短 → 提高 NetUpdateFrequency 或加大平滑时间
传送/出生时角色滑动很长一段 距离很大时仍做平滑 → 调小 NoSmoothUpdateDistance
远程角色转身黏滞 旋转平滑时间过长

有延迟抖动时的处理

Ping 波动(jitter)会让位置包到达间隔忽长忽短,平滑窗口固定时会「一下快一下慢」。高级做法是动态平滑窗口:包间隔大时拉长平滑时间,密集时缩短。CMC 的平滑内部会按 delta 处理,但极端抖动下仍建议结合 NetworkSimulatedSmoothLocationTime 微调。

视觉平滑 vs 物理碰撞

注意:模拟代理的碰撞体位置通常直接用真实位置(不平滑),而渲染网格体用平滑位置。这样既保证碰撞判定准确,又保证视觉流畅。CMC 的 Mesh 平滑(NetworkSmoothing 的 VisualOffset 机制)就是这个思路——网格体相对 capsule 有一个插值偏移。

自己的 Actor 如何实现平滑

非角色 Actor(投射物、可交互物体)需要手动平滑。最简实现:

// 复制属性:真实位置(30Hz 到达)
UPROPERTY(ReplicatedUsing = OnRep_ReplicatedMove)
FRepMovement ReplicatedMovement;      // 也可直接用内置结构

// 客户端:Tick 里向目标位置插值
FVector Target = ReplicatedMovement.Location;
VisualMesh->SetWorldLocation(FMath::VInterpTo(CurrentVisual, Target, Dt, 8.0f));

关键:物理/逻辑用真实位置,渲染用插值位置。碰撞、判定都基于权威坐标,表现层只管好看。

项目内的平滑规范

我们团队的规范(供参考):

  • 玩家角色:CMC 内置平滑,LocationTime=0.1,RotationTime=0.05
  • 投射物:自实现 VInterpTo,速率 10
  • 可交互大型物体:直接用复制位置(移动慢,跳变不明显)
  • 载具:CMC 载具模式 + 适度外插(速度小时允许向前 extrapolate 一小步,速度大时禁用)

心得:平滑参数没有「标准答案」,它取决于你的 NetUpdateFrequency、角色速度和游戏节奏。调参的闭环是:开 100ms 仿真 → 盯着远程角色看 10 秒是否拉丝/跳变 → 微调 → 重来。这个体力活在项目后期值得花,因为它直接决定「这游戏联网感觉怎么样」。