多人游戏的网络实现:帧同步和状态同步

栏目: 后端 · 发布时间: 5年前

内容简介:最近在想开发一个多人的 3D 游戏。在多人游戏中,网络同步是一个重要且不可忽视的模块。由于我之前没有做过真正意义的游戏开发,在搞懂游戏的网络编程的路上走了一些奇奇怪怪的弯路,谨以此文记录我的理解与总结,辅助自己消化,也请各位读者斧正。在多人游戏里,网络同步要实现的目的主要有:1. 看见:看见视野内其他玩家的位置、面向、动作等

最近在想开发一个多人的 3D 游戏。在多人游戏中,网络同步是一个重要且不可忽视的模块。由于我之前没有做过真正意义的游戏开发,在搞懂游戏的网络编程的路上走了一些奇奇怪怪的弯路,谨以此文记录我的理解与总结,辅助自己消化,也请各位读者斧正。

在多人游戏里,网络同步要实现的目的主要有:

1. 看见:看见视野内其他玩家的位置、面向、动作等

2. 互动:视野内其他玩家进行互动,如攻击、辅助、治疗等

3. 实时:在网络延迟客观存在的前提下,保证各玩家看到的状态尽量一致,对玩家的操作做出尽量快的反应

基于以上目的,我们来讨论多人游戏的网络实现。目前而言,多人游戏的网络同步,主流实现可以分为两大类:帧同步和状态同步。

帧同步,一般而言是 P2P 架构。它的核心思路是:对同样的输入,每个客户端做出同样的操作,即可模拟视野内其他玩家的动作。这样的同步架构,一般而言只需要把少量的玩家输入分发给其他客户端,由其他客户端在本地进行模拟即可。例如一个 RTS 游戏,只需要把玩家的选兵、行动指令分发给其他客户端,由他们进行模拟即可。

状态同步则多采用 C2S 架构。它的核心思路则是:客户端将自己的操作或者局部状态交给服务器,由服务器来运算出游戏内的全局状态,最后由服务器把游戏内的状态分发给其他客户端(可能也包括自己),而客户端只是负责根据这些状态渲染出对应的画面而已。例如一个 MMORPG 游戏,每个玩家将自己所在的位置传递给服务端,服务端收集到各个玩家的位置之后,再分发给其他客户端。

两种网络同步算法各有千秋。总的来说,帧同步算法更适合于做 RTS / FPS / MOBA 类游戏:这类游戏输入简单、对实时性要求非常高,且有“局”和“房间”的概念,一局游戏所能持续的时间有限;而状态同步算法则更适合于 MMORPG 类游戏:这类游戏动作繁多,但对实时性要求可以容忍(甚至可以加入前摇、公 CD 等机制强行增加延迟),且游戏内部状态数据非常庞大、时间轴也会延续很长。

帧同步算法

帧同步算法的实现也有很多不同的样子,这里介绍一种较为简单的方法,即“锁定帧”同步(Lock-step)。

锁定帧同步的核心在于“同步帧”。你需要在游戏中将运算帧分为同步帧和非同步帧。在非同步帧时,你收集本地玩家的所有输入,并将其发送给网络上的其他玩家;同时保存网络上其他玩家发送过来的数据包。到了同步帧,你将网络上的所有操作拿出来,并且在游戏中演算他们。为了保证游戏的实时性和公平性,在同步帧时你可能需要将所有其他玩家的操作数据都收集到,才能进行接下来的游戏过程;但这样可能会导致一个玩家卡顿、所有玩家都无法进行游戏。

关于锁定帧更详细的实现方法,可以参考 Clinton Brennan 的 Lockstep Implementation in Unity3D 一文,也可以参考 bindog 的 游戏中的网络同步机制——Lockstep 一文。

当然,帧同步也有很多不需要锁定帧的方法,这里就不再赘述了。由于传统的帧同步很难解决“开图”之类的反作弊外挂(因为所有玩家的动作都在你本地存着,只是被挡住了而已),所以也有很多和状态同步结合在一起使用的例子。

状态同步算法

状态同步的思路和帧同步相比,客户端的实现要简单很多。你只需要把所有的操作都告诉服务器,让服务器帮你运算,最后将状态同步回本地即可。

说起来非常简单,但事实上很少有游戏是把所有的运算都放到服务器上的。究其根本原因,在于人类的科技实在是太落后了,还无法将网络通信做得比光速更快……扯远了,根本原因在于有网络延迟。如果所有游戏都这么实现,那恐怕是一场灾难:用户往前一步,要等一会儿才会看到自己的角色移动了……

