2.快节奏的多人游戏 (第二部分):客户端预测和服务器协调

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_25327609/article/details/88895925

英文原文地址 Fast-Paced Multiplayer (Part II): Client-Side Prediction and Server Reconciliation

能力有限,如有错误,请谅解,并欢迎指正!

介绍


      在本系列的第一篇文章中,我们探讨了一个客户端 - 服务器模型,它具有权威服务器和愚蠢的客户端,只是将输入发送到服务器,然后在服务器发送时呈现更新的游戏状态。

      该方案的简单实现导致用户命令和屏幕上的改变之间的延迟; 例如,玩家按下右箭头键,角色在开始移动之前需要半秒钟。这是因为客户端输入必须首先前往服务器,服务器必须处理输入并计算新的游戏状态,并且更新的游戏状态必须再次到达客户端。
网络延迟的影响
      在诸如因特网之类的网络环境中,延迟可以是十分之几秒的顺序,游戏可能感觉最多没有响应,或者在最坏的情况下,呈现为游戏根本没法玩。在本文中,我们将找到最小化甚至消除该问题的方法。

客户端预测


      即使有一些开挂的玩家,大多数时候游戏服务器总是在处理有效的请求(来自正常的客户端和来自开挂玩家但在那些在特定时间不作弊的客户端)。这意味着收到的大部分输入都是有效的,并会按预期更新游戏状态; 也就是说,如果你的角色位于 (10,10) 并按下右箭头键,它将结束于 (11,10)

      我们可以利用这个优势。如果游戏世界足够确定(即,给定游戏状态和一组输入,结果是完全可预测的),

      假设我们有一个100毫秒的延迟,并且从一个方格移动到下一个方格的角色动画需要100毫秒。使用简单的实现,整个操作将花费200毫秒:
网络延迟+动画
      由于世界是确定性的,我们可以假设我们发送到服务器的输入将成功执行。在此假设下,客户端可以在处理输入后预测游戏世界的状态,并且大多数情况下这将是正确的。

      我们可以发送输入并开始渲染输入的结果,就像它们已经成功一样,而不是发送输入并等待新游戏状态开始呈现它,而我们等待服务器发送“真实”游戏状态 - 通常情况下,将匹配本地计算的状态:
在服务器确认操作时播放动画
      现在玩家的动作和屏幕上的结果之间绝对没有延迟,而服务器仍然具有权威性(如果被黑的客户端会发送无效输入,它可以在屏幕上呈现任何想要的内容,但它不会影响状态服务器,这是其他玩家看到的)。

同步问题


      在上面的例子中,我仔细选择了数字以使一切正常。但是,请考虑稍微修改的情况:假设我们对服务器有250毫秒的延迟,从正方形移动到下一个需要100毫秒。我们还说玩家连续按下右键2次,试图向右移动2个方格。

      到目前为止使用这些技术,这将是会发生的事情:
预测状态和权威状态不匹配。
      当新游戏状态到来时,我们在 t = 250 毫秒遇到一个有趣的问题 。客户端的预测状态是 x = 12,但服务器说新的游戏状态是 x = 11。由于服务器是权威的,因此客户端必须将角色移回 x = 11。但是,一个新的服务器状态到达 t = 350,表示 x = 12,所以这个角色再次跳转,继续往前。

      从玩家的角度来看,他按了两次右箭头键, 角色向右移动了两个方格,在那里站了50毫秒,向左跳了一个方格,在那里站了100毫秒,向右跳了一个方格。当然,这是不可接受的。

服务器协调


      解决这个问题的关键是要意识到客户端在当前时间看到游戏世界,但由于滞后,它从服务器获得的更新实际上是过去的游戏状态。当服务器发送更新的游戏状态时,它还没有处理客户端发送的所有命令。

扫描二维码关注公众号,回复: 6025809 查看本文章

      但是,要解决这个问题并不是非常困难。首先,客户端为每个请求添加序列号; 在我们的例子中,第一次按键是请求#1,第二次按键是请求#2。然后,当服务器回复时,它包括它处理的最后一个输入的序列号:
客户端预测+服务器协调
      现在,在 t = 250时,服务器说 “ 基于我看到你的请求#1,你的位置是x = 11 ”。因为服务器是权威的,所以它将字符位置设置为 x = 11。现在让我们假设客户端保留了它发送给服务器的请求的副本。基于新的游戏状态,它知道服务器已经处理了请求 #1,因此它可以丢弃该副本。但它也知道服务器仍然必须发回处理请求 #2的结果。因此,再次应用客户端预测,客户端可以基于服务器发送的最后权威状态以及服务器尚未处理的输入来计算游戏的“当前”状态。

      因此,在 t = 250时,客户端获得 “ x = 11,最后处理的请求=#1 ”。它将其发送输入的副本丢弃到 #1 - 但它保留了 #2的副本,该副本尚未得到服务器的确认。它使用服务器发送的内容更新内部游戏状态, x = 11,然后应用服务器仍未看到的所有输入 - 在这种情况下,输入 #2“向右移动”。最终结果是 x = 12,这是正确的。

      继续我们的例子,在 t = 350, 一个新的游戏状态从服务器到达; 这次它说 “ x = 12,最后处理的请求=#2 ”。此时,客户端丢弃所有输入到 #2,并使用 x = 12更新状态 。没有未处理的重放输入,因此处理结束,结果正确。

补充


      上面讨论的是基于运动的例子,但同样的原则可以应用于几乎任何其他事物。例如,在回合制战斗游戏中,当玩家攻击另一个角色时,您可以显示血液和表示损坏的数字,但在服务器这样说之前,您不应该实际更新角色的生命值状况。

      由于游戏状态的复杂性并不总是很容易逆转,所以你可能希望避免杀死一个角色,直到服务器这样说,即使它的生命值状况在客户端的游戏状态下降到零以下(如果另一个角色使用了在接到致命袭击之前的急救箱,但服务器还没告诉你呢?)

      这给我们带来了一个有趣的观点 - 即使世界是完全确定的并且根本没有客户作弊,客户预测的状态和服务器发送的状态仍然可能在协调后不匹配。上述设想在只有单个玩家是不可能发生的,但是当多个玩家同时连接到服务器时很容易遇到。这将是下一篇文章的主题。

总结


      使用权威服务器时,您需要在等待服务器实际处理输入时向玩家提供响应的假象。为此,客户端模拟输入的结果。当更新的服务器状态到达时,预测的客户端状态将从更新的状态和客户端发送的输入但服务器尚未确认的位置重新计算。

猜你喜欢

转载自blog.csdn.net/qq_25327609/article/details/88895925
今日推荐