【游戏原理解析】帧同步核心技术解析

帧同步核心技术解析

第一部分介绍帧同步与状态同步的区别

网络同步

  • 所谓的网络同步,即是只在两台或者多台计算机上模拟出相同的计算结果,由此延申出很多的同步模式,这里重点介绍帧同步和状态同步

状态同步

  • 状态同步的计算中心是服务器,客户端把输入等操作发送到服务器,由服务器根据操作计算,然后把由服务器计算的权威数据反馈各个客户端,客户端只负责表现
  • 服务器会把每次改变的结果,如血量,位置,等级等发送到所有客户端,由于需要同步的信息比较多,流量消耗也比较大
  • 状态同步短线重连只需要重新获取最新的数据即可恢复。
  • 这里只列举这一种方式,还有其他的状态同步方式

帧同步

  • 帧同步不同于状态同步,帧同步计算中心是客户端,服务器只负责转发操作,不负责计算,客户端根据服务器发送的权威操作信息进行驱动
  • 帧同步需要保证每一帧操作都同步,不能有误差,否则会向蝴蝶效应一样偏差越来越大
  • 后续介绍实现的中重点和难点,以及解决思路

其他同步

  • 状态同步和帧同步之间没有明确的界限,有的同步模式可能是混合两种方式
  • 还有其他的同步模式,但其目的都是为了适应项目的需求。

第二部分帧同步难点

网络方面传输难点

1.需要高频率同步的通讯

  • 由于帧同步需要快速的响应,则需要高频率的网络通讯,由于Tcp冗余的确认机制,虽然能保证消息不会丢失,但也消耗的很多时间
  • 基于此Upd更适合帧同步的模式,但Udp的缺点也很明显,就是有着不低的丢包率和每条消息到达时间不固定,对于帧同步每一帧不能丢失而且必须按顺序读取来说,这是个致命的问题

本地同步计算难点

2.不同平台的浮点数精度计算不同导致碰撞结果不同

  • 由于不同平台对于float精度计算不同,精度截取总是不相同的,例如苹果和安卓,linux和windows等等
  • 而Unity使用的碰撞使用浮点数计算的,所以帧同步不能套用Unity的碰撞器,得自己实现一个确定性的物理碰撞

3.确定性物理碰撞

  • 上面提到,Unity的碰撞是基于浮点数实现的,所以不能用,则需要开发一套自己的物理碰撞检测
  • 这个碰撞检测基于定点数实现确定性碰撞,后面会介绍
  • 这里看样子很难,但是我们并非要实现像Unity里那样复杂的碰撞,只需要很简单的碰撞关系就行了,后面会介绍实现方法

4.表现层卡顿僵硬

  • 由于帧同步是帧驱动,只有接受消息才会跑,(根据经验)每秒同步15帧是比较好的,这就导致了,每秒中数据会同步15次
  • 当我们看玩游戏时,画面每秒动15次会像是在看ppt一样,而且由于每帧数据变动,中间状态过度会很僵硬,会给人很僵硬的感觉
  • 这就需要把逻辑层和表现层分离,并在表现层做预测,后续介绍

5.同步操作如何驱动游戏逻辑

  • 我们只知道同步了操作,但是操作如何驱动复杂的游戏逻辑呢
  • 以及同步什么样的操作比较合适,这都需要一一解决,后面会介绍

第三部分帧同步原理及实现

可靠快速的网络通讯

  • 上面提到的因为帧同步的特点高频率网络通讯,不能使用tcp,只能使用Udp,但Udp存在很多的缺点,接下来就介绍如何解决这个缺点
  • 因为udp会丢包,那么要让udp不丢包就需要封装一下udp实现像tcp那样实现确认重传机制
  • 建议使用KCP通讯,KCP就是封装过的Udp,仿照tcp实现确认重传机制,并且自带消息排序。
  • KumoKyaku/KCP: KCP C#版。线程安全,运行时无alloc,对gc无压力。 (github.com)
  • 随便找的github链接,也可以在VS中搜索KCP下载,也可以学习KCP原理自己实现封装udp的通讯框架

物理模拟方面问题

1.浮点数精度问题

  • 由于平台计算浮点数精度标准不一样,则需要自己实现一套定点数运算
  • 给一个思路,把给定的int 类型和float类型,转换成long类型,无论时float还是int都只截取固定位数,并左移10位存储到long类型中
  • 用移位来提高位数,避免浮点数运算(核心),也就是避免浮点数参与运算,把所有的浮点数都变成定点数
  • 除此之外,因为位数提高,还需要实现一系列的操作符重载,因为位数提高,重载加,减,乘,除,取反等操作符,乘除时需要考虑倍数放大
  • 并且基于定点数实现 定点数向量数据结构,实现一系列向量运算的API
  • 基于此定点数数学库就完成了

