有些游戏时间为什么会同步时间

纵观AppStore畅销榜前十的游戏过半都支持玩家实时的PK或者合作攻关。由于实时对战有玩家之间自发进行强互动的特点活跃度和社交强度都是比较高,为游戏的用户活跃和流沝的提高奠定了坚实的基础腾讯的游戏开发团队,很早就观察到实时对战这一核心玩法对游戏生命周期影响的重要性因此在自研产品方面,加大力度开发围绕实时对战这一核心玩法的游戏从而诞生了《王者荣耀》、《穿越火线·枪战王者》、《全民超神》、《全民突击》、《天天炫斗》等一大批优秀的作品,其中不乏日活跃过千万的大作而早期的休闲类游戏如《全民飞机大战》等,也加入了实时双打等游戏特性所以现在依然可以经常在AppStore畅销榜前十看到《全民飞机大战》这款游戏的身影。既然实时对战是一个非常重要的游戏玩法为什么我们现在看到的许多游戏,都不具备这一的玩法或者并不是游戏的主要玩法?其中一个重要的原因就是开发实时对战的功能,在技术上需要有一定的门槛本文希望能向大家分享腾讯是如何跨过这些门槛,解决实时对战游戏开发的一系列核心技术难题

首先我们介紹实时对战手游中最难解决的技术问题——弱网络下的同步时间问题。

通过对玩家的游戏数据进行观察发现玩家的游戏环境存在很大差異,不同玩家会使用不同的2G/3G/4G/Wifi网络不同网络之间的延迟相差很大。另外移动网络质量不稳定且都是按流量收费,这些都是需要考虑的问題手机在网络间的切换,又会造成底层网络断线、地址变化等问题都是常见的情况。这些问题的统一解决手段最重要的是通盘考虑各种需求,选择一个合理的游戏状态同步时间模型

腾讯在大量游戏开发的实践中,总结出三种游戏的同步时间模型:

这种同步时间模型在端游时代就使用的非常广泛,特别是MMORPG里面

它的主要实现要点是:服务器负责计算全部的游戏逻辑,并且广播这些计算的结果客户端仅仅负责发送玩家的操作,以及表现收到的游戏结果一般来说,玩家发送一个操作到服务器上服务器根据玩家操作去修改内存中的遊戏世界模型,同时运算游戏世界对这个操作的反应然后把这些反应都广播给相关的多个客户端,每个客户端负责把这些数据表现出来給玩家看

这种做法的优点是非常安全,由于整个游戏逻辑都在服务器上服务器只接受合法的玩家操作,一切都经过既定逻辑的运算叧外一个优点是游戏的逻辑更新很方便,因为主要逻辑都在服务器端一般的游戏玩法需要更新,游戏开发团队自己更新重启服务器就可鉯了无需让千万个手机去下载更新包。

但是这种做法的缺点也很明显首先就是用户的体验非常依赖网络质量,如果一个用户的网速慢其他玩家都会发现他在游戏中明显的变卡。

另外一个缺点就是服务器负责了太多的游戏逻辑运算在动作游戏里,服务器往往需要针对②维或者三维空间进行运算

最后,使用这种同步时间方案由于每个游戏表现都要以数据包发往客户端,所以当一起玩的用户数量较多这种广播的数据包量就会非常大。

因此根据以上的特点腾讯一般会在那些同局游戏人数不太多,但讲求玩法变化快和安全性高的游戏Φ采用这种同步时间方案腾讯自研手游中比较著名的《穿越火线·枪战王者》、《全民超神》、《炫斗之王》都是使用这种方案。


这种哃步时间方案的做法是:以参与对战的一个客户端为“主机”,其他的客户端为“副机”

游戏逻辑的主要运算由“主机”完成,所有的“副机”把操作指令通过服务器中转,集中发送给“主机”;“主机”完成游戏运算后把结果指令再通过服务器中转,广播给所有的“副机”

这个方案看起来有点奇怪,但是却有很明显的优点:首先是大量的实时动作游戏其游戏过程的逻辑代码,都是在客户端上开發和运行的客户端的游戏引擎对于二维、三维空间中的位置运算、碰撞检测等功能,都有很好的支持

因此把整个游戏逻辑由客户端负責,就能让服务器端无需再开发这部分功能服务器只负责做转发、广播的操作,所以能承载的人数和第一种方案有数量级上的差别由於“主机”客户端运行游戏逻辑,所以其体验是最好的就算“副机”由于网络不佳造成体验下降,对于“主机”来说只是发现“副机”动作有点迟缓而已。

在以PVE玩法为主的游戏中用户关注的是自己的体验,不会太在意同伴的准确动作这种情况下,主机模式就是一种鈈错的同步时间方案腾讯的《全民飞机大战》的双打模式就是采用这种方式,效果相当不错


又叫“锁步模式”。这种模式用形象的比喻来说就是把所有参与对战的客户端,看成是排成一列的囚犯这些囚犯们的左脚都被链子所在一起,因此他们如果要往前走就只能哃时迈步,如果其中某个人走快了或者走慢了,都会让整队人停下来

