大白话说懂“帧同步”,以王者荣耀为例

“网络很卡,英雄动不了。”

“我按技能了啊!可ping值这么高,我有什么办法!”

我们在自己或是在看主播玩游戏的时候,经常会听到这样的抱怨---网络太差,影响游戏体验。

不知道大家发现没有,这种抱怨一般都是玩家在玩RTS/MOBA(王者荣耀、英雄联盟、DOTA、魔兽争霸、星际争霸)游戏时发出的,而在玩其他同样也需要联网的游戏,比如卡牌(炉石传说)、MMORPG(魔兽世界)时,大家很少抱怨网络卡。这是什么原因呢?

首先,我们先来看看一下什么叫“卡”。中文“卡”,从字面意思了解,差不多是“停住/顿住/不动”的意思,具体来说,是画面或者人物不动了。而网络“卡”的英文,是lag,意思则是“延迟”。综合两者,我们基本可以认为,卡 = 停住 + 延迟。

其实所有网络游戏,都会“卡”,因为:

1.网络归根结底是通过“电介质”连接的,目前最快速度的传输介质是光纤(传播速度约300000000m/s),假设中国和美国玩家联网玩游戏(物理距离7000000m),理论上至少也会有0.023秒的延迟;

2.电脑和电脑、手机和手机之间不是直接通过光纤连接的,而要通过路由器、基站...等连在一起,中间任何一个环节出了问题,都会导致“卡”;

3.网络带宽是有限的,你的王者荣耀要跟其他人的英雄联盟争抢带宽,争不过的时候就会“卡”。

4.如何规避上面的问题,尽量做到“不卡”?最好的方法是在一个小空间里组件局域网游戏,比如网吧,比如电竞现场,这也是为什么比赛为了做到公平几乎都要在线下举办的原因。

而像卡牌游戏和MMORPG,其实也会卡,只不过玩家感受不明显而已。一个具体的例子,在玩魔兽世界时,有时会看到人物“瞬移”,会看到技能明明刚释放就打到了人身上,就是“卡”导致的。

既然所有联网游戏理论上都会“卡”,那么游戏开发人员要做的是,尽量让人感觉不到“卡”。举一个实际的例子,现在英雄在释放技能的时候都会有“前摇”和“后摇”,其实这个设定最初是游戏开发人员为了让玩家在视觉上感觉不那么卡所做的技巧。假如“前摇”+“后摇”0.2秒,那么这个技能在0.05秒时放出,或是0.15秒时放出,虽然两者有0.1秒的延迟,但是对于玩家来说视觉上都能接受。

所有的技巧,都是为了能够更好的“同步”玩家操作。什么叫“同步”呢?同步,即在一场战斗中,开发人员采用一定的技术和技巧,让每个玩家感觉战斗流畅。


 模式一:状态同步

还是以王者荣耀为例,如果王者荣耀采用的是“状态同步”(实际并非如此),那么客户端(玩家手机)和服务器(游戏公司)的数据是这样交互的:

若玩家1的英雄在地点A,点地点B,此时英雄并不会立即走到地点B,而是会走这样的流程:

1.客户端发送一条消息到服务器,包含英雄速度、行走方向;

2.服务器收到消息,不断计算英雄的位置(例如每0.01秒计算一次结果,取决于服务器性能和物理引擎),然后实时广播给每个客户端(发送给玩家1、玩家2....玩家10);

3.客户端收到服务器发来的英雄位置信息,这才刷新英雄的位置。

可以看到,状态同步的核心原则是服务器作为计算大脑,战斗的计算规则布局在服务器上,每个客户端都只是“告诉”服务器要做什么,由服务器决定怎么做,再“回告”客户端,客户端才能做表现。

而如果王者荣耀采用的是状态同步,会有三个问题难以解决。

一是流量问题。想想看,玩家只是点了一下地板,为了保证英雄每时每刻都在正确的位置上,在英雄停下来之前,客户端会收到大量的位置信息。如果这个英雄可以发射“霰弹”,假设子弹有50颗,服务器就要每时每刻计算并同步50个子弹的位置,服务器压力和流量可想而知。

