文章目录

  • 一、网络同步概念理解
  • 二、网络架构与传统同步
    • 1)网络游戏架构的发展
    • 2)传统同步面临的问题
    • 3)参考文献:
  • 三、锁步同步lockstep(帧同步)
  • (三的补充帧同步的前提提要)
    • 1)早期的Lockstep(确定性锁步同步Deterministic Lockstep)
    • 2)Bucket Synchronization(乐观帧锁定)
    • 3)锁步同步协议 Lockstep protocol
      • (1)Deterministic Lockstep存在的问题:
      • (2)锁步同步协议 Lockstep protocol对lookahead cheat类型的外挂针对
      • (3)主要流程
      • (4)锁同步的缺点
      • (5)异步Lockstp的提出(asynchronous Synchronization lockstep)
    • 4)RTS中的Lockstep(优化版的锁步协议[9])
    • 5)Pipelined Lockstep protocol(流水线操作)
    • 6)TimeWarp(解决Pipelined Lockstep protocol的状态冲突与突发的高延迟问题)
    • 7)Lockstep与"帧"同步
    • 8)Lockstep小结
    • 9)参考文献
  • 四、状态同步的发展历程与基本原理(上)
    • 1).雷神之锤与快照同步 (Quake and Snapshot)
    • 2)星际围攻:部落中的网络架构 (The TRIBES Engine Networking Model)
    • 3)客户端预测与回滚(Client-side prediction and Rollback)
    • 4)事件锁定与时钟同步 (Event Locking and Clock Synchronization)
    • 5)插值技术(Interpolation and Extrapolation )
    • )参考文献
  • 五、状态同步的发展历程与基本原理(下)
    • 6)延迟补偿(Lag Compensation)
    • 7)跟随状态同步(自译)(Trailing state synchronization)
    • 8)状态同步框架的演变
    • 9)守望先锋与ECS架构
      • 1)守望先锋的游戏架构和网络同步的实现方式
      • 2)演讲内容介绍
      • 3)守望网络同步在gameplay层要解决的问题
        • (1)玩家移动
        • (2)技能行为
        • (3)命中预测
      • 4)为了增强玩家的游戏体验,游戏还对不同ping的玩家进行了逻辑的调整
      • 5)守望先锋状态同步的实现
      • 6)抛开面对对象模型,使用了面对数据编程-》突破难点:控制System 运作的次序
    • 10)状态同步历史发展总结(按时间段来)
      • 1)前情提要
      • 2)在Quake诞生前
      • 3)Quake诞生
      • 4)现在的状态同步到底指什么呢?
    • 11)文献
  • 六、物理同步
    • 1)概念与理解
    • 2)问题与解决方案
      • (1)前提
      • (2)难点
      • (3)首先谈谈物理引擎的不确定性
      • (4)即网络同步的误差是如何被物理模拟迅速放大的
      • (5)总结
    • 3)参考资料
  • 七、优化技术总结

一、网络同步概念理解

  • 举例:
    比如说用户A用他的手机点击按钮注册了一个QQ账号,那么他的手机号等个人信息就会被存储到服务器上面,这就是一个数据同步的过程。在这个过程中,数据由A的手机流向了QQ的官方服务器。对于游戏来说,其实原理也是一样,不过由于游戏中玩家关注的是游戏的视觉效果,所以我们不仅要同步数据,还要同步表现。可以简单认为,网络同步 = 数据同步+表现同步,数据同步是后端操作,而表现同步就是让前端对后端同步过来的数据进行进一步的处理从而达到表现上的一致。


不过一般Web服务器只是单纯的从服务器向客户端进行数据同步,不会把其他客户端的数据都发给你。而在游戏里面,你需要让N个客户端的显示看起来一模一样(由于网络延迟,同一时刻不可能完全一样),所以需要把其他玩家的一些数据也发给你,不能说A玩家跳了一下,B玩家看到A却趴下了,那样游戏就没法玩了。

高及时性
然而,前面我们还忽略了一个游戏中非常重要的需求(尤其是在MMO、FPS这种类型的网游中)——那就是实时性。你可以容忍微信点进去一个文章要花2秒钟,但是你不可能接受你的子弹要2秒后才打到敌人。实际上,在各种电子竞技里面,0.1秒的延迟就足以让整个游戏的局势发生逆转。像浏览器这种页面显示都吞吞吐吐的应用,如何用他流畅的玩FPS和MOBA呢?(关于云游戏这里先不谈)。可以认为 网络同步 = 实时的多端数据同步+实时的多端表现同步

二、网络架构与传统同步

1)网络游戏架构的发展

  • P2P架构:Packet Server

以某个客户端为Host主机(或叫做ListenServer)的CS架构),这样的架构不需要单独都维护一个服务器,任何一个客户端都可以是Sever,能够比较方便的支持局域网内对战,也能节省服务器的运行与开发成本。不过,虽说也是CS架构,如果Host主机不做任何server端的校验逻辑,那么其本质上还是P2P模型,只不过所有的客户端可以把消息统一发送到一个IP,Host再进行转发,这种方式我们称其为Packet Server。

  • 主流架构
    后来一些游戏团队(比如id software)又对CS架构做了进一步调整,先是将大部分的逻辑处理移到服务器上(服务器可能是一个独立的无窗口的后台程序),客户端只负责渲染。随后为了对抗网络延迟提升客户端的流畅性,又把一部分逻辑交回给客户端本地预执行,最终成为很多经典游戏和引擎的架构方式
  • 多点服务器(镜像服务器模型)
    这种模型提供了多个服务器的拷贝,避免单点崩溃影响到所有玩家的问题。类似CDN,玩家还可以选择就近的服务器进行通信,降低了通信延迟。不过,这种方式增加了服务器的租用和维护成本,在后续的游戏网络架构中并没有被大量使用,倒是WEB服务器广泛采用这种模型并不断将其发扬光大。

  • 按照业务逻辑区分单独的服务器
    再后来,游戏服务器架构不断发展。游戏存储负载和网络连接负载随后从逻辑服上拆分出来,形成独立的服务;玩家数量增多后,又将游戏拆分成多个平行世界,出现了分服和跨服;游戏逻辑进一步复杂后,又开始按照功能去划分成网关服务器、场景服务器、非场景服务器等。我们今天讨论的网络同步几乎都是在逻辑服务器(基本上无法拆分)上进行的,所以后续的这些架构方式与网络同步的关系并不是很大,这里就不再赘述

2)传统同步面临的问题

  • 停等协议
    网络游戏刚出现的时候,大部分还属于弱交互游戏,可以将其简单理解为一种回合制游戏。这种游戏采用的同步方式与计算机网络中的停等协议(stop-and-wait-type)非常相似,是一种很自然也很简单的同步模型。不过由于当时网络同步并没有形成体系,所以这种同步方式也没有名字。在局域网盛行以及玩家数量较少的条件下,这种同步方式与架构都是可行的。

不过随着游戏的种类和玩法复杂性的提升,面对的问题也接踵而来。

1)在CS架构下逻辑在客户端执行还是在服务器执行?如果逻辑都在服务器执行,那么客户端的操作都会被发送到服务器运算,服务器计算出结果后通知客户端,客户端拿到结果后再做表现,这样的好处是所有的逻辑由服务器处理和验证,客户端无法作弊,但坏处是会造成客户端的资源被浪费,服务器运算压力过大。如果逻辑在各个客户端执行,那么玩家可以在本地计算后再把本地得到的结果告知服务器,服务器只进行简单的转发,这样的好处是玩家的本地表现很流畅,但坏处是很容易在本地进行作弊。而对于P2P架构,反作弊更是一个严重的问题,我连一个权威服务器都没有,根本无法验证其他客户端消息的真伪,怎么知道其他玩家有没有作弊?

2)我们要发送什么数据来进行同步?如果发送每个对象当前的状态,那么如果一个游戏里面有大量的角色,就会大规模的占用网络带宽,造成数据拥塞、丢包等等问题。如果发送玩家指令,那这个指令是要服务器执行还是服务器转发?而且对于大型多人在线游戏又没必要处理所有不相关的玩家信息,同样浪费网络资源

3)面对日益成熟的计算机网络协议,我们选择哪种来进行同步?TCP、UDP还是Http?

(这时,游戏开发者们需要面对“发什么数据”,“在哪计算”,“发给谁”等细节问题,他们开始考虑引入更多的其他相关领域的技术(比如计算机模拟仿真)来解决游戏中的同步问题,网络同步概念初见端倪)

未完待续

3)参考文献:

[1]WIKI “History of video games”. Available:https://en.wikipedia.org/wiki/History_of_video_games[Accessed: 2020-03-24]

[2]T.A. Funkhouser.”RING: A Client-Server System for Multi-User Virtual Environments“, In Proc. 1995 Available:https://dl.acm.org/doi/pdf/10.1145/199404.199418[Accessed: 2020-03-24]

[3]Eric Cronin, Burton Filstrup Anthony R. Kurc, Sugih Jamin,“An Efficient Synchronization Mechanism for Mirrored Game Architectures”, 2004. Available: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.87.6043&rep=rep1&type=pdf[Accessed:2020-03-24]

三、锁步同步lockstep(帧同步)

(三的补充帧同步的前提提要)

  • 简介:
    最近业内的知名的开发者,KCP作者——韦易笑老师对国内“帧同步”和“状态同步”两个概念的发展历史做了说明和解释,描述了为什么国内游戏圈会滥用这两个词以及国内网络同步技术的发展简史
  • 帧同步定义:
    泛指保证每帧(逻辑帧)输入一致
  • 帧同步实现方法
1)帧锁定
2)乐观帧锁定
3)lockstep
4)bucket同步等等
  • 帧同步具体优化手段
1)要不要回滚?
2)服务器要不要跑一段完整的逻辑?
3)操作要不要是键盘鼠标还是高阶指令?
4)客户端要不要像视频播放器一样保证平滑缓存1-2帧
5)要不要保证平滑加一层显示对象的坐标插值?等等
  • 背景介绍
    Lockstep就是我们口中常说的“帧同步”。但严格来说,Lockstep并不应该翻译成帧同步,而是——锁步同步算法。(LockStep由军事语境引入,用来表示齐步行军,队伍中的所有人都执行一致的动作步伐)首次引入计算机领域[4],
  • 作用
    应该是用于计算机容错系统,即“使用相同的、冗余的硬件组件在同一时间内处理相同的指令
    +目的
    从而保持多个CPU、内存精确的同步,所以一开始与游戏并没有任何关系

1)早期的Lockstep(确定性锁步同步Deterministic Lockstep)


游戏介绍传送门
不过,早在1994年,FPS鼻祖Doom就已经采用了类似Lockstep的方式进行网络同步[5]。Doom采用P2P架构,每个客户端本地运行着一个独立的系统,该系统每0.02秒钟对玩家的动作 (鼠标操作和键盘操作,包括前后移动、使用道具、开火等) 采样一次得到一个 tick command 并发送给其他所有玩家,每个玩家都缓存来自其他所有玩家的 tick commands,当某个玩家收到所有其他玩家的 tick commands 后,他的本地游戏状态会推进到下一帧。在这里, tick command的采集与游戏的推进是相互独立的。

其实当时并没有Lockstep这个说法,doom的整篇论文里面也没有出现过这个词。不过后面概念逐渐清晰后我们会发现,Doom采用的同步方式就是我们常说的原始版本的Lockstep——“确定性锁步同步(Deterministic Lockstep)”

2)Bucket Synchronization(乐观帧锁定)


MiMaze介绍传送门
1999年,Christophe Diot和Laurent Gautier的团队开发了一款基于互联网的页游——MiMaze,基于传统的Time Bucket Synchronization[6]他们在发布的论文里面提出了改进后的Bucket Synchronization同步方法[7]。Bucket Synchronization把时间按固定时长划分为多个Bucket,所有的指令都在Bucket里面执行。考虑到网络延迟的情况,每个玩家在本地的命令不会立刻执行而是会推迟一个时延(该时延的长度约等于网络延迟),用来等待其他玩家的Bucket的到来。如果超过延迟没有到达,既可以选择放弃处理,也可以保存起来用于外插值(Extrapolation)或者使用前面的指令重新播放。在这种方式下,每个玩家不需要按照Lockstep的方式严格等待其他玩家的命令在处理,可以根据网络情况顺延到后面的bucket再执行。Bucket Synchronization可以认为是我们常说的“乐观帧锁定”

3)锁步同步协议 Lockstep protocol

(1)Deterministic Lockstep存在的问题:

1)优点:简单
2)缺点:浮点数跨平台的同步问题、玩家数量增长带来的带宽问题以及显而易见的作弊问题(在P2P架构下几乎没有任何反作弊能力)

(2)锁步同步协议 Lockstep protocol对lookahead cheat类型的外挂针对

1)lookahead cheats定义:比如客户端A使用了外挂工具,每次都将自己的操作信息推迟发送,等到看到了别人的决策后再决定执行什么(或者假装网络信号不好丢弃第K步的操作,第K+1步再发送)
2)来源:在2001年,Nathaniel Baughman和Brian Neil Levine在IEEE上发表了论文,提出锁步同步协议 Lockstep protocol [8]来对抗lookahead cheat类型的外挂

(3)主要流程

1)简介:
这里的Lockstep protocol并不是我们前面提到的Deterministic Lockstep ,相比之前的在第K步(第K个Tick Command间隔)就直接发送第K+1步的明文操作信息,Lockstep protocol每一步都分两次发送信息。
2)大概的流程如下:
①先针对要发送的明文信息进行加密,生成“预提交单向哈希(secure one-way commitment hash)”并发送给其他客户端。
②待本地客户端接收到所有其他客户端的第K步预提交哈希值之后,再发送自己第K+1步的明文信息
③等到收到所有其他客户端的第K步明文信息后,本地客户端会为所有明文信息逐个生成明文哈希并和预提交的哈希值对比,如果发现XXX客户端的明文哈希值和预提交哈希值不相等,则可以判定该客户端是外挂。反之,游戏正常向前推进。

(4)锁同步的缺点

缺点:虽然可以对抗外挂,但是很明显带来了带宽以及性能的浪费,而且网络条件好的客户端会时刻受到网络差的客户端的影响。

(5)异步Lockstp的提出(asynchronous Synchronization lockstep)

  • 概念
    大体的思路是利用玩家角色的SOI(Spheres of Influence,和AOI概念差不多),两个玩家如果相距很远互不影响,就采用本地时钟向前推进(非Lockstep方式同步),如果互相靠近并可能影响到对方就变回到严格的LockStep同步,这里并不保证他们的帧序列是完全一致的。

4)RTS中的Lockstep(优化版的锁步协议[9])

  • 来源
    2001的GDC大会上,“帝国时代”的开发者Mark Terrano和Paul Bettner针对RTS游戏提出了优化版的锁步协议[9]
  • 目的:
    一是游戏中可能发生位置变化的角色非常多,必须要合理的减少网络同步带宽,
    二是玩家对同步频率极为敏感,每一秒的疏忽都可能影响局势。
  • 概念
    (1)首先保持每一步只同步玩家的操作数据,然后对当前的所有命令延迟两帧执行的方法来对抗延迟。
    (2)具体来说,就是第K步开始检测到本地命令后会推迟到第K+2步进行发送和执行,K+1步收集到的其他客户端命令会推迟到K+3步去执行,每K步执行前会去判断本地是否有前两步的命令,如果有就继续推进。
    (关于具体的推进策略,论文里面写的不是很清楚,这里加入了作者自己的判断)

  • 为避免高性能机器受低性能机器的影响而变“卡“做的优化
    (1)“帝国时代”里面每一步(称为一个turn)的长度是可以调整的,并且完全与渲染分开处理。每个客户端会根据自身的机器性能与网络延迟情况来动态调整步长时间,
    (2)如果性能优良但是延迟高就会拉长每个turn的时间(多出的时间用于正常进行多个帧的渲染以及Gameplay的处理,虽然可能有误差),
    (3)如果性能差但是网络正常就会把大部分的时间用于每个turn的渲染,在这种条件下每个客户端相同的turn执行的本地时间虽然不同,但是执行的内容是完全一致的。

5)Pipelined Lockstep protocol(流水线操作)

  • 来源
    2003年,Ho Lee、Eric Kozlowski等人对Bucket synchronization、Lockstep protocol等协议进一步分析并针对存在的缺点进行优化,提出了Pipelined Lockstep protocol[10]。
  • 起因:
    只有当前玩家的指令行为不与其他人产生冲突,就可以连续的发送的自己的指令而不需要等待其他人的消息。
  • 举例
    举个例子,假如一个游戏只有7个格子,玩家A和B分别站在左右两边,每次的指令只能向前移动一格。那么A和B至少可以连续发送三个指令信息而不需要等待对面玩家的数据到来。
  • 题外话
    Pipelined Lockstep protocol基于Lockstep protocol,为了防止cheatahead外挂同样需要提前发送hash,这种操作同步、不等待超时玩家的确定性锁步的特性逐渐成为“Lockstep”的标准,被广泛应用于网络同步中。

6)TimeWarp(解决Pipelined Lockstep protocol的状态冲突与突发的高延迟问题)

  • 来源
    TimeWarp原本是指科幻小说中的时间扭曲,其实早在1982年就被D Jefferson等人引入计算机仿真领域[11],后续又被Jeff S. Steinrnan进行优化和调整[6][12]。

  • 概念
    TimeWarp算法基本思路是多个物体同时进行模拟,当一个物体收到了一个过去某个时刻应该执行的事件时,他应该回滚到那个时刻的状态,并且回滚前面所有的行为与状态。

  • TimeWarp对于PipleLined的优化
    前面提到的Pipelined Lockstep protocol可以流畅的处理玩家互相不影响的情况,但是却没有很好的解决状态冲突与突发的高延迟问题。参考TimeWrap这种思路,我们可以将本地执行过的所有操作指令进行保存行成一个快照(Snapshot),本地按照Pipelined Lockstep protocol的规则进行推进,如果后期收到了产生冲突的指令,我们可以回滚到冲突指令的上一个状态,然后把冲突后续执行过的事件全部取消并重新将执行正确的指令。这样如果所有玩家之间没有指令冲突,他们就可以持续且互不影响的向前推进,如果发生冲突则可以按照回退到发生冲突前的状态并重新模拟,保持各个端的状态一致。

7)Lockstep与"帧"同步

  • 关于帧
    前面提到了那么多lockstep的算法,但好像没有一个算法使用到“帧”这个概念。其实“帧同步”属于一个翻译上的失误,宽泛一点来讲“帧同步”是指包含各种变形算法的Lockstep,严格来讲就是指最基本的Deterministic Lockstep。我猜测国内在引入这个概念的时候已经是2000年以后(具体时间没有考证),lockstep算法已经有很多变形,时间帧的概念也早已诞生,所以相关译者可能就把“lockstep”翻译成了“帧同步”。当然也可能是引入的时候翻译成了“按帧锁定同步”,后来被大家以简化的方式(帧同步)传递开来。但不管怎么说,“帧”在实际应用中更多的是指画面渲染的频率,lockstep里面的“step”概念要更宽泛一些才是。

  • 逻辑帧和渲染帧
    了解游戏的朋友,都知道游戏是有帧率(FPS)的,每一帧都会运行相当复杂的运算(包括逻辑和渲染),如果运算规模达到一定程度就会拉长这一帧的时间,帧率也就会随之下降。所有影视作品的画面都是由一张张图构成的,每一个画面就是一帧,一秒能放多少个画面,就是有多少帧。在游戏里面,渲染器会不停的向渲染目标输出画面,并在帧与帧之间游戏处理各种逻辑,逻辑会改变游戏世界对象的行为,这些行为又会影响游戏画面的变化,这就是游戏的核心框架。早期的lockstep里面渲染和逻辑都是放在一个帧里面去处理的,这样一旦命令受到网络延迟的影响,玩家本地就会卡在一个画面直到消息的到来。

  • 为了解决逻辑帧和渲染帧相互影响的问题
    为了解决这个问题,有一些游戏会将逻辑和渲染分开处理(分为逻辑帧和渲染帧),
    逻辑帧每隔固定的时间去处理所有逻辑事件。在不是严格锁帧的情况下,你本地即使没有收到网络数据也可以在继续执行其他的逻辑并维持高频率的渲染(正在移动的对象不会由于短暂的延迟而静止不动)。这里面的逻辑帧就是lockstep里面的“Step”,也可以叫做“turn”,“bucket”或者“步”。

8)Lockstep小结

  • 早期帧是采用客户端内在的心跳按一定间隔的心跳前进
    早期lockstep被广泛用于局域网游戏内(延迟基本可以保持在50ms以内),所以这种策略是很有效的。lockstep每个回合的触发,并不是由收到网络包驱动,也不是由渲染帧数驱动(当然渲染帧率稳定的话也可以以帧为单位,每N帧一个回合),而是采用客户端内在的时钟稳定按一定间隔( 比如100ms) 的心跳前进。游戏的一开始,玩家在本地进行操作,操作指令会被缓存起来。在回合结束前(网络延迟在50ms以内),我们会收到所有其他客户端的指令数据,然后和前面缓存的指令一同执行并推进到下一个回合。如果玩家在一个回合开始到50ms(网络延迟的一半)都没有任何操作,我们可以认为玩家发出了一个Idle指令,打包发给其他客户端[13][14]。

  • 锁帧做回放系统容易的问题
    换个角度来看,假如一场游戏持续了20分钟,不考虑延迟的情况下整场游戏就是12000个回合(所有客户端都是如此)。现在我们反过去给每个回合添加指令,确保每个回合都收集到所有玩家的指令,那么就可以严格保证所有客户端每个回合的表现都是一样的。假如我们再把这些指令都存储起来,那么就推演出整场比赛,这也是为什么lockstep为什么做回放系统很容易。

  • 对于lockstep为什么要发送指令而不是状态的原因
    至于lockstep为什么要发送指令而不是状态,其实是与当时的网络带宽有关。很多游戏都有明确的人数限制(一般不超过10个人),玩家在每个回合能做的操作也有限甚至是不操作,这样的条件下所有玩家的指令一共也占用不了多少带宽。如果换成同步每个角色的状态数据,那么数据量可能会膨胀10倍不止。从原则上说,锁步数据既可以是游戏角色的状态信息也可以是玩家的操作指令,只不过由于各种原因没有采取状态数据罢了。在下一章,我还会对状态同步做进一步的讲解,他与lockstep的发展是相辅相成的,也不是网上常说的那种对立关系。


//lockstep操作指令的结构体
struct Input{bool up;bool down;bool left;bool right;bool space;bool Z;};
  • 当锁帧的时候,网络条件较差的客户端很容易影响其他玩家的体验的解决办法
    ①使用乐观帧锁定
    ②把渲染与同步逻辑拆开,客户端预执行
    ③指令流水线化
    ④操作回滚,关于回滚还有很多细节[16][18]
  • 锁帧的客户端运算结果误差导致的问题
    其次,lockstep的另一个问题就是很难保证命令一致的情况下,所有客户端的计算结果完全一致,只要任何一个回出现了一点点的误差就可能像蝴蝶效应一样导致两个客户端后面的结果截然不同
  • 锁帧的题外话
    这个问题听起来容易,实际上执行起来却有很多容易被忽略的细节,比如RPC的时序,浮点数计算的偏差,容器排序的不确定性,随机数值计算不统一等等。浮点数的计算在不同硬件(跨平台更是如此)上很难保持一致的,可以考虑转换为定点数,随机计算保持各个端的随机种子一定也可以解决,但是具体实现起来可能还有很多问题,需要踩坑之后才能真正解决。

9)参考文献

[1]WIKI “History of video games”. Available:https://en.wikipedia.org/wiki/History_of_video_games[Accessed: 2020-03-24]

[2]T.A. Funkhouser.”RING: A Client-Server System for Multi-User Virtual Environments“, In Proc. 1995 Available:https://dl.acm.org/doi/pdf/10.1145/199404.199418[Accessed: 2020-03-24]

[3]Eric Cronin, Burton Filstrup Anthony R. Kurc, Sugih Jamin,“An Efficient Synchronization Mechanism for Mirrored Game Architectures”, 2004. Available: citeseerx.ist.psu.edu/v[Accessed:2020-03-24]

[4]WIKI “Lockstep (computing)”. Available: https://en.wikipedia.org/wiki/Lockstep_(computing)[Accessed: 2020-03-24]

[5]JMP van Waveren, “The DOOM III Network Architecture” ,2006. Available:http://fabiensanglard.net/doom3_documentation/The-DOOM-III-Network-Architecture.pdf[Accessed: 2020-03-24]

[6]Jeff S. Steinrnan,“BREATHING TIME WARP” May 1993. Available:https://dl.acm.org/doi/pdf/10.1145/174134.158473[Accessed:2020-03-24]

[7]Christophe DIOT, Laurent GAUTIER, “A Distributed Architecture for Multiplayer Interactive Applications on the Internet”, IEEE, 1999. Available: https://www.cs.ubc.ca/~krasic/cpsc538a-2005/papers/diot99distributed.pdf[Accessed: 2020-03-24]

[8]Mark Terrano,Paul Bettner “Network Programming in Age of Empires and Beyond” GDC 2001. Available:https://zoo.cs.yale.edu/classes/cs538/readings/papers/terrano_1500arch.pdf[Accessed: 2020-03-24]

[9]Nathaniel E. Baughman, Brian Neil Levine, “Cheat-Proof Playout for Centralized and Distributed Online Games”, IEEE INFOCOM, 2001. Available: http://forensics.umass.edu/pubs/baughman.infocom01.pdf[Accessed: 2020-03-24]

[10]Ho Lee, Eric Kozlowski, Scott Lenker, Sugih Jamin, “Multiplayer Game Cheating Prevention With Pipelined Lockstep Protocol”, 2002. Available: http://www.ekozlowski.com/assets/multiplayer-game-cheating-prevention.pdf[Accessed: 2020-03-24]

[11]Dacid Jefferson,Henry Sowizral "Fast concurrent simulation using the time wrap mechanism " 1982. Available: https://www.rand.org/content/dam/rand/pubs/notes/2007/N1906.pdf[Accessed: 2020-03-24]

[12]J. S. Steinman, J. W. Wallace, D. Davani, and D. Elizandro. “Scalable distributed military simulations using the SPEEDES object-oriented simulation framework”. In Proc. of Object-Oriented Simulation Conference (OOS’98), pages 3–23, 1998.
其他参考资料:

[13]云风 “lockstep网络游戏同步方案” Available:https://blog.codingnow.com/2018/08/lockstep.html

[14]Skywind “再谈网游同步技术” Available: http://www.skywind.me/blog/archives/1343#more-1343

[15]DonaldW “网络游戏同步技术概述” Available:https://zhuanlan.zhihu.com/p/56923109

[16]Gordon “帧同步联机战斗(预测,快照,回滚)” Available:
https://zhuanlan.zhihu.com/p/38468615

[17]zhepama "帧同步的相关问题"Available:http://www.igiven.com/dotnet/lock-step/

[18]kisence"关于帧同步和网游游戏开发的一些心得"Available:https://www.kisence.com/2017/11/12/guan-yu-zheng-tong-bu-de-xie-xin-de/

[19]Glenn Fiedler “Deterministic Lockstep” Available:https://gafferongames.com/post/deterministic_lockstep/

[20]Qing Wei Lim “How do multiplayer games sync their state” Available:https://medium.com/@qingweilim/how-do-multiplayer-games-sync-their-state-part-1-ab72d6a54043

[21]treeform "Don’t use Lockstep in RTS games"Available: https://medium.com/@treeform/dont-use-lockstep-in-rts-games-b40f3dd6fddb

[22]Maksym Kurylovych “Lockstep protocol” Available: http://ds.cs.ut.ee/courses/course-files/Report%20-2.pdf

[23] Glenn Fiedler, “What Every Programmer Needs To Know About Game Networking A short history of game networking techniques”, 2010. Available: https://gafferongames.com/post/what_every_programmer_needs_to_know_about_game_networking/

四、状态同步的发展历程与基本原理(上)

  • 背景
    在二十年前,相比于使用帧同步(为了方便描述,后续的文章中以帧同步代替Lockstep)还是状态同步,开发者们更关心的是网络架构的实现方式(P2P/CS)。换句话讲,在当时业内看来,P2P架构的同步模型虽然减少了延迟,但由于作弊、跨平台、难以维护大型网络游戏等问题,人们更希望用CS架构来取代P2P。同时,开发者们虽然可以继续在CS架构下使用逻辑比较简洁的帧同步,但有不少开发者都认为刚刚诞生的状态同步貌似更符合CS架构的同步理念。
  • 状态同步与帧同步的差异
    1)数据格式与内容
    2)逻辑的计算位置
    3)是否有权威服务器等等

1).雷神之锤与快照同步 (Quake and Snapshot)

  • 快照的含义
    快照是一个通用的行业术语,即在任何给定时刻记录设备的状态并在设备出现故障时进行还原。快照技术常用于计算机的各种存储系统,例如逻辑卷管理、数据库、文件系统等。在游戏领域中,快照的含义更像是照片一样,将当前场景所有的信息保存起来。严格来说,快照同步应该属于状态同步的前身,虽然思想相似但是具体实现却有不小的差异。

  • 雷神之锤游戏背景
    1996年,在doom发行不久后,Id software就公开了新作——雷神之锤(Quake)。在Quake里他们舍弃了之前的P2P而改用CS架构,同时也舍弃了lockstep的同步方式。新的架构下,客户端就是一个纯粹的渲染器(称为Dumb Client),每一帧玩家所有的操作信息都会被收集并发送到服务器,然后服务器将计算后的结果压缩后发给客户端来告知他们有哪些角色可以显示,显示在什么位置上。

照片讲解
上述的这个过程就是我们所说的快照同步,即服务器每帧接受客户端的输入来计算整个世界的状态,然后将结果快照发送给所有客户端。Quake这里所谓的快照,就是把整个游戏世界里面所有对象的状态做一次临时保存(他更强调的是对象的可视化状态,比如位置和旋转等)。通过这个快照,我们可以还原出这一刻世界的状态应该是什么样子的。

  • 与锁帧的不同
    Quake运行时,逻辑帧率与渲染帧率是保持一致的。由于所有的核心逻辑都是在服务器进行,所以也不需要通过锁步来避免客户端不同步的问题,只要在收到服务器消息后执行渲染就好了。当然,对于性能以及网络环境较差的玩家来说,游戏体验仍然很糟糕。因为你按下一个按钮后,可能很长时间都没有反应,当收到服务器的快照消息后,你可能已经被网络好的玩家击杀了。


这里借用守望先锋的GDC分享展示快照同步

2)星际围攻:部落中的网络架构 (The TRIBES Engine Networking Model)

  • Quake和Doom的源码分享
    IdSoftware自2012年以来已经陆续把Quake以及Doom相关的源码上传到了GitHub上面[4]。如果你看过其中Quake的源码,会发现整个网络的架构还是比较简单清晰的,博主FABIEN SANGLARD就在网上分享了关于Quake源码的剖析[5](还有很多其他项目的源码剖析)

//client  WinMain{while (1){newtime = Sys_DoubleTime ();time = newtime - oldtime;Host_Frame (time){setjmpSys_SendKeyEventsIN_CommandsCbuf_Execute/* Network */CL_ReadPacketsCL_SendCmd/* Prediction//Collision */CL_SetUpPlayerPrediction(false)CL_PredictMoveCL_SetUpPlayerPrediction(true)CL_EmitEntities/* Rendition */SCR_UpdateScreen}oldtime = newtime;}}
  • 出现的问题
    但Quake里面由于客户端只是一个简单的渲染器,同步过程中会出现很多明显的问题,比如延迟过大,客户端性能浪费,服务器压力大等。而其中最明显的问题就是对带宽的浪费,对于一个物体和角色比较少的游戏,可以使用快照将整个世界的状态都存储并发送,但是一旦物体数量多了起来,带宽占用就会直线上升。所以,我们希望不要每帧都把整个世界的数据都发过去,而是只发送那些产生变化的对象数据(可以称为增量快照同步)。更进一步的,我们还希望将数据拆分的更细一些,并根据客户端的特点来定制发送不同的数据。基于这种思想,《星际部落:围攻》团队的开发者们开始对网络架构进行抽象和分层,构造出来一套比较完善的"状态同步"系统并以此开发出了Tribe游戏系列。

  • TRIBES ENGINE介绍
    The TRIBES Engine可以认为是第一个实现状态同步的游戏引擎,《星际部落:围攻》也可以认为是第一个比较完美的实现了状态同步的游戏。下图是该引擎的网络架构[6]:

  • 图片介绍
    平台数据包模块(Platform Packet Module)可以理解成被封装的Socket模块,连接管理器(Connection Manager)处理多个客户端与服务器的连接,流管理器(Stream Manager)负责将具体的数据分发到上面的三个高级管理器。
    1)Ghost管理器:负责向客户端发送需要同步对象的状态信息,类似属性同步。
    2)事件管理器:维护事件队列,每个事件相当于一个的RPC。
    3)移动管理器:本质上与事件管理器相同,但是由于移动数据的需要高频的捕捉和发送,所以单独封装成一个特殊的管理器。

3)客户端预测与回滚(Client-side prediction and Rollback)

  • 预测的出现
    《毁灭公爵》是上世纪90年代一个经典的FPS游戏系列,首部作品的发布时间与Doom几乎相同,网络架构也极为相似。在1996年发布的《毁灭公爵3D》里面,为了提高客户端的表现与响应速度,他放弃了“Dumb客户端”的方案并首次采用客户端预测来进行优化(这里主要指移动预测)[7]。即在服务器确认输入并更新游戏状态之前,让客户端立即对用户输入进行本地响应。由于这种方式可以大大的降低网络延迟所带来的困扰,很快的Quake也开始参考对网络架构进行的大刀阔斧的修改。在1997年发布的更新版本QuakeWorld里面[8][9],Quake添加了对互联网对战的支持以及客户端预测等新的内容。

  • 预测备注
    关于预测,其实就是本地先执行,所以并不需要什么特别的算法,反倒是预测后的客户端与服务器的同步处理有很多值得优化的地方。由于玩家的行为是没办法完全预测的,所以你不知道玩家会在什么时候突然停下或者转弯,所以经常会发生预测失败的情况。

  • 若客户端复测与服务器不一致
    1)
    在没有时间戳的条件下,收到了一条过时的服务器位置数据。你在本地的行为相比服务器是超前的,假如你在time=10ms的和time=50ms时候分别发送了一条指令。由于网络延迟的存在,当你已经执行完第二个指令的时候才收到服务器对第一条指令的位置同步。很明显,我们不应该让过时的服务器数据来纠正你当前的逻辑。解决方法就是在每个指令发出的时候带上他的时间戳,这样客户端收到服务器反馈的时候就知道他处理的是哪条指令信息。
    2)
    假如我们在指令里面添加了时间戳的信息,并收到了一条过时的服务器位置数据。在上一篇文章里我们提到了TimeWarp算法,即当一个对象收到了一个过去某个时刻应该执行的事件时,他应该回滚到那个时刻的状态,并且回滚前面所有的行为与状态(包括取消之前行为所产生的事件)。这个时候我们可以用类似的方法在本地进行纠正,大体的方案就是把玩家本地预执行的指令都记录好时间戳并存放到一个MOVE BUFFER列表里(类似一个滑动窗口)。如果服务器的计算结果与你本地预测相同,可以回复你一个ACKMOVE。如果服务器发现你的某个移动位置有问题时,会把该指令的时间戳以及正确的位置打包发给你。当你收到ACKMOVE的时候,你可以把MOVE BUFFER里面的数据从表里面移除,而当你收到错误纠正信息时就需要本地回滚到服务器指定的位置同时把错误时刻后面MOVE BUFFER里面的指令重新执行一遍。这里读者可能会产生一个疑问——为什么不直接拉回因为这时候他想纠正的是之前的错误而不是现在的错误,如果简单的拉回就会让你觉得被莫名其妙的拉回到以前的一个位置。同时,考虑到已经在路上的指令以及后续你要发送的预测指令,会让服务器后续的校验与纠正变得复杂且奇怪,具体流程细节可以参考下图。另外,Gabriel Gambetta博主在他的文章中,也对这种情况进行了简单的分析[10]。


(意思还是被拉回去,但是不是拉回去开始的位置,而是拉回去上个包的位置)

  • 关于TimeWarp算法的补充
    Timewarp技术最早出现于仿真模拟中[11],我们可以认为这些仿真程序中采用的是“以事件驱动的帧同步”。也就是说,给出一个指令,他就会产生并触发多个事件,这些事件可能进而触发更多的事件来驱动程序,同理取消一个过去发生的事件也需要产生一个新的取消事件才行。这样造成的问题就是回滚前面的N个操作,就需要产生N个新的对抗事件,而且这N个事件还需要发送到所有其他的客户端执行。如果这N个事件又产生了新的事件,那么整个回滚的操作就显得复杂了很多。换成前面移动的例子来解释一下,就是客户端收到服务器的纠正后,他会立刻发送回滚命令告诉(P2P架构下)所有其他客户端,我要取消前面的操作,然后其他客户端在本地也执行回滚。而在如今的CS架构状态同步的方式下,服务器可能早就拒绝了客户端的不合法行为,所以并不需要处理回滚(同理,其他客户端也是)。所以严格来说,TimeWarp技术以及优化后的BreathTimeWarp技术[12]都是针对“以事件驱动的帧同步”,并不能与预测回滚这套方案完全等价。当然,随着时间的推移,很多概念也变的逐渐宽泛一些,我们平时提到的时间回溯TimeWarp技术大体上与快照回滚是一个意思的。

4)事件锁定与时钟同步 (Event Locking and Clock Synchronization)

  • 事件锁定的出现
    1997年,Jim Greer与Zack Booth Simpson在开发出了他们第一款基于CS架构的RTS游戏——”NetStorm:Island at war“。随后在发布的文章中又提出了“事件锁定”这一概念[13],相比帧同步会受到其他客户端延迟的影响,事件锁定是基于事件队列严格按序执行的,客户端只管发消息然后等待服务器的响应即可,其他时候本地正常模拟,不需要等待。在目前常见的游戏中,我们很少会听说到事件锁定这种同步方式,因为事件锁定的本质就是通过RPC产生事件从而进行同步(也就是排除属性同步的状态同步)。事件锁定在CS架构上是非常自然的,相比帧同步,可以定义并发送更灵活的信息,也不必再担心作弊的问题。

  • 以时间同步来纠正服务器对客户端的不合法操作
    不过,由于事件中经常会含有时间相关的信息(比如在X秒进行开火)以及服务器需要对客户端的不合法操作进行纠正,所以我们需要尽可能的保持客户端与服务器的时钟同步。实现时钟同步最常见且广泛的方式就是网络时间协议(Network Time Protocol,简称NTP)[14],NTP属于应用层协议下层采用UDP实现,1979年诞生以来至今仍被应用在多个计算机领域里,包括嵌入式系统时间、通信计费、Windows时间服务以及部分游戏等。NTP使用了一种树状、半分层的网络结构来部署时钟服务器,每个UDP数据包内包含多个时间戳以及一些标记信息用来多次校验与分析,

(备注:整个时钟同步的具体算法涉及到非常多的细节,我们这里只考虑他的时钟同步算法(其他的内容请参考历年的RFC))

  • 详细讲解
    假如一个服务器与客户端通信,客户端在t0向服务器发送数据,服务器在t1收到数据,t2响应并回包给客户端,最后客户端在t3时间收到了服务器的数据。

    二者的时间差为“θ”,假如往返延迟相同,则有

    所以可以将“θ”定义为

    将往返延迟相加,那么可以得到一个RTT延迟

    当然,该操作不会只执行一次,客户端会同时请求多个服务器,然后对结果进行统计分析、过滤,并从最好的三个剩余候选中估算时间差,然后调整时钟频率来逐渐减小偏移。如果我们的系统对精度要求不是非常高,我们还可以使用简化版的SNTP(Simple Network Time Protocal),时钟同步算法与NTP是相同的,不过简化了一些流程。

  • 消除高阶的流式时间同步:
    不过无论是NTP还是SNTP,对于游戏来说都过于复杂(而且只能用UDP实现)。因此Jim Greer等人提出了“消除高阶的流式时间同步”,流程如下:

  1. 客户端把当前本地时间附在一个时间请求数据包上,然后发送给服务器
  2. 服务器收到以后,服务器附上服务器时间戳然后发回给客户端
  3. 客户端收到之后,用当前时间减去发送时间除以2得到延迟。再用当前时间减去服务器时间得到客户端和服务端时间差,再加上半个延迟得到正确的时钟差异 delta=(Currenttime - senttime)/2
  4. 第一个结果应该立刻被用于更新时钟,可以保证本地时间和服务器时间大致一致
  5. 客户端重复步骤1至3多次,每次间隔几秒钟。期间可以继续发送其他数据包的,但是为了结果精确应该尽量少发
  6. 每个包的时间差存储起来并排序,然后取中位数作为中间值
  7. 丢弃和中间值偏差过大(超出一个标准偏差,或者 超过中间值1.5倍)的样例,然后对剩余样例取算术平均
  • 总结

上述算法精髓在于丢弃和中间值偏差超过一个标准偏差的数值。其目的是为了去除TCP中重传的数据包。举例来说,如果通过TCP发送了10个数据包,而且没有重传。这时延迟数据将集中在延迟的中位数附近。假如另一个测试中,如果其中第10个数据包被重传了,重传将导致这次的采样在延迟柱状图中极右端,处于延迟中位数两倍的位置。通过直接去掉超出中位数一个标准偏差的样例,可以过滤掉因重传导致的不准确样例。(排除网络很差重传频繁发生的情况)

5)插值技术(Interpolation and Extrapolation )

  • 插值法分类
    插值技术在早期的帧同步就被应用到游戏里面了。或者说更早的时候就被应用到军事模拟,路径导航等场景中。插值分为内插值[15]( interpolation )以及外插值[16](extrapolation,或者叫外推法)两种。
    1)内插法:
    内插值是一种通过已知的、离散的数据点,在范围内推求新数据点的方法(重建连续的数据信息),常见于各种信号处理和图像处理。在这篇文章中,我们指根据已知的离散点在一定时间内按照一定算法去模拟在点间的移动路径。内插值具体的实现方法有很多,如
片段插值(Piecewise constant interpolation)
线性插值(Linear interpolation)
多项式插值(Polynomial interpolation)
样条曲线插值(Spline interpolation)
三角内插法(trigonometric interpolation)
有理内插(rational interpolation
小波内插(wavelets interpolation)


2)外插法
外插值,指从已知数据的离散集合中构建超出原始范围的新数据的方法,也可以指根据过去和现在的发展趋势来推断未来,属于统计学上的概念。与外插值还有一个相似的概念称为DeadReckoning(简称DR),即导航推测。DR是一种利用现在物体位置及速度推定未来位置方向的航海技术,属于应用技术方向的概念。DR的概念更贴近游戏领域,即给定一个点以及当前的方向等信息,推测其之后的移动路径,外推的算法也有很多种,

线性外推(Linear extrapolation)
多项式外推(Polynomial extrapolation)
锥形外推 (Conic extrapolation)
云形外推 (French curve extrapolation)

