专栏/帧同步:原理与实现

帧同步:原理与实现

2022年09月02日 06:55--浏览 · --喜欢 · --评论
粉丝:2969文章:53

本文的主题是网络游戏帧同步技术详解,会用深入浅出的方式来为大家介绍帧同步的原理,以及如何实现

版权声明

  • 本文为“优梦创客”原创文章,您可以自由转载,但必须加入完整的版权声明

  • 更多学习资源请加QQ:1517069595或WX:alice17173获取(企业级性能优化/热更新/Shader特效/服务器/商业项目实战/每周直播/一对一指导)

  • 点赞、关注、分享可免费获得配套学习资源

主题

  • 一,服务器技术的概览与服务器技术的效果展示

  • 二,状态同步VS帧同步

  • 三,帧同步的原理与实现

  • 四,《皇室战争》中的帧同步

网络游戏服务器技术栈

帧同步效果演示

  • 这是一个双人射击游戏的演示Demo,游戏场景里有两位玩家,第一位玩家是瑞典的,另外一位是巴西玩家,这两位玩家的网络延迟是不一样的,左边这位同学的网络延迟相对低一点,大概是50帧的网络延迟,右边的巴西玩家延迟比较大,有200多帧

  • 虽然玩家之间延迟不同,但这两位玩家的画面是完全相同的,这就是帧同步要实现的第一个最基本的技术点,也是帧同步的难点

  • 另外还有一个技术要点,在第一代的帧同步技术里,就是延迟高的玩家跟延迟低的玩家进行游戏时,延迟高的玩家会把延迟低的玩家卡住,而在第二代的帧同步技术里延迟高的玩家不会卡住延迟低的玩家,只会卡住他自己

什么游戏适合帧同步?

  • 帧同步的使用主要发生在三种情景下

  • 网络波动比较厉害的环境,比如坐在地铁上时手机信号覆盖不好,这时传出大量数据必然会造成数据的延迟,这样就会影响到游戏体验

  • 第二种情况是因为状态同步固有的这种技术特性是以一定的频率来进行数据同步的,所以不管怎样优化它都是会产生延迟的,这就需要使用帧同步

  • 第三种情况就是当你需要控制大量游戏单位时可以采用帧同步,因为如果采用状态同步控制大量游戏单位,为了对每一个游戏单位进行位置朝向,以及其他信息的同步,会产生非常大的数据同步量

  • 状态同步与帧同步从技术上来说有什么区别?

    • 状态同步

      • 状态同步是大型网游普遍会采用的一种同步技术,它特点是客户端会向服务器发送一些指令,比如在角色扮演类中从游戏的NPC那里买一些物品时,客户端会向服务器发送一个指令,指令中包括玩家ID、购买商品的ID号、商贩NPC的ID号,将这些信息传到服务器后,服务器会计算玩家的金钱是否足够,当前所处位置是否在NPC身边、NPC是否有这个物品等等

      • 服务器会做很多校验,校验完以后才能确定玩家是否能购买物品,如果不能买服务器就会给客户端下发一个交易失败的指令,可以购买服务器则会下发玩家在购买完商品以后,背包里面增加了哪些东西,或者减少了哪些东西的消息

      • 状态同步的特点就是状态同步发送的是一些操作,接收的是一些状态,这些状态数据可能会很大,所以在同步时所要同步的数据量也会比较大,状态同步的的好处就是安全性比较好,反作弊能力比较强

    • 帧同步

      • 帧同步的特点可以总结为六个字:发操作、收操作,也就是说客户端发送的是操作,接收的也是操作,服务器在接收到客户端发送的操作后,不会做任何运算,而是以广播的形式将这个操作发送给同一房间里的所有玩家

      • 其他玩家在接收到的操作后就会产生相应的画面表示,比如玩家1发射了一颗子弹,那么在玩家2的客户端上就应该实例化一颗子弹出来,然后让颗子弹以一定的速度朝某一个方向发射,这就是帧同步的一个特点

      • 帧同步将所有运算都放在客户端去做其实有一个非常大的好处,就是它的运算非常简洁因为就像在开发一个客户端游戏一样,服务器不需要做什么太多的工作,如果你是一个优秀的客户端开发者就可以比较轻松的实现一个相对比较完善的帧同部框架