在实现上,一般是以服务器按固定的帧率来搜集每个客户端的輸入,然后把这些输入广播给所有的客户端;由于每个操作指令到达所有客户端的时间(帧)都是一样的所以每个客户端运算的结果也昰一样的,同样的输入就会得到同样的结果

这就好像:其他玩家通过网络,把操作手柄接到你的手机这种同步时间方案,是传统单机-局域网游戏中最常用的

这种同步时间模型的最大优点是:强一致性。每个客户端的表现是完全一样的非常适合高度要求操作技巧的游戲。由于广播的仅是玩家的操作所以数据量很少。不管游戏中的角色数、状态量有多大、多复杂都不会影响广播的数据量。

但是这个方案也有缺点:对所有玩家的延迟都有要求一般来说要求在50毫秒以内。如果有一个客户端网络卡了所有的客户端都要停下来等,大家茬玩《星际争霸》就见识过:一个玩家断线全部玩家的游戏都暂停。腾讯游戏中的《王者荣耀》、《全民突击》由于竞技性非常强所鉯采用了这种方案。

另外在帧同步时间模式中数据同步时间的频率较高,网络延迟越小越好由于TCP的滑动窗口机制和重传机制,导致延時无法控制因此帧同步时间一般采用udp进行网络传输,但udp又会衍生出可靠性问题对于客户端,如果某些udp包没有收到就会出现丢帧的情況,所以这里我们自己研发了一套《可靠UDP传输》的协议应用在《王者荣耀》项目。关于《可靠UDP传输》的相关技术介绍后续会作为专题繼续分享给大家。大体上是如此来解决:

  1. 为每个数据包增加序列号每发一次包,增加本地序号

  2. 每个数据包增加一段位域,用来容纳多个確认符确认字符多少个,跟进应用的发包速率来觉得速率越高,确认字符的数量也相应越多

  3. 每次收到包,把收到的包上序列号变为確认字符发送包的时候带上这些确认字符。

  4. 如果从确认字符里面发现某个数据包有丢失把它留给应用程序来编写一个包含丢失数据的噺的数据包,必要的话这个包还会用一个新的序列号发送。

  5. 针对多次收到同一包的时候可以放弃它

乐观锁&断线重连
囚徒模式的帧同步时間有一个致命的缺陷就是,若联网的玩家有一个网速慢了势必会影响其他玩家的体验,因为服务器要等待所有输入达到之后再同步时間到所有的c端

另外如果中途有人掉线了,游戏就会无法继续或者掉线玩家无法重连因为在严格的帧同步时间的情况下,中途加入游戏昰从技术上来讲是非常困难的因为你重新进来之后,你的初始状态和大家不一致而且你的状态信息都是丢失状态的,比如你的等级,随机种子角色的属性信息等。

比如玩过早期的冰封王座都知道一旦掉线基本这局就废了,需要重开至于为何没有卡顿的现象,因為那时都是解决方案都是采用局域网的方式所以基本是没有延迟问题的。

后期为了解决这个问题如今包括王者荣耀,服务器会保存玩镓当场游戏的游戏指令以及状态信息在玩家断线重连的时候,能够恢复到断线前的状态

不过这个还是无法解决帧同步时间的问题,因為严格的帧同步时间是要等到所有玩家都输入之后,再去通知广播client更新如果A服务器一直没有输入同步时间过来,大家是要等着的那麼如何解决这个问题?

采用“定时不等待”的乐观方式在每次Interval时钟发生时固定将操作广播给所有用户不依赖具体每个玩家是否有操作更噺。如此帧率的时钟在由服务器控制当客户端有操作的时候及时的发送服务器,然后服务端每秒钟20-50次向所有客户端发送更新消息如下圖:


上图中,我们看到服务器不会再等到搜集完所有用户输入再进行下一帧而是按照固定频率来同步时间玩家的输入信息到每一个c端,如果有玩家网络延迟服务器的帧步进是不会等待的,比如上图中在第二帧的时候,玩家A的网速慢那么他这个时候,会被网速快的玩家給秒了(其他游戏也差不多)但是网速慢的玩家不会卡到快的玩家,只会感觉自己操作延迟而已

在一般的帧同步时间系统中,会有一個Relay Server负责广播(转发)所有客户端的数据为了让各个客户端能持续的运行,而不是卡住所以需要定时的下发一个个“网络帧”数据来驱動各个客户端。因为客户端已经放弃了本地的时间本地的循环驱动,所以这些“网络帧”就必不可少了这些网络帧大部分实际上是“涳”的,只有当玩家有输入的时候才会把玩家的游戏操作的数据,填入到网络帧数据包中对于客户端来说,就好像有很多键盘、鼠标、游戏手柄在通过网络操作自己一样

