帧同步游戏开发小结

本文发表于程序员刘宇的个人博客,转载请注明来源,https://www.cnblogs.com/xiaohutu/p/12402399.html

这几年做了一些网络同步项目,总结一下帧同步的一些东西。

1. 帧同步基本特点

  1. 所有的逻辑行为运算都在客户端进行,客户端保证彼此之间执行结果的一致性。
  2. 客户端将自己的所有操作发给服务器,服务器转发。
  3. 服务器维持一定的逻辑帧率向客户端发包,每次都带上一间隔的所有客户端发来的操作,如果没有就发空帧,附带上客户端需要执行此包的帧数。
  4. 客户端收到帧数据执行这一逻辑帧的行为,否则等待。

2. 同步性的保证

  • 确保需要同步部分逻辑执行次序的一致性,特别关注各种容器的底层结构以及运行过程中对容器的增删改。
  • 确保AI、物理引擎等执行结果的一致性,避免因为使用部分游戏引擎的更新特性,导致一些AI计算的时间次序问题。
  • 确保数学运算在不同cpu上的的一致性,使用定点数或者浮点数截取等方法计算逻辑。
  • 确保随机结果基于次数的一致性,使用次数一致性的随机算法,如梅森旋转算法。
  • 确保客户端数据来源的一致性,存储的静态数据以及读取过程需要特别关注。

3. 逻辑画面分离

  • 抽离开图像执行逻辑和关键帧执行逻辑的循环结构。
  • 逻辑部分可以无画面执行,正确的输出结果。
  • 数据关键帧(即执行AI的关键帧)可以调整,和图像帧的比例也可以调整,图像帧做好动画、位移、旋转等行为的平滑。
  • 在一些游戏中需要实现快速的逻辑与图像脱钩,即迅速加载画面,迅速卸载画面,是否需要画面由需求决定。

4. 乐观锁

  • 传统情况,每个客户端必须回报给服务器收到帧数,服务器再次发送确认包才执行帧数据,否则所有人等待。
  • 现在情况,基于现在手游的流行程度和若网络环境,手游一般都采取乐观锁模式。即收到服务器推帧后,客户端立即执行,不等待其他人。这样卡顿的人自己卡,不影响其他人的游戏体验。同时卡顿的人在收到数据后,自行加速补帧,追赶上正确的游戏速度。

5. 断线的处理

  • 断线重连:服务器存储好所有的开战数据和所有的帧数据,用来做断线重连(见7)。
  • 软重连:制作一定的游戏策略,在游戏画面卡住的情况下,尽量通过从服务器拉取增量帧数据来恢复现场。

6. 弱网络处理和优化

  • 应对网络抖动,缓存适当的帧在本地,作为抖动窗口,本地适当延后运行,这样可以应对网络延迟较高的情况。当然缓存的越多,客户端操作操作的延迟就越高,越少则遇到未收到帧的时候有可能会卡顿。
  • 如果对操作实时性要求比较高,可以制作回滚机制(Rollback)(见9),客户端在预测的基础上,对玩家的操作进行实时的响应,不等待服务器数据,实时的进行行为。收到服务器的推送后将自己的预测与服务器下发的数据进行行为的一致性验证,不一致则立刻回滚,执行服务器的数据。
  • 应对TCP的拥塞机制,使用RUDP一类的带保障机制的UDP协议,避免协议拥塞导致的卡顿。
  • 应对UDP丢包,首先是客户端回包带标签来补发点机制,其次也可以每次都发冗余包,比如上5个关键帧的所有数据。
  • 协议包不一定采取Protobuf等通用结构,可以采用自定义二进制来减流量,提高弱网络发包的成功率。

7. 追赶和重连的补帧

