Skip to content

Latest commit

 

History

History
90 lines (44 loc) · 8.76 KB

实现多人网络对战游戏的技术和算法(第三节).md

File metadata and controls

90 lines (44 loc) · 8.76 KB

实现多人网络对战游戏的技术和算法(第三节)

Entity Interpolation

译自 Fast-Paced Multiplayer

介绍

在这系列文章中的第一篇中,我们介绍了权威服务器的工作的原理和它如何有效防止作弊,但是,原生地应用这个技术会在可玩性和响应性方面引起潜在的致命问题,在第二篇文章中,我们提出客户端预处理的方式来克服这个问题。

这两篇文章的最终目的是介绍一套原理和技术,使一个玩家在联网游戏中的操作感受犹如单机游戏一样,即使在他通过有通信延迟的因特网络连接到权威服务器的时候也是如此。

在本文中,我们要探讨当有多个玩家操作的角色同时连接到一台服务器时会有哪些问题需要处理。

服务器 time step

在上文中,我们描述的服务器行为非常简单 - 它主要负责读取客户端的输入请求,计算更新游戏状态,把状态结果返回给客户端。当连上服务器的客户端不只一个时,服务器的主循环就会有些区别了。

在这次的场景中,多个客户端会同时向服务器发起快节奏的输入请求(玩家按键、移动鼠标和点击鼠标的速度有多快,就多快)。收到每个玩家的每个请求就立马更新游戏状态,然后把状态结果广播给所有的玩家,会消耗过多的CPU和网络带宽。

一个更好的方式是在处理请求前先按收到请求的顺序将它们排队。对游戏世界的状态进行低频率的周期更新,例如每秒10次的更新频率。每次更新之间的间隔,在本例中即100ms,被称之为time step。在每一次的更新循环中处理当前所有在排队中的待处理请求,然后将处理后的状态结果广播给所有的客户端。

总结一下,服务器以一个可确定的频率处理游戏状态更新,并且不受当前客户端和收到的客户端请求数量影响。

处理低频率更新

从客户端的角度看,这种处理方式跟之前的玩家操作一样流畅 - 客户端预处理不受更新间隙的影响,所以客户端的运行也是可预期的。然后,由于游戏状态广播降低了频率,客户端对其它在游戏世界里移动的角色的信息就变少了。

当客户端收到服务器的状态更新时,修改所有角色的坐标。这直接导致角色移动很不稳定,就是说,所有角色的坐标每隔100ms发生一次不连续的跳跃,而不是平滑的移动。

取决于你开发的游戏类型,有多种方法可以解决这个问题。总的来说,你的游戏角色越可预测,越容易处理好这个问题。

航位推测法

假设你正在开发一款汽车竞赛游戏,一辆快速行驶的汽车的轨迹是可预测的 - 比如,如果它以100米/秒的速度前行,1秒钟后它大概的位置在它出发点的前方100米处。

为什么是“大概”?因为在那一秒时间内,汽车可能会有一点加速也可能会有一点减速,也可能向左或向右转向一点 - 这里的核心关键词是“一点”。不管玩家怎么操作,在任何一个时间点,这辆高速行驶的汽车的坐标会高度依赖于它上一个时刻的坐标、速度和方向。换句话,一辆赛车不会瞬间180度调头。

在这种情形下,如何与每隔100ms发送一次状态更新的服务器工作呢?客户端从服务器收到每辆赛车权威的速度和方向数据,在接下来的100ms,它不会收到任何消息,但它仍然要显示赛车在行驶。最简单的方式就是假设在接下来的100ms内,赛车的方向和加速度保持衡定不变,按照赛车的参数在本地按物理规律行驶,然后,100ms后,当服务器的状态更新到达时,赛车的坐标也是正确的。

受很多因素的影响,在运行中客户端需要修正的数据可能会很大或相对较小。如果玩家保持赛车直线行驶并且不改变它的速度,客户端预处理后的坐标刚好就是正确坐标。在另一方面,如果赛车撞到什么东西,那预处理后的坐标就会错得很离谱。

需要注意的是,航位推测法比较适用于低速移动的情形 - 比如战舰。事实上,术语“航位推测法”也是起源于船舶导航。

实体插值法

在某些场景下,航位推测法完全无法适用,尤其是在玩家的方向和速度可以随即改变的所有场景中。例如,在3D射击游戏中,玩家通常需要快速地跑、停和转向,坐标和速度无法再通过上一刻的数据进行推测,这导致航向推测法基本上毫无用武之地。

当收到服务器的权威状态数据时,直接更新玩家坐标,玩家每隔100ms发生一次短距离的瞬时移动,这导致游戏丧失了可玩性。

你现在可用的情形就是每100ms收到一次权威数据,这里的窍门是如何向玩家显示这其间发生了什么,这个解决方案的关键点在于向当前玩家显示其它玩家上一刻的历史操作数据。

比方说你在 t = 1000 的时候收到坐标数据,在t = 900 的时候你也收到过坐标数据,所以你知道所有玩家在 t = 900 和 t = 1000 时的坐标移动。于是,从t = 1000 到 t = 1100 之间,你把其它玩家在 t = 900 和 t = 1000之间的坐标移动显示给当前玩家。通过这种方式,其它玩家显示的移动数据都是它们的真实数据,只是你把它们延迟了100ms显示。

插值到 t = 900 到 t = 1000 间的坐标数据取决于你的的游戏。插值法通常都能很好地工作,如果不能,你可以让服务器每次发送更详细的移动数据。 – for example, a sequence of straight segments followed by the player, or positions sampled every 10 ms which look better when interpolated (you don’t need to send 10 times more data – since you’re sending deltas for small movements, the format on the wire can be heavily optimized for this particular case).

需要注意的是,使用这种技术,每个玩家看到的游戏世界会有些许的不一样,因为每个玩家看到的是他自己的实时状态,但看到的其它玩家都是历史状态。即使对于快节奏的游戏,看到的其它玩家有100ms的延迟通常都不会被注意到。

当然也有例外,但你需要非常高的时空精度时,比如当一个玩家射中另一个玩家,因为看到的其它玩家都是历史状态,所以你的瞄准有100ms的延时 - 就是说,你射击的是目标100ms前所在的位置。这个问题我们将在下一篇文章中解决。

结束

在使用权威服务器的客户端-服务器环境中,在低频更新和延时的条件下,你仍然需要给玩家提供一个连续而又平滑的移动效果。在本系列的第二篇文章中,我们探讨了一种方法,通过客户端预处理和服务器校对来实现玩家移动操作的实时性,这保证了本地玩家的游戏操作具有实时效果,避免延迟导致丧失可玩性。

然而其它玩家实体仍是一个问题,在本文中,我们探讨了两个方法来应对这个问题。

第一个方法是航位推测法,通过游戏实体的上一刻的数据如坐标、速度、加速度和方向推测接下来的状态。这种方式在以上条件不满足的时候就不再适用。

第二个方法是实体插值法,完全不用预测未来数据 - 它只使用来自于服务器的真实数据,所以其它实体的状态显示会有一些延时。

最终结果是,当前玩家的角色呈现的是实时状态,而其它玩家的角色则呈现的是历史状态。这个方法通常可以创造出难以置信的流畅体验。

但是,如果当需要极高的时空精度的时候,在不做其它努力的情况下,这种流畅的幻觉就崩塌了,例如射击一个在移动中的目标:在客户端2中渲染的客户端1的位置与服务器或客户端1的位置就不吻合,那么爆头就成为不可能!既然有的游戏不可避免的要有爆头,那么我们将在下篇文章中解决这个问题。