在快速增長的小遊戲賽道上,團結引擎提供了什麼樣的一站式開發優化策略

近年來,小遊戲發展迅速並逐漸成爲遊戲公司業績增長的新引擎之一。僅微信小遊戲用戶規模已達 10 億,其中月活躍用戶 5 億。龐大的用戶基數爲開發者提供了廣闊的市場空間。 在 2024 年 11 月 2 日的 Unity User Group 深圳站,Unity 中國引擎底層架構技術主管兼小遊戲技術負責人趙亮和 Unity 中國引擎底層架構開發組長詹澤行帶來分享《團結引擎小遊戲開發指南》, 從實際案例的數據對比到底層優化的深入解析如何使用團結引擎 開 發加載更迅速、內容更豐富、畫面更精緻、運行更流暢的小遊 戲。 本文爲演講全程實錄, 請點擊文末閱讀原文下載 PPT 。 演講視頻可通過下方鏈接地址觀看。

https://www.bilibili.com/video/BV1exD3YpEuX/?spm_id_from=333.788.videopod.sections&vd_source=6ad5666ecbc7fe0e80d963da7e237d92

很高興跟大家分享微信小遊戲開發的技術。今天會從八個方面來介紹團結引擎對小遊戲的技術支持、優化和新功能。

遊戲案例

首先講一下游戲案例。從團結引擎今年 1 月 1 日上線到現在剛好 10 個月,目前已經有400多款微信小遊戲上線,都是通過團結引擎開發的,其中包括重度遊戲,如《誅仙手遊》小遊戲和《遠征2》。

這是誅仙遊戲的畫面,左邊三個截圖是安卓 native APP 的畫面,右邊是小遊戲的畫面,可以看到畫面上區別不大。

這個視頻是在微信小遊戲的實時錄屏,運行還是很流暢的。

《誅仙手遊》小遊戲最開始開發是在 Unity 2022.3 開發的,後來通過跟我們合作轉到團結。首先內存直接在 iPhone 14 上從 866MB 降到785MB,這個優化能夠讓遊戲在 iPhone 8 Plus 這款設備上連續跑2個小時,幀率也都是滿幀。在紅米 K30 Pro 的設備上可以明顯看到內存的優化,幀率也有略微的上升。

另外一款是客戶使用團結之後給我們反饋的遊戲數據。切換團結之後很快拿到比較大的內存收益,同時遊戲的卡頓率也下降了。

最後一個數據是近期跟我們合作的遊戲,大概花了兩週時間就把遊戲上線到了團結引擎。一週時間升級引擎,另外一週開啓團結的 GPU skinning,也獲得了非常不錯的效果,打包的首包大小、內存、CPU、FPS 都有一定的提升。

平臺適配

第二是聊一下平臺適配,因爲很多人沒有做過微信小遊戲。微信小遊戲和傳統的 native APP 之間有一些細微的區別,這些區別導致我們在開發過程中可能遇到一些困難。

首先內存,微信小遊戲內存在 iOS 上限制得非常嚴格,在比較高端的 iPhone 的峰值內存不應超過 1.4GB,如果超過 1.4GB 就會出現閃退,給用戶的遊戲體驗不好。在優化過程中,需要注意微信小遊戲上的內存佈局不太一樣,它除了傳統的UnityHeap還有其他的內存需要關注。

CPU這邊,因爲微信小遊戲是單線程狀態,多線程加速的計算是沒辦法做的。可能之前不是瓶頸的地方在微信小遊戲這邊會成爲一個新的瓶頸需要進行優化。Wasm 的執行效率不是很高,是原生的三分之一的左右。

渲染能力,微信小遊戲是支持 WebGL1/2 的級別,WebGL2 正好對齊到 OpenGLES3.0 的級別,如果是高於 OpenGLES3.0 以上的,比如 Compute Shader 是沒辦法使用的,導致遊戲以前 Compute Shader 做的後處理或者 VFX 都沒有辦法使用,需要做一些適配。

網絡通信方面,因爲一些安全問題,微信小遊戲沒有原來的 Socket,需要用 WebSocket 進行替換,基於 Socket 實現的 TCP/UDP 這些協議也需替換爲微信提供的 WXTCP/WXUDP。

文件系統,傳統的 native APP 很多遊戲資源放到遊戲包裡,或者是進入遊戲之前先下載下來,後面直接讀文件,都是同步操作。但是在微信小程序要進行快速啓動,首包非常小,其他資源是後續通過網絡進行下載,都是異步的加載,會涉及到遊戲同步到異步的改造。另外關於持久化存儲文件,小遊戲是從 WebGL 過來,WebGL 在瀏覽器裡是一個沙盒的環境,爲了安全會限制訪問設備本地的文件。所以遊戲退了之後有一些想要存儲的數據,必須要通過微信提供的特殊接口來存儲。