在游戏中,一般按照线性外推或匀变速直线运动推测即可。不过,对于比较复杂的游戏类型,我们也可以采用三次贝塞尔曲线、向心Catmull-Rom曲线等模拟预测。

总之,无论是内插值还是外插值,考虑到运算的复杂度以及表现要求,游戏中以线性插值、简单的多项式插值为主。

3)插值法应用
早期的lockstep算法中,在一个客户端在收到下一帧信息前,为了避免本地其他角色静止卡顿,会采用外插值来推断其接下来一小段时间的移动路径[17][18]。普通DR存在一个问题(参考下图),t0时刻其他客户端收到了主机的同步信息预测向虚线的方向移动,不过主机客户端却开始向红色路径方向移动,等其他客户端在t1时刻收到同步信息后会被突然拉倒t1’的位置,这造成了玩家不好的游戏体验。为了解决从预测位置拉扯到真实位置造成的视觉突变,我们会增加一些相应的算法来将预测对象平滑地移动到真实位置。


在状态同步中,由于客户端每次收到的是其他的角色的位置信息,为了避免位置突变,本地会采用内插值来从A点过度到B点。插值的目的很简单,就是为了保证在同步数据到来之前让本地的角色能有流畅的表现。

)参考文献

[1]"State Synchronization's Role in High Availability"  Available:http://etutorials.org/Networking/Check+Point+FireWall/Chapter+13.+High+Availability/State+Synchronization+s+Role+in+High+Availability/[Accessed:2020-07-17]
[2]WIKI, "Check Point VPN-1" Available: https://en.wikipedia.org/wiki/Check_Point_VPN-1[Accessed:2020-07-17]
[3]Check Point Documentation, "Synchronizing Connections in the Cluster" Available:https://sc1.checkpoint.com/documents/R80.10/WebAdminGuides/EN/CP_R80.10_ClusterXL_AdminGuide/html_frameset.htm?topic=documents/R80.10/WebAdminGuides/EN/CP_R80.10_ClusterXL_AdminGuide/7288[Accessed:2020-07-17]
[4]id-Software,"GitHub Game Source Code" Available:https://github.com/id-Software [Accessed:2020-07-17]
[5]FABIEN SANGLARD," FABIEN SANGLARD'S WEBSITE With GameSource Code Analysis " Available:https://fabiensanglard.net/ [Accessed:2020-07-17]
[6] Mark Frohnmayer, Tim Gift, "The TRIBES Engine Networking Model or How to Make the Internet Rock for Multi player Games", 1998. Available: https://www.gamedevs.org/uploads/tribes-networking-model.pdf[Accessed:2020-07-17]
[7]WIKI, "Client-Side Prediction" Available:https://en.wikipedia.org/wiki/Client-side_prediction  [Accessed:2020-07-17]
[8]id-Software,"The Quake 2 Networking Data Flow" Available:http://www.gamers.org/dEngine/quake2/Q2DP/Q2DP_Network/Q2DP_Network.html#toc4 [Accessed:2020-07-17]
[9]WIKI, "QuakeWorld"  Available:https://en.wikipedia.org/wiki/QuakeWorld[Accessed:2020-03-24]
[10]Gabriel Gambetta," Client-Server Game Architecture" Available:https://www.gabrielgambetta.com/client-side-prediction-server-reconciliation.html[Accessed:2020-07-17]
[11]Dacid Jefferson,Henry Sowizral "Fast concurrent simulation using the time wrap mechanism " 1982.Available: https://www.rand.org/content/dam/rand/pubs/notes/2007/N1906.pdf[[Accessed:2020-07-17]
[12]M. Damitio S. J. Turner,"Comparing the Breathing Time Buckets Algorithm and the Time Warp Operating System on a Transputer Architecture” January 1999. Available:https://www.researchgate.net/publication/2763616_Comparing_the_Breathing_Time_Buckets_Algorithm_and_the_Time_Warp_Operating_System_on_a_Transputer_Architecture[Accessed:2020-07-17]
[13]Jim Greer, Zack Booth Simpson, "Minimizing Latency in RealTine Strategy Games" Game Progamming Gems 3 chapter 5.1, 2001.
[14]]WIKI, "Network Time Protocol" Available:https://en.wikipedia.org/wiki/Network_Time_Protocol[Accessed:2020-07-17]
[15]WIKI, "Interpolation" Available:https://en.wikipedia.org/wiki/Interpolation[Accessed:2020-07-17]
[16]WIKI, "Extrapolation" Available:https://en.wikipedia.org/wiki/Extrapolation[Accessed:2020-07-17]
[17]Jesse Aronson, "Dead Reckoning: Latency Hiding for Networked Games" September 19, 1997.Available:https://www.gamasutra.com/view/feature/131638/dead_reckoning_latency_hiding_for_.php[Accessed:2020-07-17]
[18]梁白鸥等,“Dead Reckoning技术在网络游戏中的应用” 2007.Available:http://www.arocmag.com/getarticle/?aid=2f665567e92cf534[Accessed:2020-07-17]

五、状态同步的发展历程与基本原理(下)

6)延迟补偿(Lag Compensation)

  • 背景
    2001年,Valve的新作《半条命》发布,打破了传统FPS游戏玩法。不久之后,其Mod《反恐精英》更是火遍了全球并作为独立游戏发布出去。

  • 游戏介绍
    由于半条命是基于“QuakeII引擎修改的GoldSrc引擎”开发,所以游戏同样采用了CS架构以及状态同步。不过,为了能达到他们心中理想的效果,半条命在网络同步上做出了不小的改动。首先,半条命也采用了客户端预测逻辑来保证本地玩家能够有流畅的手感,同时(1)为了让客户端提高预测准确率(保证客户端与服务器上的代码逻辑一致),所以半条命里面他们让客户端与服务器执行的是同一套代码。其次,(2)考虑到本地玩家的时间总是领先服务器,玩家开枪的时间到服务器执行时就一定会被延迟,所以为了尽量减小延迟所带来的问题,他们提出了一种名为延迟补偿的技术。
  • 延迟补偿的缺点
    不过,延迟补偿并不是一个万能的优化方式,采用与否应该由游戏的类型与设计决定。考虑一个ACT类型的网游,玩家A延迟比较低、玩家B延迟比较高。在A的客户端上,玩家A在T1时间靠近B,而后立刻执行了一个后滚操作,发送到服务器。在B的客户端上,同样在T1时间发起进攻,然后发送命令到服务器。由于A的延迟低,服务器先收到了A的指令,A开始后滚操作,这时候A已经脱离了B的攻击范围。然后当B的指令到达服务器的时候,如果采用延迟补偿,就需要把A回滚到之前的位置结果就是A收到了B的攻击,这对A来说显然是不公平的。如果该情况发生在FPS里面,就不会有很大的问题,因为A根本不知道B什么时候瞄准的A

7)跟随状态同步(自译)(Trailing state synchronization)

  • Trailing state Synchronization对Timewrap回滚方式的优化
    2004年,Eric Cronin等人在传统的Timewrap的回滚方式上提出了Trailing state synchronization算法[20](TSS)。在他们看来,TimeWarp需要频繁的生成游戏快照进而占用大量内存(每次发送命令前都要生成一份),而且每次遇到过期信息就立刻回滚并可能产生大量的对冲事件(anti-message)。这种同步方式是不适合Quake这种类型的FPS游戏的。
  • Tss算法介绍
    1)在TSS算法中,游戏的快照不是随每个命令产生,而是以某种延迟(比如100ms)间隔为单位对游戏做快照。他事先保存了N个完整的游戏状态(快照)以及命令链表,让这N个状态以不同的延迟去模拟推进。游戏中延迟最低且被采用的状态称为Leading State,其他的称为Trailing State,每个状态都记录着一个命令链表(执行的以及未执行的),各个状态的延迟间隔由开发者设定。
    2)Leading State向前推进的时候会不断的收到其他端的指令并添加到PendingCommands里面,如果某个命令的执行时间小于当前已经推进到的时间(比如图A CommandB指令在时间225ms才被Leading State执行,正常200ms就执行完了),就会放在表的最前面立刻执行,这时候其实我们已经知道这个命令已经由于延迟错过正常执行时间,可能要进行回滚操作了。但是对于后续的Trailing State,这些过期Commands是可以被放到正确的位置的。当Trailing State执行到某个命令且发现Leading State在对应的位置没有这个命令的话,他就会触发回滚(如果该命令对当前游戏无影响,其实也可以不回滚),将当前Trailing State的状态信息拷贝到Leading State里面,然后设置错误命令时间至当前本地执行时间的所有命令为pending状态,触发这些状态的重新执行。

图A

图B Trailing State S1检测冲突并触发回滚

  • TSS对于Timewarp的最大优势
    TSS相比TimeWarp,最大的优势就是1)大大降低了快照的记录频率(由原来的按事件记录改为按延迟时间分开记录),同时他2)可以避免由于网络延迟造成的连续多次指令错误而不断回滚的问题(Leading State不负责触发回滚,Trailing State检测并触发)。

  • TSS的缺点
    不过TSS同时维护了多个游戏世界的快照,也无形中增加了逻辑的复杂度,在最近几年的网络游戏中也并没有看到哪个游戏使用了这种同步算法。在我看来,其实我们不必将整个世界的快照都记录,只要处理好移动的快照同时使用服务器状态同步就可以满足大部分情况了。

8)状态同步框架的演变

    1. 介绍
      在2011年的GDC上,光环(Halo)项目的网络技术负责人David Aldridge就其网络同步框架发表了一次演讲。通过视频[21],可以看到David同样借鉴了TribeEngine的网络架构并在此基础上进行更多细节的调整。
  • 2)网络图层

    图片说明:
光环项目的网络架构同样被分层,但相比Tribe却更加简洁和精炼。上图的Replication层是Gameplay开发中比较重视的,他决定了我们逻辑上层可用的同步
手段。Halo里面有三种基本协议,State Data、Event、ControlData,分别是指
“基于对象的属性同步”、
“通过调用产生的事件同步”以及
“玩家的输入信息同步”,
------其中移动同步归类于ControlData协议。
    1. 虚幻4的网络同步架构
      2015年,游戏业内著名的商业引擎——Unreal Engine正式开源,其中内置了一套非常完善的网络同步架构[22]

      图片说明:
      虚幻引擎的前身是FPS游戏——“虚幻竞技场”。该游戏早在1998年就发布,当时与Quake属于同类型的竞品。虚幻本身也是基于CS架构的状态同步,不过由于无法查找到当时的资料,笔者认为一开始可能也是与Quake非常相似的同步架构。后来在参考Tribe引擎的基础上,进行调整和优化,形成了如今的Netdriver /Connection /Channel /Uobject的模型,以及RPC和属性同步两种同步方式,这已经是网络同步发展至今非常典型且完善的状态同步方案了(后面要提到的OverWatch与其有很多相似之处)。作为一款游戏引擎,虚幻并没有将所有常见的同步手段都集成到引擎里面,只是将移动相关的优化方案(包括预测回滚、插值等)集成到了移动组件里面。其他的诸如延迟补偿,客户端预测等,他们放到了特定的Demo以及插件(GameplayAbility)当中。有兴趣的朋友可以去阅读一些Unreal的源码,看看最近几年其网络架构的发展变化。更多的细节也可以参考文章:“使用虚幻引擎4年,我想再谈谈他的网络架构”[23]

