在 2025 年 5 月 24 日的 Unity User Group 北京站活動中,殼木游戲的 CTO 鄺圣凱帶來《團結引擎的 H5 項目優化實踐分享》。本文為演講全文實錄,點擊閱讀原文,可下載演講 PPT 資料。
![]()
點擊圖片,觀看演講視頻
感謝大家,我們作為團結引擎的用戶方,今天也很榮幸得到 UUG 的邀請,介紹我們通過團結引擎做一個手游項目時向 H5 遷移實踐方面的經驗。
游戲介紹
今天分享一些研發當中的優化細節和最佳實踐。先介紹一下我們的游戲《Dreamland(DL)》,它是 SLG 題材游戲,它的主城家園有類《饑荒》的外圍包裝,后面再引導進入大世界地圖。
![]()
先介紹一下主要場景,游戲當時立項面向手游,目標是在 Native APP 里面榨干手機所有的性能,還引入了光影系統。 雖然是 2D 游戲,但規模是比較大的,從下方視頻可看出 算是一個重 CPU 規格的手游。
![]()
我們的大地圖戰斗規模還是比較大的,支持英雄的種類、Spine 體量比較大,戰斗達到 200vs200 同屏最高規格,英雄 Spine 50vs50,每個英雄帶 3 個小兵 UV 動畫 150vs150。
![]()
下方視頻中展示了比較極端的戰斗場景。我們當時決定把這個游戲遷移到 H5 版本時也做了比較久的權衡,進行了不少可行性分析。
挑戰和痛點
![]()
總體痛點如下:
第一是進游時間,對于 Native APP 用戶可以到 App Store 下載,然后進行游戲,游戲大部分前期資源都是已經準備完成的,不存在 H5 進游的時候零占用開始準備加載的過程。
第二個突出問題是內存問題。這個游戲面向 Native APP 設計,內存是比較超標的,到 H5 方面,比如做微信小游戲,可用的空間只有 1 點幾 G。這個游戲在 Native 版內存峰值可以達到 1.8G 接近 2G,在一些比較老的 iOS 設備上就會崩掉。
第三是功能響應速度,H5 算力并不差,但有些資源是后置加載,一些前期功能用戶在用到的時候可能資源還沒有下載完,如果安排不好就有加載響應的問題。
最后是幀率問題。H5 雖然 GPU 沒有太多折損,但 CPU 能力大概是 Native APP 的三分之一,包括多線程不能被支持,這是比較不利的地方。
![]()
介紹一下宏觀思路。解決內存問題一個比較重要的是通過微信代碼的分包機制,再就是開啟團結引擎設計的高性能+的模式,以及 Spine 優化、游戲業務相關的優化,后面會詳細介紹。
![]()
OOM 問題解決之后,運行期方面我們主要重點是在 Spine 性能優化上。包括 Spine 拆面主要解決內存問題,運行時的效率問題也能夠得益。我們根據機型限制 Spine 動畫的 Update 頻次,根據兵種大小降到 15fps、20fps,在有些比較小的角色上是看不出來的,對 CPU 是很大的節省。還有 2D 管線優化。預加載預實例會用到異步的實例化。再就是特效裁剪、相機 SkipCulling 等等。
![]()
為什么選擇使用團結引擎?我們立項的時候做 Native APP 使用的是 Unity。轉團結引擎是因為團結專門對 H5 做了大量的優化,包括剛才說的進游時間、內存優化、WebAssembly 內存峰值等當時使用 Unity 不可逾越的問題。我們等了大概半年時間,團結引擎 1.0 release 之后馬上跟進,確實能夠達到很好的效果。如果大家計劃做 H5,建議使用團結引擎。
下面分幾個方面展開:資源、管線、啟動、運行時優化、上線前部署。
資源管理
資源管理,分幾個角度展開:
![]()
首先是顆粒度的問題。APP 版打 Bundle 是好幾個資源放在一起,但 H5 版資源是按需下載,用到 A 的時候才下載 A,前期沒有辦法把所有的資源都下下來。如果下載的 A 資源處在比較大的 Bundle 中,會占用比較長的下載時間。所以我們把資源切到一個非常細粒度的層次,確保每個 Bundle 非常小,缺點是要下載 Bundle 的數量更多。不過 HTML 連接是復用的,實踐中比合并打 Bundle 的策略要好,做 H5 項目的時候可以考慮嘗試,Bundle 的粒度盡量控制得比較小。
![]()
第二,我們做微信小游戲用的是 WXAssetBundle,相對于 Unity Bundle 會做一個基于磁盤的局部檢索,加載里面某一個資源的時候只會讀取中間某一段資源的數據,而不是把整個 Bundle 放到內存里面去做讀取,對內存的占用比較有好處,也是微信小游戲、抖音小游戲官方推薦的機制。
![]()
資源壓縮也是老生常談了,關閉 Read/Write, Generator Mipmaps 選項,使用特定的壓縮規格。
![]()
具體的資源管理使用的是 AssetGraph 對每個資源進行壓縮規格的管理。
![]()
使用 SpriteAtlas 變體。因為我們一邊在開發 Native APP,一邊做 H5 版本,兩條線并行,所以 H5 方面我們對某些圖集做一些 Scale 壓縮,通過修改 AtlasVariant Scale 來實現 H5 的更加輕量化,同時保持跟 Native APP 相對穩定的鏈接。
![]()
再就是 Secondary Texture,有些 sprite 設置或使用了一些第二貼圖,但第二貼圖有時候是不必要的,這也是一個檢查點。
![]()
資源下載方面,之前已經講過推薦使用 WXAssetBundle。
![]()
卸載的時候需要特別注意,之前的 Bundle 在卸載時一般會基于引擎做一些輔助管理,做一些半自動的回收。比如保守的策略,在資源有可能在被占用的時候引擎就不會真正的卸載。但是到 H5 這樣對內存要求比較嚴苛的平臺,我們建議完全強制管理資源卸載,使用強制卸載的選項。缺點是某一處引用可能還在,沒有注意到,可能卸載之后游戲產生錯誤。建議讓這個階段在測試期盡可能暴露出來,并且修復,以此來換取比較干凈的或者更加受控的內存管理,這是策略上的選擇。
![]()
URP管線定制
接下來講 URP 管線定制。我們的游戲雖然是 2D 游戲,但對光影方面做了相當大的工作,有自己的光照系統。中間有一些流程管線的簡化都是共通的,比如移除 XR 相關、刪除不必要的 Graph 組件、3D 光影系統等。
![]()
接下來是我們針對這個游戲光影系統做的優化。SortingLayer 我們用得比較少,對游戲排序跟光影控制有我們自己的算法去實現,單獨做光影烘焙,包括對象排序都是自己實現的,所以光照相關的 SortingLayer 我們取消掉了。
![]()
混合模式管線里面帶了 4 種,我們都去掉了,只留下簡單的 Multiply,輔以美術對貼圖的控制處理來做集中方式的表現。視錐體的 culling 我們基于游戲本身的邏輯自己去做,對復雜的光照和視錐 cull 選擇性取消,也是一個可能的優化點。所有光源烘焙成小的光照圖,相當于緩存機制,實時布燈在場景里面,但布燈操作不是特別頻繁,通過 DrawMeshInstanced 把它渲染出來。
啟動速度 - 首包優化
接下來進入到首包優化方面的介紹。首包很影響游戲速度,分為資源包和代碼包兩部分。
![]()
通過 AssetStudio GUI 工具分析首包場景,可以發現一些明顯體積比較大的包體資源,包括一些不合理的引用進包,這是最容易忽視的。
![]()
進一步剔除掉一些沒有用的 Package。DL 剔除了 analytics、unityanalytics、recoder、android-logcat、uielement、androidjni、physics2d、terrain、unitywebrequestwww、vehicles、wind、remote-config、test-framework、xr、terrainphysics、remote-config、jobs、TeshMeshPro、Burst、Mathematics 也是不需要的。
![]()
接下來介紹團結引擎專有的 Wasm Inline Threshold。Inline 即內聯函數,把一些函數實現注入到調用的函數里面去,會增大代碼體積,但能夠顯著運行時期的性能,少了入棧出棧的操作。團結引擎這個選項我們目前會做一個控制,適當設置 Threshold 閾值,對運行期有幫助,這是一個可以參考的優化點。
![]()
代碼編譯選項設置,除非資源特別大,一般選擇 Faster Runtime。
![]()
然后是代碼的 Strip,團結引擎針對引擎代碼提供 Strip Advice 建議選項,對于一些不用的模塊可以勾上。
![]()
有的時候 Strip 比較多的時候團結引擎提供 DryRun 方式,可以在運行時把 strip 掉的并且仍然在調用的函數打印出來。
![]()
Lua 方面,我們使用了 Lua & xLua,也做了一些相關的 Lua Binding 的優化,對一些不需要用 API 的 Binding 通過黑名單去掉,精簡一部分代碼體積。
![]()
最后是 Wasm Analysis 工具,可以統計每個函數的指令數量及影響程度。對于一些不尋常的系統調用,或者一些間接的調用,可以通過這個工具發現有沒有剔除到位。
![]()
分包是微信開發者工具里面提供的代碼分包機制,把加載期的代碼跟后期使用的代碼或者不需要用的代碼分割或者剔除,這是非常重要的一個減少首包大小的方式。而且減少游戲加載之后的 WASM 編譯時間。
![]()
分包涉及到調用函數的收集,理想的情況下我們會把不需要調用的 API 或函數去掉,如果有新的函數調用增加進來,通過這個收集捕捉到,追加到名單里。它提供的 Profile 分析包就是用來收集的,在游戲中統計哪些函數可以調用到就把它保留下來,如果哪些函數沒有調用到就去除掉。有的時候一開始去的比較激進,調用失敗的時候會把日志打印出來,最終補充進去,最后通過 release 的方式生成和發布。
啟動速度 - 啟動優化
啟動優化,是進入游戲之前先代碼下載、代碼編譯、資源下載,再進入啟動畫面的階段。
![]()
啟動畫面出現之前的預下載階段比較重要,微信會在這個階段提供比較多的下載隊列,可以同時進行多個隊列下載。因為默認這個階段 CPU 渲染占用比較低,比較重要的資源建議放在這里下載。
![]()
下載的配置建議不要用固定文件名,而是要用文件名的 MD5 或文件名帶版本號,解決下載中緩存的問題導致更新失敗。
![]()
鴻蒙預下載機制目前有一些問題,后期可能會修復。
啟動速度優化的一些策略,首先第一個場景盡量小,最好只要一個背景圖,盡快讓用戶能夠看到你的游戲畫面。Shader 是很大的加載負擔,所以 Include Shaders 盡量能刪就刪,Preloaded Shaders 盡可能清理干凈,否則會顯著增加第一場景的進場時間。
![]()
關于 Shader 變體 warmup,Unity 默認提供了 Shader 變體 Warmup 機制,我們建議采用 WarmUpProgressively,或后面會推出的 WarmUpAsync 異步 Warmup 機制。因為 Unity 默認的 Warmup 是阻塞的方式,會導致 H5 在加載時卡很久。當時我們做的時候還是內部接口,后面應該會放開。通過異步的方式預熱 shader,避免游戲阻塞。
![]()
運行時優化
運行時優化分為以下幾個方面:
![]()
下載方面,可以考慮做一個任務池。后臺下載對 H5 非常重要,可以把一些高優先級的任務、必須立即下載的任務、非高優先級的任務等做隊列的管理。
![]()
文件 IO,跟 Warmup 一樣非必要不同步。H5 里面能不阻塞的調用盡量不要阻塞,包括 IO 一鍵讀取也會容易掉幀,推薦采用異步存取的方式。
![]()
字體方面,為了提高加載速度我們做了幾個層次的優化。剛開始進入游戲使用 2M 的只包含游戲內部使用的字體;后期需要加載較完整的字體,因為涉及到用戶姓名和聊天的顯示,會使用微信字體。
![]()
但微信字體在 iOS 18 后目前有一個 BUG 暫時不能使用,未來會修復。在這之前我們會提供一個全字體 fallback 機制,在加載失敗之后會 fallback 到全字體,所以目前是有三套。
![]()
再就是一些針對機型分辨率降低的適配方式。微信提供了 API,能夠獲取機器的等級。Benchmarklevel 是一個絕對的值,目前是不超過 50,它不會隨著時間推移改變,機器是多少級永遠是多少級。
![]()
ModelLevel 表示相對于當前年代,機型屬于當前的高端機、中端機、或低端機。它會隨著時間變化,比如今年的高端機型,過了五年變成中端或者低端。大家根據具體的范疇使用這兩個參數做相應的適配。
![]()
音頻方面也比較簡單,注意不要同時播放太多音效,盡量能夠自己去管理。我們除了背景音樂以外音效控制在 3 個以下,如果特別多會關閉一些音效。
![]()
Spine 是我們這邊 CPU 最大的瓶頸。首先是資產優化,再就是一些規格的優化。骨骼數同屏不超過 3000,單個 Spine 骨骼數不超過 70。但是我們角色比較復雜,為了讓骨骼數降低,我們對角色朝向進行了拆面。本來每個英雄所有朝向都在一個 Spine 里面,現在我們把各個朝向的角色拆成了不同的 Spine,可以節省運行期的內存和性能。
![]()
對一些比較小的個體不使用 Spine,我們把 Spine 烘焙成 GPU 動畫的方式,把 Spine 每一幀頂點信息存儲在貼圖里,然后在 GPU 里面根據這個時間去插值每個頂點的位置,把頂點繪制出來。這個方式有一個局限,必須要求 Spine 是純頂點動畫,如果 Spine 包含頂點之外的動畫變化就不能用這個方法。這個方法對性能有比較大的提升,特別是對屏幕數量特別多的小兵,優化還是非常大的。
![]()
再就是 Spine 讀取,使用 NativeArray 代替 TextAsst.byte,提升性能。
![]()
延遲加載是把英雄的動作進行拆分,每次按需加載一部分動作,而不是把所有動作都加載進去,可以減少內存的占用。我們本身項目角色動作會占用比較大的內存,多達 200M。
![]()
再就是視口裁剪等常規的手段,超過視口的 disable 掉,不讓它更新和計算。
![]()
其他優化,首先包括 UI 優化。整屏 UI 彈出來以后讓背景場景的渲染暫停。
![]()
第二,相機的 Culling 優化。團結引擎提供了 Skip Culling 功能,我們會做一些自己內部的 Culling,把 Culling 放在選項里面去,在需要自己 Culling 的時候就跳過相機 Culling,減少相應的開銷。
![]()
還包括做一些減法,比如 Native 中一些植被的動畫,我們在 H5 里面把它變成靜幀。
![]()
還包括 UI 方面做了一些比較大的精簡、特效刪減等。
![]()
Texture2DArray 無法正常支持,所以我們做光影烘焙的時候用的是 Texture 方式代替 Texture2DArray。
![]()
上線前部署
最后講一下上線部署。如果大家考慮要熱更的話,因為資源是通過 URL 去標識的,同一個 URL 會有緩存的問題,如果 URL 不變,文件內容變了,有可能會緩存到舊的,所以需要注意把版本號資源號放到文件名里,或者下載目錄包含版本號的信息,以作區分。
![]()
![]()
最后是 CDN 預熱。大家部署的時候可以在服務器這邊做 CDN 預熱機制,也會提高前期下載效率。
![]()
因為我們是一個比較新的項目,在開發過程中跟團結引擎團隊、微信團隊有密切的合作,謝謝大家。
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.