在卡顿情况和短线重连发生之后,都要在短时间内执行大量的帧数据来追赶上其他人的游戏进度,这个周期下客户端有一些特点,分情况看一下:

  • 断线重连时,因为其他人的当前帧数远超过自己,此时自己的玩家输入操作是基于很旧的数据执行,如果立即同步过去,其他人的客户端会存在非法和不合理的情况,所以一般这个时候会屏蔽或者丢弃掉。大多数网游都是使用Loading画面屏蔽掉这个过程,因为玩家自知是断线,可以理解。
  • 较短时间的补帧,服务器是可以设计为接受操作的。对于自己来说,这个操作实际时延是当前展示的逻辑帧到收到服务器下一个最新逻辑帧的时间。因为存在延迟,对于所有客户端来说,这个操作也有可能在执行时被标记为非法,比如攻击一个小兵的操作,实际上过一两帧执行的时候,小兵已经死亡。客户端对于帧数据的处理必须要处理好异常情况,多个客户端使用同样的异常处理则结果仍然是同步的。

8. 战斗录像

这一块对于帧同步来说天生是有优势的,采用和断线重连一样的机制,按正常速度播放就可以在客户端重播整个战斗过程。一个战报数据包括:

  • 开始数据(单位属性,ID,战场信息等)
  • 过程数据(总帧数,有操作的帧数据)

9. 回滚(Rollback)和高阶录像

先说说回滚的实现:

  1. 首先游戏的逻辑、数据、表现拆解的特别完美,可以实现数据变化后画面尽量不违和的变化。
  2. 将所有数据可以按照帧存储,可以采用Attribute来采集指定变量,也自己写好接口实现(这一步会比较复杂,但是可行),这一步我们称之为快照(Snapshot)。
  3. 在游戏运行时,存储一定量的帧本地数据,比如100帧。
  4. 在游戏逻辑进行预测,与服务器数据不一致失败后,进行数据的回滚,同时通知表现层回滚,执行相应的表现层逻辑。

这个时候,我们可以得到录像的拖动功能:

  1. 录像播放的时候实时存储所有快照。
  2. 制作进度条,可以反复拖动。拖动后在数据层执行上面同样的第4步操作,回退画面。

10. 防作弊

  • 首先做完整的战斗离线校验是没有问题的,类似战斗录像的过程,可以采用跑客户端同一套代码的方式来进行。这对于服务器来说是一个性能问题,取决于游戏类型和数据大小。
  • 根据录像数据验证合法性的基础上,还要加入技能CD等确认客户端输入合法性的校验。
  • 对于多个客户端上报不同结果的情况,需要根据不同情况具体分析,如果是多人对战项目,倾向于采信结果相同多的一方。无论如何都要进行离线校验。
  • 同时也可以进行实时在线校验,采用和客户端一套代码运行在服务器的形式,来模拟一个客户端验证各种数据的结果。
  • 对于开图等客户端外挂,需要额外手段防护,无法通过帧同步机制来防止。
  • 对于加速外挂,天生可以防止,不存在问题。

11. 同步性的调试

一个成熟的项目,中期一定要经历大量的调试来能保证帧同步一致性,这里有一些我用过的技术手段和方法。

  1. 首先战斗校验服,客户端的发布全自动化,通过游戏更新机制里的程序版本号和资源版本号来保证客户端资源的一致性。
  2. 战斗校验服也自动发布一个客户端的纯逻辑执行版本,用来离线在客户端调试战斗纯逻辑过程。
  3. 在战斗过程中加入日志用来查看调试,在战斗校验服、客户端调试模式、客户端运行模式下均生成同格式的日志,用来进行比对。
  4. 不一致的战斗数据通过战斗校验服、客户端调试模式、客户端运行模式的日志进行查看比对。
  5. 有了以上机制后,制作全自动校验验证的程序,采集一定的战斗用例,每次发版本或者长期多机器、多平台自动化24H运行,避免修改导致的一致性问题。

12. 总结

  • 总体来说,帧同步对于服务器压力很小,承载较高、流量也小。
  • 对于客户端来说,实现简单,调试难度较高,作为一个实现实时战斗的放来,性价比较高。
  • 帧同步比较适用于弱联网情况下的强同步游戏,有足够多的方式应对弱联网环境。
  • 帧同步制作需要中途加入的游戏比较简单。
  • 帧同步实现录像、校验等难度不高。

写到这里差不多把个人的一些经验与大家分享完毕,有问题欢迎与我联系,谢谢大家的时间。

posted @ 2020-03-04 17:06  游戏程序员刘宇  阅读(3823)  评论(2编辑  收藏  举报