帧同步的一点人参经验

帧同步的逻辑比较简单,网络上也有大量的文章了,这里几句话概括一下:

  1. 客户端与服务器约定一个频率更新步进,比如66ms。
  2. 每个步进周期,服务器都会收集每个玩家的输入动作,组装成步进帧。
  3. 在步进结束的时候,广播给每个玩家。
  4. 如果某个玩家少了某个步进帧,会卡住自己,这时需要到服务器请求缺失数据。

下面讲下几个设计细节:

  1. 可靠性
  2. 响应性
  3. 流畅性
  4. 一致性

一、 可靠性–协议设计

在传输协议的选择上,选择UDP。优点是速度快、节省流量,缺点是不可靠。也有人喜欢用TCP,但我认为不合适。ACK机制、重传机制、保序机制都是也没必要的开销。帧同步需要所有数据到齐才能往下走,对延迟特别敏感。

如果使用UDP,需要考虑乱序和丢包问题进行优化。对于乱序,可以把先到的后续包存起来,这样等晚到的包到达,就可以把顺序修正。对于丢包,可以每次下发冗余一帧数据来优化丢包,必须连丢两帧才算丢包。

如果需要优化网络,那么一定要基于可视化的Profile工具去做。

二、 响应性–降低延迟与预表现

可以把延迟初略划分为,网络延迟与执行延迟。网络延迟就是网络传输的延迟,只能在外部优化,跟服务器稳定性、连接质量相关。而执行延迟则是具体实现的逻辑,这个延迟的优化基本在代码层面。

我的经验就是用时序图把各种网络情况枚举出来,把网络延迟、执行延迟、总延迟都统计清楚,针对每一种情况进行优化。

还有一个经验就是考虑如何隐藏延迟,角色是可以进行预表现,让玩家觉得响应迅速,可以参考很多状态同步的经验,这个话题比较大,不进行展开。

三、 流畅性–减少卡顿

首先这跟客户端与服务器的频率是否匹配相关,避免客户端过快消耗服务器下发的数据,也要避免过慢,否则要么卡顿,要么延迟。

还有就是如果客户端发现自己落后了,快进的时候,可以根据情况进行“陡坡缓降”,这样画面就不会产生严重拉扯。

还有一个很容易忽略的就是客户端自己的FPS是否达标。

在优化卡顿上最常见的是,客户端预存n帧的步进来播放,预存多了会延迟,预存少了会卡顿。可以根据具体的网络状况来调整处理能力。

还有一种更好的方法,就是将显示对象与逻辑对象分开,这样显示对象可以跟着Update进行更新,逐步跟上逻辑对象,这样逻辑层不管怎么卡顿,显示层都有余力去调整效果,并且不用做复杂的步进缓存调节,同时还能降低延迟。

四、 一致性–如何定位问题

这是帧同步的基石,做帧同步最怕就是发生了蝴蝶效应。

浮点数和随机数这些老生常谈的就不细说了,要在框架层面保证确定性,让开发业务的同学省心。

下面提供四个定位的方法:

首先是减少不一致的发生,我们可以精简需要帧同步的逻辑层,把显示层给剥开。把显示层剥开还有另外一个好处,就是客户端快进的时候,更加迅速。

除了精简帧同步的范围,还可以约束每个帧同步的对象,多设计一些没有反作用的对象,那么就算这个对象不一致了,也不影响后续,让不同步局部化。

最后就是问题定位,怎么定位不一致?可以每帧将所有相关的变量算一个值,然后存起来来对,就可以知道哪一帧不同步。

知道发生之后,还需要查为什么。我们可以把每帧的输入输出给记录下来,多台客户端进行对比。

总结

最后,帧同步在中小型(玩家数、逻辑复杂度、时间跨度、空间跨度)游戏中是一种相当好的同步方式,对服务器负担小、开发简单(也意味着可以投入更多精力打磨细节)、不随着单位增加流量、天然支持回放、强一致。采用帧同步的最大顾虑就是作弊问题以及游戏玩法。

从游戏效果上对比帧同步与状态同步:帧同步的整体感会更强,角色与环境的交互更加自然。而状态同步个人感受会更强,视角受限(因为无法观察整个世界)的第一人称用状态同步会非常合适。当然,理论上状态同步的天花板要比帧同步更高,能精耕细作出更加好的体验,然而成本也高不少(除非有类似UNet的架构)。

帧同步非常适合用于快速试错的游戏开发模式,而且《王者荣耀》的大规模成功也给了帧同步技术一支强心针。

参考资料

I Shot You First[http://www.gdcvault.com/play/1014345/I-Shot-You-First-Networking]

Physics Networked[http://www.gdcvault.com/play/1022195/Physics-for-Game-Programmers-Networking]




转载标明原文链接:jjyy.guru/about-lockstep


你可能还喜欢: