Unite 2017 | 从《闹闹天宫》看MOBA游戏里的网络同步技术
http://mp.weixin.qq.com/s/0v0EU79Q6rFafrh8ptlmhw
在Unite 2017 Shanghai案例分享专场,来自蓝港互动《闹闹天宫》项目组的主程序陈实为大家分享了MOBA游戏里的网络同步技术,总结了使用Unity开发MOBA类型游戏在网络同步上可能遇到的问题,以及如何规避这些问题的宝贵经验。本文就为大家带来此次分享的技术要点。
MOBA类游戏的网络同步有三大特点,也可以理解为MOBA游戏对网络同步的三大要求,即实时性、流畅性以及游戏公平性。满足这些网络同步要求常用的技术方案有两种:状态同步与帧同步。下面就分别针对这两种常用方案及其对应的实际案例,来分析两种方案对于各种网络环境的表现。并着重分享《闹闹天宫》项目选用帧同步方案进行开发的过程中,所遇到的一些难点与挑战。
下面附上本次主题分享的现场视频:
状态同步
状态同步,即同步玩家的状态信息,例如位置、属性以及其它玩法相关数据等。通常在此种方案下,游戏主逻辑会放在服务端进行计算,客户端仅用作显示。而通信需要的网络流量大小,取决于玩法设计中需要同步的实体数量。
状态同步结构图
对于像《星际争霸》这类RTS游戏来说,这种同步方案就不太适用,因为玩家可以操作的小兵数量可能上百个,状态同步会占用大量流量。状态同步需要客户端接收玩家输入后,上传到服务端进行数据处理,然后将状态信息传回客户端进行展示。
帧同步
帧同步仅同步玩家指令,不同步状态,游戏逻辑通常会在客户端分别计算。网络流量大小取决于指令大小以及同步帧率。像《星际争霸》这类游戏即使需要同步的单位增加到两三百,所需的网络流量也不会有明显增加。而帧同步的技术重点及难点在于如何保证所有客户端的计算结果一致。
帧同步结构图
帧同步大致的结构为,客户端将输入发送给服务端,然后服务器以统一的帧率将各个客户端的输入转发给其它客户端,各个客户端收到所有输入后在本地统一进行计算。
优缺点比较
状态同步
联系方式
录像文件大小
一等奖
138XXXX9572
流量消耗
一等奖
138XXXX9572
开发调试难度
一等奖
138XXXX9572
安全性
高
138XXXX9572
能否离线战斗
额外开发
138XXXX9572
网络卡顿时表现
额外开发
见下文
通常采用帧同步的开发和调试难度相比状态同步会高一些,因为状态同步仅需维护好服务端的一份逻辑,而帧同步较难保证所有客户端的一致性。从安全性来说,状态同步相比帧同步会更加安全,帧同步在客户端进行计算会面临被Hack的问题。对于运行在手机网络环境下至关重要的离线战斗功能,采用帧同步机制无需额外开发,而状态同步则需要单独创建离线战斗的逻辑。
帧同步方案下的录像文件更小,因为帧同步仅需保存指令。如果使用状态同步,这个文件相对会很大,在储存与传输上难度都较高。
状态同步通常需要消耗更大的网络流量,因为可能需要同步的数据量很大。在网络卡顿的情况下,状态同步方案会出现角色闪现的问题,因为状态同步通常会在本地进行一些预计算,可能会与最终服务端的计算结果不一致,重置客户端计算结果时就会发生闪现的问题,可以额外做一些处理来避免闪现。而帧同步方案在网络环境不理想时,会出现一些动作和输入的延时。
案例分析
目前采用状态同步方案的游戏有大家熟知的《Dota 2》、《守望先锋》及《全民超神》。采用帧同步方案的有《Dota》、《风暴英雄》及《王者荣耀》。通用的两种方案都有各自的优缺点与适用情况,建议大家根据实际项目需求选择最合适的方案。而《闹闹天宫》游戏采取了帧同步的方案,下面就结合《闹闹天宫》的开发经验,为大家分享帧同步方案的重难点。
Dota游戏截图
帧同步方案的重难点主要在于三个方面:保证确定性、流畅性以及针对外挂的处理。保证确定性即确保所有客户端的计算结果一致,这也是帧同步方案最大的难点。首先,要注意确保客户端的核心逻辑和同步帧率一致。另外,对于需要使用随机数的地方,保证初始化种子同步,且所有客户端的随机数调用次数一致。还需考虑到变量初始化、基准判断数据以及数据结构遍历顺序等可能会导致客户端不同步的问题。
其次是流畅性,影响游戏流畅性的因素有二,网络延迟与画面卡顿。网络延迟会导致玩家输入与反馈之间的延迟,画面卡顿则说明游戏性能存在问题。对于实时性要求较高的MOBA类游戏,推荐使用UDP,然后手动处理丢包与顺序问题。对于画面卡顿,要将显示与逻辑分离,保证即便在底层逻辑上物体是抖动的,在客户端显示上也能看到物体流畅移动。
最后讨论针对外挂的处理,外挂通常会修改游戏属性,如果使用帧同步,就需要检测本地数据是否有修改,或者将本地数据进行Hash运算后传到服务端进行校验。如果服务端没有该逻辑,也可以将其它客户端的数据发送到服务端比较两个客户端数据是否不一致,从而判断是否某个客户端出现了异常。帧同步除了同步指令外,也可以选择性地同步一些关键数据,来避免出现客户端数据被修改的问题。
结语
关于《闹闹天宫》的网络同步经验就为大家分享到这里,相信其中分享的内容对于非MOBA类的其它游戏也会有帮助。我们还会继续分享一些Made with Unity游戏及VR/AR内容的开发经验在Unity官方中文社区(unitychina.cn),请保持关注!
转自:http://www.gameres.com/750888.html
在5月13日Unite 2017 案例分享专场上,蓝港互动《闹闹天宫》项目组的主程序陈实分享了MOBA游戏的网络同步技术经验,以下为详细内容:
大家早上好,现在自我介绍一下,我叫陈实,来自蓝港互动,现在是《闹闹天宫》项目组的主程序。我今天的主题是MOBA游戏的网络同步技术。这个主题昨天王者荣耀跟今天《球球大作战》都涉及到一些,我讲的内容可能会更底层一些,更接触我们实践细节的部分。我不是在这里教会大家怎么做这个网络同步的技术,而是想把我们在使用这些技术的时候,遇到的坑,还有我们认为的重点,就是跟大家分享一下,然后可以让大家在使用同样的技术方案的时候,可以有一些坑大家就绕过了,如果节约大家开发的时间或者调试的时间,我觉得这次分享的价值应该就体现出来了。
我们讲这个MOBA游戏里面的网络同步技术,首先需要来了解一下,MOBA游戏这个同步的一个特点。我把它归纳为三点,首先要求动如脱兔,就是实时性要求比较高,然后要顺滑如丝,就是流畅性要求比较高。还有一个就是疏而不漏,就是公平性也要有一定要求。但是允许你在一些显示的部分,我们可以说两边有一些差别,这是可以接受的。但是涉及到核心的游戏玩法的部分必须保证是一样的。
接下来我会分三个方面跟大家阐述一下这个主题,首先会讲一下我们在做这个网络同步的时候,常用的两种方案,状态同步和帧同步,然后我会结合实际的案例来分析一下,哪些游戏用哪种方案,大家用对应的方案,可以分析一下对应的做的比较好的游戏,然后还会看一下,就是在网络延迟的情况下两种方案表现有哪些不一样。最后会着重介绍一下我们项目在使用帧同步这个方案的时候,遇到的一些问题,然后里面的一些重点和难点。
那么讲一下状态同步和帧同步。先说一下状态同步,状态同步顾名思义,就是同步玩家的状态信息,比如位置,他的属性,还有其他跟玩法相关的数据。通常使用这种方案主逻辑基本上都在服务器运行,都在服务器进行计算,客户端只是作为一个显示。还有一个特点,就是我们通讯的网络流量大小是依赖于你的游戏里面,你的玩法里面,需要同步的一个实体数量。
我们知道以前玩的比较多一些RTS游戏,像星际争霸,就不太会使用这种状态同步的方案,他们可以操纵兵的个体的数量是非常多,可能有上百个单位,如果这样用状态同步的话,流量就会非常大。我们看一下这个方案的结构图,大家可以看到,客户端会把一些,把他们的输入发给服务器,然后服务器会做一个处理,把状态信息发到客户端做一个显示。
我们再介绍一下帧同步,帧同步不太一样的地方就是,只同步玩家的指令,不同步状态。通常游戏的逻辑都是在客户端进行各自计算的。这肯定带来一个问题,就是说你要在各客户端各自计算,你怎么保证在各客户端算出来的结果是一样,这是帧同步的技术重点也是难点。然后就是网络流量是不依赖于同步实体的个数,只依赖于指令,这么一个大小,还有一个同步的频率。像星际争霸这种游戏,即使需要同步的单位从一百个加到两百个,加到三百个,对同步的网络流量也不会有特别明显的增加。
我们再看一下这个帧同步的结构图,大家看到,客户端会把它们的消息跟它们的输出发送给服务器,服务器会以一个统一的频率把各个客户端的输入转发给其他的客户端,然后其他各个客户端收到大家的输入之后,统一地进行运算。
下面我就比较一下,我们认为这几个重点的方面,使用这两种解决方案可能有哪些不一样。我们来比较一下开发和调试难度,我们认为帧同步的难度是比状态同步的开发调试难度会高一些。因为状态同步的话,可能只需要有一份逻辑在服务器运行,只要写好服务器的逻辑,保证正确就行了。帧同步因为各个客户端去运算,很难说去保证一个一致性,很容易就会出现我们所谓的不同步的现象,就各个客户端整个游戏的流程就分开了,两个客户端看到完全不一样的游戏状态。
下面是一个安全性,我们认为状态同步的安全性会高一些,因为所有逻辑都在服务器,因为帧同步逻辑是在客户端运算,就存在一些hack,后面会降到一些外挂的问题,我们如果用这个帧同步方案的话,我们怎么避免它。
还有一个是离线战斗,我们知道在手机上的MOBA游戏,手机上的网络,或者使用场景,网络是不稳定的,可能我们希望为了体验好一点,比如单排的时候,或者刚进来的时候第一场新手战斗,我们希望跟AI在战斗。跟AI打的时候,不需要去跟服务器进行通讯,保证刚开始的时候,体验是非常好的。这个离线战斗也是一个很强的需求,如果你使用的是状态同步的这种方案,可能需要额外去开发,因为通常我们的Server和Client使用的开发语言是不一样的,帧同步本身在客户端算,只需要简单转发一下网络的消息,就可以实现说这么一个离线战斗的逻辑。
然后消耗流量的大小,通常来讲状态同步比较高,原因也说过了,因为可能需要同步很多很多的属性,然后还依赖于需要同步的实体的个数,还有一个就是网络卡顿的时候的一个表现,状态同步通常的表现是闪现,帧同步就是动作和输入会出现一些延时。为什么会出现闪现,因为状态同步通常使用的策略,会在本地做一个预算,你的预算很明显很可能跟服务器算出来的最终结果不一样,这样他就会把客户端重置到一个正确的状态,就出现一个闪现的情况。这种是原生就带来这个问题,但是额外做一些处理的话,还是可以尽量去避免这种问题。
还有一个就是我们说的录像文件的大小,录像文件的大小,这也是比较重要的。因为现在手机游戏大家都希望去分享你的战斗,分享你的精彩的时刻。如果你使用状态同步的话,这个录像文件比较大,帧同步因为只存指令是比较小的,如果录像文件比较大,你的储存、传输都是很大的问题。
下面我再结合具体案例再讲一下,我们来看一下,如果想使用状态同步的话,你可以去研究一下《Dota2》,他使用状态同步的一个原因,因为Dota2的厂商,有一个Source引擎,这个引擎基本上就是为FPS类游戏开发的,而FPS通常都是使用状态同步,因为他们使用这个引擎,选择状态同步这么一种方案,很多代码就可以复用。所以可以看到状态同步也可以在MOBA游戏上得到一个很好的表现。还有就是大家比较熟悉的《守望先锋》,他就是一个典型的FPS游戏。我们看一下使用帧同步的游戏,帧同步就是《Dota》,他是基于魔兽争霸3,还有就是《风暴英雄》,《风暴英雄》使用星际争霸2的引擎,所以说就沿用了帧同步这么一个结构。
还有现在最火的《王者荣耀》,如果大家使用对应的方案可以研究一下这些游戏,他们的表现。下面截了一张,大家可以看一下,这是腾讯的全民超神,大家可以看到使用状态同步方案,这个在400毫秒延迟的情况下,大家看一下下面的小兵和英雄,就会出现闪现的情况,还有一个你看最后英雄被击杀的时候,没有人打你,你却莫名其妙的死了,这是一个典型的状态同步在弱网络环境下的表现。再对比看一下《王者荣耀》,王者荣耀同样是400毫秒延迟的情况下,看一下王者荣耀的表现,你明显可以看到左边的摇杆往下之后英雄并没有立即跟着往下,而是过了一会儿才往下。但是你是怎么死的,或者你的运行轨迹是怎么样的,还是看的非常清楚的。我觉得可能右边这种表现是更好的一种表现。
上面就粗略地介绍了一下,我们通用的两种方案,我认为没有最好,只有最合适的,大家根据自己游戏的一些需求和你策划的玩法来选择,使用哪一种同步方案。
帧同步的重难点
我会从三个方面来讲,一个是怎么样保证确定性,然后怎么样说能保证是流畅的。最后再来讨论一下外挂的问题,外挂的处理。
首先讲一下确定性,要保证确定性,这是帧同步方案最难的地方。那么你要保证这个确定性,就是说各个客户端根据同样的输入,算出同样的结果,虽然这句话很简单,但是想要做到算出同样的结果是非常困难的。我们可以注意哪些问题呢,首先一定要把各客户端的核心逻辑的同步频率保持一致。如果你的频率不一样,比如用Unity的函数,每一帧运行时间间隔不一样,如果这个不一样的话会带来非常多的问题,基本上是不可能算出一样的结果,首先第一步就把这个频率调成一样的。
然后是随机数,我们很多游戏的话,肯定会使用到一些随机的东西,随机数在使用的时候,我们几乎不能使用Unity自带的随机数,因为我们知道我们通常使用的是伪随机数,我们为了保证在各客户端的随机数是一样的,需要做哪些工作。首先需要把初始化的种子要同步,它们是一样。然后还要保证在各个客户端上,随机数调用的次数是一样。
为什么不能使用Unity提供的随机数,因为这个随机数可能会被一些粒子系统,一些别的内部系统去调用,我们是没有办法控制这些调用的次数的,所以,我们需要实现一个自己的随机数,来保证他的调用次数。然后浮点数,这也是讨论比较多的主题了,到底我们是应该使用浮点数还是定点数来计算这个物理。据我所知,除了一些在主机上发布的,因为他们只在PS4或者xbox上运行他们的游戏,因为PS4和Xbox硬件架构是一样,可以得出结果。但是硬件架构不一样,或者用不同的编译器编译我们的代码,能不能保证浮点数是一样的结果,应该是可以,但是基本没有看到哪家厂商有这样实现过,为了做到这一点需要做非常多额外的工作,可能需要针对不同的CPU,然后不同的编译器来优化我们的代码,还要做一些额外的工作,才能保证算出来的结果是一样。我们通常还是用定点数来算。
下面这点,为什么说帧同步开发难度比较高。如果不是很了解这个架构的人去写这个游戏逻辑的话,就很可能很自然使用一些本地的数据,就是一些客户端独有的数据,这个人能不能看到,渲染物体的位置等,可能这个位置在各客户端上都是不一样,如果拿这个来做判断的话,就很容易显示出来不同步,因为条件不一样,那么算出来结果自然就不一样了。这个可能就得根据你们游戏的一个设计,还有你的编码来去判断了,就大家可能需要注意这个问题。
如果我们在使用一些第三方库的话,比如使用一些box2D这种库,使用自己开发的库来做物理运算的时候,还是注意这个问题,可能一些变量没有初始化,那在不同平台上,或者不同编译器编译出来的代码,初始的值是不确定,如果用这个变量去运行一个逻辑的话,很可能就不同步了,而且还很难意识到这个问题。如果脑子里面有这个概念,变量是必须初始化,就能避免这个问题,同时发生问题的时候,也可以往这个方面想一下。
然后就是一个遍历的顺序也会导致不同步,我们的一个经研究说,我们经常用Dictionary这个结构,你用这个结构是有问题,你可能天真地认为,往Dictionary顺序插入一二三四个元素,他是不是按一二三四的顺序去遍历,官方是不能保证的,如果用这个结构,大家可以尝试使用另外一种SortedDictionary这个容器,这个容器有一些开销,如果不频繁插入的话是没有什么问题。
然后就是Raycast返回的结果,在使用Unity的函数,可能会反馈给你一个结果,你碰到哪些东西。这个结果的东西也是不能保证,这个怎么办,我们解决方法给每个实体会有一个唯一的ID,这是我们自己逻辑里面的唯一ID通过这个ID做一个排序,最后排完序以后再使用Raycast的结果。
我们游戏一些上层游戏逻辑是用Lua来编写的,Lua里面有一个Table,使用这个结构也是注意这个问题,把他当成hash表来遍历的时候,这个顺序也很难保证,我们一定要使用这个ipairs来遍历,就是当成一个数组来遍历。
下面是流畅性,讲流畅性之前,先了解一下,你不流畅的时候,到底是一个什么样的表现。通常玩家反馈给你,只会反馈给你,一个是卡,或者说一卡一卡,或者卡卡的。玩家不会告诉你说,我是看到这个东西网络是不是延迟了,或者说怎么样?不会这样,他会给你的是一些很感性的词。当玩家在说卡的时候,他在说什么?其实他在说输入和反馈之间存在延迟。输入和反馈之间存在延迟,这很可能就是我们的网络,可能存在说你丢包要重发,就是网络延迟。
还有一个是画面卡顿,画面卡顿的话,游戏存在性能问题,刚才我们《球球大作战》跟我们分享了优化的对大家也是挺有帮助的。然后还有一个就是游戏物件在来回抖动,可能不是因为你的网络延迟,也不是因为性能问题,可能单纯只是因为你的这个物体,由于你逻辑上有问题,导致它看起来在抖动,我们就遇到过这个问题。
下面讲一下我们怎么来保证这个流畅性,TCP还是UDP,这个问题讨论的非常多了。通常来说,我们认为对实时性要求比较高的MOBA游戏,我们还是选择UDP,有些游戏也是选择TCP,其实也是做的挺好,也可以根据自己的游戏需求,如果你觉得TCP还OK,那就选择TCP没有问题,很成熟,也很可靠。不需要做额外的工作。如果你觉得你的游戏对你的实时性要求比较强,还是推荐你用UDP,使用UDP带来什么问题,首先UDP是不可靠的传输协议,可能需要自己去处理一下丢包,还有顺序。通常常用的解决方案就是增加冗余数据,如果一次发包,可以一次带两帧的数据,如果丢包之后,丢了一个包,但是仍然可能还是有额外的一帧供你使用,继续去渲染。
使用UDP,还有一个问题是说,中国网络国情,有些网络是连不上UDP的,在这种时候还是需要处理一下,就是在UDP连不上的时候,是不是还要回去使用TCP。如果大家想要进一步了解TCP和UDP,我推荐大家一本书TCP/IP详解,这个里面讲的是非常清楚的。
第二个是Tran位置的更新顺序,这是流畅性提到第三点问题,其实并不是因为网络延迟,也不是因为你的性能有问题,而是因为你的,就是更新有问题,而导致玩家看到抖动。然后会常用的一个方案就是说,使用这个运动补间,这就没什么好说的。这个还有一个问题,大家还会有疑问,刚才说到了,我们刚才说的,要所有客户端算出一样的结果,如果用运动补间,因为这个运动补间是基于渲染帧,这个帧率也不能保证是固定间隔去调用的,这个就牵出下面一个问题,就是说你的显示和逻辑一定要分离。你看到的这个人物的真实位置并不是逻辑位置,就是客户端已经拿到真实位置,但是并不把真实位置显示到屏幕上,如果把真实位置显示到屏幕上人物就是抖动,这个玩家体验是不好,但是底层逻辑是抖动,但是显示的物体是流畅的在移动的,我觉得这个玩家是可以接受的。
下面再来讨论一下外挂,外挂这个问题的话,通常外挂就是修改属性,修改属性的话,如果是使用帧同步的话,修改属性基本上没好说的,虽然是在客户端各自算的,但是由于你的数据并没有说去同步,如果只是简单地把本地的数据给改了,比如把攻击力从10调到一万,去秒杀你,但这没什么用,因为别的客户端攻击力还没有提高,别的客户端看到还是正常,这种时候我们可能需要一个检测,是否本地有一些修改。可能你会会把一个玩家所有的属性做一个Hash,然后去服务器上做一个校验,如果你服务器没有运行这个逻辑,不知道这个值算的对不对,可能需要把别的客户端也发出来做一个仲裁,如果这个人算出来跟其他几个人算出来不一样,那更倾向于认为这个客户端肯定是有问题的。
修改属性除了刚才说的方法,可以通过一些加密变量的方案,让外挂找不到真实的值在哪里。通常这个效果不是特别好,如果想改的话也是能改,通常还是会去使用一个校验。
第二个是透视外挂,以前老玩家玩的Dota比较多的,这个外挂还是比较多的,因为在Dota里面视野是非常重要的,由于所有数据都在本地,你的控制这个玩家显不显示,可能只是一个变量的问题,改了这个东西,就可以看到这个东西。但是对于一些其他的游戏可能,就相对来说没有那么重要,还是说看你们的游戏,这个重不重要。如果重要的话,我们会怎么办?可能需要,一些关键数据。虽然帧同步只同步指令,但是应对外挂问题上,关键的数据还是选择去同步。比如你的透视只是一个变量的问题,可能就是一个字节的问题,对你网络的带宽就传输的东西影响是非常小的,但是就可以完全避免这个问题。
还有自动操作外挂,对我们这种类DOTA的MOBA游戏影响不大,能模拟一个玩家的操作表现很困难。但是对《守望先锋》,这种操作外挂就影响非常大,比熟练玩家瞄的准多了,这个没有特别好的方法,如果所做的是一种类DOTA的MOBA游戏,就不需要去关注这种外挂,一因为也不太可能搞出这种外挂,如果做FPS这种游戏还需要关注一下,目前业界没有特别好的解决办法,还是会靠一些发动一些玩家去检测来,用真人来检查,来解决这个问题。
最后还有皮肤美化的外挂,如果很严重可能需要处理一下,去做一些检查。如果不是特别严重,也不需要做一些额外的工作。以上就是我的所有的内容了,希望对大家有所帮助,谢谢大家。