除了這些限制之外,我們在平臺適配的過程中還需要注意一些工程配置,比如紋理 ASTC 壓縮,能很大程度降低遊戲的內存。還有 il2cpp code generation 和 code optimization 的優化選項,對微信小遊戲性能的影響也是比較大的。

由於微信小遊戲是團結這邊新增的平臺,有些第三方插件及代碼也要進行一些簡單的設置,比如說設置插件勾上微信小遊戲平臺,以及微信小遊戲中不支持的插件推薦把它刪掉,因爲它會影響首包及內存。

前面也提到內存非常受限,所以有些選項要設置得比較合理,避免因爲突然的內存峰值超了 1.4GB,導致遊戲崩潰的現象。

啓動加載

第三部分說一下啓動加載。

這裡把小遊戲的啓動時序分成了三塊:

1. 代碼包及資源包的下載。微信小程序首包包含代碼包和資源包,第一塊優化是如何讓這兩個包儘快下載下來。我們推薦首包壓到 15-25MB,通過 Brotli 壓縮將 15-25MB 壓縮到 3MB,資源包也是差不多的水平。微信給我們提供的數據是,大多數用戶的下載速度大概是 2mb/s,所以一個遊戲進入的時間是可以通過包體大小和網速進行反推的。

這裡是我們提供的優化建議。

左邊是如何減代碼包,通過把多餘的 Package 和第三方插件刪除掉,以及避免很多 Lua 和 Protobuf 自動生成大量代碼。另外需要把引擎裡面的 Managed stripping level 調高,讓它裁減掉更多的代碼。最後是使用 WASM 代碼分包工具減低代碼。

右邊是如何讓它下載更快。首包資源一般就包含簡單的 Logo 和登錄頁面,這樣比較輕量。另外有些文字顯示需求,推薦用微信系統字體,這樣可以避免文字的下載。也推薦用一些 AutoStreaming 或者是 TextureManager 的工具自動化把資源處理掉,因爲有些項目是已經做好的項目,用這些工具更方便節省開發時間。

小遊戲啓動時序的第二部分,這部分主要是引擎的初始化和首場景的加載。下面是 BoatAttack 的啓動數據,可以看到它的啓動總共是 200 多毫秒。我們推薦首場景的複雜度一定要控制好,因爲引擎初始化耗時很短,控制好首場景能夠讓啓動更快。

這裡是我們對遊戲進入啓動的耗時分析,最多的耗時是 C# 代碼的耗時。第二是渲染進程 Shader 的編譯。第三是 Shader 本身的變體比較多,導致數據的解壓和解析比較慢。最後是紋理上傳,因爲小遊戲是單線程,它沒有辦法專門給一個線程上傳資源,所以紋理、Shader 的上傳會阻塞主線程。

我們給的優化建議是:

首先優化 C# 代碼,建議不要在 MonoBehaviour Awake 以及 Start 構造函數裡放太多邏輯,這些邏輯都是在啓動的時候首幀畫面顯示之前執行的。

第二,紋理音頻要用合適的壓縮格式,可以很大程度上減少 CPU 的傳輸帶寬,對功耗和內存都有幫助。

第三要減少 Shader 的解析和編譯,這裡主要是通過減少 Shader 的變體數量。儘量少用 Always include shader,少用 keyword 以減少 Shader 變體。

最後是取消 Auto Graphics API,只從 WebGL1/2 中選一個就可以,這樣 shader 變體可以少一半。

這裡是引擎側的優化,比如說託管代碼精簡和引擎代碼的剔除,會盡量讓 WASM 變得更小。我們打包時加了一個 Wasm 內聯優化,這樣分包可以把首包分得更小。資源方面也做了默認引擎資源精簡以及 il2cpp 元數據精簡。還有 AutoStreaming 裡面的工具可以幫助輕量的遊戲快速上線微信小遊戲。右邊是我們在運行時加速啓動過程的優化。

託管代碼精簡,主要從Managed code stripping level入手,我們在 Unity 原來 High Level 之上加了一個Extreme Level的級別,它的剔除更激進。之前的剔除不會剔除 MonoBehaviour 的代碼,而 Extreme Level 會把 MonoBehaviour 用不到的代碼也剔除掉。

