《合金彈頭:覺醒》是由騰訊天美工作室開發的“合金彈頭” IP 手游。在 Unite Shanghai 2024 游戲生態專場中,騰訊游戲高級工程師田亞濤先生帶來演講,介紹了《合金彈頭:覺醒》團隊以 Unity 引擎為基礎的雙端(前后臺)方案及其優化過程。內容涵蓋為提升 Unity DS 承載能力和前端性能所進行的多房間改造、邏輯多線程等工作,同時分享了團隊在此過程中面臨的挑戰與積累的寶貴經驗。
本文為演講全程實錄。
大家好!我叫田亞濤,來自騰訊天美 J6 工作室,2014 年加入騰訊,前后參與了《魂斗羅:歸來》和《合金彈頭:覺醒》這兩款游戲,目前作為《合金彈頭:覺醒》的客戶端負責人,負責版本研發相關工作。
簡單介紹一下《合金彈頭:覺醒》這款游戲,它是去年 4 月份上線的一款橫版動作射擊手游,作為 2023 年騰訊首發戰略級新品,上線之初取得了一些不錯的成績,包括上線后 app store 霸榜多日、谷歌熱門推薦以及近期歐美日韓發布后的整體表現等等。
今天的分享包括三塊內容:《合金彈頭:覺醒》作為一款橫版動作射擊游戲最初的技術選型,和研發過程中所做的兩次比較大的結構性改造。
技術背景
先看技術背景。《合金彈頭:覺醒》前作是《魂斗羅:歸來》,所以最初的技術方案也是自然地繼承。
從上圖可以看到前后端都是基于 Unity 實現的,在此基礎上完成了網絡和一些基礎模塊的搭建,游戲邏輯的部分也是前后端復用。從結構上講是經典 C/S 架構,同步方面實現了一套基于 UDP/RUDP 的對象同步(包括插值、預測、回滾等)。
我們被問到最多的是:為什么要用 Unity 做 DS?
回答這問題前先看看當時面臨的問題:首先我們是一款強聯機的射擊游戲,彈道同步實時性要求高且命中感知非常明顯,對聯機同步的要求很高,同時在反外掛方面要求也非常高。 項目早期我們也做過一些方案對比,包括 Unity 只做客戶端,服務器單獨研發,但這帶來的問題就是開發過程中前端和后端面向協議對接。 如果要實現服務器跑完整邏輯,就需要兩端在邏輯上分別開發,同時還要保證邏輯執行的一致性。
另外效率層面也是我們考慮的重點,《合金彈頭:覺醒》這款游戲 80% 是聯機模式,所以日常開發過程中大量面向聯機的內容制作。綜合考慮之后決定實現 Unity 上的 DS 方案。
來到如何用 Unity 做 DS的話題 ,簡單地來講包括幾步:構建 Unity 的 Linux 版本、組件裁減、邏輯表現分離、邏輯前后端復用,上圖右側是整個過程。
值得一提的是邏輯表現分離,這個做完之后對于后面的兩次比較大的改造幫助非常大。這個事情除了技術層面,還涉及研發過程中的規范問題,未來大家有類似的需求是可以提前啟動的。
前面聊到《合金》80% 的玩法需要聯機,同時我們也是一款內容型的游戲,現在的關卡量級差不多接 近 1000 關,所以在聯機制作的維度最初目標是希望像做單機一樣做聯機。 上面是我們的日常開發環境: 客戶端開發過程中可以實時查后臺 DS 上的表現,調整和調試、暫停都是很方便的,在研發過程中大多數情況不需要關注聯機。 左邊是我方便錄屏用的模擬器,目的是我們在手機上體驗版本時,遇到聯機或 DS 問題的時候可以在編輯器上直接連上來,加快問題的定位和調試效率。
Unity DS 承載優化
用這樣一套前后端 DS 的方案,另一個被問到最多的問題是性能。作為后端,性能影響的是體驗也是承載,再進一步講就是成本,所以這塊是我們比較關注的。
除了在 CPU 側做了一些邏輯級別的優化如:分幀、降頻、合包、邏輯分離等等,我們嘗試把邏輯改造成支持 15 幀(這里也是因為前面做了邏輯表現分離,所以能單獨控制更新頻率),以及 AB part(把 1 幀拆成了上半幀和下半幀兩次執行來緩解卡頓問題)。
用原生 Unity 編譯出來做 DS 的時候,它的線程數非常多,而后端更關注的是進程的使用率,對于線程來講收益不大,反而會帶來一些線程調度上的開銷,所以我們做了裁減。最終就是右下圖的樣子。系統調用也是非常頻繁,我們同樣做了一些裁減。
除了 CPU 方面,大家思考一個問題:承載優化除了 CPU 優化,還有沒有其他的,或者我們優化 CPU 承載是不是就上去了?
答案不是的。因為我們在算承載的時候要考慮服務器資源本身的 CPU 內存的比例,假設我以比較早的一個服務器 1260 舉例(12 核 60G 的配置),拿比較早期的機型舉例,按照一局 10% 的 CPU 占比算,12 核 120 局,但是一局占用 3G 的內存,60G 只能 20 局,所以它會卡在內存上的承載是 20 局。
為了解決這個問題我們引入了多房間的改造。先看一下 Unity DS 進程默認的樣子:構建完是一個進程拉起之后只跑一個實例,也就是一場戰斗,就是一個客戶端一個 DS 進程。如果一個陪玩 AI 就是一個進程,從資源占用上講是非常奢侈的。
第二就像右邊看到的,原始是一個房間,除了游戲邏輯本身,還包括引擎基礎消耗、預加載的一系列資源和池子、網絡等公共資源都只能同時服務一場戰斗,占用還是非常高的,它就會帶來 CPU 和內存的不對等。并且前期做復用的時候更多是串行,一局結束重置,之后再開下一局的這種方式。
我們希望做的是右邊這樣子,多個實例可以并行,在一個進程上解決多局并存的問題。這樣還有一個好處我們可以降低進程拉起的頻率和玩家等待時間。
一開始也預研過一些方案,希望在同一個進程上并行多局戰斗,即便站在同一個位置它們之間也要完全不影響。考慮過幾個方案:第一是空間分割,通過坐標偏移來做這個事情,但問題是《合金彈頭:覺醒》有一些大世界玩法本身就是通過坐標偏移、關卡拼接的方式,在此基礎上疊加偏移會讓復雜度提高的同時精準也會下降。
第二是場景疊加,這是 Unity 本來有的能力,即便我們實現了這個能力,它在多局之間也沒有辦法完全隔離的,因為 Unity 最初的設計也是服務于一個客戶端。
之后是第三方案,把當前工程邏輯做了一個改造,把它變成了邏輯層的 room,我們要做的是對物理、邏輯和 Unity 組件等模塊的隔離工作。
Room 包括哪些東西呢,簡單來講就這幾塊:一個是 scene,包括各種 game object;第二是物理,包括 Physics Scene 和 Physics World;最大一塊還是我們的游戲邏輯。除了這幾個改造之外,我們一個重點的事情是在這個環節把 Physics 做了一些調整,停掉了全局 Physics,改為通過一個個 Physics world 進行模擬。更新也是一樣,我們會放在 Room 里更新。最后我們把之前的調用接口做了替換。
除此之外還有大量的 Unity 內置對象需要改造,默認是全部在一起的。之后是游戲狀態、基建設施:如網絡、Socket 復用、同步、日志系統等。
改造完的效果如上方視頻,我們可以在 DS 上做一個模擬創建多個房間,然后客戶端是多個客戶端連上來相互不受影響,這也是我們改造完之后的一個效果。兩局連的是同一個 DS,因為我們做了多個房間的隔離,所以它們之間是完全不受影響的。
最后聊一下收益的部分,這是一系列優化和改造完的結果:單局 CPU 優化提升了41.6%,承載提升了37.4%,提升最明顯是內存部分也就是10倍。我們把單個進程的所有內存合在一起復用,我們每一局所占的內存也就是運行時的內存分配。
我們在外網也做了一些數據的驗證,最終上線的時候是以 15 個房間在并行戰斗(也支持按玩法配置),這套方案目前應用于《合金》國內和海外正式環境。
就在上周我們完成了進一步的優化,把每一個進程里的戰斗合并在了一起。基于 Linux 的 fork 的機制把 Unity 進程的拉起方式支持了父子進程的形式,由原來的一個個進程拉起改為由父進程 fork 出子進程。這樣好處是什么,首先進程之間只讀內存的部分就只有一份,進程之間復用。其次在拉起時間上,我們原本拉起一個進程差不多在 40—50 秒,通過 fork 的方式拉起子進程在毫秒級別,對應進程拉起過程中 CPU 毛刺也明顯減少。
邏輯多線程
接下來把視角切到客戶端。
《合金彈頭:覺醒》是一款橫版射擊游戲,玩法就是在彈幕和清怪,反復地創建和銷毀,峰值的時候一屏有 100 多顆子彈、怪也有 50+,并且每一種子彈的傷害非常重,各種的被動事件,同時在玩法層面效果希望越來越酷炫,玩法上希望有一些割草體驗,整體在性能面臨巨大壓力。
合金是一款全球化的游戲,需要考慮海外市場和兼容低端設備。目前兼容到的 8 檔機是 A73,它是 2017 年 Q3 出的一款設備,性能大概等于現在 8 Gen3 的十分之一,我們需要確保它的基礎體驗。
接起來面臨的挑戰是性能空間耗盡。除了前面 DS 承載時的一些基于 CPU 優化(前后端復用的,所以前面的優化在客戶端也是可以用到的)外,在客戶端也做了一系列的優化工作,包括通過 Job/Thread 把一些獨立的模塊線程化,但隨能單獨拆出來做多線程的模塊越來越少,每個版本對于性能的提升也就變得非常吃力,究其原因可以看圖右側:除了邏輯本身還有一些渲染前的準備工作在主線程完成,主線程負載非常重,而其他線程卻很閑,所謂一核有難、多核圍觀。
對于一款去年剛上線的游戲,希望面向長線找到解決方案,于是跟兄弟項目學習了一些過往的實踐經驗和可行性評估,之后啟動了內部叫做開著飛機換引擎的計劃,將邏輯從主線程拆出去。最終就是右圖的樣子,拆一個邏輯線程出來,把邏輯部分全部轉移到子線程。
這里涉及到的改動主要是幾大塊:第一是邏輯剝離,這步要做的是把原本依賴 Unity 的組件轉移到子線程之后能夠正常運行,我們做了一套虛擬引擎,目的是完成子線程后對原本 Unity 能力的承接。
在原有研發管線的兼容方面,使用 Prefab、Scene 編輯配置,導出對應的 Component 信息配置,運行時組裝對應的 Virtual Engine Object。對于 prefab 之類的序列化,額外做了一層到邏輯線程的映射,編輯器還是用 Unity,然后運行時會自動綁定到虛擬引擎中。
在物理方面,我們在前面 DS 多房間改造的時候把物理從全局物理改成了多個 physics world,現在又面臨一個問題是把主線程的物理支持多線程。這里考慮的不僅是客戶端,還有 DS,在兼容 DS 的情況下解決 Unity physics 非主線程調用的問題。
前期評估過幾種方案:首先是重寫物理實現,好處是不依賴任何三方庫,但如何保證模擬結果跟 Unity physics 完全一致還挺難的。
其次是直接用原生 PhysX,因為 Unity 引擎底層也是 PhysX,這種方案用于后臺 DS 沒問題,但是放在前臺會出現兩套物理并存,對于包體和運行時效率來講不合適。
為了解決這個問題,我們決定將 Unity physics 實現多線程能力,之后在引擎內部實現一套橋接(bridge),使得 Unity 引擎自帶的 PhysX 與原生 PhysX 都能兼容。這樣的好處是,邏輯層只需要面對這個中間層,對客戶端來講可以直接用原生 Unity 物理。而在 DS 端,我們可以多一種選擇:可以直接用 Unity,同時因為邏輯子線程已經實現,我們也可以考慮脫離 Unity 運行,在邏輯子線程中直接使用原生 PhysX。通過這種方案,我們最終成功實現了這一目標。
接下來,我們需要處理的是子線程的 Tick 邏輯,這與主線程的處理方式類似。關鍵的問題是,當我們將邏輯從主線程轉移到子線程后,我們如何進行 Tick 操作,以及是否需要進行同步。在尋找解決方案時,我們參考了 Halo 5(光環)游戲在改造過程中的做法(圖中模型一):這種做法有幾個優點,首先,我可以在主線程開始時就發起一個或者多個線程,就像光環游戲那樣。然后,當主線程結束時等待數據返回,整個過程在一幀內完成,既簡單效果又好。
但是放在《合金彈頭:覺醒》來講不太行,因為邏輯很重,即便放在子線程也很重,如果還采用第一種方案變成了主線程等邏輯線程,會產生畫面卡頓。
我們再看第二個方案,同樣會在 input 之后喚醒邏輯線程,但過程中主線程不做等待,正常往后運行,主線程始終用的是邏輯線程上一幀的數據,這里會有一幀的延遲。 當邏輯線程出現卡的時候會沒有數據可拿,于是這里做了一些預測和平滑處理來緩解主線程卡頓。 在某些情況下也會有問題,舉例: 移動過程中如果邏輯線程持續沒有數據可拿(卡頓),這個時候主線程也不能一直預測,所以這里做了一個兜底的機制,我們會往前預測,但是只預測一個邏輯幀的長度。
最終的收益從低端機來講,本來設備能力有限,在高負載的模式下提升 25% 以上,同理邏輯消耗越重的模式提升越明顯,測下來最重的玩法提升 40% - 50%,因為把主線程的邏輯轉到了子線程。第二個比較大的收益是幀率穩定性,我們衡量幀率穩定性的標準是一局中大于某個幀率的比例,改造完之后整體地提升了 82%,對于穩定幀率的效果是非常好的。
回顧邏輯多線程改造前期,團隊內部對于這個事還是有很多擔憂,一方面是能不能做到,另一方面是即便做完了敢不敢上線的問題,畢竟對線上項目做如此體量的改造還是很有挑戰的。現在看來第一步完成了,結果還符合預期。同時就在上周我們帶著多線程的海外版本在歐、美、日、韓幾個區域同步上線,整體運行平穩,預計 Q4 會把多線程版本在國內上線。
接下來是今天內容的回顧:前面跟大家分享了基于 Unity 客戶端 DS 的聯機方案,在此基礎上做了針對 DS 的承載優化,一個是 CPU 側的優化,另一塊是通過 DS 多房間的改造大幅解決內存問題,解決了資源均衡帶來的承載瓶頸。之后通過 linux fork 父子進程的方式進一步完成進程間內存復用和把拉起時間從 45s 降低到毫秒內。最后是在客戶端維度完成了邏輯多線程改造,把主線程邏輯轉移到子線程,解決了一直困擾我們的主線程負載重的問題,在線程同步模型上完成了《合金彈頭:覺醒》本身邏輯比較重的解決方案,最終解決延遲和卡頓問題。在物理引擎方面做了兩次改造,第一次從單 world 轉成了多 world,第二次從單線程改成了多線程。
以上是今天的分享內容,謝謝大家。
Unity 官方微信
第一時間了解Unity引擎動向,學習進階開發技能
每一個“在看”,都是我們前進的動力
特別聲明:以上內容(如有圖片或視頻亦包括在內)為自媒體平臺“網易號”用戶上傳并發布,本平臺僅提供信息存儲服務。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.