游戏之网络同步【转载】

囚徒模式的帧同步,有一个致命的缺陷就是,若联网的玩家有一个网速慢了,势必会影响其他玩家的体验,因为服务器要等待所有输入达到之后再同步到所有的c端。另外如果中途有人掉线了,游戏就会无法继续或者掉线玩家无法重连,因为在严格的帧同步的情况下,中途加入游戏是从技术上来讲是非常困难的。因为你重新进来之后,你的初始状态和大家不一致,而且你的状态信息都是丢失状态的,比如,你的等级,随机种子,角色的属性信息等。 比如玩过早期的冰封王座都知道,一旦掉线基本这局就废了,需要重开,至于为何没有卡顿的现象,因为那时都是解决方案都是采用局域网的方式,所以基本是没有延迟问题的。

后期为了解决这个问题,如今包括王者荣耀,服务器会保存玩家当场游戏的游戏指令以及状态信息,在玩家断线重连的时候,能够恢复到断线前的状态。不过这个还是无法解决帧同步的问题,因为严格的帧同步,是要等到所有玩家都输入之后,再去通知广播client更新,如果A服务器一直没有输入同步过来,大家是要等着的,那么如何解决这个问题?

采用“定时不等待”的乐观方式在每次Interval时钟发生时固定将操作广播给所有用户,不依赖具体每个玩家是否有操作更新。如此帧率的时钟在由服务器控制,当客户端有操作的时候及时的发送服务器,然后服务端每秒钟20-50次向所有客户端发送更新消息。
在这里插入图片描述
技能同步
游戏中有很多是和概率相关的,比如说技能的伤害有一定概率的暴击伤害或者折光被击等。按照帧同步的话,基于相同的输入,每个玩家的client都是独立计算伤害的,那么如何保证所有电脑的暴击伤害一致那。这个时候就需要用到伪随机了。

大部分编程语言内置库里的随机数都是利用线性同余发生器产生的,如果不指定随机种子(Random Seed),默认以当前系统时间戳作为随机种子。一旦指定了随机种子,那么产生的随机数序列就是确定的。就是说两台电脑采用相同的随机种子,第N次随机的结果是一致的。

所以在游戏开始前,服务器为每个玩家分配一个随机种子,然后同步给client,如此每个client在计算每个角色的技能时候,就能保证伤害是一致的。这也是多数帧同步游戏采用的方案,包括王者荣耀。

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

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

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

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

帧同步的技术原理相当简单,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之间的距离和坐标都是完全一样,但是画面上看到他们可能会不重合,那就是你把它们分离之后的表现。网络极其好的情况下,它应该是重合的,但是在网络差的情况下,可能会有些偏差。这里面是最重要的一块优化。

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

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

第一点是热点的处理。

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

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

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

第二点是GC,这块刚才说不用反射,还有装箱和拆箱的行为也是尽量少用。

Unity指导过我们的优化,从GC上面的考虑,他们建议每一帧应该在200个字节以内是比较好的状态,其实很难做到,王者也是每一帧在1k左右,很难做到200。

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

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

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

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

上面提的是我们的第一代,也是在去年5月份以前做的优化方案。5月份以后,我们还做了另外一件事情:GameCore。

首先,为什么我们觉得iOS比安卓的优化效率高一些,一方面是iOS的CPU架构包括系统确实都优化的比较好,另一方面我们用的Unity4.6,在IOS下面它本身效率高一些,在安卓端的机器各种各样,性能也是千差万别,我们只能用性能比较差的方式。

因为我们已经做到逻辑和表现的分离,那么我们能不能把逻辑独立出来,做成一个C++的东西,实际上我们在去年开始已经在这样做了。做之前也测试过C++和Mono性能的差别,大概是2.5左右,本身我们的逻辑占比游戏消耗20%多,逻辑不是一个大头,我们做了这件事情之后,还是有效的,帧率提升了2到3帧,花的时间很长。

其次,做GameCore以后最明显的变化是我们以前逻辑上的GC没有了,我们有自己内存的管理、对象的管理,包括里面所有的容器类这些东西都是我们自己实现的,包括反射整个一套。它有了自己的内存管理,本身的效率就会比较高,这就已经是一个比较明显的优势了。

再次,有了GameCore之后,又多了很多应用场景,这个东西就是玩法的服务器版本,应用场景运行服务器要做很多的分析,还有第三方使用都是可以的。

最后,GameCore还有可以扩展多线程的潜力。

今后,我们也有几个计划。

第一,我们考虑能不能在热更新上面有所突破。因为王者这样一个游戏类型,包括它的体量,我们对于性能有一个比较极致的追求,不会轻易使用脚本层面在性能层面本身就不是最好的。这个我们要去研究的就是热更新,性能最好的方式。

第二,我们也在和硬件厂商沟通,他们希望游戏能够真正发挥多核性能上的优势,大部分的游戏在单核上面,把一个核吃的满满的,很多时候我们现在得出的结论,GPU性能也很强,王者并没有对GPU占满,可能只用了30%,CPU反而吃的比较满,吃满以后它还有另外一个坏处,它的发热、降频,你如果用多线程、多核去尽量平坦,让它不要处于高频的工作方式,反而会有更好的效果。

第三,我们现在用的是Unity 4.6版本,但Unity已经进化到5.7版了,后面他们还会推出新的特性,我们希望结合一些Unity新特性,现在已经有些游戏用5.6可以提升性能。

最后,不光是提升性能问题,Unity在多线程的渲染,也有很好的作用,使用引擎优势也是很必要的。随着性能的提升,我们会对王者的画质进行升级。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值