爲了解決等級調高之後腳本丟失的問題,我們新增了一個Dryrun的模式,在這個模式下不會真正剔除代碼,而是在要被剔除的代碼中打一條 log,快速收集可能會被誤剔除的代碼,通過 link.xml 把它保護起來。

這裡是我們的Wasm 內聯優化。微信小遊戲分包是以函數爲粒度,一個函數用了就會保留,沒有用就被剔除,如果一個函數非常大,只用了其中一兩行就會全部保留起來,這是不太合理的。因此我們做了一個內聯優化的控制,通過限制內聯讓它變成更多的小函數,沒有用到的小函數會直接被剔除掉,之後得到的首包也會更小。在一款重度的 MMO 小遊戲上,首包從 25MB 降到了 23.7MB,降了 1M 多。

這是我們使用的界面,在打包時可以直接拖動進度條。值越少內聯會越少,值越大跟沒有優化前是一樣的。不推薦用太小的值,太小的話函數調用過程比較多,可能會影響運行效率。

這是IL2CPP 元數據精簡。IL2CPP 默認的元數據結構可以支持超過 21 億個類型或方法,但是小遊戲實際應用的大概是幾萬到十幾萬的級別。所以我們做了元數據精簡,打包小遊戲時,根據方法的數量自動選擇滿足當前要求的、精簡的元數據結構,這樣可以減少 global-metadata.dat 15% 的體積。

這是我們在運行時加速優化的數據。第一個是 IL2CPP 的初始化加速減少了 24ms,第二個是 GLES Device 的初始化也優化了 24ms,Shader 變體及 Monoscript 也減少了十幾毫秒,整體大概減少了 100ms 的樣子。

邊下邊玩

第四部分是邊下邊玩。

小遊戲啓動的時候,經常要求快速進入遊戲,所以邊下邊玩是比較重要的功能模塊。對於一些大遊戲來說可能有些框架用得比較好,直接用 Asset Bundle 邊下邊玩也可以做到比較好的體驗,但是這裡有一個問題是 AB 的加載時機、拆分粒度可能不太好,比如說遊戲內單個 AB 有 30-40MB,用戶點擊按鈕後就要加載很久。依賴關係和拆分 AB 的大小、粒度都是比較重要的點。

關於 AB 依賴不合理的問題,有遇到過一個 AB 依賴幾十個上百個 AB,這對於下載等待以及內存 footprint 都是災難。比如可能只需要一張紋理,但這個紋理所在的 AB 依賴了很多個 AB,導致原本只需要下載 1-2MB,變成不得不下載幾十 MB。

團結有兩個工具幫客戶快速做邊下邊玩,一個是AutoStreaming,在打包時將 AB 的重度資源放到雲端上,運行時會自動把這些重度數據加載回來。這個工具主要針對比較輕量的微信小遊戲,重度的遊戲推薦自己通過 AB 的機制優化。

另外一個工具是Texture Manager。微信小遊戲有一個比較尷尬的難點是 PC 上和手機上支持的紋理壓縮格式不一樣,而且沒辦法兼容,PC 只能使用 DXT,手機只能使用 ASTC,如果不符合這個要求就導致內存比較多,體驗不好。所以 Texture Manager 首先支持的是一個紋理可以打印多套紋理壓縮格式,運行時自動選擇,比如 PC 上使用 DXT,手機上使用 ASTC,不會導致性能問題。 二是 Texture Manager 會管理紋理的生命週期,紋理依舊存活但是沒有使用的情況下,會把紋理的數據從 GPU 上踢下來,節省 GPU 的顯存。 Texture Manager 也會在打包時做紋理重映射,我們發現不合理的 AB 組織結構或加載方式導致紋理重複造成浪費,Texture Manager 會進行重新處理,相同的紋理只會出現一張,不會重複。

內存優化

第五是內存優化,內存優化是小遊戲上線最重要的一個點。

這是小遊戲的內存分佈,和傳統的 native APP 不太一樣,除了比較熟悉的 UnityHeap、GPU,小遊戲獨有的內存還包括 WASM 編譯、音頻、基礎庫和 Canvas。音頻、GPU 顯存、UnityHeap 及 WASM 編譯這四塊是我們比較能夠控制的內容,接下來會聚焦這四塊的內存優化。

