位置同步之移动预测

Posted by 恶毒的狗 on January 7, 2020

传统的状态同步流程

最近在整理前项目的代码,顺带把我们之前状态同步的一些技巧记录一下,本文是关于移动预测的。

下图是单人环境下 小甜甜 风骚的疾跑:

img

现在考虑一下把这个疾跑同步给其他玩家。

按照mmo传统的 状态同步客户端先行 的做法,我们的主角在 移动状态 开始时向服务器发送 移动包,服务器收到包后进入 移动状态 并立刻广播给第三方,第三方客户端中我们的主角也进入 移动状态

在移动的过程中,如果我们改变了摇杆方向,那么就把这个改变继续同步到服务器,服务器继续广播给第三方。

最后,我们松开摇杆,我们的主角切换到 站立状态,同样的,我们向服务器发送 站立包,服务器进入站立状态并广播给第三方,第三方客户端中我们的主角也进入 站立状态

理想的时序图如下:

img

第三方的动画抖动

上面的流程看上去没问题:客户端和服务器严格同步状态,并且按照相同的算法各自更新状态机,从而完成同步。

不过实操的过程中我们会发现,第三方的玩家会经常 抖动 或者 滑步,即便我们提高同步频率也无法完全避免这种情况,这是为什么呢?

原因很简单:我们以相同间隔发送的同步包,第三方的客户端未必会以相同的间隔收到,现实世界的间隔可能是这样的:

img

因为 TCP 的缘故,包的时序不会有问题,但是客户端收到包的间隔相较发包时可能会出现较大波动,造成这个波动的主要原因如下:

  • 网络

  • 服务器逻辑

  • 客户端逻辑

举个例子,比如我们的客户端先发了一个 移动包,移动 100ms 后,再发了一个 站立包。第三方的客户端可能会在同一帧就收到这两个包,也可能收到 移动包 后过了 200ms 才收到 站立包,这种不稳定我们可以统一认为是 网络波动

那么要怎么应对这种波动呢?

针对移动的同步,我们应该把 网络波动 考虑到同步算法中去。

预测

我们的做法比较简单:人为加一点 预测,用来补偿这个 波动

首先是目标位置的预测:

当客户端发 移动同步包 时,我们会用当前位置沿当前朝向计算出 5帧预测位移,并以 预测位置 做为服务器和第三方的移动目标点。

第三方的客户端在收到 移动同步包 后会向 预测位置 移动,此时可能会出现2种情况:

  • 我们的主角 3帧 就停下来了,但第三方的客户端因为还没收到 站立包 所以移动超过了 3帧 的距离

  • 第三方的客户端移动到了 5帧 的预测位置,依然没有收到下一个同步包。

针对第二种情况,我们会让第三方客户端继续向前跑一定的距离,这是第三方客户端自己的预测。

第一种情况其实和加了预测后的第二种情况是一致的,本质上都是因为第三方的客户端还没收到下一条同步指令,所以按照预测的方向继续移动一定的距离。

这里的预测可以很好的对抗 网络波动 造成的 动画抖动,唯一要解决的问题是当下一条指令真正到来的时候,如何处理 预测位置实际位置 偏差的问题。

事实上,如果我们在持续移动,这里无需做特别的处理,因为第三方客户端一致保持着追赶正确位置的状态。

并且这里的误差不会累积,因为我们会根据追赶距离按照一定的策略调整移动速度的倍率。

当最后收到 站立包 的时候,我们才需要处理 预测位置实际位置 的偏差问题。

下面来看一下 正常跑步疾跑 的第三方表现。

正常跑步

正常跑步的移动速度较慢,预测位置实际位置 出现的偏差比较小:

下图是第三方玩家绕圈的效果:

img

可以看到,这里绕圈的路径没有那么圆。

因为考虑到 网络流量的优化,我们并没有每帧同步移动状态,而是设置了 180ms 的最小同步间隔,所以第三方收到的移动包是会丢一些路点的。

不过这个效果对于常规mmo来说已经ok了。

疾跑

疾跑的速度很快,预测位置正确位置 比较容易出现较大的偏差。

当误差较小的时候,我们直接 瞬移,表现是有一点轻微抖动。

当误差较大的时候,我们会 加速 跑到 正确位置 去。

下图是第三方玩家疾跑的效果:

img

可以看到停下的时候,因为 预测位置实际位置 有一定的偏差,所以这里有一点瞬移造成的抖动。

不过这个效果也是可以接受的了。

夕阳下的奔跑

好了,差不多到这里。

最后,背诵一句台词,并且附图一张:

我的生涯一片无悔,

我想起那天下午夕阳下的奔跑,

那是我逝去的青春。

img

拜拜!