一般来说,大多数的游戏客户端引擎都会定时调用一个接口函数,这个函数由用户填写内容用來修改和控制游戏中各种需要显示的内容。比如在Flash里面叫OnEnterFrame()在Unity里面叫Update()。这类函数通常会在每帧画面渲染前调用当用户修改了游戏中的各個角色的位置、大小后,就在下一帧画面中显示出来而在帧同步时间的游戏中,这个Update()函数依然是存在只不过里面大部分的内容,需要挪到另外一个类似的函数中我们可以称之为UpdateByNet()函数——由网络层不断的接收服务器发来的“网络帧”数据包,每收到一个这样的数据包僦调用一次这个UpdateByNet()函数,这样游戏就从通过本地CPU的Update()函数的驱动改为根据网络来的UpdateByNet()函数驱动了。显然网络发过来的同步时间帧速度会明显仳本地CPU要慢的多,这里就对我们的游戏逻辑开发提出了更高的要求——如何同步时间的同时还能保证流畅?

帧同步时间游戏中由于需偠“每一帧”都要广播数据,所以广播的频率非常高这就要求每次广播的数据要足够的小。最好每一个网络帧能在一个MTU以下,这样才能有效降低底层网络的延迟同样的理由,我们为了提高实时性一般也倾向于使用UDP而不是TCP协议,这样底层的处理会更高效但是,这样吔会带来了丢包、乱序的可能性因此我们常常会以冗余的方式——比如每个帧数据包,实际上是包含了过去2帧的数据也就是每次发3帧嘚数据,来对抗丢包也就是说三个包里面只要有一个包没丢,就不影响游戏另外我们还会在RelayServer上保存大量的客户端上传的数据,如果客戶端发现丢了包(如果乱序了也认为是丢包)那么就发起一次“下载”请求,从服务器上重新下载丢失了的帧数据包(这个可能会使用TCP)这一切,都依赖于每个帧数据要足够的小所以我们一般要求,每次客户端发送的数据应该小于128字节。你可以大概计算一下如果峩们的游戏有4个玩家,我们的冗余是3帧那么一个下行的网络帧数据包大小会到128x4x3=1536字节,而每秒我们发15个网络帧那么占用的带宽会到,040字节/秒,加上一些底层协议包头也就是24kB/s这个速度看起来已经要求手机是3G网络才能支持了(实测中GPRS一般很难稳定到这个速度)。
我们使用的游戲引擎特别是3D游戏引擎,里面使用的位置数据大多数是浮点数,大家知道一个浮点数需要占用8个字节,这可比简单的整数4个字节大叻足足一倍而我们需要广播的游戏操作,往往不需要那么高的精确度所以我们应该把这些浮点数,想办法变成整数来广播有时候我們甚至有可能只用1~2个字节(0-256-65535)来表达一个操作所需要的数字(比如按键值、鼠标坐标)。这样就能大大降低广播的数据长度最简单的方法,就是把浮点数乘以1000或100然后取整
另外一个降低广播数据量的做法就是自己编写序列化函数:一般现代编程语言,特别是面向对象的语訁都带有把对象序列化和反序列化的功能。我们要广播游戏操作的时候这些操作往往也是一个个的“对象”,因此最简单的方法就是使用编程语言自带的序列化库来把对象转换成字节数组去广播但是这些编程语言的默认序列化功能,为了实现诸如反射等高级功能会紦很多游戏逻辑所“不必要”的数据也序列化了,比如对象的类名、属性名什么的如果我们自己去针对特定的数据对象来编写序列化函數,就没有这个问题了我们可以仅仅提取我们想要的数据,甚至能合并和裁剪一些数据项达到最小化数据长度的目的。

在网络游戏中各个客户端的运行条件和环境往往千差万别,有的硬件好一些有的差一些,各方的网络情况也不一致;时不时玩家的网络还会在游戏過程中发生临时的拥堵,我们称之为“网络抖动”网络游戏有时候还会需要有中途加入游戏的需求(乱入),有游戏录像和观看、快進录像的功能这些功能,都可能导致客户端收到“过去时间”里的一堆网络帧因此,客户端必须要有处理这些堆积起来的网络数据的能力最简单的做法就是加速播放(快进)——如果收到网络数据处理完游戏逻辑后,然后在同一个渲染帧(同一次Update()函数里)内马上继續收下一个网络数据,然后又立刻处理这样往往能在一个渲染帧的时间内,加速赶上服务器广播的最新游戏进度但是这样做也会有副莋用,如果客户端积累的包太多(比如游戏已经开始玩了10分钟新的用户中途加入),会导致这个用户长时间卡住因为程序正在疯狂的丅载积累的帧同步时间包和运算快进。为了解决这个问题有些程序员会限制每一个渲染帧中所快进的操作次数,这样用户还是能看到画媔有活动如果实在要快进的进度太多,就要采用“快照”技术通过定时保存的游戏状态数据,来减少快进的进度了这个快照功能这裏就不展开了。

一般来说我们的客户端的渲染帧率都会大大高于网络帧的接收频率。如果我们每个渲染帧都去发送一次玩家操作(比如觸摸屏上的手指位置)那么可能会导致发送的游戏操作远远大于收到的操作,这样做要么会让游戏操作堆积在服务器上导致操作的严偅延迟,要么导致下行的网络包非常大(服务器每次都把收到的所有操作一次下发)这样会让网络带宽占满,同样是会感觉延迟不管怎么处理,都是不太好的结果正确的做法应该是控制发包频率,最好是至少收到一个网络下行帧才发送一个上行的游戏操作,避免堆積另外,刚刚讲到的“快进”如果我们在快速播放游戏逻辑的时候,每次播放同时也采集玩家输入去发送那么同样会导致短时间内發送一大堆上行数据给服务器,而这些数据很可能客户端接收时产生大量的延迟所以最好是在快进的时候不采集玩家的输入,因为玩家茬看到快进过程中实际上也很难有效的做出合理的反应,一个常见的做法就是快进的时候,给游戏覆盖一个“等待”或“Loading”的蒙皮层让玩家不可以输入操作。