同样是控制100个游戏单位移动,帧同步与状态同步有什么区别?

  • 使用状态同步来控制100个游戏单位移动

    • 如果采用状态同步控制100个游戏单位进行移动就要发送这100个单位的ID号到服务器中,光是发送ID号就要发送掉400个字节

    • 还有这100个单位的目标点也是要占8个字节的,因为要传一个X坐标和一个Z坐标,X坐标四个字节,Z坐标四个字节加起来就是八个字节了,发过去就是408个字节,如果游戏中有多个玩家,就要发多个408个字节过去,状态同步的耗费就在这里

    • 状态同步更耗费的一点在于服务器往客户端同步数据时的开销,因为服务器往客户端同步数据时要定时同步,就算我们服务器的同步频率比较低,那一秒至少也要同步一次,这时假设客户端一秒同步一次,我有100个单位,那么每次同步我都要同步100个游戏单位的ID号也就是400个字节,刚才我们算过了

    • 我还要同步这100个游戏单位走到了什么位置,因为中间会存在一些路径障碍,所以这些玩家的坐标位置在一秒以后都是不一样的,所以还要计算这100个游戏单位一秒以后的位置,那么每一个位置是占8个字节,现在有100个单位,那么一共是多少个字节呢?

    • 一共是1200个字节,这只是一个玩家的开销就已经很恐怖了,那假设我从起点到目标点需要5秒钟,那总共需要同步多少个字节呢?就是6000个字节,这是怎么算的呢?就是100个游戏单位,每个单位的ID号4个字节,坐标位置8个字节,加起来是12个字节,100乘以12就是1200,再乘以五就是6000了

  • 使用帧同步来控制100个游戏单位移动

    • 帧同步是当玩家执行操作时客户端会向服务器发送一个帧号,帧号是什么意思呢?就是当前是在进行第几帧操作,帧同步是定频的(固定数据发送的频率)

    • 游戏单位是400个字节,目标地点是8个字节,就要发送408个字节,这和状态同步差不多,但由于帧同步是直接把操作返回给所有的客户端,让客户端自己计算,所以它的开销就很小

    • 帧同步只要传回这个操作是在第几帧发生的、发生时玩家要移动到什么位置、哪些目标要往这个地点移动,所以它接收的数据是408个字节,发送的数据也是408个字节

  • 可以看出帧同步与状态同步的差异非常大,因为状态同步所同步的是状态数据,而帧同步所同步的只是操作数据,返回的数据量不大,而且帧同步所有的运算压力都在客户端上面,不在服务器上,服务器只要带宽足够大,能够定时中转数据就可以了

帧同步和状态同步的比较

  • 服务器代码编写复杂程度

    • 帧同步

      • 帧同步的服务器代码编写相对来说比较简单,因为帧同步只是转发数据

    • 状态同步

      • 因为状态同步的大量逻辑运算都是在服务器上进行的,所以如果你不是一个熟练的服务器开发者,你所要面对的第一个问题就是无法在服务器中调用Unity里的那些方便的API,比如计算坐标、计算朝向等等,这些都需要服务器程序员自己实现

  • 客户端代码的编写复杂程度

    • 帧同步

      • 帧同步的客户端代码编写复杂程度比状态同步要复杂的多,帧同步必须要保证每个客户端的计算结果是完全一致的,比如游戏中常用的一些随机数,帧同步必须要保证所有玩家生成的随机数序列是相同的,因为这个原因,在帧同步中字典是不能用的,因为字典是一个无序列表

      • 因为数学的物理运算都会牵涉到一些浮点数,而浮点数是具有不稳定性、不精确性的,所以在帧同步中不能用传统的Unity里提供的那些数学和物理库了,必须自己实现一套

    • 状态同步

      • 状态同步的逻辑都是在服务器上进行运算,所以就算服务器上有一些浮点数误差也没有关系,只要发给所有客户端的结果是一样的就行了

  • 中途加入游戏

    • 帧同步

      • 帧同步要中途加入游戏需要追帧,所以一些采用帧同步的网络游戏大多都要一个很明显的特点,就是中途加入一局游戏时都需要等一会儿

    • 状态同步

      • 因为状态同步是直接下发当前的角色状态,所以它不需要等待

  • 服务器带宽要求

    • 帧同步对服务器带宽要求比较小,因为帧同步只需要传递操作

    • 状态同步因为同步数据量比较大,所以对服务器带宽要求比较高

  • 客户端体验

    • 帧同步的逻辑运算是在本地进行的,所以它的反应更加灵敏,打击感更强

    • 状态同步的延迟相对要高一些

  • 反作弊

    • 帧同步在反作弊上是有点问题的,帧同部需要所有的客户端进行协作来判定玩家作弊,所以比较容易出现一些很变态的外挂

    • 状态同步就算有外挂,也大多是辅助性的外挂,危害性要稍微弱一些

  • 离线战斗

    • 所谓的离线战斗就是不连服务器,也能进行战斗,帧同步很容易就能实现,因为帧同步本身的框架就是基于每一帧数据的,帧同步在进行离线战斗时会在客户端做一个虚拟的服务器,这个服务器会把客户端发送的数据发回给自己

    • 状态同步要做离线战斗就不太容易

  • 下面是一些采用不同方案的游戏案例

  • 可以看到有一些对打击版要求比较高的游戏采用的是状态同步,比如英雄联盟和DOTA2,这两种游戏都是对实时性要求比较高的,为什么采用照样同步呢?因为英雄联盟和DOTA2都是PC端的游戏,它的网络是相对比较稳定的,所以可以采用状态同步

  • 而王者荣耀这样的手机端游戏,因为存在网络波动问题,所以更适合采用帧同步