首先是Wasm 瘦身。Wasm 大小直接關係到編譯的內存,如果 Wasm 是微壓縮狀態,10MB 的 Wasm 大概佔運行時內存 70-100M,大概是 7-10 倍。我們推薦要對 WASM 瘦身,一般推薦上線遊戲首包 Wasm 不要超過 20MB,這是比較上限的位置。在 AB 加載這邊也經常看到用戶踩到一些坑,使用 Unity 默認的 Web request 下載,導致有一部分緩存長期駐留在內存裡沒有清理,這裡推薦直接把下載機制換成微信的WxAssetBundle,可以自動把內存沒有使用的踢掉。

對音頻、紋理這些資源要設置好分辨率和壓縮格式。微信提供了一個 Performance+ 方 案,原來小遊戲是 GPU 的顯存和內存是在同一個進程裡面,蘋果這邊是單個進程如果內存超過了 1.4GB 就給你殺掉了。而 Performance+ 會把 GPU 的渲染放到另外一個進程,相當於把把兩個內存放到兩個進程,單個進程的內存上限有提高,可以讓小遊戲被殺的概率減少。

引擎側我們也做了很多具體優化,來降低遊戲的內存,接下來我會詳細講講。

這是一個案例,誅仙這款小遊戲我們是從頭跟到尾的,所以比較清楚它的數據。最開始 Wasm 有 90MB,通過移除不用的 UnityPackage 和第三方庫降到了70MB多。經過精簡 Gameplay,調整遊戲裡的 Code Strip 設置和優化設置,最終降到了51MB左右的 Wasm ,用的是 il2cpp 方案, 經過微信的分包,最終得到首包的 WASM 是 20MB 多一點。如果切換到 .NET8 方案的話,因爲 .Net 不會把 C# 編譯成 WASM,WASM 會進一步降低到14.6MB。

.NET8是團結引擎特有的 backend,Unity 只有 IL2CPP,所有的 C# 都會轉化成 C++ 代碼,再編譯成 WASM。.NET8 直接把 DLL 通過容器封裝成 wasm 的格式,本質上還是 DLL,通過 WASM 上跑一個虛擬機來執行 DLL。.NET8 這個平臺在內存方面優勢是比較大的。

這裡是 IL2CPP 內存優化的實例,可以看到這款遊戲 il2cpp 總的內存佔用是 63.89MB,元數據的佔用是 37MB,通過 il2cpp 元數據延遲加載的優化,比如說把暫時用不到的 Method 等延遲加載,可以把元數據的內存從 37MB 降到13MB。

這是 Shader 內存的優化,以前引擎加載 Shader 的邏輯主要是把它所有的變體都加載進內存,爲了在微信小遊戲上減少內存,修改了邏輯,只是創建一個空的 Shader 變體,真正編譯 shader 的時候才從 Blob 數據把 shader 加載進來,這個優化在 Boat Attack 中減少了 30 多 MB的內存。

接下來是內存分配器優化。Unity 引擎內存分配器,會記錄每一次分配的信息,並且在這個層面引發多一次對齊,即使是在 release 版本也是如此。微信小遊戲內存非常緊張,爲了節省開銷進行了優化,把一些Overhead去掉,對齊也從 16 字節優化爲 8 字節,在中重度遊戲上也能夠減少 10-12MB 的內存。

這是Remapper的優化。Remapper 是一個表,它存的是對象序列化的位置和 InstanceID 之間的雙向映射關係。這個表如果同時加載資源非常多,就會非常大,內存可能達到 70-80MB。

之前的策略是 InstanceID 每次加二不復用,而且存在哈希表裡,這個哈希表是稀疏結構,內存是翻倍的增長過程。我們把它優化成了數組,它是緊湊的結構,每次都是線性增長。這裡可以看到優化後是 3MB。可能原本的大小就是 4MB,現在不夠了,如果是 MAP 就增長到 8MB 去了,但是數組可能只是4.1MB的增長幅度,不會有太多的內存空間浪費。

這是總的內存對比,左邊是小遊戲當前進程的內存,從 Unity 2022.3.13f1 切到團結 1.1.3 之後,內存大概是降低了55MB。右邊的表格是通過前面每個細節項對比拿到的數據,總計是 66MB,每次運行內存都有一些小波動,基本上和我們的數據吻合。

這些是前面表上的詳細數據。

這個是從團結的 Il2CPP 切到團結 .NET8 平臺的內存對比,可以看到內存直接從 755MB 降到了不到 600MB,降低了 150 多 MB。右邊的表格是預估內存的收益,能解釋大概 130MB 左右,其他是內存波動帶來的差異。

性能優化

第六是性能優化。前面講過 CPU 沒有多線程,以及 WASM 執行效率不高,所以性能的優化也是非常重要的。