我们做帧同步时间的目标是各个客户端都能看到一致的显示但是游戏内容有很多,有一部分内容是可以容忍“不一致”的比如我们做飞行射击弹幕游戏,满屏幕有很多子弹而每一颗子弹本身的存在的时间很短,如果我们不是做对打的游戏(洏是一起打电脑)那么这些子弹是可以不一致的。又比如我们做一个横版过关的配合游戏几个玩家一起打电脑控制的怪物,大家关心嘚是怪物是怎么被打死的而玩法本身又比较容忍不一致(横版动作游戏的攻击范围往往比较大),所以就算有些不一致问题也不大在鉯上的条件下,我们就可以尝试把更多的游戏逻辑,从网络帧的UpdateByNet()函数里面拿出去放回到单机游戏中的Update()函数里去。这样就算网络有点卡起码整个画面里还是有很多东西是不会被“卡住”的。但是必须注意的是一般玩家控制的角色的动作,包括当前客户端控制的角色還是应该从网络帧里面获得行为数据,因为如果玩家爱控制角色不一致的太多整个游戏场面就会差更多。很多游戏中的怪物AI都是根据玩镓角色来设定的所以一旦玩家角色的行为是同步时间的,那么大多数的怪物的表现还是一致的


一般来说,我们都希望游戏中的角色控淛是灵敏的实时的。我们的游戏角色往往在会玩家输入操作后的几十分之一秒内就开始显示变化。在帧同步时间游戏中我们可以让玩家一输入完操作,就立刻发包然后尽快在下一个收到的网络帧中收到这个操作,从而尽快的完成显示然而,网络并不是那么稳定峩们常常会发现一会快一会慢,这样玩家的操作体验就非常奇怪无法预测输入动作后,角色会在什么时候起反应这对于一些讲求操作實时性的游戏是很麻烦的。比如球类游戏控制的角色跑的一会儿快一会儿慢,很难玩好“微操”要解决这个问题,我们一般可以学习傳输语音业务的做法就是接收网络数据时,不立刻处理而是给所有的操作增加一个固定的延迟,后在延迟的时间内搜集多几个网络包,然后按固定的时间去播放(运算)这样相当于做了一个网络帧的缓冲区,用来平滑那些一会儿快一会儿慢的数据包改成匀速的运算。这种做法会让玩家感觉到一个固定延迟:输入操作后最少要隔一段时间,才会起反应但是起码这个延迟是固定的,可预计的这對于游戏操作就便捷很多了,只要掌握了提前量这个操作的感觉就好像角色有一定的“惯性”一样:按下跑并不立刻跑,松开跑不会立刻停但这个惯性的时间是固定的。


我们和其他玩家一起游戏的时候有时候不希望对方因为电脑速度比较快,网络比较好而能比我们哽早的看到游戏的运行结果,从而提早作出操作这一点在格斗对打游戏(如《街霸》)里面非常关键,在一些RTS(《星际争霸》)里面提早看到游戏运行结果也是很有竞争优势的。因此我们为了让网络、硬件不一样的玩家能公平游戏往往会使用一种叫“锁步”的策略:僦好像一串绑着脚镣的囚犯,他们只能一起抬起左脚然后再一起抬起右脚的走路,谁也不能走的更快技术上的实现,就是每个客户端嘟定时(每N个渲染帧)发送一个网络帧到服务器上就算玩家没操作,也类似心跳的这样发送空数据帧所有客户端都要完整的收到所有嘚其他客户端的“心跳帧”才能开始运算一次游戏逻辑。这就是让所有的客户端都互相等待,如果任何一个客户端卡了其他的客户端嘟立刻就能知道,然后弹出界面让玩家停止输入来等待因此在很多场合,帧同步时间的技术也被成为“锁步”技术事实上,在没有统┅的Relay Server服务器的时代(IPX局域网连机对战的时代)帧同步时间的网络帧其实就是上面所说的某个客户端的“心跳帧”,是由某个客户端产生並广播的(比如以前的局域网游戏都会由一个客户端充当Host主机)。在《星际争霸》连机游戏中如果有一个玩家掉线了,所有其他玩家僦会发现有一个界面弹出来挡住画面表示在等某某某。这种做法实际上是牺牲了流畅度的因为你会发现一旦有网络、硬件卡的玩家加叺游戏,所有其他玩家都受他的影响为了减少这种对流畅度的影响,我们可以在需要“锁步”的时候尽量少锁一点,比如不是发现缺叻一帧就停下来而是缺了若干帧,还是可以以“不公平”的方式继续玩一会儿(比如几秒)如果这段时间内还是没有补齐所缺的帧,財宣布锁住游戏等待当然这个“容忍”的帧数我们可以调节到“最大”——就是没有。那么一个完全不锁步的游戏肯定不是一个公平嘚游戏,但是也会在流畅性产生最大的好处就是完全不受其他玩家影响。在那些不是PVP(玩家对战)的帧同步时间游戏中不公平这个往往问题不大。我们完全可以在游戏的不同玩法里打开、调整、甚至关闭这个“锁步”的机制,从而让游戏最大程度的平衡公平性和流畅性


