首页 > 代码库 > 网络游戏同步问题综述

网络游戏同步问题综述

http://blog.csdn.net/yxriyin/article/details/23377271

最近看了比较多的网络同步问题因为我只做过回合制,没做过arpg,所以稍微去看看大致是怎么实现的。基本上可以归为两个问题:玩家位置同步和战斗指令同步。玩家位置同步的情况要比战斗指令同步的情况简单,不过正因为如此,你为了更好的同步,就会采取复杂的同步策略。另外,这两种同步其实是息息相关的,战斗肯定会涉及到位置判断。所以我打算一起思考这两个问题。

     首先我们要知道在arpg游戏中,是需要对时协议的,就是说客户端和服务端的时间戳要基本保持一致。至于这个对时的频率和策略,可以有各种优化的版本,我们只考虑最简单的情况,就是每隔一段时间同步时间戳。一般可能是5HZ或者10HZ,当然战斗时候肯定会高些,平时可以降低些。

     为啥需要一个时间戳呢?因为不论是位置同步,还是防止玩家使用加速器,包括客户端作弊等,在有时间戳的情况下会简化很多。

      首先利用对时协议可以让客户端和服务端得知网络延迟的大概时间。虽然是大概,但也是比较准确的。

     为了更好的阐述问题,我们假定玩家A,网络延迟为1s,玩家B,网络延迟为2s。这是非常大的延迟,因为如果是100ms左右的延迟,其实不用采取任何技术你都能跑得比较爽。

     玩家A本来位于坐标5,下达了向坐标10移动的指令,玩家速度为1,服务端接收到指令的时候,玩家理论上其实已经到坐标6了,这里我们是无法让玩家A等待服务器响应再移动的,因为没人受得了我移动一下都要卡1s。服务端知道网络延迟为1s,所以可以轻易估算出位置为6,同时向B通知A的位置, B获得了A的坐标,其实A应该已经到8了,当然B也知道网络延迟,所以也能估算出A的位置应该是8。一切看上去都似乎还可以,但不难发现一些问题。首先,网络延迟是不稳定的,忽高忽低,根据时间戳去计算,是需要检测其合法性。比如延迟高达10s,你估算的结果都快让玩家瞬移了,我们可以判定10s的延迟肯定是不合理,甚至可以丢弃这个包。假定2s一下的延迟才会进入估算,而2s以上的,恐怕还是当做2s来处理。那玩家如果作弊呢?发各种各样奇怪的位置过来,这也是需要检测的,服务端肯定有玩家原来的位置,通过与玩家新位置的比较,看看两者的时间差有没有可能做这样的移动,如果没可能,说明有作弊嫌疑,多次发现,就直接踢下线处理。这样似乎解决了这个问题。但再思考,玩家A下达这个指令后,B将A同步到8,突然A的第二条指令来了,A跑到5.5之后,其实往回跑了,同理,服务端接收到信息,发现A的位置是5.5,这和本来预测的位置6相差0.5,说明自己的预测产生了误差,我们依然采取相信客户端的做法,因为假如你强行同步A位置为6,那么玩家A会产生往回跑的奇怪体验。这时服务端通过估算A在4.5,当然A可能突然停下来,这个问题我们不考虑,因为只需要增加一个停下来的指令或者判断目标位置就可以解决。服务端这时候通知B,A到4.5了,B收到信息,估算出A应该在2.5,发现问题所在了吧,A本来应该在8的怎么突然跑到2.5了?当然由于中间只经过了0.5秒,所以A在B看来还没到8,只是在向8跑的路上,大概是7。如果我们没有任何估算,那么情况会变成A在6,服务端认为是5,同时告诉B A在5,延迟了1, 如果A跑到5.5回头,那么A其实在4.5,服务端认为是5,B收到也是5,这样看起来似乎也没那么糟糕。那么问题的症结来了,如果不估算,那么毫无疑问,服务端获得的位置是客户端延迟前的数据,客户端得知其他玩家的位置则是两边延迟的叠加。这绝不是一个好的策略。那么估算的风险我们也看到了,可能会误打误撞反而是误差变大。我们发现误差变大的原因是玩家突然变向往回跑,导致经过一堆延迟后的速度相反,反而是误差增大。但也许这些都不是问题,这里我拷贝云风大大的一段话:

 

一种,收到的信息是属于其它玩家的。我们从最新得到的状态信息,预测一段时间之后(比如一秒后的状态),用一条直线运动去修正。即,设想一秒后这个玩家在哪里,然后反推回现在应该用什么速度运动可以在一秒后到达那个地方。

另一种,收到的信息是属于自己的,即服务器认可的自己的状态(并广播给别人了)。这个偏差是由于服务器的预测补偿造成的。为了保持用户的操作手感,对于不太极端的偏差,我们全部不修正,而是依然发送客户端自己操作的位置状态给服务器。服务器那边玩家是处于一种离散的运动状态的。而其他人见到你会再做预测补偿;如果和服务器相差过于剧烈,则直接跳转到服务器认可的新位置。

这里几乎全部相信客户端的行为,以获取最好的操作手感。防止客户端作弊是另外一个话题,也不是不能解决的,但目前不要碰了。

