1.什么是网络同步
所谓同步,就是要多个客户端表现效果是一致的,比如我们玩王者荣耀的时候,需要十个玩家的屏幕显示的英雄位置完全相同、技能释放角度、释放时间完全相同,这个就是同步。
2.如何实现网络同步,不同方案比较
2.1 状态同步
2.1.1 定义
客户端需要哪个对象的状态,就请求哪个对象的状态,或者服务的把这个物体的状态信息推过来,然后更新这个对象的显示【将其他玩家的状态行为同步的方式(请求其他玩家的状态并显示在NPC上),一般情况下AI逻辑,技能逻辑,战斗计算都由服务器运算,将运算的结果同步给客户端,客户端只需要接受服务器传过来的状态变化,然后更新自己本地的动作状态、Buff状态,位置等就可以了,但是为了给玩家好的体验,减少同步的数据量,客户端也会做很多的本地运算,减少服务器同步的频率以及数据量】
2.1.2 优点
(1)安全性非常高,外挂基本上没有什么能力从中收益。
(2)对于网络的带宽和抖动包有更强的适应能力【即便出现了200、300的输入延迟再恢复正常,玩家其实也感受不到不太舒服的地方】
(3)在开发游戏过程中,断线重连比较快【如果游戏崩溃了,客户端重启之后只需要服务器把所有重要对象的状态再同步一次过来,重新再创建出来就可以了】
(4)逻辑性能优化有优势,客户端性能优化优势也比较明显【比如优化时可以做裁剪,玩家看不到的角色可以不用创建,不用对它进行运算,节省消耗】
2.1.3 缺点
(1)开发效率低(人力消耗),相对帧同步而言要差一些,很多时候需要保证服务器与客户端的每一个角色对象的状态之间保持一致,但事实上很难做到一致【比如客户端和服务器端更新的频率,对优化的一些裁剪,网络的抖动等等,要让每一个状态在客户端同步是比较难的,而想调试这些东西,来优化它带来的漏洞、不一致的现象,花费的周期也会比较长,想要达到优化好的水平也比较难】
(2)打击感差(表现效果),它比较难做出动作类游戏打击感和精确性【比如说你要做一个射击类角色,他的子弹每秒钟要产生几十颗,基于状态同步来做是比较难的,因为系统在很短时间内,会产生很多数据,要通过创建、销毁、位置运算来同步】
(3)流量会随着游戏的复杂度逐渐增长(流量消耗)【比如角色的多少。希望在3G、4G的网络条件下也能够玩PvP,所以我们希望它对付费流量的消耗能控制在比较合理的水平,不希望打一局游戏就消耗几十兆的数据流量。(比如一个角色放了个AOE,一片角色都要进行状态更新】
2.2 帧同步
2.2.1 定义
帧同步是一种前后端数据同步的方式,一般应用于对实时性要求很高的网络游戏。
简单来说,就是相同的状态+相同的指令+ 按帧顺序执行 = 相同的结果。
状态:所有客户端确保逻辑一致,接收一样的随机种子(randomseed)和房间信息;
指令:服务器只负责收集接收每个客户端操作指令(cmd),转发指令,服务器以恒定帧率(30帧1秒)派发指令,没有指令或指令没有变化也需要派发;
执行:真正游戏逻辑由各个客户端单独计算 ,客户端需要收到服务器派发的指令才能推进逻辑,没有收到指令时不能推进逻辑(LockStep)
2.2.2 优点
(1)它的开发效率比较高(人力消耗),但如果开发思路的整体框架是验证可行的,并把它的缺点解决了,那只需要遵从这样的思路,尽量保证性能,程序该怎么写就怎么写,服务端逻辑简单,只需要负责转发指令,压力也小【比如要在状态同步下面做一个复杂的技能,有很多段位的技能,可能要开发好几天,才能有一个稍微过得去的结果,而在帧同步下面,英雄做多段位技能很可能半天就搞定了】
(2)能实现更强的打击感(表现效果),打击感强除了各种反馈、特效、音效外,还有它的准确性。利用帧同步,游戏里面看到这些挥舞的动作,就能做到在比较准确的时刻产生反馈,以及动作本身的密度也可以做到很高的频率,这在状态同步下是比较难做的。
(3)它的流量消耗是稳定的(流量消耗)。帧同步只会随着玩家数量的增多,流量才会增长,如果玩家数量固定的话,不管你的游戏有多复杂,你的角色有多少,流量消耗基本上都是稳定的。
(4)可以更方便地实现观战,录像的存储、回放,以及基于录像文件的后续处理。
2.2.3 缺点
(1)最致命的缺点是网络要求比较高,帧同步是锁帧的,如果有网络的抖动,一段时间调用次数不稳定,网络命令的延迟就会挤压,引起卡顿。
(2)反外挂能力更弱,帧同步的逻辑都在客户端里面,你可以比较容易的修改它。但为什么《王者荣耀》敢用帧同步,一方面是因为当时立项的时候开发周期很短,半年时间要做上线,要有几十个英雄,存在时间的压力,另一方面,MOBA类游戏不像数值成长类的游戏,它的玩法是基于单局的,单局的作弊修改,顶多影响这一局的胜负,不会存档,不会出现刷多少钱刷多少好的装备的问题,而且作弊之后我们也很容易监测到,并给予应有的惩罚,所以我们认为这不是致命的缺点。
(3)它的断线重回时间很长。比如碰到过闪退以后重回加载非常长的情况,甚至加载完以后游戏也快结束了,这是帧同步比较致命的问题。
(4)它的逻辑性能优化有很大的压力。大型游戏是的每一个逻辑对象都是需要在客户端进行运算的。如果你做一个主城,主城里面有上千人,上千人虽然玩家看不到它,但游戏仍然需要对他们进行有效的逻辑运算,所以帧同步无法做非常多的对象都需要更新的游戏场景。
(5)debug困难。出现不同的情况,难以查找问题所在,一般通过debug输出关键改变信息来定位问题,但问题可能在1-20个函数之内,但只在第20个函数打了debug信息,然后需要一层层去查找出现问题的所在,考虑把出问题的局录下来,然后不断重播和调试,方便找到问题。
2.2.4 适用范围
RTS即时战略,ACT多人实时格斗,MOBA类提供帧同步解决方案建议。上述类型的网络对战游戏的特点是:实时性要求极高,追求公平竞技,打击感。
2.2.5 核心原理实现的解决方案
帧同步游戏开发中,主要有三种:
1.0 帧锁步[Lockstep]
- 当客户端A存在网络延迟导致服务器第X帧收集不到A的第X帧输入指令包时,
- 服务器就需要等待所有客户端的X帧指令收集完成,才会下发X帧的所有客户端包数据以此保持同步,
- 也就意味着一人延迟,所有客户端都得等,显然这种同步概念不太适用于竞技性比较高的游戏,玩家体验会很差。
2.0 乐观帧
- 其他乐观帧是在帧锁步的基础上进行改良,为了提高游戏体验,服务器不再要求必须收集所有客户端X帧的操作指令才进行分发,
- 而是每个客户端都进行本地输入指令快照存储,存储各自的输入指令(并带有帧号)。
- 如果A客户端因为延迟从第2帧(逻辑帧)开始丢包直到第5帧传输正常,那么服务器即使收不到A客户端2-5帧的输入指令还是继续分发,
- 直到第5帧再将A客户端2-5帧的本地快照一并分发出去,同时将其他客户端2-5帧的输入指令同步到本地客户端,当然丢失3帧的数据直接在一帧同步
- 到客户端会导致卡顿瞬移,所以A客户端需要进行加速操作执行指令操作,通常会做差值计算,通过补间动画的方式让同步更平滑。
3.0 预测回滚
- 预测回滚是一种同步机制,是乐观帧的一种解决方案,上面提到的乐观帧有个最大的缺点是一旦A客户端丢包,那么其他客户端的A在丢包期间将不会有任何
- 操作指令同步,这样会影响游戏体验,所以这里引用预测机制,如果客户端在X帧没有指令数据那么服务器会给它在X帧一个预测的输入指令,当A客户端连接后,
- 服务器会对预测数据和真实丢失快照数据进行和解,回滚。
3.帧同步如何保证一致性(客户端独自计算的正确)
不同的调用顺序,时序,浮点数计算的偏差,容器的排序不确定性,coroutine内写逻辑带来的不确定性,物理浮点数,随机数值带来的不确定性等等。
最基础的:通过一个统一的逻辑tick入口,来更新整个战斗逻辑,而不是每个逻辑自己去Update。保证每次tick都从上到下,每次执行的顺序一致。
随机数值:做随机种子即可。
注意代码规范:比如在帧同步的战斗中,逻辑部分不使用Coroutine,不依赖类似Dictionary等不确定顺序的容器的循环等。
物理方面:看战斗逻辑是否需要物理,碰撞是否都是自己做的碰撞逻辑,再去处理。
定点数代替浮点数:关于定点数的实现,比较简单的方式是,在原来浮点数的基础上乘1000或10000,对应地方除以1000或10000,这种做法最为简单,再辅以三角函数查表,能解决一些问题,减少计算不一致的概率,但是,这种做法是治标不治本的方式,存在一些隐患(举个例子,例如一个int和一个float做乘法,如果原数值就要*1000,那最后算出来的数值,可能会非常大,有越界的风险)。最佳的解决办法:使用实现更加精确和严谨,并经过验证的定点数数学库。