2.确定性碰撞检测

  • 定点数数学库开发完毕后,就可以用这个定点库计算碰撞了

  • 这里不会提及AABB,四叉树,八叉树那种碰撞检测算法,这里也不考虑不规则碰撞,只考虑两种类型的碰撞,圆形和长方形

  • 场景中需要碰撞的实体都可以用一个或多个圆形或正方形概括,然后实现圆形和圆形碰撞检测,圆形和正方形的碰撞检测,(这里不先不考虑正方形和正方形)因为我们操作的角色时用圆形包围,正方形主动去检测情况很少,先简单介绍几种,明白原理后可自行扩展更多类型的碰撞

  • 1.每个物体都用一种类型的碰撞包围(圆形或者长方形),这里的类型是一个自定义的类(圆形就记录圆心,半径,正方形记录三个轴向,x,y,z),这些记录的类型都是定点数类型的

  • 2.初始化碰撞环境,把场景中碰撞器放到一个List链表中,用帧去驱动来检测是否达到碰撞条件,然后位置进行修正。(帧驱动可以看成逻辑层的Update)

  • 3.碰撞检测以及方向修正,例如两个圆形发生碰撞,需要检测两个圆的距离是否小于半径之和,如果小于则发生碰撞,那么碰撞之后呢,需要检测两个圆相交多少,然后给一个修正值给正在检测的圆,让它修正位置
    在这里插入图片描述

  • 4.除了修正位置以外,还要修正移动方向,毕竟不能碰到就停,把垂直的速度的分量置0,平行方向可以继续
    在这里插入图片描述

  • 5.圆形和正方形,可以参考这个原理可以自我思考如何实现。

  • 正方形需要考虑圆和正方形相交问题,有些复杂

  • 还需要考虑多个碰撞器碰撞的关系,就需要记录所有的碰撞到的碰撞器

  • 后续有需要再专门展开讲,这里大致介绍原理

3.帧驱动

  • 所谓的帧驱动正是帧同步的核心,这一点保证了每一帧结果的都同步
  • 什么是帧驱动,帧驱动就是每次运行都有服务器发送的操作来运行,例如有一个Recive方法来接受网络消息,这个Recive就可以看成逻辑层的Update,有个更好的名字Tick,这个Revcive就是帧驱动,其他由这个函数驱动的方法都可以放到这里面
  • 由于帧驱动,只有接受消息逻辑层才跑,不接受就锁帧(不跑),但每一帧到达的时间是不固定的,这就需要我们约定一个Tick时间,也就是约定每一个Tick间隔
  • 举个例子,有由Tick驱动位移,约定Tick间隔是15ms,那么在知道速度(大小和方向)的情况下,当前Tick中速度乘以Tick间隔(15ms)就是这一帧要位移的距离,这样无论接受快慢,每次Tick都把它看成间隔15ms(可以看成Mono的Time.deltaTime)即可,只用保证固定顺序到达消息

4.逻辑层表现层

  • 上面提到过帧同步是帧驱动的,每秒钟同步15次,这个速度对于肉眼的刷新率是很慢的,也会让角色很僵硬
  • 基于此我们提出逻辑层与表现层分离的概念
  • 1.逻辑层,就是每个实体的权威信息,如位置,旋转,血量,等等的记录,只是一份数据,不关联场景中任何一个物体
  • 2.表现层,表现层需要获取对应的逻辑层数据生成对应实体,可以是子弹,特效,模型等等
  • 3.逻辑层驱动:逻辑层数据所有都用定点数进行计算,包括位移,朝向,伤害计算,等等,并且逻辑层用帧驱动
  • 4.表现层驱动:表现层不能干预逻辑层,或者说表现层只是对逻辑层单方面读操作,不能有写操作,表现层可以使用浮点数,可以由Mono的Update尽心驱动。

6.预测及修正

  • 分离了逻辑层与表现层之后,就可以通过对表现层操作,来实现看起来很平滑流畅的感觉
  • 1.预测:由于每个Tick间隔15ms,也就是逻辑层数据刷新间隔是15ms,表现层读取逻辑层数据的间隔也是15ms,在这个间隔内可以做的事情,这并不影响逻辑层的数据,只需要后续根据逻辑层数据修正即可
  • 在预测中,可以通过记录的方向,继续在Update中运动,然后等到下一个Tick刷新数据时强制修正表现层位置
  • 预测可以采用Lerp函数进行平滑过度,如方向旋转,位置移动等

第四部分帧同步应用实现

同步操作数据

  • 都知道帧同步是同步操作,但是同步什么样的操作
  • 之前我一直以为是同步按键,比如按下WSAD啥啥的,但其实并不是这样的操作
  • 而是关键信息,例如移动的操作信息是,方向,而技能操作信息,技能ID(包括位置,或者方向),由这些关键的信息,就模拟一个角色移动和技能释放。
  • 可以随时随地向服务器的发送操作数据而不是每帧只能发一条,可以频繁发送很多条,但在本地要有限制,避免发送无用冗余的信息,例如频繁的小幅度转向,正在CD中的技能,这些都要加以限制和优化。
  • 服务器在每次Tick,会把两个TIck间隔内收集的操作信息下发到每一个客户端,然后重新收集放到一个队列中,直到下一个Tick在全部广播。

断线重连

  • 帧同步的短线重连不同于状态同步,并没有记录当前状态的信息,但是可以在服务器记录操作
  • 可以在服务器下发之前所有的操作信息,由于帧驱动,只要在本地加快驱动速度就可以追上当前游戏进度,毕竟约定的Tick间隔是固定的,而且是定点数计算,每一次的结果也是固定的

防作弊

  • 这个地方了解的不多,可以模仿状态同步的方式,不定时上传当前角色的属性,或者其他信息,然后比较各个客户端是否有不同,不同则视为作弊。

最后

  • 这是我学习帧同步的总结和见解,如果对你有帮助,请点个赞吧。
  • 文档中可能会有错别字,如有不对的地方,欢迎指正。

猜你喜欢

转载自blog.csdn.net/leave_snow/article/details/126569197
今日推荐