9)守望先锋与ECS架构

1)守望先锋的游戏架构和网络同步的实现方式

守望先锋可以说是近年来将网络同步优化到极致的FPS游戏,其中涵盖了我们可以用到的大部分同步优化技术。在2018年的GDC上,来自守望先锋的Gameplay程序TimFord分享了整个游戏的架构以及网络同步的实现方式[24]。

2)演讲内容介绍

虽然OverWatch基于CS架构,但是却同时用到了帧同步(逻辑帧概念)以及状态同步包含的多种技术手段。为了实现确定性,他们固定了更新周期为16毫秒(电竞比赛时7毫秒),每个周期称为一个“命令帧”(等同于Lockstep中的“Turn”、“Bucket”)。在所有与客户端预表现和玩家行为有关的操作不会放在Update而是放在固定周期的UpdateFixed里更新,方便客户端预测与回滚。不过,整个游戏同步的核心还是状态同步,玩家也并不需要等待其他客户端的行为

一句话概括:守望先锋采用的是基于ECS架构的带有预测回滚的增量状态同步

3)守望网络同步在gameplay层要解决的问题

(1)玩家移动

客户端本地会不断读取输入并立刻进行角色移动的模拟,他会在客户端记录一个缓冲区来保存历史的运动轨迹(即运动快照),用于后续与服务器纠正数据进行对比以及回滚。

(2)技能行为

客户端添加了一个buffer来存储玩家的输入操作(带有命令帧的序号),同时保留历史的技能快照。一旦服务器发现客户端预测执行失败,就会让客户端先通过快照回滚到错误时刻(包括移动和技能),然后把错误时刻到当前时间的所有输入都重执行一遍

图片说明:左边是服务器通知客户端被眩晕,右边是客户端收到后进行回滚

(3)命中预测

伤害计算在服务器,但是命中判定是在客户端处理(所以可能存在一些误差)。延迟补偿技术也被采用,但是不是在服务器回滚所有玩家的位置,而是检测当前玩家的准星与附近敌人的逻辑边界(bounding volumes)是否有交集,没有的话不需要回滚

4)为了增强玩家的游戏体验,游戏还对不同ping的玩家进行了逻辑的调整

(1)一旦PING值超过220毫秒,我们就会延后一些命中效果,也不会再去预测了,直接等服务器回包确认。
(2)PING为0的时候,对弹道碰撞做了预测,而击中点和血条没有预测,要等服务器回包才渲染。
(3)当PING达到300毫秒的时候,碰撞都不预测了,因为射击目标正在做快读的外插,他实际上根本没在这里,这里也用到了前面提到的DR(Dead Reckoning)外推算法(插值推测法)。

5)守望先锋状态同步的实现

状态同步是如何实现的。同样在2018年的GDC上,来自Overwatch服务器团队的开发工程师Phil Orwig分享了有关回放与同步的相关技术细节[25]。

  • 细节
    客户端玩家操作后,这些指令会立刻发给服务器,同时本地开始执行预测。随后,服务器会将这一帧收到的所有玩家的输入进行处理和计算。在服务器上,每个对象产生的变化都会被记做一个Delta,并且会持续累积所有对象状态的变化并保存到一个临时的“每帧脏数据集合”(per frame dirty set)里。同时,服务器会对给每个客户端(每个Connection)也会维护一个对应的“脏数据集合”,这个集合可能保存一些之前没有发送出去的信息(如下图的C1是到客户端1的,C2是到客户端2的)。每帧结束时,所有客户端对应的“脏数据集合”会与当前脏集合F合并,随后当前脏集合F会被清空。、

    同一个Tick的后期,这些对应不同客户端连接的脏集合(C1、C2等)会被序列化并发送给对应的客户端,同时从脏集合中移除。这里的序列化并不是完全使用原生的状态数据,而是维护了一个经客户端确认收到的状态数据的历史记录(比如我们服务器上已经记录了玩家的大部分信息,每次位置变化只序列化位置信息就可以了),这样我们就可以使用“增量编码”来改善带宽模型,即减少带宽的占用。

    通过前面的分析,我们可以了解到整个网络同步的逻辑是很复杂的,细节也非常多。所以,我们也需要考虑是否能从底层和框架上做一些调整和优化。在守望先锋里面,他们并没有采用常见的面向对象模型(OOP),而是使用了数据与操作行为分离的ECS架构[26]。Entity代表一个空的实体、Component代表一个只包含数据的组件、System代表一个处理数据的系统。在这个架构下,我们将面向对象编程转为面向数据编程,游戏的不同模块可以划分成不同的系统,每个模块只关心自己需要的数据(Component),这种模式下可以方便我们处理快照与回滚的逻辑。ECS系统看起来有着缓存友好、逻辑解耦等优点,但是操作起来问题也不少,其中最难处理的一个问题就是如何控制System 运作的次序。

6)抛开面对对象模型,使用了面对数据编程-》突破难点:控制System 运作的次序


图片说明:最后,简单说一下底层的一些优化。

目的
1)为了提高通信效率,守望也采用定制的可靠UDP,
因此会有不可避免的丢包情况。为了对抗丢包,每一帧的数据包包含的是最近N帧的数据,即使某一
个数据包丢了也没什么影响。
2)除此之外,他们还在服务器添加了一个缓冲区,
记录玩家的输入信息。缓冲区越大,就能容忍越多的丢包,但是也意味着同步延迟越大。所以,在网络
条件良好的情况下,他们会尽力减小这个缓冲区的大小,而一旦客户端丢包,那么就可以提高客户端发送数据频率,进而服务器收到更多的包,缓存更多
的数据用于抵消丢包。

10)状态同步历史发展总结(按时间段来)

1)前情提要

“状态同步”同步的是对象的状态信息,如角色的位置、生命值等。

2)在Quake诞生前

  • 背景
    其实也存在直接传输游戏对象状态的游戏,但是那时候游戏都比较简单,相关的概念也并不清晰。当时的架构模型以P2P为主,考虑搭配带宽限制等原因,军事模拟、FPS等游戏都采用了“Lockstep”的方式进行同步。
  • 问题
    由于作弊问题日益严重、确定性实现困难重重等因素,CS架构逐渐代替P2P走向主流

3)Quake诞生

  • 背景
    CS架构逐渐代替P2P走向主流。我们也发现似乎所有的游戏状态信息都可以保存在服务器上,客户端只需要接受服务器同步过来的状态并渲染就可以了。按照这种思路,Quake诞生了,他抛弃了Doom的架构并带着状态同步的方式进入我们的视野。这时候的状态同步还只是简单的快照同步
  • 缺点
    每次同步前服务器都需要把整个游戏世界的状态信息打包发送给客户端,快照同步太浪费带宽了,不同的玩家在一段时间内只能在很小的范围内活动,根本没有必要知道整个世界的状态。同时,每次发送的快照都与之前的快照有相当多重复的内容,确实过于奢侈
  • 题外话
    此,星际围城:部落的开发团队构建出了一个比较完善的状态同步系统,用于对同步信息进行分类和过滤。后来,光环、虚幻竞技场、守望先锋、Doom等游戏都在Tribe Engine的基础上不断完善状态同步,形成了如今的架构模型

4)现在的状态同步到底指什么呢?

目前的状态同步多用于CS架构,客户端通过RPC向服务器发送指令信息,服务器通过属性同步(增量状态同步)向客户端发送各个对象的状态信息。我们可以采用预测回滚、延迟补偿、插值等优化方式,甚至也可以采用“命令帧”的方式对同步做限制。不过在这个过程中,传递的内容以状态信息(即计算后的结果)为主,收到信息的另一端只需要和解同步过来的状态即可,不需要在本地通过处理其他端的Input信息来进行持续的模拟。

最后,再次拿出虚幻引擎的网络同步模型来展示当今的状态同步。

11)文献

[19]Yahn W. Bernier,"Latency Compensating Methods in Client/Server In-game Protocol Design and Optimization" 2001.Available: https://developer.valvesoftware.com/wiki/Latency_Compensating_Methods_in_Client/Server_In-game_Protocol_Design_and_Optimization[Accessed:2020-07-17]
[20]Eric Cronin, Burton Filstrup Anthony R. Kurc, Sugih Jamin,"An Efficient Synchronization Mechanism for Mirrored Game Architectures", 2004. Available:  http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.87.6043&rep=rep1&type=pdf[Accessed:2020-07-17]
[21]David Aldridge, "I Shot You First: Networking the Gameplay of HALO: REACH", GDC, 2011. Available: https://www.bilibili.com/video/BV1Vt4y127op[Accessed:2020-07-17]
[22]Epic Games, " UnrealEngine: Networking and Multiplayer". Available: https://docs.unrealengine.com/en-US/Gameplay/Networking/Overview/index.html[Accessed:2020-07-17]
[23]Jerish, "使用虚幻引擎4年,我想再谈谈他的网络架构".Available: https://zhuanlan.zhihu.com/p/105040792[Accessed:2020-07-17]
[24]Timothy Ford," 'Overwatch' Gameplay Architecture and Netcode", GDC, 2018. Available: https://www.bilibili.com/video/av44410490[Accessed:2020-07-17](翻译链接:https://gameinstitute.qq.com/community/detail/114516)
[25]Philip Orwig," Replay Technology in 'Overwatch': Kill Cam, Gameplay, and Highlights", GDC, 2018. Available: https://www.bilibili.com/video/BV1aA41147bY[Accessed:2020-07-17](翻译链接:https://gameinstitute.qq.com/community/detail/115186)
[26]WIKI,“Entity component system”.Available: https://en.wikipedia.org/wiki/Entity_component_system[Accessed:2020-07-17]

六、物理同步

1)概念与理解

  • 什么是物理同步
    所谓“物理同步”,字面上讲就是“带有物理状态对象的网络同步”,严格上来说它并不是一个标准的技术名词,而是大家约定俗成的一个概念。按照我的个人理解,可以进一步解释为“在较为复杂的物理模拟环境或有物理引擎参与计算的游戏里,如何对持有物理状态信息的对象做网络同步”。在英文中,我们可以使用Replicate physics simulated objects 或者Networked physics来表示类似的概念。

    不过,考虑到并不是所有物理现象都交给物理引擎处理,而且有物理引擎参与的网游也并不一定需要对同步做任何处理,所以我们常说的物理同步更多的是指在网络游戏中,如果玩家的位置或者与玩家交互对象的位置需要经过物理引擎的模拟处理来得到结果,那么其中涉及到网络同步技术就可以称为物理同步!(这里的物理模拟一般指整个对象完全交给物理引擎去计算碰撞、位置、约束等,很多情况下可以等价为对Ragdoll的模拟)

