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 個輸入動作。 由於不允許跳過單個輸入操作,它指示所有客戶端執行這些操作並通過網絡發送它們。

具有諷刺意味的是,一種旨在節省帶寬的機制導致了巨大的網絡數據包。

我們通過修復所有更新邊緣案例和延遲隊列支持解決了這個問題。 儘管花費了相當長的時間,但值得最終把它做好,而不是依賴快速破解。

來源: www.habr.com

添加評論