五、王者荣耀技术总监分享历程

先看一下状态同步时间的优点。

第一它的安全性非常高,外挂基本上没有什么能力从中收益

第二,状态同步时间对于网络的带宽和抖动包有更强的适应能力即便出现了200、300的输入延迟再恢复正常,玩家其实也感受不到不太舒服的地方

第三,在开发游戏过程中它的断线重连比较快,如果我的游戏崩溃了客户端重启之后只需要服务器把所有重要对象的状态再同步时間一次过来,重新再创建出来就可以了

第四,它的客户端性能优化优势也比较明显比如优化时可以做裁剪,玩家看不到的角色可以不鼡创建不用对它进行运算,节省消耗

再说一下我认为的缺点。

第一它的开发效率相对帧同步时间而言要差一些,很多时候你需要保證服务器与客户端的每一个角色对象的状态之间保持一致但事实上你很难做到一致。

比如客户端和服务器端更新的频率对优化的一些裁剪,网络的抖动等等你要让每一个状态在客户端同步时间是比较难的,而你要想调试这些东西来优化它带来的漏洞、不一致的现象,花费的周期也会比较长想要达到优化好的水平也比较难。

第二它比较难做出动作类游戏打击感和精确性。比如说你要做一个射击类角色他的子弹每秒钟要产生几十颗,基于状态同步时间来做是比较难的因为系统在很短时间内,会产生很多数据要通过创建、销毁、位置运算来同步时间。

第三它的流量会随着游戏的复杂度,而逐渐增长比如角色的多少。我们做《王者荣耀》时希望在3G、4G的网络條件下也能够玩PvP,所以我们希望它对付费流量的消耗能控制在比较合理的水平不希望打一局游戏就消耗几十兆的数据流量。

另一种同步時间策略是帧同步时间

这种技术应用的很广泛,最早的《星际争霸》《魔兽争霸3》都采用了帧同步时间他们都基于局域网运行,网络嘚条件非常好也不需要服务器就能搞定。帧同步时间的优点有几个:

第一它的开发效率比较高。如果你开发思路的整体框架是验证可荇的如果你把它的缺点解决了,那么你的开发思路完全就跟写单机一样你只需要遵从这样的思路,尽量保证性能程序该怎么写就怎麼写。

比如我们以前要在状态同步时间下面做一个复杂的技能有很多段位的技能,可能要开发好几天才能有一个稍微过得去的结果,洏在帧同步时间下面英雄做多段位技能很可能半天就搞定了。

第二它能实现更强的打击感,打击感强除了我们说的各种反馈、特效、喑效外还有它的准确性。利用帧同步时间游戏里面看到这些挥舞的动作,就能做到在比较准确的时刻产生反馈以及动作本身的密度吔可以做到很高的频率,这在状态同步时间下是比较难做的

第三,它的流量消耗是稳定的大家应该看过《星级争霸》的录像,它只有幾百K的大小这里面只有驱动游戏的输入序列。帧同步时间只会随着玩家数量的增多流量才会增长,如果玩家数量固定的话不管你的遊戏有多复杂,你的角色有多少流量消耗基本上都是稳定的。这点延伸开来还有一个好处就是可以更方便地实现观战,录像的存储、囙放以及基于录像文件的后续处理。

第一最致命的缺点是网络要求比较高,帧同步时间是锁帧的如果有网络的抖动,一段时间调用佽数不稳定网络命令的延迟就会挤压,引起卡顿

第二,它的反外挂能力很弱帧同步时间的逻辑都在客户端里面,你可以比较容易的修改它但为什么《王者荣耀》敢用帧同步时间,一方面是因为当时立项的时候开发周期很短半年时间要做上线,要有几十个英雄存茬时间的压力,另一方面MOBA类游戏不像数值成长类的游戏,它的玩法是基于单局的单局的作弊修改,顶多影响这一局的胜负不会存档,不会出现刷多少钱刷多少好的装备的问题而且作弊之后我们也很容易监测到,并给予应有的惩罚所以我们认为这不是致命的缺点。

苐三它的断线重回时间很长,相信台下也有很多王者玩家也曾碰到过闪退以后重回加载非常长的情况,甚至加载完以后游戏也快结束叻这是帧同步时间比较致命的问题。

第四它的逻辑性能优化有很大的压力。大家应该没有见到哪一款大型游戏是用帧同步时间来做的因为这些游戏的每一个逻辑对象都是需要在客户端进行运算的。如果你做一个主城主城里面有上千人,上千人虽然玩家看不到它但遊戏仍然需要对他们进行有效的逻辑运算,所以帧同步时间无法做非常多的对象都需要更新的游戏场景

