本文的同步模型基于上篇 小球大作战-帧同步

结论:在网络抖动的情况下,可以通过预测玩家操作来达到平滑过渡的目的。

项目 GIT:https://git.oschina.net/dreamyouxi/XYGame

同步频率 40,帧间隔 25ms。

一个 BUG 引发的思考

BUG 现象:在地图中用摇杆持续控制玩家走动,角色偶尔会停顿一瞬间,然后继续走。

初步排查:这一帧服务器下发的方向控制数据是默认值,而非玩家实际操作的值。严格检查客户端代码后,发现一种可能性:摇杆逻辑只负责写入摇杆角度变量,逻辑帧发送时读取该值并将其重置为默认值。正常情况下不会出现不连贯,但如果 Unity 的帧率低于逻辑帧率(40fps),就会出现摇杆逻辑写入一次、逻辑代码读取两次的情况——第一次读取后置为默认值,第二次读到的就是默认值,导致客户端发出的数据不连贯。将重置默认值的代码去掉后测试,问题依然存在。而且在 Unity 帧率高于逻辑帧率时,按上述原因,每次逻辑读取之前摇杆至少应写入一次有效值,但此情况下问题同样复现。

暂时排除客户端原因后,转向服务端排查。服务端异步接收数据,25ms 内未收到则默认客户端无操作,摇杆角度为默认值。如果规定时间内服务端未收到数据,就会出现这种情况。测试环境是本地服务器,起初不认为会在 25ms 内收不到数据,但咨询大佬后得知:局域网同样存在网络抖动,实际收到数据的时间会在 25ms 左右波动。

服务端预测方案

基于上述原因,解决方案也随之清晰:在游戏规则约束下,规定时间内未收到客户端数据时,原来默认发送空帧数据,现改为预测为上一个操作的摇杆角度。

修改 Player.cpp 部分代码如下:

这种预测基于服务端,对于本游戏而言预测内容仅为玩家的摇杆操作。虽然画面看起来更连贯,但对于服务器到客户端方向的网络波动问题——即客户端预测——还需进一步考虑。

数据包取舍规则

此外有一个额外问题,虽然之前已有设计,但值得记录一下:网络波动导致规定时间内未收到数据,并不意味着已经丢包,下一帧服务端可能会收到 2 条甚至更多客户端数据。为保证完全一致,在未丢包的情况下需舍去多余数据,确保数据与下发给客户端的保持一致;若已丢包,则无需考虑取舍问题。

取舍规则可根据游戏特色来决定。在本游戏的 40 同步频率下,玩家的技能操作相对较弱,多个包的优先级为:

  • 携带技能权重数据的包
  • 有技能操作的包
  • 无技能的普通数据包

客户端预测

客户端若因网络延迟、抖动等问题在帧时间内未收到数据,同样可以进行预测,等数据到达后与预测结果对比,预测失败则直接回滚——玩家看到的现象是卡顿一下后被拉回正确状态。

常规状态同步的客户端预测较易实现;帧同步则复杂得多,预测帧数据会涉及整个游戏系统的回滚。对于复杂度不高的游戏,可以为每个逻辑操作对应一个逆操作,甚至可以暴力备份整个游戏系统数据再模拟(类似备忘录模式)。这种方式适合轻度 IO 游戏,庞大的游戏则不适合。

关于逆操作:预测的目的是减少玩家网络突然变差时的卡顿感。逆操作可能非常复杂,回滚时间也可能很长,难以达到减少卡顿的预期效果。即便如此,在网络卡顿持续到一定程度时,相比长时间卡顿,一瞬间的卡顿体验仍然更好。

此外,客户端还可以做插值处理:将网络同步模型与表现模型分为两类,表现模型可以通过插值进行预测平滑处理。

待续……