WASM 的執行效率是原生 APP 的三分之一,這是我們找到的數據支撐。 渲染方面,除了一些渲染 Feature 不支持外,渲染本身執行效率和原生 APP 的差別不大,但是功耗方面可能會有一些上升。

我們提供了一些優化建議。

CPU 這邊,我們也看到了一些遊戲的 CPU 卡點。首先是解析遊戲的配置表,如果是 XML、JSON 的結構解析是比較費時間的,而且微信小遊戲沒有多線程的情況下,可能會卡幾秒鐘,推薦把配置表直接轉二進制的形式去讀會快很多。

另外,有些遊戲 LUA 邏輯比較重,到微信小遊戲相當於是多了一層虛擬機,執行效率是比較低的,所以避免使用 LUA 做重度計算。

另外還有兩個引擎裡面的優化選項:Il2CPP Code Generation和Code Optimization,這兩個選項對小遊戲的性能影響比較大,所以我們會單獨拿出來講講。

引擎側也做了很多優化讓性能得到提升,包括 GPU Skinning、Shader 並行編譯,也會在渲染方面優化,比如優化 glInvalidateFramebuffer 調用順序,清除多餘的 GL 調用等。

這裡重點提一下 IL2cpp Code Generation 在 player settings 中的兩個選項, Faster runtime 和 Faster (smaller) builds。如果選擇 Smaller,它會用 Full Generic Sharing 技術縮短打包時間,減少生成的 wasm 體積,這也可能會帶來一些性能的回退。如果你的遊戲是比較注重性能的話,可以選擇 Faster runtime,如果更看重遊戲包,本身性能還可以的話,比較推薦使用 Smaller Builds 的選項。

這是 Code Optimization 的選項,右邊列出了選擇不同選項的區別,我們的編譯參數、鏈接參數都不太一樣。我們推薦使用的是Runtime Speed的選項,至於 LOT 推薦可以去試一下,如果有異常可以回退到 Runtime Speed。如果遊戲的 WASM 真的特別大,而且性能還可以的話,也可以選擇 Disk Size 這個選項,可以讓 WASM 的體積變小,內存也會好一點。

這是在誅仙做的 Demo 對比,左邊是 CPU Skinning,因爲微信小遊戲沒有 compute shader,所以說原來基於 compute shader 的 GPU Skinning 這個方案是做不了的。我們通過 transform feedback 及 vertex shader 支持了兩種 GPU skinning 方案,差別很明顯,左邊不到 10 幀,右邊基本上可以跑到 30 幀的水平。

這是關於Skinning的介紹,GPU-Vertex Shader和GPU-Transform Feedback是在團結上新支持的兩個選項。開啓方式也非常簡單,Transform Feedback 直接打開就好了,Vertex Shader 就需要在 shader 代碼中改一行代碼。右上角的圖是需要修改的操作,加入一行代碼,下面的圖是修改完代碼,引擎的編譯器就自動處理成下面的結構,自動支持 GPU Skinning。

還有一些注意事項。目前Vertex Shader GPU Skinning是我們比較推薦的方式。Transform Feedback 方案在模型比較小、數量比較多的時候,我們確實觀察到了性能回退的現象。

Vertex Shader GPU Skinning 有一些限制條件,首先是動畫骨骼不能超過 64 根,我們現在也在拓展,當前如果超過了需要做一些拆分。引擎內部的 Shader 大部分已經自動支持 Skinning,用戶這邊的 shader 就需要加一行代碼。

GPU Skinning 方案和原來的 CPU skinning 是可以共存的,可以只改一部分比較重度的代碼,有些不好改的繼續用 CPU Skinning 方案,渲染還是一樣的。

最後一個注意點是一個 renderer 上可能有多個材質,如果要支持 GPU Skinning 需要把每個材質所使用的 Shader 都需要修改纔可以,否則會回退到 CPU Skinning 的狀態。

這是我們對 shader 並行編譯的優化。前面也提到 shader 編譯單線程,不支持 binary shader 的問題導致編譯比較慢。引擎本身是支持 Warmup 的接口,但是因爲 WebGL 這些缺陷,可能調用一個 warmup 接口會卡 4s,體驗非常不好。

我們是基於 WebGL 的 KHR_parallel_shader_compile 拓展,去做了一個 shader 並行編譯,相當於我們只是提交了一個 shader 編譯,主線程就可以離開去做其他事了,過段時間再來檢查這個 shader 狀態就好了。這樣對主線程的阻塞時間從 4s 降低到 100 多 ms。

