Megapack:Factorio 如何解决 200 人多人游戏问题

Megapack:Factorio 如何解决 200 人多人游戏问题
今年XNUMX月,我作为选手参加了 KatherineOfSky MMO 活动. 我注意到当玩家数量达到一定数量时,每隔几分钟就会有一些人“掉下来”。 幸运的是你(但不是我),我是那些球员中的一员 每一次即使连接良好。 我将其视为个人挑战,并开始寻找问题的原因。 经过三周的调试、测试和修复,bug 终于被修复了,但过程并不那么顺利。

多人游戏中的问题很难追踪。 它们通常发生在非常特定的网络参数和非常特定的游戏状态下(在这种情况下,超过 200 名玩家)。 并且即使可以重现问题,也无法正确调试,因为插入断点会停止游戏,打乱计时器,并且通常会导致连接因超时而超时。 但是多亏了毅力和一个叫做 笨拙 我能够弄清楚发生了什么。

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

Megapack:Factorio 如何解决 200 人多人游戏问题
这个问题很根本,我花了 2 周的时间来解决它。 这是非常技术性的,所以我将在下面解释多汁的技术细节。 但首先,您需要知道,自 0.17.54 月 4 日发布的 XNUMX 版本以来,面对暂时的连接问题,多人游戏变得更加稳定,延迟隐藏的错误更少(更少的制动和传送)。 此外,我还改变了隐藏战斗延迟的方式,希望这会让它们更平滑一些。

多人超级包 - 技术细节

简而言之,游戏中的多人游戏是这样工作的:所有客户端通过仅接收和发送玩家输入(称为“输入动作”)来模拟游戏状态 输入动作). 服务器的主要任务是传输 输入动作 并确保所有客户端在同一周期内执行相同的操作。 您可以在帖子中阅读更多相关信息。 FFF-149.

由于服务器必须决定采取什么动作,玩家的动作沿着以下路径移动:玩家动作 -> 游戏客户端 -> 网络 -> 服务器 -> 网络 -> 游戏客户端。 这意味着玩家的每个动作只有在它通过网络完成往返路径后才会执行。 正因为如此,游戏会显得非常慢,所以在游戏中出现多人游戏后,几乎立即引入了隐藏延迟的机制。 延迟隐藏模拟玩家输入,而不考虑其他玩家的行为和服务器决策。

Megapack:Factorio 如何解决 200 人多人游戏问题
Factorio 有一个游戏状态 游戏状态 是地图、玩家、实体和其他一切的完整状态。 它是根据从服务器接收到的操作在所有客户端中进行确定性模拟的。 游戏状态是神圣的,如果它开始与服务器或任何其他客户端不同,就会发生去同步。

此外 游戏状态 我们有延误状态 潜伏状态. 它包含主要状态的一小部分。 潜伏状态 不是神圣的,只是根据玩家的输入代表未来游戏状态的图片 输入动作.

为此,我们保留生成的副本 输入动作 在延迟队列中。

Megapack:Factorio 如何解决 200 人多人游戏问题
也就是说,在客户端的流程结束时,图片看起来像这样:

  1. 申请 输入动作 所有玩家到 游戏状态 从服务器接收这些输入操作的方式。
  2. 从延迟队列中删除所有内容 输入动作,根据服务器,已经应用于 游戏状态.
  3. 清除 潜伏状态 并重置它,使其看起来与 游戏状态.
  4. 将延迟队列中的所有操作应用到 潜伏状态.
  5. 基于数据 游戏状态 и 潜伏状态 将游戏呈现给玩家。

所有这一切都在每一个小节中重复。

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

  • 跳过的滴答声:当服务器决定 输入动作 将在比赛的节奏中被执行,然后如果他没有收到 输入动作 某些玩家(例如,由于延迟增加),他不会等待,而是会通知该客户“我没有考虑到您的 输入动作,我将尝试在下一个栏中添加它们。 这样做是为了防止由于一个玩家的连接(或计算机)问题,地图更新不会减慢其他人的速度。 值得一提的是 输入动作 不会被忽略,而只是被推迟。
  • 完整的往返延迟:服务器尝试猜测每个客户端在客户端和服务器之间的往返延迟是多少。 每 5 秒,它会根据需要与客户端协商一个新的延迟(取决于连接过去的行为方式),并相应地增加或减少往返延迟。

就其本身而言,这些机制非常简单,但是当它们一起使用时(连接问题经常发生),代码逻辑变得难以管理并且有很多边缘情况。 此外,当这些机制发挥作用时,服务器和延迟队列必须正确地实现一个特殊的 输入动作 标题 在下一个刻度中停止运动. 因此,如果出现连接问题,角色将无法自行运行(例如,在火车下)。

现在我需要向您解释实体选择是如何工作的。 传递的类型之一 输入动作 是实体选择状态的变化。 它告诉每个人玩家用鼠标悬停在哪个实体上。 如您所见,这是客户端发送的最频繁的输入操作之一,因此为了节省带宽,我们对其进行了优化,使其占用尽可能少的空间。 这是这样实现的:当选择每个实体时,游戏不会存储绝对的高精度地图坐标,而是存储与先前选择的低精度相对偏移量。 这很有效,因为鼠标选择通常非常接近先前的选择。 这就产生了两个重要的要求: 输入动作 决不能跳过,必须按照正确的顺序进行。 满足这些要求 游戏状态. 但是因为任务 潜伏状态 对于玩家来说“looking enough”,他们并不满足于延迟状态。 潜伏状态 不考虑 许多边缘案例与跳过时钟和改变往返传输延迟相关联。

你已经可以猜到这是怎么回事了。 最后,我们开始了解 megapackage 问题的原因。 问题的根源在于实体选择逻辑依赖于 潜伏状态, 而这个状态并不总是包含正确的信息。 所以 megapacket 是这样生成的:

  1. 播放器遇到连接问题。
  2. 跳周期和调节往返传输延迟的机制开始发挥作用。
  3. 延迟状态队列不考虑这些机制。 这会导致某些操作被过早删除或以错误的顺序运行,从而导致错误的 潜伏状态.
  4. 播放器没有连接问题,最多模拟400个循环赶上服务器。
  5. 在每个周期中,都会生成一个新的动作并准备发送到服务器,从而更改实体选择。
  6. 客户端向服务器发送一个包含 400 多个实体选择更改的巨型数据包(以及其他动作:开火状态、行走状态等也遇到此问题)。
  7. 服务器接收到 400 个输入动作。 由于不允许跳过单个输入操作,它指示所有客户端执行这些操作并通过网络发送它们。

具有讽刺意味的是,一种旨在节省带宽的机制导致了巨大的网络数据包。

我们通过修复所有更新边缘案例和延迟队列支持解决了这个问题。 尽管花费了相当长的时间,但值得最终把它做好,而不是依赖快速破解。

来源: habr.com

添加评论