备注:物理一词涉及的范围非常广,在游戏里面应用的场景也很多,但是并不一定需要进行网络同步,比如简单的抛物线运动,射线检测,与玩法无关的场景破碎等。

  • 背景
    早在上世纪70年代,就诞生了许多围绕物理特性产生玩法的游戏,不过由于当时计算机系统算力有限,涉及到的物理计算都非常简单(比如乒乓球游戏中小球的移动模拟[1])。随着计算机性能的飞速提升,开发者们考虑将环境中的所有对象都交由统一的物理模块驱动,由此慢慢的催生出了通用的物理引擎[2]。很快的,各个游戏开发商逐渐将物理引擎集成进来,将更多更复杂的物理模拟过程应用到游戏中,制作出了诸如极品飞车、FIFA、NBA、愤怒的小鸟等围绕物理特性进行玩法设计的游戏。另一方面,随着计算机网络的发展,游戏中的网络同步技术愈加成熟,网络游戏的品质也不断向单机游戏靠拢,我们也得以将传统的单机游戏拓展成多人游戏。物理模拟作为提升游戏趣味性的一大技术也自然逐渐被纳入其中,物理同步变得重要起来。

2)问题与解决方案

(1)前提

正如所前面解释的那样,物理同步并不是一种特殊的同步方式,而是在物理引擎和网络同步技术共同发展的条件下而诞生的一种综合行性解决方案,其核心手段还然是我们熟悉的帧同步或者状态同步。使用帧同步技术我们需要每帧把玩家的Input信息发送出去,然后让另一端的物理引擎根据输入去模拟结果。如果使用状态同步我们则需要本地模拟好数据并把物理位置、旋转等关键信息发送到其他的客户端,然后其他客户端可以根据情况决定是否再执行本地的物理模拟(如果是快照同步,由于拿到的就是最终的结果,那么就不需要本地再进行模拟了

(2)难点

这样看来,物理同步好像与常规的同步也没什么本质上的区别,那么为什么他却是一个难题呢?我认为原因有以下两点:

1)物理引擎的不确定性
2)在物理引擎参与模拟的条件下,网络同步的微小误差很容易被迅速放大

(3)首先谈谈物理引擎的不确定性

  • 归纳
    首先,我们谈谈物理引擎的确定性问题。很不幸,目前所有的物理引擎严格来说都不是确定性的,因为想保证不同平台、编译器、操作系统、编译版本的指令顺序以及浮点数精度完全一致几乎是不可能的。关于物理确定性的讨论有很多[3],核心问题大致可以归类为以下几点
1.编译器优化后的指令顺序
2.约束计算的顺序
3.不同版本、不同平台浮点数精度问题[4][5]
(问题1与问题3其实是密切相关的)
  • 这里摘选一段PhysX物理引擎的描述[6]:

The PhysX SDK can be described as offering limited determinism(注:提供了有限程度的确定性). Results can vary between platforms due to differences in hardware maths precision and differences in how the compiler reoders instructions during optimization. This means that behavior can be different between different platforms, different compilers operating on the same platform or between optimized and unoptimized builds using the same compiler on the same platform(注:不同平台、编译器、优化版本都会影响确定性). However, on a given platform, given the exact same sequence of events operating on the exact scene using a consistent time-stepping scheme, PhysX is expected to produce deterministic results. In order to achieve this determinism, the application must recreate the scene in the exact same order each time and insert the actors into a newly-created PxScene. There are several other factors that can affect determinism so if an inconsistent (e.g. variable) time-stepping scheme is used or if the application does not perform the same sequence of API calls on the same frames, the PhysX simulation can diverge.

  • 不同引擎比较
    如果游戏只是单个平台上发行,市面上常见的物理引擎(Havok,PhysX,Bullet)基本上都可以保证结果的一致性。因为我们可以通过使用同一个编译好的二进制文件、在完全相同的操作系统上运行来保证指令顺序并解决浮点数精度问题,同时打开引擎的确定性开关来保证约束的计算顺序(不过会影响性能),这也是很多测试者在使用Unity等商业引擎时发现物理同步可以完美进行的原因。当然,这并不是说我们就完全放弃了跨平台确定性的目标,比如Unity新推出的DOTS架构[7][8]正在尝试解决这个问题(虽然注释里面仍然鲜明的写着“Reserved for future”)。

  • 导致的结果
    考虑到物理引擎的确定性问题,我们可以得出一个初步的结论——完全使用帧同步做物理同步是不合适的(或者说做跨平台游戏是行不通的)。而对于状态同步,我们可以定时地去纠正位置信息来避免误差被放大。如果一定要使用帧同步去做跨平台同步,那么只能选择放弃物理引擎自己模拟或者用定点数来改造物理引擎,这可能是得不偿失的。

  • 大师的办法
    下面不妨先排除掉一致性的问题,来看看如何实现所谓的“物理同步”。实际上,无论是优化手段还是实现方式与前两篇提到的方案是几乎一致的,帧同步、快照同步、状态同步都可以采用,增量压缩、Inputbuffer等优化手段也一样可以用于物理同步的开发中。Network Next的创始人Glenn Fiedler在2014年撰写了一系列的物理同步相关的文章[9],使用一个同步的Demo非常详细地阐述了同步技术是如何应用以及优化的。涉及到的技术点大致如下,涵盖了网络同步的大部分的知识细节:

1)如何确保物理引擎的确定性
2)如何实现物理帧同步
3)Inputbuffer如何改善帧同步
4)为什么用UDP替代TCP
5)如何实现快照同步
6)怎样用插值解决网络抖动
7)如何通过快照压缩减少网络流量
8)如何实现增量压缩
9)如何实现状态同步
  • 另外,在2018年的GDC上,Glenn也对物理同步进行一次演讲分享[10],具体的细节建议大家移步到Glenn Fiedler的网站以及GitHub[11]去看。

    Glenn Fiedler的物理同步Demo

(4)即网络同步的误差是如何被物理模拟迅速放大的

接下来,我们再来谈谈第二个难点,即网络同步的误差是如何被物理模拟迅速放大的(尤其在多人交互的游戏中)。我们在前面的章节里也谈过,为了保证本地客户端的快速响应,通常会采取预测回滚的机制(Client prediction,即本地客户端立刻相应玩家操作,服务器后续校验决定是否合法)。这样我们就牺牲了事件顺序的严格一致来换取主控端玩家及时响应的体验,在一般角色的非物理移动同步时,预测以及回滚都是相对容易的,延迟比较小的情况位置的误差也可以几乎忽略。然而在物理模拟参与的时候,情况就会变得复杂起来。

主控(Autonomous/Master)以及模拟(Simulate/Replica)都是针对某个对象而言的。
假如有两个客户端,玩家A控制小车1,玩家B控制小车2。小车1在玩家A的客户端上就是主控的,小车2在玩家A的客户端上就是模拟的。同理,小车2在B客户端上就是主控的,小车1在B客户端上就是模拟的。

假如在一个游戏中(带有预测,也就是你本地的对象一定快于远端)你和其他玩家分别控制一个物理模拟的小车朝向对方冲去,他们相互之间可能发生碰撞而彼此影响运动状态,就会面临下面的问题。

1.由于网络同步的误差无法避免,那么你客户端上的发生碰撞的位置一定与其他客户端的不同

(▶本地客户端的模拟小车(对手小车)一定落后其控制端)

2.其次,对于本地上的其他模拟小车,要考虑是否在碰撞时完全开启物理模拟(Ragdoll)。如果不开启物理,那么模拟小车就会完全按照其主控端同步的位置进行移动,即使已经在本地发生了碰撞他可能还是会向前移动如果开启碰撞,两个客户端的发生碰撞的位置会完全不同。无论是哪种情况,网络同步的误差都会在物理引擎的“加持”下迅速被放大进而导致两端的结果相差甚远。


(不开启物理模拟条件下,模拟端不会在碰撞时立刻停下)

3、其实对于一般角色的非物理移动同步,二者只要相撞就会迅速停止移动,即使发生穿透只要做简单的位置“回滚”即可。然而在物理模拟参与的时候,直接作位置回滚的效果会显得非常突兀并出现很强的拉扯感,因为我们几乎没办法在本地准确的预测一个对象的物理模拟路径。如果你仔细阅读了前面Glenn Fiedler的文章(或者上面总结的技术点),你会发现里面并没有提到常见的预测回滚技术,因为他只有一个主控端和一个用于观察结果的模拟端,并不需要回滚。

  • 在2017年的GDC上,来自育碧的技术负责人Matt Delbosc就《看门狗2》中的载具同步进行了演讲[12],详细的分析了多个主控端控制不同对象发生碰撞时应该如何处理。

《看门狗2》的网络模型是基于状态同步的P2P,主控角色预测先行而模拟对象会根据快照(snapshot,即模拟对象在其主控端的真实位置)使用Projective Velocity Blengding做内插值,他们在制作时也面临和上面描述一样的问题。假如两个客户端各控制一个小车撞向对方,由于延迟问题,敌人在本地的位置一定是落后其主控端的。那么就可能发生你开车去撞他时,你本地撞到了他的车尾,而他的客户端什么都没有发生。

  • 时间差来解决这个问题
    所以,首先要做的就是尽量减少不同客户端由于延迟造成的位置偏差,Matt Delbosc引入了一个TimeOffset的概念,根据当前时间与TimeOffset的差值来决定对模拟对象做内插值还是外插值,有了合适的外插值后本地的模拟对象就可以做到尽量靠近敌方的真实位置。

而关于碰撞后位置的误差问题,他们采用了Physics Simulation Blending技术,即发生碰撞前开启模拟对象的RigidBody并设置位置权重为1(快照位置的权重为0),然后在碰撞发生后的一小段时间内,不断减小物理模拟的权重增大快照位置的权重使模拟对象的运动状态逐渐趋于与其主控端,最终消除不一致性,腾讯的吃鸡手游就采用了相似的解决方案[13]。


不过实际上, Matt团队遇到的问题远不止这些,还有诸如如何用插值解决旋转抖动问题,人物与载具相撞时不同步怎么办等等,知乎上有一篇译文可以参考[14]。

可能有些朋友会问,如果我不使用预测回滚技术是不是就没有这个问题呢?答案依然是否定的,假如你在运行一个车辆的中间突然变向,而这个操作被丢包或延迟,只要服务器不暂停整个游戏来等待你的消息,那么你本地的结果依然与其他客户端不同进而产生误差。也就是说除非你使用最最原始的“完全帧同步”(即客户端每次行动都要等到其他客户端的消息全部就绪才行),否则由于网络同步的延迟无法避免,误差也必定会被物理模拟所放大。

  • 同样在2017年,另一款风靡全球的竞技游戏——《火箭联盟》悄然上线,可谓是将物理玩法发挥到了极致。次年,《火箭联盟》的开发者Jared Cone也来到了GDC,分享了他们团队是如何解决物理同步问题的[14]。

