帧同步在小游戏实践中的那些坑(二)体验篇

时间:2024-03-20 21:25:03

上文我们讲到了帧同步的数据一致性,本篇主要介绍如何进行体验优化。

二,体验优化

网络是不可控的,可能会出现延迟,丢包,重发,乱序等各种情况。网络帧是低频的,比如15帧;渲染帧是高频的,比如60帧。这里面存在着不对等。如果不能妥善的处理好这里的不对等,就会产生卡顿感。

那么如何应对网络造成的延迟以及卡顿问题?有下面的几点措施:

1.影子跟随算法

影子跟随由Dead Reckoning(航位推算)算法发展而来。核心思路是把游戏中的可移动物体分成渲染实体和逻辑影子两部分。数据帧驱动逻辑影子不断改变,而渲染实体则不断地追随着逻辑影子移动。影子是离散跳变的,而实体移动是相对连续的。

实现影子跟随的首要方式是从开发架构上着手,采用逻辑与渲染分离的方式。这里推荐:网络层-逻辑层-渲染层三层结构。网络层向逻辑层发送数据帧,驱动逻辑层数据更改;逻辑层向网络层递交操作指令,进而同步到远端。逻辑层通过事件机制触发渲染层更新;渲染层不能修改逻辑层的数据,这个过程是单向的。这样就能保证各客户端在逻辑层的数据保持绝对一致,而在渲染层有了平滑显示的空间。
帧同步在小游戏实践中的那些坑(二)体验篇

由于网络不稳定,为了让渲染实体表现的很平滑,有两种做法:

  • 使用帧缓冲区。即将数据帧缓存几帧再给到游戏,这样能使得收帧更稳定,但带来的弊端是玩家操作会存在较大的延迟。
  • 不使用帧缓冲区。每来一个数据帧就立刻执行,这样操作的延迟感最低,但也就对平滑处理提出了更高的要求。

2.平滑处理

先看一段平滑处理的demo。有两架飞机,左边飞机本地直接运行(作为参照物)、右边飞机走网络回包再运行(作为考查对象),左上角是按顺序加码的平滑手段,右下角是右边飞机的波动程度曲线,曲线越震荡、说明抖动越厉害。

帧同步在小游戏实践中的那些坑(二)体验篇
帧同步在小游戏实践中的那些坑(二)体验篇

                                                                                (扫码体验传送门)

  • 如果不做平滑处理,那么右边飞机的移动,卡顿特别明显,就跟玩游戏只有15帧一样。波动图也震荡的很厉害。
  • 增加线性插值。即将一个逻辑帧要移动的距离分解成几个渲染帧来完成。右边飞机的卡顿感减少了,但是依然存在持续性的机身颤动。
  • 增加抖动优化。优化线性插值的实现方式、将速度变化控制在一个平缓的范围内。这样颤动感就很少了。但是这样,如果网络收帧延迟,还是容易出现一下快、一下慢的情况。
  • 增加预测。物体不是朝着数据帧接收的位置平滑,而是朝着数据帧向前预测几帧后的位置平滑。这样就能很好地抵抗网络的抖动,表现上就很平滑了。但是观察到还是有一个弊端:当飞机突然停下的时候,容易出现往前走再回拉的情况。
  • 增加加速度机制。即速度不是从最大值到0突变的,而是引入加速度a,通过 v = a*t计算速度的变化。从运动到停止,有个缓慢减速的过程。这样就既很平滑又很少出现回拉了。
    帧同步在小游戏实践中的那些坑(二)体验篇

3.减少网络延迟网络

延迟有下行数据帧不稳定,和上行操作指令RTT慢的问题。有下面措施来减缓延迟。

(1)使用帧冗余

帧同步在小游戏实践中的那些坑(二)体验篇
假设开了三倍冗余。第100、101帧没有收到,但收到了第102帧,也会将100、101帧带下来,也就不至于卡顿。这样就能在一定程度上抵抗丢帧带来的影响。

但如果连续3帧都没有收到,就会产生空洞,例如上图的103,就需要向server请求重发。通常来说,冗余倍数开的越高、抵抗网络抖动的能力越强、网络流量消耗也就越大。但也受到玩家人数与数据帧数据量影响。

(2)空帧优化

经过统计发现,很多游戏,绝大多数帧都是空的;如果数据帧下来的时候,带上一个参数,标明此前的多少帧都是空帧,这样哪怕中间丢了几个空帧也没关系,可以通过SDK构造出来。

(3)MTU优化

我们在实践中遇到了上行操作延迟较大的问题。而且人数越多、输入越频繁、延迟就越明显。

究其原因,是网络中存在MTU(Maximum Transmission Unit)的设定,一旦数据报过长,则有可能产生报文分割、IP分片的情况。经过这样分片、再重组的过程,延迟就大了。因此精简输入数据就很有必要了。

举个栗子,一个json对象{“uid”: 3894702652001, “dir”: 1, “x”: 30.541236710020079, “y”: 50.671002923458520, inputType: 3, param: 2},如果直接用JSON.stringify就占99字节了。如果改为“,”连接符,uid改成索引,x、y坐标进行截取,那么该数据可以用“ 0,1,30,50,3,2”,共计13个字节来表示。此外,还可以使用位图法进一步精简,比如用3bit表示8个方向,3bit表示8个技能。

4.其他tips

除了以上机制以外,有些游戏机制,对于帧同步的体验提升也是很有必要的。比如:

  • 子弹分瞬发和有弹道两种,瞬发就直接判定;有弹道的子弹运行时间不能太短,给表现层预留显示余地。
  • 将表现分成可预测和不可预测两种。可预测的本地先展示,不可预测的等待数据帧。比如某游戏两个人抢道具,碰到道具可以立马播放动画和音效;但具体是谁吃到了道具,则由收到的数据帧来判定。
  • 上行操作的采样频率和下行数据帧的下发频率是可以调节的。可根据游戏类型(比如对操作即时性要求高不高、数据量大不大),去选择适合自己的频率。

链接:

帧同步在小游戏实践中的那些坑(一)数据篇:
https://blog.csdn.net/weixin_42109916/article/details/109307299
帧同步在小游戏实践中的那些坑(二)体验篇:
https://blog.csdn.net/weixin_42109916/article/details/109310916
帧同步在小游戏实践中的那些坑(三)性能篇:
https://blog.csdn.net/weixin_42109916/article/details/109311921