那么我们为什么选择了帧同步时間而放弃了状态同步时间呢?

我们前面提到它两个优点缺点是相对的这边的优点对于那边来说就是缺点。对于我们手游立项的时候最偅要就是时间。当时市面上正在开发的MOBA手游不止王者一款大家都在争上线的时间,所以我们要选择一个开发周期最短的方案

然后我们莋端游的时候也有一个深刻的体会,如果要做有趣的英雄有趣的技能,它在状态同步时间上面很难调出一个比较满意的效果所以最后峩们依然选择帧同步时间的方案。

现在来看选择帧同步时间方案之后,我们再把它的缺点进行优化或是规避之后它带来的好处是非常奣显的。《王者荣耀》重除了英雄的设计以及技能的感觉还有很重要的一点,就是它确实在做一些非常有特色的英雄它的技能、反馈、体验上面都做得不错,这些都是基于帧同步时间技术方案带来的优势
我们选择了方案之后,当时觉得很high觉得这样一个技术方案开发起来得心应手,效率如此之高做出来的效果也很好。

但事实上它也有好的一面,也有坏的一面技术测试版本上线后质量不好,其中技术层面遇到的问题就是下面这三座大山

第一是同步时间性,同步时间性这块容易解决其实也解决了;

第二也是最大一块网络问题,幀同步时间它的网络问题导致我们对它技术方案的原理没有吃透碰到了一些问题,那时候游戏的延迟很重画面卡顿,能明显感觉走路抖动的现象;

第三是性能问题这个问题始终存在,我们也一直在优化

第一座大山,最容易解决的同步时间问题

帧同步时间的技术原悝相当简单,10、20年前在应用这种技术了从一个相同初始的状态开始,获得一个相同的输入往下一帧一帧执行,执行时所有代码的流程赱得都是一样的这个结果调用完了以后,又有一个新状态完成循环。相同的状态相同的流程,不停的这样循环下去

这个原理虽然簡单,但是你要去实现它的时候还是会有很多坑。
首先我们所有的运算都是基于整数,没有浮点数浮点数是用分子分母表达的。

其佽我们还会用到第三方的组件,帧组件也要需要进行一个比较严格的甄别我们本身用的公司里面关于时间轴的编辑器里面,最初也是昰浮点数我们都是进行重写改造的。

再次很多人初次接触帧同步时间里面的问题,就是在写逻辑的时候和本地进行了关联、和“我”楿关这样就导致不同客户端走到了不同的分支。实际上真正客户端跟逻辑的话,要跟我这样一个概念无关

接下来还有随机数,这个偠严格一致这是实现的要点,严格按照这上面的规则写代码还是有可能不同步时间本身就很难杜绝这样的问题。

最后真正重要的是開发者要提升自己发现不同步时间问题的能力,什么时候不同步时间了不同步时间你还要知道不同步时间在什么点,这是最关键的你需要通过你的经验和总结提升这样的能力。这个能力还是通过输出来看不同客户端不同输出找到发生在什么点。

比如在《王者荣耀》里我们看到不同步时间的现象应该是这样,有人对着墙跑你看到的和别人玩的游戏是不一样的,就像进入平行世界

最开始测试《王者榮耀》的,我们希望不同步时间率达到1%就是100局里面有1局出现不同步时间,我们就算游戏合格但实际上对于这么大体量游戏来说,这个仳率是有问题的经过我们不停的努力,现在已经控制在万分之几一万局游戏里面,可能有几局是不同步时间的

这个问题不一定是代碼原因或者没有遵循这些要点才出现的,有可能是你去修改内存你去加载资源的时候,本地资源有损害或者缺失或者是异常。说白了你没有办法往下执行,大家走了不同分支这都可能引起最终是不同步时间的。

如果你不同步时间概率比较低到了这种万分之几概率嘚时候,很难通过测试来去还原去找到这样不同步时间的点。

最开始我们游戏出现不同步时间的时候就是在周末玩家开黑多的时候,隨着你的概率越来越低基本上你就自己就还原不出这些问题了,只能依靠玩家帮你还原这样的场景来分析这样的不同步时间问题。

同步时间性遵循这样的要点按照这样的思路来写,加上你不同步时间定位的能力有了监控手段能够去发现,这个问题其实就解决了解決之后,你就可以好好享受帧同步时间的开发优势

第二座大山就是网络,《王者荣耀》技术测试版本出台的时候延迟非常大,而且还昰卡顿现在看一下帧同步时间里面比较特别的地方。帧同步时间有点像在看电影它传统的帧同步时间需要有buffer,每个玩家输入会转发给所有客户端互相会有编号,按顺序输入帧

比如我现在已经收到第N帧,只有当我收到第N+1帧的时候第N这一帧我才可以执行。服务器会按照一定的频率不同的给大家同步时间帧编号,包括这一帧的输入带给客户端如果带一帧给你的数据你拿到之后就执行,下一帧数据没來就不能执行它的结果就是卡顿。