现在很多的状态同步游戏,会在本地做一些运算,并把运算的结果告知服务器。比如移动,可能会定期把当前的坐标信息发给服务器。而服务器收到之后呢,就把这些坐标再告诉其他客户端,其他客户端收到后,再对应的坐标渲染一个人物,这就完事了。

可以看出来,在有网络延迟的情况下,客户端收到的其他玩家的坐标信息必然是有延迟的。在忽略服务器处理的延迟时间的情况下,其延迟时间等于两个玩家到服务器的延迟的总和。在当今的网络条件下,100ms 的延迟都是很常见的,因此玩家看到的其他玩家可能延迟 200ms。如何处理这 200ms ,也许各个游戏都应该有自己的策略:

预测-修正算法

这种方法在云风的BLOG 开发笔记(12) : 位置同步策略 中有提到,在 Gabriel Gambetta 的 Fast-Paced Multiplayer (Part III): Entity Interpolation 一文中也有类似的算法。最初,我也采用这种方法来实现我的游戏。这种方法追求实时性,即,希望玩家看到其他玩家“现在”的位置。

例如,现在的时间 t = 1000;当你收到一个来自 200ms 以前(t=800)、其他玩家的位置数据,你应当根据他的速度,来计算出 400ms 以后(t=1200)它在哪;然后,通过补间动画,将这个玩家平滑的移动到它的位置。这样,如果这个玩家一直在往前移动,当 t=1200 时,你看到的它的位置将是准确无误的。

当然这种方法有很明显的缺点:如果这个玩家在 t=1000 的时候突然掉头往反方向行走,那么你在 t=1200 计算出来的位置是明显错误的;此时,你可能看到这名玩家突然加速向反方向移动。此外,如果玩家在移动过程中进行跳跃,也是很难模拟的。

延迟-回溯算法

在 Gabriel Gambetta 的 Fast-Paced Multiplayer (Part IV): Lag Compensation 一文中提到了一种“延迟补偿”算法,我个人认为更加适用于大多数 MMORPG 的场景。这种算法不再追求实时,而追求准确。换句话说,它不再在乎玩家看到的是“实时”的画面——既然我们做不到让你看到其他人的实时位置,那么让你看到他们在两秒前的位置如何?

通过这种思路,我们可以很轻松的写出网络同步的算法,它对网络的要求也更低——只需要定期采样位置,每隔一小段时间把采样的位置打包回报即可,无需发出大量的网络包来保持同步。同时,客户端也非常好实现,只需确定好延时,然后在过去收到的网络包中,挑出离当前时间减去延迟时间最近的位置,做一个补间动画即可。

当然,它也有它的问题:你看到的是你的“实时”状态,外加其他人的“过去”状态,就像是你超光速旅行一样。这大部分时候没有什么问题,但当你想和其他人交互的时候——比如你瞄准了对方的脑袋开了一枪——此时现在的你对过去的对手开了一枪,可你开枪的时候你的对手已经不在那个位置了。

这个时候,就需要“回溯”了。服务器收到你的“开枪”请求,需要判断你是在哪个时间点开的枪,并且把你开枪的目标的状态回溯到那个时间点,判断你的射击是否命中。如果命中了,再同步到你对手的客户端,告诉他,你中弹了!

显然,你的对手可能对这种情况感到奇怪:我明明已经走远了,为什么他还能打到我?因为同样地,他看到的是你的过去,而你现在已经走远了……嗯,怎么说,只能接受了。

总结

看了很多游戏的同步算法,可以说,在客观延迟存在且无法解决的前提条件下,各种同步算法在尽力保证本地游戏体验的同时,或多或少都牺牲了一些东西。要么牺牲其他玩家的准确性(预测类算法),要么牺牲实时性(延迟类算法),要么牺牲流畅性(锁帧类)……怎么说,鱼与熊掌不可兼得嘛。

对于我而言,我还是期望人类发明什么超光速无延迟通信方案,这样就可以随便怎么做都很开心啦!


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Web Scalability for Startup Engineers

Web Scalability for Startup Engineers

Artur Ejsmont / McGraw / 2015-6-23 / USD 34.81

Design and build scalable web applications quickly This is an invaluable roadmap for meeting the rapid demand to deliver scalable applications in a startup environment. With a focus on core concept......一起来看看 《Web Scalability for Startup Engineers》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

SHA 加密
SHA 加密

SHA 加密工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具