二是卡顿问题。因为网络延迟总是存在,如果采用状态同步,英雄会出现只要一卡就静止不同的情况。比如玩家1的网络不好时,他会看到所有的英雄和小兵都像幻灯片一样卡着移动(dota1经常出现),显得十分“不流畅”。

三是操作合并的问题。例如,玩家A在0.1秒时用了“闪烁”到了玩家B身边,在0.3秒时用了技能把玩家B杀死,在0.5秒时又闪烁回原地。如果网络良好的情况下,玩家B看到的是,玩家A先闪到自己身边,再杀死自己,再回到原地;而如果玩家B的突然网络不好,延迟大于0.5秒,就会看到玩家A在原地杀死了自己,这是因为延迟导致客户端在同一时刻收到2个位置,客户端并不知道之前的位置是在什么时候被改变的(就像一个菜谱如果只有烹饪顺序没有火候时间,你无法还原出这道菜)。这也是在一些游戏上会看到英雄小兵莫名死掉,或者瞬移(据说腾讯的《全名超神》因为采用状态同步,存在此问题)。

那为什么魔兽世界这样的MMORPG游戏要用帧同步呢?

一是因为地图和人数。MMORPG的地图一般非常大,一个地图上的人数可能非常多,需要能计算一张地图上所有人的行为。而对于客户端来说,既不能要求每个客户端都能计算所有的行为,也不会把一张地图上的行为同步给客户端。一般的做法是AOI(Area of Interest)管理和同步,把大地图分成很多个小区域(如ABCDE区域),服务器只同步小区域内的玩家行为(玩家在A区域,服务器只告诉他A区域中的人和NPC的行为)。

二是状态同步难以作弊。因为所有的数据都在服务器上,如果服务器不同步数据,所有客户端并不知道其他玩家的位置和状态。而由于魔兽争霸、王者荣耀用的不是状态同步,所有玩家的位置和状态在客户端都有保存,所以外挂只需要把黑色迷雾隐藏,便可以全图透视。

 

 

总结:状态同步,战斗计算写在服务器上,服务器告诉客户端如何表现,怎么移动、是否回血/死亡/放技能都由服务器决定。缺点:耗费流量、服务器计算压力大、网络差时会出现“莫名移动及死亡”。优点:难以作弊,超大地图依赖服务器计算必须使用状态同步。


模式二:帧同步

我们看看帧同步是如何解决状态同步的三大难题的。

一流量问题。状态同步所有的计算都放在客户端,服务器只是转发操作。例如玩家A点了移动,服务器并不需要每时每刻同步英雄的位置,只需要告诉每个客户端,玩家A移动的速度和范围,由客户端自行计算移动轨迹;

二卡顿问题。帧同步在网络抖动时也会卡顿,不过可以用指令buffertime warp来“掩盖”,让玩家感觉流畅;

三操作合并的问题。帧同步同步的是指令而不是状态,并给每个指令加上了时序(帧序号),不管延迟多大,指令都是有顺序的。

关于第三点,举一个生活中的例子便于理解。

状态同步就像天气预报,帮你算好天气的变化,每小时告诉你是出太阳还是下雨,比如12时太阳,1时也是太阳,可是如果12:20开始下雨,12:40停雨了,你就没办法了;

帧同步像气象站,直接告诉你12:10有大风、12:15有暖湿气流,你自己计算整个天气的变化。

帧同步的核心原则是客户端是计算大脑,服务器只作为转发点,战斗的计算规则布局在每个客户端上。

 

帧同步实现方案也被称为“锁步”,即:
1.服务端定好一个时间间隔(例如0.1秒,每秒10帧);

2.每个客户端收集操作指令,按照这个间隔上传指令,如第一帧,客户端在第0.05秒时上传操作;

3.服务器收集所有客户端的第一帧指令后,在第0.1秒时广播给所有客户端;