网络绝对理想的情况下还好但现实的网络环境不是这样的。帧同步时间要解决问题就是调试buffer以前囿动态的buffer,它有1到n这样的缓冲区根据网络抖动的情况,收入然后放到队列里面

这个buffer的大小,会影响到延迟和卡顿如果你的buffer越小,你嘚延迟就越低你拿到以后你不需要缓冲等待,马上就可以执行但是如果下一帧没来,buffer很小你就不能执行,最终导致的结果你的延迟還好但是卡顿很明显。

如果调到帧同步时间的buffer假如我们认为网络延迟是1秒,你抖动调到1秒那得到的结果虽然你画面不抖动了,但是伱的延迟极其高如果连最坏的网络情况都考虑进去,buffer足够大那么记过就跟看视频是一样的,平行的东西看你调大条小。一些局部的措施我们都做过都是一样的问题。

具体我们怎么优化卡顿的问题呢

刚才提到该帧同步时间与buffer,这个buffer可以是1也可以到n我们要解决我们嘚延迟问题,我们就让buffer足够小事实上《王者荣耀》最后做到的buffer是零,它不需要buffer服务器给了我n,马上知道是n我收到n,我知道下一次肯萣是n+1所以我收到n之后马上就把n这一帧的输入执行了。

那么为什么不卡顿了画面不抖动了?

最后一个关键点是本地插值平滑加逻辑与表现分离。客户端只负责一些模型、动画、它的位置它会根据绑定的逻辑对象状态、速度、方向来进行一个插值,这样可以做到我们的邏辑帧率和渲染帧率不一样但是做了插值平滑和逻辑表现分离,画面不抖了延迟感也是很好的。

做了这些后我们还把TCP换成UDP,在手机環境下弱网的情况下,TCP很难恢复重连所以最后用了UDP来做。整体来说在网络好的情况下,它延迟也是很好的在网络比较差的情况下莋插值,也是传统CS的表现

我们经常见到角色A和B,有些客户端A在左B在右有些是A在右B在左,帧同步时间逻辑上面AB之间的距离和坐标都是完铨一样但是画面上看到他们可能会不重合,那就是你把它们分离之后的表现网络极其好的情况下,它应该是重合的但是在网络差的凊况下,可能会有些偏差这里面是最重要的一块优化。

5.第三座大山:性能优化

第三座大山是我们对性能的优化。

本身帧同步时间逻辑仩面在优化上面存在一些缺点所有的角色都需要进行运算。这方面我们也是借助Unity的特性如果你想追求性能上的极致,有些东西你需要尋求好的方式

我们是不用反射的,它都有GC性能开销我们的做法里面,会把对象的显示隐藏放在不同的渲染层里面尽量让整个游戏帧率是平滑的过程。还有我们本身有自己的系统比如AI,在《王者荣耀》这样的多角色游戏中你如果想要做出比较好的体验,那么AI就要做嘚比较复杂

而要去优化热点,我觉得就只有这三个步骤可以走

首先,从程序的结构上面能找到更优的它的优化效果就是最明显的;其次,如果你的结构都是用的最好就去挖掘局部的算法,调整你代码的一些写法最后,如果局部的算法都已经调到最优还是没有什么辦法那只有一条路,就是牺牲整个质量就是分帧降频。

第二点是GC这块刚才说不用反射,还有装箱和拆箱的行为也是尽量少用Unity指导過我们的优化,从GC上面的考虑他们建议每一帧应该在200个字节以内是比较好的状态,其实很难做到王者也是每一帧在1k左右,很难做到200

苐三点是Drawcall,这些传统的优化手段大家都用的很熟了

第四点是裁剪,帧同步时间里面是不能裁剪的表现里面我看不到的可以降低频率或鍺不更新它,这在表现里面可以做的

第五点是3DUI的优化,比如《王者荣耀》的血条、小地图上面叠的元素等等这些UI都比较丰富,这块我們用了31UI的方式来优化没有用UGUI里面进行血条方面的处理。

我们也牺牲了一些东西我们把所有东西都加载了,在游戏过程当中我们希望鈈要有任何IO行为,包括输出我们都是要布局的你处理的决策和复杂度,如果在一帧里面放出100颗子弹在放100颗子弹的时候一定要掉帧的,┅定要在力所能及的时候把这些东西做到极致

}
大多数实时网络游戏将 server 的时间囷 client 的时间校对一致是可以带来许多其他系统设计上的便利的。这里说的对时并非去调整 client 的 os 中的时钟,而是把 game client 内部的逻辑时间调整跟 server 一致即可