《火箭联盟》的核心玩法是“用车踢球”,每个玩家控制一个汽车,通过撞击足球来将其“踢”进敌方的球门。由于是多人竞技游戏,所以一定要有一个权威服务器来避免作弊,最终的结果必须由服务器来决定。相比于《看门狗》,他们遇到的情况明显更复杂,除了不同玩家控制不同的小车,还有一个完全由服务器操控的小球。按照常规的同步方式,本地的主控玩家预测先行,其他角色的数据由服务器同步下发做插值模拟。但是在这样一个延迟敏感且带有物理模拟的竞技游戏中,玩家的Input信息的丢失、本地对象与服务器的位置不统一都会频繁的带来表现不一致的问题,而且FPS中常见的延迟补偿策略并不适合当前的游戏类型(简单来说就是延迟大的玩家会影响其他玩家的体验,具体原因我们在上一篇延迟补偿的章节也有讨论)。

ji+ 解决方案
为了解决这些问题,Jared Cone团队采用了“InputBuffer”以及“客户端全预测”两个核心方案。
1)InputBuffer,即服务器缓存客户端的Input信息,然后定时的去buffer里面获取(buffer大小可以动态调整),这样可以减少网络延迟和抖动带来的卡顿问题。

2)客户端全预测,即客户端上所有可能产生移动的对象(不仅仅是主控对象)全部会在本地预测先行,这样本地在预测成功时所有对象的位置都是准确的,客户端与服务器的表现也会高度一致,当然预测失败的时候自然会也要处理位置回滚。

仔细分析这两款游戏,你会发现他们采用都是“状态同步+插值+预测回滚”的基本框架,这也是目前业内上比较合适的物理同步方案。

(5)总结

除了同步问题,物理引擎本身对系统资源(CPU/GPU)的消耗也很大。比如在UE4引擎里面,玩家每一帧的移动都会触发物理引擎的射线检测来判断位置是否合法,一旦场景内的角色数量增多,物理引擎的计算量也会随之增大,进而改变Tick的步长,帧率降低。而帧率降低除了导致卡顿问题外,还会进一步影响到物理模拟,造成更严重的结果不一致、模型穿透等问题,所以我们需要尽量减少不必要的物理模拟并适当简化我们的计算模型。

3)参考资料

参考资料:
[1] WIKI, "Pong", WIKI, 2020.Available:https://en.wikipedia.org/wiki/Pong[Accessed:2020-12-12]
[2] Tony Wang, "游戏物理模拟简史", 知乎, 2020.Available:https://zhuanlan.zhihu.com/p/106977617[Accessed:2020-12-12]
[3] Theraot, "How can I perform a deterministic physics simulation?",Gamedev Stackexchange, 2019.https://gamedev.stackexchange.com/questions/174320/how-can-i-perform-a-deterministic-physics-simulation[Accessed:2020-12-12]
[4] Yossi Kreinin, "Consistency: how to defeat the purpose of IEEE floating point", Personal Blog , 2008. Available:http://yosefk.com/blog/consistency-how-to-defeat-the-purpose-of-ieee-floating-point.html[Accessed:2020-12-12]
[5] Glenn Fiedler, "Floating Point Determinism", Personal Blog , 2010. Available:https://gafferongames.com/post/floating_point_determinism/[Accessed:2020-12-12]
[6] NVIDIA, "NVIDIA PhysX SDK 3.4.0 Documentation Determinism", NVIDIA , 2020. Available:https://docs.nvidia.com/gameworks/content/gameworkslibrary/physx/guide/Manual/BestPractices.html[Accessed:2020-12-12]
[7]MelvMay, "How much deterministic is Physics from Unity3d In 2019?",Unity Forum,2020.Available:https://forum.unity.com/threads/how-much-deterministic-is-physics-from-unity3d-in-2019.711311/[Accessed:2020-12-12]
[8]Unity, "Burst User Guide",Unity Manual, 2020.Available:https://docs.unity3d.com/Packages/com.unity.burst@1.0/manual/index.html?_ga=2.60059693.1096806956.1607653832-2097754989.1600740353[Accessed:2020-12-12]
[9] Glenn Fiedler, "Introduction to Networked Physics", Personal Blog,2014. Available: https://gafferongames.com/post/introduction_to_networked_physics/[Accessed:2020-12-12]
[10]Glenn Fiedler,"Physics for Game Programmers : Networking for Physics Programmers", 2018.Available:https://www.gdcvault.com/play/1022195/Physics-for-Game-Programmers-Networking[Accessed:2020-12-12]
[11]Glenn Fiedler,"UnityDemo: Networked Physics in Virtual Reality: Networking a stack of cubes with Unity and PhysX" , 2018. Available:https://github.com/fbsamples/oculus-networked-physics-sample/[Accessed:2020-12-12]
[12] Matt Delbosc, "Replicating Chaos Vehicle Replication in Watch Dogs 2", GDC, 2017.  Available:https://www.bilibili.com/video/BV1KA41187jk[Accessed:2020-12-12]
[13]Ned,"手游中载具物理同步的实现方案", 腾讯游戏学院, 2018.  Available:https://gameinstitute.qq.com/knowledge/100044[Accessed:2020-12-12]
[14]Funny David, "看门狗2的载具同步(翻译)", 知乎, 2019. Available:https://zhuanlan.zhihu.com/p/95560180[Accessed:2020-12-12]
[15]Jared Cone, "It IS Rocket Science! The Physics of 'Rocket League' Detailed", GDC, 2018. Available:https://www.bilibili.com/video/av44416219[Accessed:2020-12-12]

七、优化技术总结

游戏思考04补充:网络游戏同步算法的理解(参考网易雷火jerish的文章,未完待续7/23,参考文献附尾,物理同步已更新完)相关推荐

  1. 游戏思考04:网络游戏同步算法产生原因和相关介绍:跟随(插值)、预测、延迟补偿,三种算法(未完待续02/07)

    文章目录 一.网络同步的典型场景 二.简单粗暴的数据同步为何行不通?(帧同步的难题) 1)网络同步的流程 2)网络延迟和抖动的直观解释 3)产生卡顿.瞬移的原因分析 4)测试网速网速 三.网络同步的优 ...

  2. 2020网易雷火游戏客户端开发实习生面试题

    2020网易雷火游戏客户端开发实习生面试题 第一题 计算堆叠正方形面积 第一个题 计算堆叠正方形面积 第二个题 计算广告牌面积 第三个题 神奇数 第四个题windows窗口显示处理问题 具体内容晚上补 ...

  3. ieg技术总监_游戏C++客户端面经(完美,360,腾讯IEG,网易雷火)

    一.前言 先说说自己的一些情况吧,本人来自双非一本理工科学校,软件工程大三学生.大学期间参加过ACM竞赛,也进过实验室做过项目.主要学习的语言是C++,但是项目开发却是用的Unity+C#进行项目开发 ...

  4. 游戏思考04总结:针对帧、状态、物理同步的总结(之前写的太长,现在简略下)

    文章目录 一.什么是帧同步 (1)帧同步定义(lockstep) (2)帧同步实现方法 (1)确定性锁步同步Deterministic Lockstep (2)乐观帧锁定Bucket Synchron ...

  5. 网易雷火游戏测试实习一二三四面面经

    面后复盘, 呦吼~ 四面 自我介绍 为什么选择游戏测试(情绪/兴趣+使命/责任感+性格 :自认为不必提自己的知识掌握程度好,因为面试的过程就是在考核你的知识.如果自夸掌握程度好,后面发挥不大理想的话, ...

  6. 网易雷火游戏功能测试面试记录

    一共三面,记录一下留个底,以后再争取吧. 第一面 自我介绍: 给出一个拜师系统来设计测试点和测试用例,以及在你所设计的测试用例中用到了什么测试用例的设计方法(测试点:等级.修为等级.可发布收徒消息入口 ...

  7. 【2022网易雷火】游戏研发笔试-AC代码及题目分享

    3个小时,4道题目,雷火的题目不在于难,更在于对细节的把控和写大模拟的耐心,花了一个半小时,AC了3.85,直接交卷退了.第一题AC,第二题AC,第三题85%,第四题AC.下面分享一下大致的题意和对应 ...

  8. 【游戏逆向】FPS网络游戏自动瞄准漏洞分析以及实现二

    开始分析人物结构 由于人物结构是一个结构体,该结构体的起始地址为人物对象地址,所以,我们继续用CE的结构体分析工具去分析人物对象地址,也就是刚才的eax地址: 通过观察,我们立刻就得到了人物名称的偏移 ...

  9. 游戏思考17:寻路引擎recast和detour学习二:recast导航网格生成流程\源码剖析流程\局限性,附录计算点线面举例代码

    一.recastnavigation使用介绍 1)模式选择 Solo Mesh:单块生成 Tile Mesh:分块生成 Temp Obstacles:分块并支持动态阻挡 这里测试的话选单块生成 2)模 ...

  10. 游戏音乐是游戏内涵的补充

    当我们的祖先意识到交流的重要性时,便产生了语言:我们在劳动过程中心有所感,便出现了音乐:时至今日,语言与音乐已经形成博大精深的体系,没有声音,这个世界将会枯燥乏味:没有音乐,再优秀的游戏也会黯然失色, ...

最新文章

  1. 自定义View合辑(8)-跳跃的小球(贝塞尔曲线)
  2. 自己实现Java RPC框架一:RMI
  3. error C2664: 'MessageBoxW' : cannot convert parameter 2 from 'const char [2]' to 'LPCWSTR'
  4. java对象类型转换分为_java-如何将类对象转换为通用对象类型?
  5. 【转】使用手势对UIImageView进行缩放、旋转和移动
  6. 实践GoF的23种设计模式:SOLID原则(上)
  7. linux 卸载lv,Linux系统LVM(逻辑卷管理)的删除
  8. ARM指令学习,王明学learn
  9. 萦绕在头脑中的思路_我的编程梦们 【更新至2010.06.03】
  10. atoi函数_linux网络编程之POSIX 消息队列 和 系列函数
  11. sim卡没坏但苹果手机无服务_苹果8PLUS换壳导致无服务维修
  12. JVM内存模型和结构详解(五大模型图解)
  13. 小米mini路由器刷固件
  14. 团队任务2:原型设计与UI设计
  15. iOS 第三方库插件和知名博客
  16. 基于历史对比学习的时序知识图谱推理
  17. Ubuntu下flash运行方法(2020.12后可用)
  18. 华师大 OJ 2850
  19. 开启Go学习之旅:开发环境搭建
  20. 信息系统集成-模拟二错题

热门文章

  1. 推荐9个web前端模板框架
  2. 中职组“网络空间安全赛项”linux安全加固
  3. DBUtil-一款简单好用的数据库操作工具
  4. Win10蓝屏原因分析记录
  5. Java回调函数详解
  6. 豆瓣电影 Top 250
  7. 数据运营平台-数据采集
  8. 02 华为交换机配置telnet远程登录
  9. Visual Studio 2019 C++实现socket通信,添加ws2_32.lib库,新手代码
  10. 什么是chirp信号