這是SIMD 支持,目前引擎大多使用的還是標量運算,對一些性能不是很友好。所以我們在 Math 庫上支持了 Wasm 的 SIMD。爲什麼選擇 Math 庫呢?因爲引擎裡包括 transform、動畫、粒子等很多都依賴 Math 庫,這樣可以讓引擎整個都能享受到 SIMD 優化的好處。需要注意的是 SIMD 在 iOS 上是 16.4 版本上才支持,如果微信小遊戲用戶羣覆蓋廣,可能這個功能暫時還不能用上。

異步實例化, Unity 2023.3 裡面加了這樣一個接口,團結針對這個接口做了一些修復,特別是針對微信小遊戲的處理。

首先同步 instantiate 是單幀執行、單幀完成的。第一步是反序列化、構建對象。第二步是初始化對象,包括 script Awake, upload texture, upload mesh 等耗時操作。

如果是用 InstantiateAsync 異步就是把這兩步拆分到兩幀去做完。如果它是在 native 平臺,可以通過 job 加速對象的構建。但是微信小遊戲的環境比較特殊,它是單線程, JobSystem 無法加速,另外上傳的資源沒有辦法放在另外一個線程,所以 Integration 這步耗時會相對更長。所以這裡針對這一步做了一些拆分,讓它可以在 integration 這步分成更多幀執行。拆分的過程中,會確保子任務執行的順序和原來一模一樣,不會出現順序的問題。

這裡面也會涉及到部分對象之間是有關聯的,比如說粒子和 Collider,所以還是有切分粒度的問題。但是腳本和資源是可以切分到很細,單個腳本和單個資源都沒問題的。

這是我們在實際案例上測試的數據。 上面同步是在單幀內73 毫秒執行完,異步是4 幀把 instantiate 做完。

總結起來數據是左邊的表,可以看到如果是同步實例化是 73ms,異步是 84ms,如果從單幀最高時間及平均每幀時間來看,總共 7 幀,同步單幀卡頓最長,平均每幀率耗時也更高一些。

所以異步實例化解決了兩個問題,一是可以減少遊戲的卡頓,二是一定程度上把平均幀率提上去,可以把工作分攤到更多的幀裡。右邊是用真實遊戲測出來的數據,通過 InstantiateAsync 改造之後,卡頓率和 FPS 都有所改進。

開發提效

最後提一下對開發提效的工具支持。

首先,1.3 版本新增了Wasm 分析工具,用戶可以在打包時勾選一個選項,或者是打包之後讀 Wasm 文件,告訴用戶哪個模塊佔了多大的體積。打包時可以給用戶一些選項,兩次打包可以進行對比,發現哪些 Wasm 是可以優化的。

現在還只支持完整的 wasm 包,未來我們還會支持經過微信分包之後的 Wasm,並且還可以進行 wasm 差異對比,以及 wasm 函數名的排序、查找功能。

另外是我們做了一個Dev Host 安卓的小遊戲宿主,比微信這邊有三個優點:高時鐘精度 Timeline profile、支持 Frame Debugger,還可以做一些代碼調試。以前微信 WebGL 平臺做不到像 Frame Debugger 和 C# Debugging,受平臺限制沒有辦法實現這些功能。

啓動這個工具也很方便,直接在 Package Manager 裡搜索 cn.tuanjie.minigame.host 即可。點擊構建並上傳後,使用 Connect App 掃碼即可運行。

這裡說一下我們的時鐘精度,這是我們在微信開發者工具裡面拿到的性能數據,可以看到這裡有一個算矩陣的逆的操作,花的時間是 0.2ms,這是個很簡單的算法,不可能花到這麼多,所以說這個信息可能會對優化函數造成誤導。可能只是採集性能數據時剛好採到這個點,認爲後面所有的 0.2ms 都是這個函數消耗的。

這是我們對比了幾個不同的平臺看時間精度,微信真機上通過 Unity profiler 去連接,拿到的時間精度最多是 1ms;移動端 chrome 是 0.1ms;如果到 Dev host 宿主裡面是 0.001ms。這三個圖都是同一個函數的時間調用,時間精度上升之後就能看到比較細的函數的調用時間,幫助我們優化。

這裡是Frame Debugger的視頻,目前支持在微信小遊戲、安卓及 iOS 使用 Frame Debugger 的能力,來查看比如有沒有 batch 和渲染出錯等問題進行調試。

最後是C# Debugging,之前有用戶抱怨我們做微信小遊戲查 Bug 只能打 log,所以我們加了一個 C# Debugging 的能力。