4.所有客户端收到指令集合(第一帧),放入逻辑引擎中(如box2d引擎),根据引擎计算结果展现效果;

5.客户端把第二帧(0.10秒-0.15秒,或0.06秒-0.15秒)的操作上传服务器,以此类推。

关于第4点,如果是收集的是0.10秒-0.15秒的操作,则在0.06秒-0.10秒这段时间的操作都必须隐匿,个人感觉可以采用0.06秒-0.15秒。

以上是在网络状况良好的情况下,即网络抖动不明显。如果网络抖动太大,比如玩家A的延突然从0.01秒变成0.5秒,由于服务器必须要收集所有客户端的操作,则其他所有玩家都要等待0.5秒后才能收到指令。客户端的表现为:本来大家一点都不卡,有一个玩家延迟了0.5秒,所有玩家的画面都会卡0.5秒,接着所有玩家的操作都变为延迟0.5秒;只要有一个玩家掉线了,所有玩家都会一直卡住,直到这个玩家重新连上线(魔兽争霸3便是这样的)。

但是不管怎么卡,该有的操作还是会有,还是以“玩家A在0.1秒时用了“闪烁”到了玩家B身边,在0.3秒时用了技能把玩家B杀死,在0.5秒时又闪烁回原地。”这个为例,当玩家B的延迟变为0.5秒时,在帧同步下,客户端表现为:所有玩家都看到“玩家A在0.1秒时用了“闪烁”到了玩家B身边,在0.8(0.3+0.5)秒时用了技能把玩家B杀死,在1.0(0.5+0.5)秒时又闪烁回原地。”

 

 

 

一人卡而卡全部人的方式显然不符合人性,玩家希望看到的是,你卡就卡你自己好了,不要连累其他人。于是,就有了“乐观帧锁定”的概念。

具体流程是这样的:

1.服务端还是定好一个时间间隔(例如0.1秒,每秒10帧);

2.每个客户端收集操作指令,还是按照这个间隔上传指令,如第一帧,客户端在第0.05秒时上传操作;

3.服务器不再等收集所有客户端指令,只要到了该帧结束的时候(第一帧为第0.1秒),直接广播指令。

这样就会出现一个情况,如果客户端A延迟很大,他的第一帧指令会在第1+N帧时被服务器接收到,这时服务器可以按照第1+N帧的指令才处理。

也就是说,玩家A在0.09秒时放大招,如果他的延迟时0.5秒,则服务器告诉大家,玩家A是在0.59秒时放大招,每个客户端再去判断这时是否能放大招(如在0.2秒时时英雄死了,则指令无效)。

 解决了其他玩家卡的问题,有没有办法把这个延迟高的玩家卡的问题也解决呢?有的。只要让他“感觉”不到卡即可。

方法一:缓存指令(buffer),让这名玩家所有的指令都延迟0.5秒执行。这样做玩家A会感觉一直延迟0.5秒,但不会卡(即不会一下又延迟一下无延迟);

方法二:客户端先行(time warp)+插值。例如玩家A延迟0.5秒,则他点下地板的同时,立即让他(比原来慢得多的速度)开始转身并开始走动,因为这时还没有收到服务器回包,所有的行动都是“预测”行动。等0.5秒后收到指令,如果跟预测的操作一直,则加速按照原路径前进;如果预测错了,比如在中间被其他英雄冻住了,由于开始走的速度很慢,离正在的位置偏移很小,玩家不太能感觉到。这里的关键是,所有的“预测”行动,都只是客户端表现而已,并不能在逻辑上触发机制。比如玩家A走到了泉水旁,并不能真正回血,只有等收到服务器回包,“预测”正确后才能回血。因此把逻辑层表现层分离很关键。


 

参考资料:

http://youxiputao.com/articles/11842 

https://zhuanlan.zhihu.com/p/38468615

posted @ 2021-12-30 09:22  银龙背上的骑士  阅读(2654)  评论(1编辑  收藏  举报