客户端到底以怎样的频率发送那些位置信息给服务器呢?

策略应该是这样的:

每次发送完一个完整的位置信息后,预测服务器看待这个位置信息包一秒后的位置大约在哪里。每次变化做一个累积,一秒内都但不用立刻发送。但每次小的状态改变都和假设的预测位置做一些比较,如果位置偏差比较大,就可以提前发送。否则一直累计到一秒再发送。

这个一秒的周期可以根据实际测试情况来调整。可能一秒太短,也可能过长了。

每次收到服务器发送过来的新的玩家位置信息时,都在里面会找到一个时间戳,表识的包发出的服务器时间。客户端可以验算之前的网络延迟是否正确。如果网络延迟稳定在一个固定值,说明没有问题。但如果延迟值为负数,则说明之前的对时流程中网络不稳定(可能是因为上下行时间偏差比较大造成的,也可能是当时服务器负载很大,造成了较大的内部延迟),造成本地时间和服务器时间的时差计算错误。这个时候重新发起对时流程就好了。

以上就是云风博客中内容,我们可以看到对于B玩家而言,A玩家其实已经不再误差范围内,应该直接将它变成2.5或者4.5,这么来看由于刚才我们分析B大概在7左右,如果从7变成4.5似乎还是可以接受的。毕竟本来我们就是从5变成了8.一切也没那么糟,当然如果你要把它变成2.5,那么只能使用瞬移了,不然玩家总是以一种比常人快的速度运动了。所以,我个人更喜欢保守估计,不要说太满,宁愿延迟一些位置,也不要过头了。

 

基于上述的结论,我们来看看玩家只是跑动的话毫无疑问是感觉到流畅的,因为所有人都没有等待就看到自己开始移动了,一切似乎都很完美。但是,如果是战斗呢,先不说pvp,我们就将pve好了,玩家对怪物进行攻击,如果有网络延迟,我们不得不等待,因为这个是不能放在客户端计算的(当然确实有游戏这么做,这个我会再写一篇博客探讨)。我们不能信任客户端,所以玩家延迟1s在攻击出手,这似乎非常蛋疼。然后看怪物,怪物攻击玩家是客户端发起呢还是服务端处理呢?首先我们对怪物也是要做位置同步的,因为怪物的AI肯定是客户端直接处理。假设是客户端发起,它同样也有延迟的问题,不过由于是怪物,也就无所谓了。但是我们说了移动是瞬间的,那么玩家攻击了,然后直接跑走,却发现攻击要在1s后才响应,自己明明要跑走,却突然去攻击了对方一下。这里有一个技术叫做客户端预测。当你攻击怪物的时候,客户端按照服务端的代码去执行,所以你确实看到自己毫无延迟的砍了下去,当然服务端那边也会计算,不过是1s之后的了,这时候你收到了服务端计算的结果,如果结果和客户端计算的是一样的,那么恭喜,你啥都不干就行了,玩家非常流畅的结束了战斗。但如果服务端计算和客户端不一致呢?没错,由于暴击概率,闪避概率等等的存在,很可能你客户端打中了服务端却没有打中,当你发现状态不一致,你就直接同步状态。那么问题来了,我客户端预测的意义在哪?只是为了播放那几下攻击,然后发现怪物慢慢的在1s后作出响应,而我还是默默的攻击着。虚幻引擎作者说:用缓冲技术,客户端存储状态和指令,当服务端状态发送过来时不是单纯的同步,而是将缓冲中无效的指令先去掉,比如已经过时的指令。然后再把服务端发过来的状态插入第一个,用操作缓存中的指令继续预测,获得到最新的状态进行同步。但你马上就意识到了,那岂不是不停的重复指令了?没错,你会发现你杀死了一个怪,突然这个怪又活了,又被你杀死了。为了避免这个问题,我们要确保每个指令其实只是被执行一次,也就是说你每次预测同步都只是执行一次的动作后更新结果,如果这些指令执行过了,那么你不用执行了,单纯的去同步结果就好了。当然还是会有怪物突然加血减血的问题。而且这个技术的最大问题在于你的客户端也要有服务端的对应的逻辑代码。对于真大作才能如此霸气。所以不打算采用这种技术。

 

那么我们只能老实接受这个延迟带来的坑爹现象了嘛?还是有些小技巧的。比如攻击前摇,你攻击的时候,先要抖一下,这个抖可能没有实际作用,但确实的提供了短暂的时间等待服务端返回结果。这当然不算啥技术,但是我们不得不正视一个事实,网络不好肯定体验就会变差,而且打不过网络好的玩家。比如pvp,无延时的玩家,几乎是发出一个指令就打到你了,你一点办法都没有。老实的等待被打吧。当你攻击别人时,你觉得打中了,但到了服务器那边发现人家老早走远了。所以如果是pve,还是能对网络不好的玩家做些补偿,毕竟能打中就当做打中好了。

 

基本就这些了,讲的可能比较乱。想看其他资料的参考我转载的一篇博客,网络同步啥啥的,有几篇英文写的特别好。

网络游戏同步问题综述