之前爲什麼不支持這個能力是有原因的,首先 Debug 需要有一個調試的 Agent,所以要有多進程。另外像 visual studio、Rider 接受消息都是 Socket 協議,但 WebGL 不支持 socket,只支持 Websocket,兩邊協議是不兼容的,沒辦法直接建立連接。還有一個是 Websocket 沒辦法監聽、廣播。

針對微信小遊戲平臺,我們首先增加了多線程支持,讓 Debug Agent 在單獨的線程上運行起來。然後在小遊戲宿主中新增一箇中間代理,一邊通過 Socket 去跟 IDE 交互,另一邊通過 Websocket 跟 Debug Agent 交互。這個中間代理具備廣播和監聽端口的能力,讓 IDE 可以方便的搜尋到需要調試的小遊戲。

使用起來也比較方便,首先要在打包的時候把 Script Debugging 給勾上;在宿主上需要去點一下打開 C# 代碼調試按鈕。同時確保 IDE 和宿主運行在同一個局域網內,就可以建立連接,進行代碼調試了。

現在暫時不支持 .Net8 scripting backend。

這是使用過程中的截圖。

左圖顯示在 Rider 中點擊 Attach to Unity Process 後,就能搜尋到可以調試的微信小遊戲進程。 右圖顯示 IDE 調試窗口,可以看到當前中斷的代碼行,變量的數值,以及執行的堆棧。

未來展望

接下來介紹一下我們正在做的工作,以及接下來的計劃。

現在做的工作主要是獨立渲染線程。剛纔提到很多小遊戲上只有單個線程,帶來的問題是主線程時間消耗很長、幀率比較低,而且會帶來功耗的問題,不能發揮出多核優勢。

同我們還在做GPU Instancing,之前選擇了 SRP Batch,它的優先級是高於 GPU Instancing 的,會導致雖然 set pass 比較低,但是它 draw call 數還是比較高的,這也是一個值得優化的地方。

還有動畫的壓縮,用了 ACL 的壓縮之後,可以使 Animation clip 變得更小。

同時我們也對 C# 的編譯器在做一些優化。 再有就是平臺渲染能力的拓展,是我們跟微信一起在探討的,如何把小遊戲平臺的渲染能力進一步提升。 接下來給大家詳細介紹一下這幾點。

首先是獨立渲染線程。這是當前的進展,目前可以支持Mesh Renderer、Skinned Mesh Renderer、UI canvas都是可以支持的。測下來在 iOS 和安卓上都有比較明顯的功耗下降,在安卓上有 FPS 的提升。預期在明年上半年發佈。

大家看一下具體的測試數據,在 iPhone 14 上,原本單線程的執行時間是 2.8+4+4.7ms,雙線程的整體時間消耗多了一點,但是看一下測試電流的功耗反而下降了,因爲核降頻了,降頻之後運行的效率更高。

在天璣 9300 上,開了雙線程之後,把渲染單獨拿到一個線程上執行,整體這一幀 frame time 的是降低的,幀率有所提升,同時功耗下降。

在驍龍 865 上跑,也是和天璣 9300 類似的結果。Frame time 降低,同時功耗下降。這對小遊戲來說是幫助挺大的 feature,同時也需要微信那邊配合我們把小遊戲的多線程環境支持做好,目前的方案已經有了,微信正在做基礎設施的建設。

這是獨立渲染線程大概的原理。小遊戲上沒有像在原生 APP 上一樣搞很多 thread,目前在 WebKit 和 V8 的環境中每多跑一個線程,內存的開銷增加都蠻高的,不像原生的 APP 上多增加一個線程都是 free 的情況。所以我們只增加一個線程,減少內存的增量,同時獲得多核的收益。

需要注意的地方是採用獨立渲染線程之後,Game Thread 和 Render Thread 並行處理的不是同一幀的,當 Render Thread 處理第 N 幀的時候,Game Thread 已經處理了第 N+1 幀,所以說我們需要把第 N 幀的渲染場景做一個快照,從 Game Thread 傳到 Render Thread 去,使得 Game Thread 處理第 N+1 幀率的時候,Render Thread 處理第 N 幀,它們相互之間不會有衝突。

接下來介紹 SRP Batch 上增加了GPU Instancing的支持。這個 feature 我們預期在團結的 1.4.0 即 2024 年 12 月底可以發佈出去。右側有一個 profiler 的結果,打開了 GPU Instancing 之後,我們的 Setpass 數是差不多的,但是 Drawcall 數會大幅降低。假如不開 Instancing,對於有些場景 instance 數量特別多的情況下,drawcall 有可能到 5000 以上,打開之後可以通過 Instance 的方式繪製,這樣 Drawcall 的數量降幅會非常明顯,變成之前六分之一的樣子。無論是 iPhone,還是安卓,高端和低端設備上都能看到 FPS 有明顯的提升。