帧同步的基本原理

  • 假设在帧同步游戏中,两个玩家匹配进入到一个房间里,客户端与服务器约定的数据同步频率为每五帧同步一次,这时服务器需要发送初始帧给客户端A和客户端B,告诉它们游戏可以开始了,初始帧在上图中用UPDATE0表示,服务器给客户包发的都是UPDATE,后面的序号表示它是第几帧的数据

  • 初始帧的同步数据发给两个客户端后就要开始准备了,让它们都处于相同的准备状态,客户端收到服务器发送的初始帧消息还要以每五帧同步一次的频率给服务器发送消息,上图中的CTRL0就是客户端发给服务器的第0帧操作

  • 客户端A和客户端B在第5帧开始之前,已经把它们的操作发送到服务器去了,也就是说服务器在5帧延迟以内收到了A和B的操作,这时服务器就会将A和B的操作进行打包,再发回给所有的客户端,客户端A会同时收到A和B的操作,客户端B也一样,它们会根据服务器发送的数据来进行运算

  • 比如客户端A发了移动消息而客户端B没有任何的操作,客户端B也要发送这一帧没有操作的消息到服务器,服务器收到后,会把这些消息再转发给所有客户端,客户端收到服务器发出的消息后就要表现客户端A的玩家和客户端B的玩家在这些操作下的动作,如果没有操作就不动

  • 以上就是第一帧画面的同步过程,同步完以后咱们就开始同步第2帧画面,在同步第2帧画面时由于客户端B的网络比较好,它立刻就把操作发到了服务器,而客户端A的网络不太好,发生了延迟,延迟到第十帧画面以后了

  • 也就是说服务器在第10帧的位置上只收到了一个玩家的数据,这样它就不能够把数据发送给客户端,必须要等到玩家A发过来的数据后,才把这些数据发给玩家A和玩家B,这就是为什么帧同步会产生等待的原因

一套完整的帧同步游戏框架要实现什么?

可靠UDP协议

  • TCP协议为了保证数据的时序性,重传机制、应答机制、粘包机制,会有50毫秒的默认数据延迟,比如有一些只有一个字节的数据包,这时TCP协议如果一个字节,一个字节的发,那么它本身的数据包裹大小就比这个一个字节的数据包要大得多,所以TCP协议有一个粘包机制

  • 粘包机制不是一个缺点,而是一个特性,它会等待50毫秒,然后将这50毫米以内的小数据包合并成一个包发送

  • 这就是TCP协议的问题,要解决这个问题需要有可靠UDP协议,可靠UDP协议可以说是UDP协议的升级版,因为UDP协议本身不能保证时序性,不能进行数据校验和重传,也没有应答机制,不能确定数据的顺序,但是UDP的一个好处就是没有最小延迟

  • 可靠UDP就是在UDP协议上增加一些东西,来实现时序性、重传机制、以及应答机制

