所有文章

用 Unreal Engine 5.8 打造 MMORPG — 開發日誌 #1:權威式伺服器與世界架構

Ancient Online 的架構:Unreal Engine 5.8 用戶端、以 C++ 撰寫的權威式伺服器、25 Hz 世界迴圈,以及可即時編輯的單一世界。

燈光昏暗的工作室裡,螢幕與筆記型電腦並排擺放的工作站

MMORPG 與單人遊戲的差別,在於你所見的一切背後都有一台伺服器。在《Ancient Online: Remastered》開發日誌的第一篇,我們要說明自己如何用 Unreal Engine 5.8 打造 MMORPG:用戶端與伺服器如何分工、世界如何模擬,以及我們如何做到即時編輯一個完整的單一世界。

兩個部分:用戶端與伺服器

Ancient Online 由兩套獨立的軟體組成:

  • 用戶端以 C++ 在 Unreal Engine 5.8 上撰寫。算繪、音效、介面與輸入都是它的工作。
  • 伺服器獨立於 Unreal 之外:我們用 C++23 從零開始撰寫。遊戲的所有規則都在這裡執行。

為什麼我們把伺服器獨立出來寫,而不是放在引擎裡?因為 MMORPG 伺服器的需求和遊戲引擎不同:它不需要圖形,卻要處理成千上萬個實體、持久保存的資料、長時間不中斷的運作,以及可預期的效能。獨立的伺服器讓我們只做必要的工作,並掌控每一個位元組。

權威式伺服器:不信任用戶端

我們的基本原則是:用戶端提出主張,伺服器做出決定。

  • 移動:玩家的輸入送往伺服器,由伺服器計算位置。用戶端以平順的內插顯示伺服器回報的位置。因此,就伺服器而言,「自行瞬間移動」或穿牆而過的用戶端哪裡也去不了。
  • 戰鬥:傷害、命中與擊退等結果都在伺服器上計算。舉例來說,一記連段攻擊是否把對手擊倒由伺服器決定,並在同一時間把結果廣播給所有人。
  • 經濟:道具、貨幣與商店交易只會在伺服器與資料庫中變動。

這種做法的代價是必須妥善處理網路延遲;回報則是一個作弊困難得多、所有人適用同一套規則的世界。

25 Hz 的世界迴圈

伺服器每秒更新世界 25 次。每一步中,怪物 AI、移動、戰鬥、技能效果與天氣都會向前推進。固定的步長讓模擬保持可預期,並確保相同的輸入永遠產生相同的結果。

並不是每位玩家都需要知道所有事情。伺服器把世界劃分成網格,只把每位玩家關注範圍內(附近)的實體更新傳送給他。即使在擁擠的城市裡,網路流量也能維持在合理的範圍。

輕量的二進位通訊協定

用戶端與伺服器之間沒有 JSON 或 REST;每則訊息都是版面配置固定的二進位封包。這同時節省了頻寬與 CPU 時間。新增系統時(例如天氣、移動或遊戲內帳號服務),我們會為它另外撰寫一份有版本編號的封包協定,並用共用的標頭檔確保用戶端與伺服器依循同一份協定運作。

資料保存:存檔不讓遊戲等待

玩家資料存放在 PostgreSQL 資料庫中。但讓世界迴圈在每一步都等待資料庫,是無法接受的。因此,存檔以延後寫入的方式在背景進行:世界伺服器把變更丟進佇列(Redis),再由背景寫入程式分批寫入資料庫。遊戲的流暢度不會受資料庫速度牽制。

頻道與登入伺服器

玩家會先連線到登入伺服器,由它負責帳號驗證、角色選擇與工作階段。接著再進入四個遊戲頻道(CH1–CH4)的其中之一。各頻道的即時狀態顯示在遊戲網站的伺服器狀態頁面。

可即時編輯的單一世界

Ancient Online 的世界被設計成一個完整的單一世界,而不是被載入畫面切開的一張張地圖;它被劃分成區塊,以串流方式載入。

世界的內容(地形、物件、怪物配置)不是寫在程式碼裡,而是以版本控管的方式存放在資料庫中。我們可以用遊戲內的世界編輯器來編輯這個世界:變更會先在草稿中進行、經過檢查,然後才發布。每次發布都會產生新的版本,用戶端則透過比對摘要,確認自己手上的世界資料與伺服器一致。如果不一致,它會等資料更新完成後才繼續進入遊戲。

任務:以 Lua 作為腳本語言

任務與 NPC 對話在伺服器上以 Lua 5.4 撰寫。這讓我們不必重新編譯伺服器,就能開發並測試任務。

玩家訊息絕不離開我們的伺服器

遊戲內的聊天翻譯不使用任何外部翻譯服務。我們在自家伺服器上執行只用 CPU 的本機翻譯模型;玩家訊息不會傳送到任何外部 API。如此一來,使用不同語言的玩家可以彼此溝通,訊息卻始終不會離開工作室。

接下來

開發日誌的第二篇,介紹我們為了讓同一個世界能用滑鼠、觸控螢幕與手把遊玩所做的決定。想認識工作室,請閱讀「Astronaut Entertainment 是誰?」;想了解遊戲本身,請參閱遊戲頁面。

分享:
其他語言版本29