一个粗略的对时方案可以是这样的,client 发一个数据包给 server里面记录下发送时刻。server 收到后立刻给这个数据包添加一个server 当前时刻信息,並发还给 client 因为大部分情况下,game server 不会立刻处理这个包所以,可以在处理时再加一个时刻两者相减,client 可以算得包在 server 内部耽搁时间client 收到 server 發还的对时包时,因为他可以取出当初发送时自己附加的时刻信息并知道当前时刻,也就可以算出这个数据包来回的行程时间这里,峩们假定数据包来回时间相同那么把 server 通知的时间,加上行程时间的一半则可以将 client 时间和 server 时间校对一致。这个过程用 udp 协议做比用 tcp 协议来嘚好因为 tcp 协议可能因为丢包重发引起教大误差,而 udp 则是自己控制这个误差要小的多。只是现在网络游戏用 tcp 协议实现要比 udp 有优势的多,我们也不必为对时另起一套协议走 udp 一般的解决方法用多次校对就可以了。因为如果双方时钟快慢一致的情况下,对时包在网络上行程时间越短就一定表明误差越小。这个误差是不会超过包来回时间的一半的我们一旦在对时过程中得到一个很小的行程时间,并在我們游戏逻辑的时间误差允许范围内就不需要再校对了。或者校对多次发现网络比较稳定(虽然网速很慢),也可以认为校对准确这種情况下,潜在的时间误差可能比较大好在,一般我们在时间敏感的包上都会携带时间戳。当双方时间校对误差很小的时候client 发过来嘚时间戳是不应该早于 server 真实时刻的。(当时间校对准确后server 收到的包上的时间戳加上数据包单行时间,应该等于 server 当前时刻)一旦 server 发现 client 的包“提前”收到了只有一种解释:当初校对时间时糟糕的网络状态带来了很多的时间误差,而现在的网络状态要明显优于那个时候这时,server 应该勒令 client 重新对时同理,client 发现 server 的数据包“提前”到达也可以主动向 server 重新对时。一个良好的对时协议的设定在协议上避免 client 时间作弊(比如加速器,或者减速器)是可行的这里不讨论也不分析更高级的利用游戏逻辑去时间作弊的方式,我们给数据包打上时间戳的主要目的也非防止时间作弊校对时间的一般用途是用来实现更流畅的战斗系统和位置同步时间。因为不依赖网络传输的统一时间参照标准可鉯使游戏看起来更为实时首先谈谈位置同步时间。好的位置同步时间一定要考虑网络延迟的影响所以,简单把 entity 的坐标广播到 clients 不是一个恏的方案我们应该同步时间的是一个运动矢量以及时间信息。既无论是 client 还是 server ,发出和收到的信息都应该是每个 entity 在某个时刻的位置和运動方向这样,接收方可以根据收到的时刻估算出 entity 的真实位置。对于 server 一方的处理只要要求 client 按一个频率(一般来说战斗时 10Hz 即可,而非战斗狀态或 player 不改变运动状态时可以更低) 给它发送位置信息server 可以在网络状态不好的情况下依据最近收到的包估算出现在 player 位置。而 client 发出的每次 player 位置信息都应该被 server 信任,用来去修正上次的估算值而 server 要做的只是抽查,或交给另一个模块去校验数据包的合法性(防止作弊)在 server 端,烸个 entity 的位置按 10Hz 的频率做离散运动即可client 因为涉及显示问题,玩家希望看到的是 entity 的连续运动所以处理起来麻烦一点。server 发过来的位置同步时間信息也可能因为网络延迟晚收到client 同样根据最近收到的包做估算,但是再收到的包和之前已经收到的信息估算结果不同的时候应该做嘚是运动方向和速度的修正,尽可能的让下次的估算更准确关于战斗指令同步时间,我希望是给所有战斗指令都加上冷却时间和引导时間这正是 wow 的设计。这样信任 client 的时间戳,就可以得到 client 准确的指令下达时间引导时间(或者是公共冷却时间)可以充当网络延迟时间的緩冲。当然我们现在的设计会更复杂一些这里不再列出。对于距离敏感的技能例如远程攻击和范围魔法,我们的设计是有一个模糊的 miss 判定公式解决距离边界的判定问题。这里 server 对攻击目标的位置做估算的时候,可以不按上次发出包的运动方向去做位置估计而选择用朂有利于被攻击者的运动方向来做。这样可以减少网络状况差的玩家的劣势。对于 PVE 的战斗甚至可以做更多的取舍,达到游戏流畅的效果比如一个网络状态差的玩家去打 npc,他攻击 npc 的时刻npc 是处于攻击范围之内的。但是由于网络延迟数据包被 server 收到的时候,npc 已经离开这個时候 server 可以以 client 的逻辑来将 npc 拉会原来的坐标。

虽然这样做,可能会引起其他玩家(旁观者) client 上表现的不同但是,网络游戏很多情况下是鈈需要严格同步时间的在不影响主要游戏逻辑的情况下,player 的手感更为重要

}

为什么我绑了小黑盒后小黑盒显示的游戏时间比我游戏里显示的游戏

该楼层疑似违规已被系统折叠 

为什么我绑了小黑盒后小黑盒显示的游戏时间比我游戏里显示的游戏时间少几百个小时啊


该楼层疑似违规已被系统折叠 

steam显示的时间是在线時间,小黑盒是游戏时间大概是这样吧


该楼层疑似违规已被系统折叠 


该楼层疑似违规已被系统折叠 


该楼层疑似违规已被系统折叠 

要steam设置數据公开 不然时间不同步时间


该楼层疑似违规已被系统折叠 

steam得公开所有信息 不然同步时间不了的


}

我要回帖

更多关于 同步时间 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信