确定性的数学和物理运算库

  • 在C#程序里算一下0.1加0.2,得到的结果一定不是0.3,而是比0.3多一点,为什么呢?因为浮点数是不精确的,所以如果你使用了Unity的物理组件、碰撞组件,就会使游戏出现两边客户端不一致的问题

  • 因为Unity所有的物理、导航、动画、碰撞全部都是基于浮点数运算的,所以我们必须要自己实现一套能够精确运算浮点数的运算库啊,另外如果仅仅只是实现浮点数的优化并不能达到多个客户端完全同步的目的,还必须要保证随机数一致,你使用的容器在存储数据时的顺序必须一致

  • 所以稳定的玩具代码和稳定的商业项目是完全不同的,我们的课程会告诉大家如何避免这些坑点,当然,浮点数的问题有很多解决方法,比较简单的粗暴的就是容许小概率误差,更好的办法就是利用过往的开发经验来把这些问题都排除掉

  • 使用非确定物理引擎会产生的问题(如下图)

  • 上图左边是正常运行的游戏,右边是断线重连的游戏,在左边完成一局游戏后右边进行断线重连时可以发现左边的三个怪物跟右边的三个怪物的位置是不一样的,因为这里用的是浮点数学

  • 浮点数学在运算时会产生不正确性,特别是在回放速度比较快a的时候,运算的误差还会被放大

  • 如何解决?

  • 1,取整计算法

    • 这个问题可以通过取整计算法来解决,但这种方法也有一点小问题

    • 取整计算法把浮点数取整后的精确性是有误差的

  • 2,容许小概率的误差

    • 在初始开发时可以允许小概率的误差,出现问题时只要在服务器上给点补偿就行了

  • 3,逻辑表现分离

    • 可以允许表现层使用浮点数有误差,只需要保证逻辑层没有误差

断线重连演示

  • 上图是两个玩家的游戏画面,左边是玩家1,右边是玩家2

  • 可以看到玩家1在进行断线重连后通过追帧重新演算到了当前帧的游戏状态,追到当前帧后,玩家1与玩家2又可以进行同步移动了

比赛回放

  • 比赛回放的实现还是比较简单的,只要在服务器上记录一下关键帧,然后进行回放就可以了

  • 如果要在客户端回放,就要下发到客户端

反作弊

  • 服务持记录的关键帧数据还可以用于反作弊,就像在法庭上面进行辩论一样,如果罪犯嫌疑人不认罪,而陪审团里的多数人认为嫌疑人犯罪了,那么就是少数服从多数

基于分布式架构的商业帧同步框架

  • 上图是帧同步的技术方案和框架,以及实现要点,我们的帧同布是基于分布式架构的,这样的架构是一个稳定的,能够用于商业游戏的帧同步方案,作为一个商业游戏,自然不能光做一个战斗场景,必须要有登录流程、要实现玩家最基本的登录认证

  • 登录服务器

    • 而且仅仅只是开发一个玩具一样的帧同步也是不行的,如果一台服务器上有几万个玩家那肯定连登陆都登不上,所以必须要有负载均衡功能

  • 大厅服务器

    • 玩家登录成功后会进入大厅服务器,大厅里的玩家会被服务器分配到合适的房间里,这是大厅服务器需要实现的功能,其中会涉及到匹配算法

  • 房间服务器

    • 匹配成功后会进入到房间服务器,房间服务器除了要管理这个房间里的所有玩家外,还要实现反作弊、比赛回放、断线重连、游戏结束,和胜负判定功能

  • 商业游戏里还有很多东西是跟帧同部无关的,也是必须要实现的,比如协议的定义,协议就像写程序时的数据结构,如果数据结构不设置好,最后做出来的算法肯定会有问题

  • 帧同步框架里要做的业务逻辑除了实现战斗以外,还有玩家数据、游戏卡牌数据、游戏的商城、游戏的部落、游戏的社交、甚至还包括游戏的比赛,这些都需要有相应的业务逻辑支撑

写在最后

  • 更多学习资源请加QQ:1517069595或WX:alice17173获取(企业级性能优化/热更新/Shader特效/服务器/商业项目实战/每周直播/一对一指导)

  • 点赞、关注、分享可免费获得配套学习资源

  • 详细内容可参考下方完整视频


投诉或建议