對 C# 編譯器我們也做了優化。一是做更好的增量分包,比如小遊戲發了一個包,隔一段時間對代碼做了一個小的改動,需要再更新一個版本。這個時候我們不希望重新做一次採集分包的過程,因爲採集分包比較耗時,而是想利用第一次採集的結果。這意味着做少量代碼改動時重新編譯生成的 WASM 裡面的函數簽名需要很穩定,這樣原來採集的信息纔是有效的。Unity 原本用的 Rosyln 編譯生成出來的函數名不是很穩定,主要是 Lambda 的表達式裡有一個 MethodOrdinal,編譯器對於一個函數假如有重載,在裡面會通過數字標識不同的重載,這個數字生成的時候不穩定,導致可能代碼壓根沒有改動,但是兩次編譯函數生成的簽名不一樣,導致採集 broke,這一塊我們打算修掉。

第二是,用 foreach 遍歷 List,跟不用 foreach 來遍歷 List,會發現這兩個生成的 Wasm 代碼差異非常大。發現 foreach 在 Rosyln 編譯的時候會進行展開,裡面會增加一個 try-catch。假如自己從 list 拿一個 enumerator 進行一次循環的話,生成的 WASM 大概只有 100 多條,但是如果套了 Try-catch 之後,再調取 disposable,就會有 500 多個 instruction。當一個遊戲裡大量使用 Foreach,會導致帶來很多 Wasm 的體積膨脹及運行時內存增加。Try-catch 很多時候發現它不是必需的,所以我們準備在 Rosyln 的編譯器裡面給它一個選項,當你選擇不需要 try-catch 的時候就可以優化掉。

動畫壓縮,主要是集成 ACL 庫,它有更高的壓縮比。用一個 animation clip 進行對比,不開啓壓縮佔了 327K,原本通過 Keyframe Reduction 或者是 Optimal 的方式能降到 62K,用了 ACL 之後能夠降到更低,是 Optimal 的六分之一,只有 10.3K。這對於小遊戲特別有用,讓打包的 animation clip 資產變小,使得加載更快,包體更小,同時運行時內存也有相應的降低。ACL 對於 animation clip 的 sample 也會更快。我們預計也在 12 月底發出這個 feature,性能收益也會整理出來。

渲染能力的拓展,這是我們跟微信共同研究推進的地方。目前的思路主要有三個:

1.在當前的 WebGL 裡增加對 computer shader、SSBU 或者是 indirect draw 的支持。

2.對 WebGPU 的支持,使得更新的 Graphics API 得到支持;

3.嘗試讓小遊戲直接使用 Vulkan/Metal。

這樣有很多好處,可以不再受限 WebGL 2.0,也不侷限使用 WebGPU。因爲 WebGPU 本身走向成熟需要一定時間,而 Metal 當前就是成熟的。另外在使用 WebGPU 的情況下,手機底層驅動還是 Metal,需要一個 WebGPU 到 Metal 的轉換過程,它不是 Free 的。我們跟微信探討直接在 Performance+ 這樣的方案上支持 Metal,通過這種方式,未來大家就可以在小遊戲上使用上Visual Effect Graph和GPU Resident Drawer這樣的功能,小遊戲上繪製的場景天花板會逐漸增高。這個功能有可能會到明年的 Q2、Q3 發佈,需要我們跟平臺側把能力共建起來。

最後歡迎大家掃碼加入小遊戲官方交流羣,如果有問題可以在 QQ 羣裡溝通。

謝謝大家。

從超休閒到MMO、策略遊戲等中重度遊戲,小遊戲的品類越來越豐富,用戶體驗不斷提升。Unity 中國不斷加大對小遊戲的支持,推出的產品“團結引擎”在小遊戲開發的輕量化、多平臺適配、穩定性提升等方面做了大量優化。2024 年 12 月 6 日 Unity Open Day 技術開放日廈門站將聚焦小遊戲及開源鴻蒙平臺的技術實現及市場探索,探討如何利用團結引擎的最新技術,爲小遊戲賽道、移動端遊戲注入強勁增長動力。還有閉門 Round Table ,直接對話引擎研發!亮點多多,點擊!

Unity 官方微信

第一時間瞭解Unity引擎動向,學習進階開發技能

每一個“在看”,都是我們前進的動力