简介
在第一篇文章中,我们介绍了权威服务器的概念和其防作弊的能力。然而,该框架简单的实现会导致一系列糟糕的响应性和可玩性。在第二篇文章中,我们介绍了客户端预测与服务器协调技术来克服这些缺点。
这两篇文章使用的技术使得单一玩家在在网络游戏上能够获取和单机游戏一样的游戏体验,即使在因特网具有一定延迟的情况下。
本文,我们将讨论多个玩家连接同一个权威服务器的情况。
服务器时间步长
上一篇文章中服务器的行为非常简单——读取客户端输入,更新游戏状态,返回给客户端。然而当有多个客户端存在的情况下,服务器的主循环会有所不同。
在这个场景下,不同的客户端会同时发送指令给服务器,而且是以相当高的频率(考虑MOBA游戏中玩家点击键盘和鼠标的节奏)。如果服务器每当收到一个客户端的指令就更新一次游戏世界的状态并广播给所有客户端,服务器将消耗极大的CPU资源和网络带宽。
一个更好的处理方式是使用一个队列记录客户端输入的指令而不是马上去处理。服务器以一定的频率周期性的去处理用户输入,如每秒10次。每两次更新之间的时间间隔,如本例中的100ms,被称作时间步长。在每个时间步长迭代中,所有未处理的客户端请求被一并处理,然后广播给所有客户端。
总之,无论客户端的数量和发送命令频率是多少,服务器总是以一个可预测的频率更新自己的游戏状态。
低频率更新
对于一个客户端的情况下,该框架和之前的框架一样工作良好——因为客户端预测与服务器更新频率无关。然而,由于游戏更新频率较低(仍然以100ms为例),客户端获取到的游戏世界中的实体信息太少。
下面以2个客户端的位置移动做举例说明。此时在 客户端2的视角下,客户端1控制的玩家会发生跳跃现象。
航位推算法(Dead reckoning )
假设你在开发一款赛车游戏。赛车运行的速度为100m/s。1s之后的位置大概在正前方100m处。
为什么说“大概”?因为这一秒钟内,赛车可能进行了一点点加速或者减速,或者转弯。注意此处的“一点点”。一辆可控的赛车在这种高速之下,其位置高度依赖于其之前的位置、速度和方向,而无论用户对其做了什么操作。例如,一辆赛车不可能瞬间做出180°转弯。
对于一个更新频率100ms的服务器而言,客户端收到服务器发来的所有赛车权威的速度和方向信息。在接下来的100ms中,客户端接收不到任何服务器发来的信息,但是仍然需要显示这些赛车的飞驰状态。最简单的办法就是假设这100ms中,这些赛车的方向和加速度都是保持不变的,并用这些参数来计算赛车的飞驰状态。100ms后,服务器发来了最新的游戏状态,然后客户端用该状态来矫正本地的状态。
这个矫正可能是极小或者极大的,依赖于很多因素。如果玩家保持汽车的直线运行状态并没有改变车速,则客户端预测的状态与服务器发回的状态将完美一致。反之如果玩家撞车了,则客户端预测的位置将完全错误。
注意航位推算法适用于低速运动的场景,如战舰。实际上,这个词来最初便来自航海导航领域。
实体插值
航位推算法在某些情况下会失效——当玩家的方向和速度立即发生改变时。以3D射击游戏为例,玩家高速进行跑、停、转弯等操作,使得航位推算法完全无效,因为完全无法依据之前的状态来预测未来的状态。
客户端不能在收到服务器最新权威状态时直接更新玩家位置,否则将导致玩家每100ms跳跃一次,游戏变得毫无可玩性。
客户端每100ms确实获取了权威的位置信息,关键是在这100ms之间如何表现玩家的状态。解决问题的核心是显示其他玩家相对于自身玩家的过去时。
下面举例说明。t=1000时客户端2收到客户端1的位置信息。当然此时已经有t=900时的位置信息了。t=1000到t=1100这段时间内客户端2看到的客户端1状态实际上是t=900到t=1000这段时间内的。虽然是过去时的状态,但毕竟是真实存在的。
如何利用t=900到t=1000这段时间内的位置信息进行插值取决于游戏的设计。简单的插值通常就能工作的很好。否则,你可以让服务器给客户端发送更多的运动信息——如玩家的运动直线轨迹线段,或者每隔10ms记录一次的玩家位置信息(数据量不会增加10倍,因为可以发送增量信息,数据很容易被优化和压缩),使得插值更加准确。
值得注意的是,使用这个框架,每个玩家看到的游戏世界都是有少许差别的——每个玩家看到的自己的状态是现在时,而看到别的玩家确都是过去时的。即使是对于快节奏的多人在线游戏,看到玩家自己没有延迟,而看到其他实体延迟100ms也是难以察觉的。
然而也有很多对时间和空间要求严格的的情况。例如一个玩家向另外一个玩家射击的情况。因为玩家看到其他的玩家都是100ms之前的,当他觉得目标玩家正中靶心的时候,实际上目标玩家已经跑出去100ms之外了!我们将在下一篇文章中解决这个问题。
总结
本文描述了在权威服务器、低速更新、考虑网络延迟的情况下如何使得玩家的运动表现的连贯和流程。文章系列2讨论了客户端预测与服务器协调技术使得玩家自己的运动得到实时响应。然而对于游戏中其他的实体而言,仍然存在问题。本文中我们讨论了2种方法来解决这个问题。
第一个方法是航位推算法,适用于能够根据过去的位置、速度和加速度信息来推算未来的情况。
第二种方法是实体插值,这种方法完全不去预测未来,而是忠实的使用服务器发回来的过去信息,在时间段之间进行插值
网络的延迟效应导致玩家看到的其他实体都是过去的,这种情况创造了难以察觉的无缝游戏体验。
然而对时间和空间要求严格的的情况,例如玩家1向玩家2射击的情况,由于看到的玩家2处于过去时,玩家1不可能爆头运动中的玩家2!考虑到几乎不存在没有类似于“爆头”设计的网络游戏,我们将在下一章继续讨论这个问题。
================================================
原文链接:http://www.gabrielgambetta.com/fpm3.html