网络层(六) - 网络同步解决方案
当前网络游戏中网络同步方案有三种,即状态同步,实时广播同步,帧同步。三种方式并不互相排斥,它们可以混合使用。很多时候我们在开发的时候,为了能都让游戏显得更加逼真,会选择多种的同步方案一起使用。例如魔兽世界这种开放世界的多人在线RPG游戏,起初它们就使用了状态同步和位移信息同步两种方案,绝地求生、和平精英等战地竞技类游戏,也同样使用了状态同步与实时广播同步方案,而传奇世界、热血传奇等传奇类游戏因为有严格的寻路同步机制所以就只使用了状态同步,而王者荣耀、英雄联盟等一批5v5地图类竞技游戏则更多使用了帧同步方案。
===
同步方案的目标是在针对多人游戏中如何用更少的信息同步量来逼真的’模拟‘其他玩家的一举一动,让我们在玩游戏的时候能知道并看到其他玩家的位置、动作、及状态。这里的关键词是’模拟‘,我们本地设备中获取的信息由于网络因素的关系通常都是延后的,如何通过这些延迟的信息来模拟真实角色的位移、动作、特效是整个方案的关键。
在同步的解决方案中不仅涉及到信息同步还涉及到同步的范围,如果我们将每个玩家每次的变化信息向游戏内的所有玩家都广播一遍,那么我们需要同步的数据量太大,这不仅客户端承载不了这样大量的渲染压力和信息通信压力,服务器也同样无法承受如此巨大的数据传输压力(会占满服务器网络带宽或让网络费用过大)。因此同步方案对游戏来说不仅仅只是用来逼真模拟其他玩家的一个解决方案,它还需要解决和优化网络传输数据带来的压力和部分渲染压力。下面我们就来讲解下三大网络同步解决方案的方法论。
状态同步法
为什么要状态同步?同步机制主要目的模拟,倘若我们每帧(以每秒30帧为标准)都将自己的信息同步给所有人,那么需要传输和广播的信息量就太大,因此我们需要尝试更节省流量的方式。游戏角色通常可以用状态形式来代表某一段时间的行为,因此如果我们使用状态信息作为同步信息广播给其他设备去模拟的话就会比较节省流量,这样当我们收到同步的广播信息后,就可以在一段时间内不需要其他信息就能模拟出这些角色的动作、位移、以及特效。
为了能更逼真的模拟其他玩家的行为,我们把每个人的行为方式抽象成若干个状态,每个状态都有一套行为方式将,有时尽管3D模型不一样但所调用的程序是一样的。比如空闲状态,所有人都会站在某个位置循环播放站立动画,这时我们只要告诉玩家说我在某个位置进入了空闲状态,只要我们的状态不变,其他玩家就可以在不收到任何同步数据的情况下知道我就是一直在原地并循环播放着站立动画。当然这是最简单的状态,我们也可以有其他比较复杂的状态,比如包括攻击状态、追击状态、防御状态、奔跑状态、技能状态、寻路状态等。
在状态同步里,角色身上每个状态就相当于一个具有固定逻辑的行为模式,这个固定行为模式就像个黑盒,只要给到需要的数据,就能表现出相同的行为,比如攻击状态,就会播放一个攻击动画并在某个时间点判定攻击效果,攻击动画完毕后结束攻击状态。比如打坐休息状态,就会循环播放一个打坐动画,并每隔一段时间恢复一次血量。又比如寻路状态,就会从某点到某点做A星的寻路计算,边播放行走动画边跟随路线向各个路线节点移动,最终到达到目地完成寻路状态。最复杂的应该属于技能状态,技能有很多种很多个,每种技能都有不一样的流程,同一种技能还有不通的动画和特效,一些复杂的技能更是需要配备复杂的逻辑,但有一点是可以肯定的也必须做到的,那就是向技能状态输入相同的数据应该展示出相同的表现,例如向技能状态中,输入火球技能ID,目标对象,施法速度,角色在收到数据后就需要展示火球技能的动画,在等待吟唱时间后向目标发射火球。
这些状态都有一个共同的特点就是只要我们给予所需的相同的数据就能展现出相同画面的个体效果。现在我们要让这些状态连贯起来拼凑成一个拥有一系列动作的角色,当我们向这个角色发送各种各样的指令时,就是在告诉它你应该触发这个状态然后触发那状态,由于指令中包含了状态需要的数据,这些数据广播给每个需要看到的玩家,收到这些状态信息的设备就需要通过这些同步数据去模拟角色的行为,从而让画面看起来像是很多玩家在操控自己的角色。
在状态同步中,我们说的通俗点,服务器端扮演了幕后操纵木偶人的那个大老板,而客户端里渲染的对象就是那个木偶人,服务器端发出指令说ID为5的木偶人开始攻击,客户端就执行它的指令,找到那个ID为5的木偶人并开始进入攻击状态的相关逻辑。当动画播放到一半服务器端又发来指令说,被攻击的那个ID的怪兽受伤了并受到500点伤害,这时客户端就会在指定的怪物头上冒出500点的伤害值并且让怪物进入受伤状态播放受伤动画。
现在当攻击动画播放完毕时,服务器又发来指令说继续攻击,客户端又根据这个指令找到ID为5的木偶人将它从站立状态切换到了攻击状态,不过这次攻击状态快结束时服务器发来指令说,这个怪物受到600点伤害并且死了,于是客户端根据这个指令找到这个怪物并在头上冒出600点伤害的数字,然后让怪物进入死亡状态播放死亡动画。木偶人也在播放完攻击动画后,进入了空闲状态循环播放了站立动画。
服务器扮演着发送指令操控木偶人的角色,这个木偶人也包括玩家自己的角色,即当玩家操控’我‘自己的在游戏中的角色时,也同样遵循经过服务器的同意并发送指令给玩家的过程,当玩家收到同步信息指令时当前的角色才会进行状态的切换和模拟。
不过也未必要一定要经过服务器同意才做模拟渲染,为了让玩家能够在网络环境糟糕的时候也能够看起来比较顺畅,我们在制作网络同步逻辑时,让玩家可以随意操控自己的角色,并不受限于服务器的延迟指令,但是在稍后的服务器校验时再对玩家进行矫正,最明显的例子就是我们在玩传奇类游戏中时的状态同步,在网络环境不太顺畅的情况下我们任然能操作自己的角色不停的移动,但在过一段时间后,当客户端接收到服务器发来的正确的数据后,客户端对角色进行了位置和状态的矫正。
状态同步的是根据角色拥有不同的状态,每个状态在某一段时间下的行为可以根据数据被预测,除非状态被改变,否则相同的状态数据得到的是相同的个体状态结果,这样角色实体的行为就可以通过状态切换来模拟表现画面。
实时广播同步法
在一些FPS类型的竞技游戏中,人物的行动速度和旋转速度在不断的变化而且频次比较高,如果想要模拟不同玩家在游戏场景中的位置与旋转角度就要实时更新这些信息,这时状态同步就不能满足这样需求,由于我们移动的速度和旋转的变化太快频次太高,因此无法做到拆分同步状态来模拟。不过状态同步依然可以用于除了移动和旋转意外的同步方式,因为除了移动和旋转,其他信息都没有这样快速、多样的变化,并且角色很多数据仍旧遵守状态的规则,对于这些数据我们可以继续用状态来划分,因此状态同步常常与实时广播同步同时存在。
实时广播同步方案的主要特点是,位置和旋转信息由客户端决定。客户端将自身的位置、旋转信息发给服务器端,再由服务器端分发给其他玩家,当其他玩家收到位置、旋转信息后根据收到的位置和旋转信息预测其当前的位置、速度、加速度,以及旋转速度,旋转加速度并进行模拟和展示。
在这种竞技性比较强,移动速度比较快的游戏中,通常都需要玩家不停的改变移动速度和旋转角度来体现其控制角色的灵活性。比较常见为枪战类游戏CS,玩家不停在变化自己的位移速度和旋转角速度以适应战斗的需要。除了FPS类型的游戏,赛车类游戏也是类似的情况,在跑跑卡丁车游戏中,玩家要在高速移动下,不停的调整自己的方向和速度,让自己能够躲过众多障碍,同时在急弯处要旋转自己的车进行漂移等。魔兽世界也会使用实时广播同步法,这种开放世界下的RPG游戏,需要不停的改变自己的速度与旋转角度来让战斗显得更加丰富和灵活,不过魔兽世界在实时广播之上加了些验证机制让客户端不能为所欲为的决定自己的位置。
为了能更加逼真的同步模拟这种变化频率很高的人物移动和旋转,我们不得不让客户端来决定其位置和旋转角度,这将牺牲一些数据的安全性来让画面模拟的更加真实流畅。
每个玩家设备上的客户端会在1秒内向服务端发送15-30次左右自身的移动和旋转数据,为的就是让其他玩家在收到广播数据时能更加顺畅的模拟玩家在游戏中的移动旋转的表现,也只有这样才能让其他的游戏客户端不停的更新玩家的位置、移动速度和旋转角度。不过只是单纯的更新位置和旋转数据,会导致玩家在屏幕中不停的闪跳,因此我们用速度的方式表示它们的移动方式会让角色模拟运动得流畅些。当我们收到广播的玩家实时数据时,先计算速度和预测速度,以及加速度,让模拟的对象按速度和加速度的形式在屏幕中运动,而不是只更新位置,这让角色在画面中模拟行走的位置和方向时更加流畅。
实时广播同步的算法和公式并不复杂,首先要取得已经收到的该玩家的位置信息前5个除以间隔时间,就能得到一个平均的速度,再取这样5个一组的3-5组,就能得到一个平均的加速度,根据这个速度和加速,就能让角色在屏幕中模拟出相对准确的跑动位置、速度和方向。不只是速度和加速度,我们还需要角色当前的面向的角度,以及旋转的角速度,在角度的同步上也可以按照这种速度和加速度的方式去预测,取最近5个角度的值得到平均旋转速度,再取5个一组的3-5组这样的数据计算得到旋转加速度。
虽然我们用前面的数据计算当前的位置、速度、加速度、旋转角度、旋转角速度,但还是会有偏差,由于网络延迟大且不稳定的关系,很容易造成位移的偏差,所以我们依然需要定时的矫正。矫正会比较生硬,比如由于网络宽带关系,我们很久没有收到实时广播数据了,一下子收到了很多广播数据,这时由于位置相差太远就会一下子将角色置于最后定位的位置,这种看起来矫正会比较生硬。我们可以在生硬的基础上加入一些当前数据的预测,让矫正不是直接飞到那个位置而是加速移动过去,速度取决于延迟的时间大小。这也正是为什么我们在玩CF穿越火线,跑跑卡丁车这类游戏时,假如对方的网络比较卡,就会看到对方角色不停的一闪而过,因为预测数据和矫正数据偏离的太多了,客户端在不断的矫正角色的位置和速度。
帧同步
状态同步既能控制数据计算的安全性,也能保证所有客户端的同步性,不过在位置和角度变化很快的竞技游戏中,状态同步无法承受这样又多又快的位置和旋转变化,所以就加入了实时广播同步的解决方案。实时广播同步解决方案放弃了玩家的位置和旋转角度的强校验,使得各个客户端能更加顺利和准确的模拟其他玩家的位置和旋转角度。
更加严格的同步要求既能做到移动与旋转的准确定位也同时能同步角色状态还能有比较强的同步校验,这时实时广播同步已不能满足需求,实时广播同步解决方案虽然能预测模拟玩家的位置与速度,但不能做到强校验,无法保证数据的正确性就容易被专空子作弊。由于每个玩家的手机和电脑端的设备好坏都不一样,网络环境也不同,一台好的机子和手机,在同一时间段能位移的距离可能也不一样,在差异性巨大的设备和网络通信之间做到精准的同步比较困难,前面我们说的状态同步是基于状态可拆分的模式,把角色分为几种状态,每个状态都做自己的事,当玩家改变状态时发送数据给周围的人让他们各自去模拟改变后的状态信息,但是状态同步的问题是当我们需要频次高且精确的同步时它就无能为力了。
帧同步解决方案就很好的解决了状态同步和实时广播同步解决不了的问题。
在同步性和安全性要求很高的游戏中,例如王者荣耀,拳皇类格斗游戏,游戏中的每一帧都是非常关键的,一两帧的计算就有可能决定双方的胜负,所以不能有分毫之差,对于这种类型的游戏,同步的要求特别高而且精确计算的要求也很高,帧同步的解决方案正好契合这种类型游戏。
与状态同步和实时广播同步法不同的是,帧同步的逻辑不再由客户端本身的逻辑帧Update来决定,而是转由从网络收到帧数据包来驱动执行逻辑更新,这也是帧同步最大的特点。其所有逻辑更新都放在了收到帧数据包时的操作中,包括人物角色的移动,攻击,释放技能等,每收到一个服务器发过来的帧数据包,就会更新一帧或更新前面因延迟累积的帧数。
帧同步的服务器需要向每个客户端每秒发送15-30个帧数据包,每隔0.033-0.05秒发送一个,即使没有任何信息也会发送空的帧数据,因为客户端要根据这些帧数据包来‘演算’游戏逻辑。因此帧数据的集合被认为是一条时间线,平时我们用秒来计算时间,现在我们帧来代替,例如某个动作做5帧而不是5秒,这颗子弹向前滑行10帧而不是XX秒,其实质是用整数的方式来计量时间线上的位置而不再使用浮点数因为这样更准确。
为什么要说‘演算’呢,一个比较容易理解的比喻是原来在客户端的Update里角色每帧移动xx米的逻辑,转移到了从网络收到的帧数据包的时刻,每收到一个帧数据包角色就调用一次移动逻辑(当然不只是移动逻辑,也不只会收到一个帧数据包,确切的说应该是更新逻辑),这样使得不同的游戏设备在拥有不同的帧率的情况下,执行相同数量的逻辑帧的同时也执行了相同次数的逻辑指令。指令存储在帧数据里,不同设备收到的帧数据一致,执行顺序一致,执行结果也将一致(这里有精度问题导致结果不一样将在本节后面讨论)。
帧数据中主要存储的就是指令以及指令相关的参数,一个帧数据可能有很多个指令分别指向不同的角色。当玩家通过屏幕或摇杆操作时将操作指令发给服务器,服务器在随后广播的帧数据中带就有我们上传的指令数据,除了有指令数据外的帧数据,其他没有任何指令的帧数据其内容是空的,空帧也需要被传达到每个客户端,因为这关系到逻辑的更新。
我们客户端的执行步骤为,随着客户端不断收到从服务器端广播的帧数据,每帧都执行一次更新逻辑,执行到某一帧其带有指令数据就执行该帧内的所有指令,同时也更新逻辑。比如帧数据中指令为某角色以每帧1米速度向前移动,那么客户端就开始启动移动状态执行该指令,在接下来收到的帧数据中客户端每每执行逻辑更新都会执行每帧1米的逻辑,比如后面总共收到20帧的网络空数据帧,那么就执行了20次每帧1米的行走逻辑,直到玩家再次操作停止了移动指令,并把该指令发送给服务器端,服务器端再以帧数据的形式广播给所有玩家,任何玩家设备收到这个带有停止指令的帧数据时执行停止指令停止了移动。
上述描述的客户端执行帧数据的逻辑步骤中,会有渲染与逻辑的差异。其原因是渲染时我们通常会一直在10-60帧范围内变化,而帧数据的频率则是固定的每秒15帧,这导致了帧数据的逻辑计算和渲染的差异。
假如我们移动中帧数据在逻辑更新时不断的计算当前的位置以此来作为渲染帧中位置的依据,则我们将看到角色在一跳一跳一顿一顿的逐级向前’跳动‘。为了能更加平滑的模拟帧数据中的移动内容,我们在渲染时也要进行预测和模拟。移动时我们知道每个数据帧中角色的位置,一帧代表多少秒,这样至少2个帧数据就能知道当前的速度,至少3个帧数据就预测加速度,这和我们前面介绍的实时广播同步在模拟角色速度与位移时的方法一样,用最近3-5个点位数据来预测当前的速度和加速度以及旋转角度和角速度。当网络原因帧数据延迟的很厉害时,我们就应该能从收发的数据中得知网络有较大的延迟,例如距离最近一个帧数据收到已经是5秒前的时间了,那么我们就知道网络造成了严重的堵塞,现在的预测和模拟已经不再准确了应该停止才对,等待帧数据的到来再重新矫正位置和速度。
现在的逻辑运算在网络收发的数据帧中执行,这就相当于服务器控制了所有玩家设备上的播放帧的速率和帧数长度,让所有玩家拥有相同的帧数据,执行相同的指令数量以及相同的指令顺序,由于执行逻辑的时间点(我们前面说过时间已经不再被用秒计算了而是用帧的数量计算),执行的逻辑的次序和次数相同,从而使得所有收到帧数据的客户端做出的表现也是相同,这就是帧同步的最大特点。它由服务器发送的帧数据来完成所有客户端的同步执行操作,在每个客户端设备中所使用来‘演算’的算法一致,最终在所有设备中执行的结果一致,反应到屏幕画面上表现的出来的行为也是一致的。
同步快进
现实中网络环境不太稳定特别是手机设备,不是所有客户端的网络都是流畅的,通常的网络环境都是时好时坏的,客户端在接收帧数据时经常都是波动的,时而会有一堆帧数据涌过来,或者又忽然完全收不到了帧数据,时常会有在两个极端之间的状况。因此如何预测和模拟延迟部分的表现,以及快速同步落后的很多帧的客户端画面成了客户端解决同步问题的关键。
当网络造成延迟,帧数据一下子收到很多,为了能同步帧数据的逻辑,我们可以使用最简单的方式,瞬间执行全部堆积在队列里的帧数据,这样我们一下子就能到达逻辑数据中的最后一帧,接着继续正常接收和模拟画面。但是这样一次性执行所有的很多帧数据的方式会有问题,如果堆积的数据帧太多,会导致因执行逻辑更新太多而让游戏卡住很久,画面会因此停止不动一段时间,游戏体验会比较差。解决这个问题我们可以在执行帧逻辑时每次执行N帧(N大于10)来使画面快速推进,这样一来玩家又能看到动态的画面,又能快速的跟上最后的同步帧数据,同步完所有的帧数据后,再把执行速度恢复到正常,从而继续接收数据帧来做正常的同步工作。
如果落后的太多太多,比如我们掉线后重连时相当于落后了整个数据帧,如果是一场20分钟的比赛相当于落后了206015=18000帧,这时快进的方法也不管用了,因为执行的帧数落后太多导致按普通快进的节奏得要快进很久才能跟上大部队,如果快进的速度太快则执行的帧数上升也会导致画面卡顿感太重。无论如何执行全部的帧逻辑消耗的CPU太多时间过久,如果是手机设备有可能会因消耗过大而发热发烫。
这时我们可以用内存快照的方式做快进操作,快照在很多领域都有这样的概念,特别是硬盘快照,就是将硬盘里的数据全部复制一份作为备份,当下次要构建一个一模一样的新机器时就不用这么费力了,直接从快照中复制一份就可以完成装机工作。内存快照也是同样的理念,只是与硬盘快照不同的是内存快照做的内存备份,意思是把内存中关于战斗的所有数据包括玩家,怪物,可破坏的障碍等等都备份一份在文件上存储在本地或服务器中,当客户端需要所有帧数据时其实需要的是最后一帧数据计算后的内存数据,这时客户端就可以直接使用该快照数据获得内存数据的结果,以此为依据渲染画面。由于这一帧的快照离最后需要同步的帧数最近,中间可能跨越了几千或上万的帧数,从该帧数据开始快进到最近的数据帧,相对于从头开始快进来说要快了许多也节省了许多CPU的消耗。
上面讲的都是模拟帧数据的画面表现,我们在发送操作指令时也需要注意一些问题,如果发送的指令过于频繁也会造成网络数据的灾难,比如玩家控制角色不断释放技能或者不停的旋转奔跑,就会让客户端以渲染帧的速度每帧大量的向服务器发送指令数据像机关枪一样扫射式的发射,这种方式就会造成帧数据混乱,一帧数据中当前玩家的指令应该最多只有1个,每个玩家的指令在1个帧的数据中只能有一个,只有这样才能不造成逻辑的混乱。
为了在不让发送过多的指令混乱帧数据,我们可以选择把需要发送的指令存起来,等到收到一个从服务端来的网络帧数据时再发送,如果有很多指令则不断替换未发送的指令直到收到网络帧数据才发送最后一个替换的指令。这也符合多端帧同步的规则,其实是我们规定了每帧只能有一个操作,因为我们需要遵循’不能在同一帧有多个操作’的规则。这个规则确保了我们在逻辑计算时不能在同一帧中既前进又后退,也不能在同一帧中既释放技能又取消技能,确保了游戏逻辑的简单化。
在实际操作中摇杆的在旋转和位移的变化以及技能的释放这些操作在用户端是非常快的,如果客户端等到有网路数据帧接收到时再发送当前的指令,那么用户操作的指令会被自己的指令覆盖好几次。实际确实是这样,很多手速非常快的玩家受到了网络数据帧频率的限制,为了解决这个问题很多游戏会提前模拟玩家的操作,客户端模拟玩家可以根据自己的操作自由行走甚至攻击或释放技能,而后再由网络接收到的数据来矫正位置与角度,或者先把指令序列存储起来,等到网络帧来的时候再将指令序列发送出去,这样的话可能是另一种网络延迟体验。
精度问题
帧同步的核心战斗的计算逻辑放在了玩家各自设备的客户端中进行,我们前面说了由于所有设备执行的帧数一致,执行的指令和时间点一致,执行的算法一致,就能得出相同的结果。理论上确实是这样,但是这里会出现一个问题,由于不同设备上的浮点数精度损失结果不同,导致同样的浮点数公式在不同设备上计算出来的结果有细微的差别,经过多次并长时间的计算后这种误差会扩大到不可接受的地步。
浮点数在各不同设备上的计算结果有细微的差异,随着计算量的增多差异会变得越来越大,即使我们逻辑业务的执行次数、时间、算法都一致,最终计算出来的结果还是会由于浮点数计算结果不同而不同。因此浮点数的精确计算也是帧同步在各设备同步问题上的一大关键,其根源是因为帧同步方案把计算过程交给不同设备而导致的问题。
其实也并不是什么难题,我们有很多用浮点数精度问题的解决方案,定点数就是其中之一。所谓定点数,就是把整数和小数部分分开存储,小数部分用整数来计算,整数部分继续用整数,这样计算起来就不会有误差了。
通常的浮点数在计算机中的表示法为 V = (-1)^s x (1.x) x 2^(E-f) 也就是说浮点数的表达其实是模糊的,它用了一个数的指数来表示当前的数。而定点数则不同,它把整数部分和小数部分拆分开来并都用整数的形式表示,这样计算和表达可以用整数的方式。整数的计算是确定的没有误差的,这样就不会存在不同设备上的误差,缺点是变量占用的内存空间多了一倍,计算的量也多了一倍,同时计算范围缩小了。
用定点数来替换浮点数计算就能保证在各设备上的计算结果一致性,C#自带的decimal类型就是定点数,它在金融和会计领域的使用比较多,在游戏项目中使用的多,因为也并不顺手,比如无法和浮点数随意的互相转换,在计算前也依然需要进行封装,又比如无法控制末尾小数点,使得精度还是无法根据项目需求来控制,同时它占用128位数据来存储,游戏中很少需要这么多的位数,这会大量增加内存的负担。
因此大部分项目都会自己去实现定点数的重新封装,把整数和小数拆开来都用32位整数表示封装在某个类中,再写一些关于定点数之间以及定点数与其他类型数字的数学计算库。数学库内的函数没有我们想象的那么多,其实就是把定点数与其他类型数字的加减乘除重写一下,如果涉及到更多的图形学运算的,则加入一些图形学的基本运算公式,比如线段交叉、点积、叉乘等,参数用我们封装的定点数代替。
最最最快速简单,性价比最高的的方式,其实是将所有浮点数改为整数并乘以1000或者10000表达完整的数,以整数的方式来计算结果就不会有问题。把所有需要计算的数字都以这种方式存储,只有在需要在UI界面上展示小数点的时候才除回来变成浮点数再进行展示,但也仅仅是展示的用途不涉及逻辑运算。这样即控制了精度的一致性,也不用这么麻烦去实现定点数的封装。只是这样做,原本能表达的精度范围就减少了,整数部分缩小到了2000万或200万以内,小数部分只保留了3位或4位小数点精度,不过仍然能在部分游戏中使用,因为在很多帧同步游戏中,200万的数字都已经是足够大了,所以有时他们无需劳心费神的去封装一个定点数以及定点数数学库,而且封装后还难以与表现层有很好的过渡。直接乘一下就能达到一样的效果,大大降低了研发成本。不过两者各有优势利弊,不一定说哪种方式一定好或者坏,按照项目的需求做不同的决策是应该的也是必要的。
我们这里也了解下同步锁的机制。在更加严格的同步类游戏中,比如星际争霸1中,如果有玩家网络环境不好,希望能够等待该玩家的进度,就会使用同步锁的机制。同步锁的机制,要求每个客户端每隔一段时间都发送一个锁帧数据,类似‘心跳’数据包,服务器端在帧数据中嵌入心跳包,用这样的方式告诉其他玩家该玩家仍然在线并正常游戏中,如果客户端在接受帧数据时超过50帧没有收到某个玩家的锁帧数据的话则停止播放网络帧数据,等待该玩家跟上大部队后,再所有客户端同时从最近的一次锁帧数据点开始,以该点为最后一数据帧,一起继续各自演算。
参考文献:
《漫谈游戏帧同步》作者:布尔君de二次方