Megapack:Factorio开发人员如何通过200位玩家的多人游戏解决问题

图片

在今年5月,我以选手身份参加了KatherineOfSky MMO活动 。 我注意到,当玩家人数达到一定数量时,每隔几分钟他们中的一些就会“掉下来”。 对于您(但不是我)而言,幸运的是,我是每次都断开连接的玩家之一,即使连接良好。 我将其视为个人挑战,并开始寻找问题的原因。 经过三周的调试,测试和修复,错误最终得到修复,但是此过程并不那么简单。

多人游戏的问题很难找到。 通常,它们是在网络参数非常特定的条件下以及游戏的特定条件下出现的(在这种情况下,存在200个以上的玩家)。 并且即使有可能重现该问题,也无法对其进行正确的调试,因为插入控制点会使游戏停止,使计时器混乱,并且通常会由于超过等待时间而导致连接终止。 但是多亏了笨拙的固执和出色的工具我才得以弄清正在发生的事情。

简而言之:由于错误和延迟状态模拟的实施不完全,客户有时会发现自己处于一种情况,即他必须在一个时钟周期内发送网络数据包,其中包括玩家选择大约400个游戏实体的动作(我们称其为“超大包装”)。 之后,服务器不仅应正确接收所有这些输入操作,而且还应将它们发送给所有其他客户端。 如果您有200个客户,这很快就会成为问题。 到服务器的通道很快被阻塞,这导致数据包丢失和一系列重新请求的数据包。 推迟输入操作会导致一个事实,甚至更多的客户开始发送超级数据包,并且他们的雪崩变得更加强烈。 成功的客户设法恢复过来,其余的全部“掉下来”。

图片

这个问题非常根本,我花了两个星期的时间来解决它。 这是非常技术性的,因此下面我将解释多汁的技术细节。 但是首先您需要知道,自6月4日发布版本0.17.54以来,面对临时连接问题,多人游戏变得更加稳定,并且隐藏延迟的错误也大大减少了(减少了制动和传送)。 此外,我更改了隐藏战斗延迟的方式,希望借此,他们可以更流畅一些。

多用户Megapack-技术细节


简而言之,游戏中的多人游戏的工作方式如下:所有客户端都模拟游戏的状态,仅接收和发送玩家的输入(称为“输入动作”,即Input Actions )。 服务器的主要任务是传输输入操作并控制所有客户端在一个周期内执行相同的操作。 在帖子FFF-149中了解更多有关此的内容。

由于服务器必须决定要执行的动作,因此玩家的动作沿以下路径进行:玩家的动作->游戏客户端->网络->服务器->网络->游戏客户端。 这意味着每个玩家的动作只有在他通过网络进行往返路径之后才能执行。 因此,游戏似乎非常缓慢,因此几乎在多人游戏出现后,便引入了隐藏延迟的机制。 隐藏延迟可模仿玩家的输入,而无需考虑其他玩家的动作和服务器决策。


Factorio具有一个称为“游戏状态”的游戏状态 -这是地图,玩家,实体和其他所有内容的完整状态。 根据从服务器收到的操作,在所有客户端中确定性地模拟它。 游戏状态是神圣的,如果它开始与服务器或任何其他客户端不同,则将发生不同步。

除了游戏状态外 ,我们还有一个延迟状态延迟状态 。 它包含基态的一小部分。 延迟状态并非神圣无缺,它只是根据玩家引入的输入动作来呈现游戏状态在未来的外观。

为此,我们将创建的输入操作的副本存储在延迟队列中。


也就是说,在客户端的过程结束时,图片如下所示:

  1. 从服务器收到这些输入动作后,将所有玩家的输入动作应用于游戏状态
  2. 根据服务器,我们从延迟队列中删除了已应用于Game State的所有Input Actions
  3. 删除等待状态并重置它,使其看起来与游戏状态完全相同。
  4. 将延迟队列中的所有操作应用于延迟状态
  5. 根据来自游戏状态延迟状态的数据,我们将游戏呈现给玩家。

所有这些在所有措施中都会重复。

太复杂了吗? 不要放松,这还不是全部。 为了弥补不可靠的Internet连接,我们创建了两种机制:

  • 错过的滴答声:当服务器决定要在游戏节拍中执行输入操作时,如果服务器未收到玩家的输入操作 (例如,由于延迟增加),它将不会等待,但会告诉此客户“我没有您的输入操作 ,我将尝试将它们添加到下一个度量。” 这样做的目的是,由于一个播放器(或计算机)的连接问题,地图更新不会对其他所有人造成影响。 值得注意的是, 输入操作不会被忽略,而只是被推迟了。
  • 往返延迟:服务器试图猜测每个客户端在客户端和服务器之间的往返延迟。 如果需要,他每5秒钟与客户端讨论一个新的延迟(取决于连接在过去的行为),并相应地增加或减少来回传输数据的延迟。

这些机制本身非常简单,但是当它们一起使用时(通常在连接问题中发生),代码的逻辑就变得难以管理,并且遇到了许多边界情况。 此外,当这些机制发挥作用时 ,服务器和延迟队列必须正确实现名为StopMovementInTheNextTick的特殊输入操作 。 因此,由于连接问题,角色将无法自行运行(例如,在火车下)。

现在,您需要向您解释实体选择的工作原理。 传递的输入操作的类型之一是实体选择状态的改变。 它告诉所有人玩家悬停在哪个实体上。 如您所知,这是客户端发送的最频繁的输入操作之一,因此为了节省带宽,我们对其进行了优化,以使其占用尽可能少的空间。 它的实现方式如下:当选择每个实体时,游戏会保留先前选择的低当前相对位移,而不是保留地图的绝对高精度坐标。 这很好用,因为鼠标选择通常会非常接近先前的选择。 因此,产生了两个重要要求:切勿跳过输入操作,并且必须以正确的顺序执行。 满足游戏状态的这些要求。 但是由于延迟状态的任务是为玩家“看起来足够好”,因此他们对延迟状态不满意。 延迟状态未考虑到许多与跳过周期和更改往返延迟有关的临界情况

您已经可以猜测一切进展了。 最后,我们开始了解大包装问题的原因。 问题的根源在于,在决定是否通过选择更改的动作时,实体选择逻辑依赖于Latency State ,并且此状态并不总是包含正确的信息。 因此,将生成一个大型软件包,如下所示:

  1. 播放器有连接问题。
  2. 跳过时钟和调节往返延迟的机制开始起作用。
  3. 排队状态延迟不能解决这些机制。 这会导致某些操作过早地删除或以错误的顺序执行,从而导致不正确的延迟状态
  4. 播放器的连接出现问题,为了赶上服务器,他最多模拟了400个时钟周期。
  5. 在每个时钟周期中,都会生成一个更改实体选择的新操作,并准备发送给服务器。
  6. 客户端向服务器发送一个巨型数据包,其中包含400多种实体选择更改(以及其他操作:拍摄,行走等状态也遭受此问题)。
  7. 服务器接收400个输入操作。 由于不允许他跳过单个输入操作,因此他命令所有客户端执行这些操作并通过网络发送它们。

具有讽刺意味的是,旨在节省通道带宽的一种机制因此产生了巨大的网络数据包。

我们通过更正所有更新和支持延迟队列的临界情况来解决此问题。 尽管花了相当长的时间,但最后还是值得正确实施所有操作,而不要依赖快速的hack。

Source: https://habr.com/ru/post/zh-CN466135/


All Articles