Unity 小遊戲轉換(一)—— WebGL+XLua匯出

小小钊發表於2024-06-23

轉載或者引用本文內容請註明來源及原作者

一、前言

  • 小遊戲的紅海賽道,給遊戲市場帶來了新的活力。小遊戲依託微信、抖音等第三方平臺,因為買量成本較低、開箱既玩的特性,使得許多開發廠商開始佈局小遊戲平臺。同時Unity引擎也花費了大量的精力(團結引擎),慢慢更改開發者對於Unity龐大繁重,不適合開發微信小遊戲的刻板印象,目前市面上已經有多款Unity引擎開發的遊戲經受了市場的檢驗。當Unity引擎解決了這個核心的缺點,Unity引擎在統一公司開發棧、可程式設計渲染管線、活躍的社群平臺、良好的外掛生態等方面,具有別的引擎系統無法比擬的優勢。
  • 可以想象,當遊戲開發初期,可以透過Windows、Android等平臺,快速驗證遊戲玩法,確定遊戲原型。後期可以根據公司策略,快速匯出不同平臺進行釋出。同時Unity在表現力上有更高的上限,也能夠保證美術效果的還原。
  • 當然,Unity通向小遊戲的路上,最重要的一點就是匯出WebGL工程。當整個工程能夠匯出WebGL工程並執行,後續的小遊戲處理工作,大部分只需要對接不同平臺的SDK即可。

二、WebGL匯出

本文不討論WebGL的匯出流程,我們聚焦於Web平臺 + XLua構建遇到一些棘手的問題。詳細的匯出教程推薦構建和執行 WebGL 專案(Unity官方文件)Unity WebGL 開發指北(完全篇)等等

1. 如何引入XLua

  • 步驟1:將WebGLPlugins放到工程的Assets同級目錄下

    • 注意:官方使用的Xlua版本為Lua5.3,如果使用低版本或者LuaJit需要注意業務語法是否支援。
      • LuaJit中會處理迭代安全(列表遍歷移除自身),而Lua5.3中則不會
      • string.format("%p", value)在標準Lua5.3中不可用,是LuaJit的擴充方法
      • Lua5.3不支援math.pow,透過擴充math方法進行解決
  • 步驟2:在Assets/Plugins/WebGL下建立一個Include檔案,用來宣告用到的lua庫檔案。這裡我取名叫xlua_webgl.cpp,將第一步驟下的WebGLPlugins內的lua檔案加到這個宣告檔案中(當然也可以包含自己寫的C檔案),邏輯程式碼如下:

    • extern "C" {
      #include "../../../WebGLPlugins/lapi.c"
      #include "../../../WebGLPlugins/lauxlib.c"
          等等...
      #include "../../../WebGLPlugins/perflib.c"
      #include "../../../WebGLPlugins/xlua.c"
      }
      

2. 指令碼編碼格式問題

  • 問題:

    • 當你匯出的WebGL工程執行起來遇到這個問題:unexpected symbol near '<\239>',意味著你遇到了編碼格式的問題。最常見的原因是你的檔案中包含了UTF-8的BOM(Byte Order Mark,位元組順序標記)。BOM是一個可選的字元,用於標記檔案大端還是小端。它的二進位制表示為 "11101111 10111011 10111111",轉換為十六進位制就是 "\xEF\xBB\xBF"。很多文字編輯器在儲存UTF-8檔案時會自動新增BOM,但是一些語言處理器(如Lua、PHP和JavaScript等)不識別BOM,它們會把BOM當成一個不可識別的字元而報錯。

  • 解決方法:

    • 透過命令列快速的處理: grep -r -i -l $'^\xEF\xBB\xBF' . | xargs sed -i 's/^\xEF\xBB\xBF//g' ,就是遞迴地在當前目錄下刪除所有檔案中的BOM。
  • 原因:

    • 為什麼在App中不需要關心字元編碼,但是網頁端卻需要呢?原因在Web開發中,字元編碼的設定非常關鍵,因為它直接決定了使用者看到的網頁內容是否會出現亂碼。因此,開發者需要在網頁的頭部明確指定字元編碼,這樣瀏覽器才能正確地解析和顯示網頁。但是,如果伺服器傳送的字元編碼和網頁頭部的字元編碼設定不一致,或者文字中包含了某些特殊字元(如BOM),就可能會導致顯示問題。
    • 而App的開發和Web開發不同,編碼問題相對要少一些,這主要是因為App中的字串和文字通常都是開發者直接控制的,而且很多程式語言都預設支援UTF-8,並且能夠處理一些特殊字元。另外,App的介面也不依賴瀏覽器去渲染,而是由作業系統直接繪製,因此它們不太可能因為字元編碼的問題出現顯示錯誤。

3. 禁止使用任何 .NET 網路類

  • 問題:
    • 當你在App上透過Socket與服務端建立連線的邏輯,在WebGL上會發現連握手都握不上
  • 解決方法:
    • 方法一:透過UnityWebRequest,官方有詳細的使用方法
    • 方法二:透過JSPlugin,這裡我使用了第三方庫UnityWebSocket。直接透過PackeManager可以方便的進行安裝和擴充
  • 原因:
    • JavaScript 程式碼無法直接訪問網際網路協議 (IP) 套接字來實現網路連線。具體來說,WebGL 不支援System.Net名稱空間內的任何 .NET 類

4. 禁止使用阻塞程式碼

  • 問題:

    • 使用類似while(!www.isDone()) {}來等待業務完成,會導致WebGL工程直接卡死
  • 解決方法:

    • 透過協程,配合yield等待來完成下載。可以繼承CustomYieldInstruction封裝一些自己的業務載入器。類似的程式碼:

      public class CoroutineLoader : CustomYieldInstruction
      {
          private bool _keepWaiting;
          private string assetName;
          public override bool keepWaiting => _keepWaiting;
      
          public object asset;
      
          public CoroutineLoader(string assetName)
          {
              this.assetName = assetName;
              asset = null;
              _keepWaiting = true;
              AssetUtil.LoadAssetAsync(assetName, (cacheAsset) =>
              {
                  asset = cacheAsset;
                  _keepWaiting = false;
              });
          }
      }
      
  • 原因:

    • WebGL 是單執行緒執行,所以沒有子執行緒去修改等待的變數的值,這將導致你的阻塞程式碼無法跳出迴圈,最終卡死

5. 資源同步載入

  • 問題:
    • WebGL不支援資源同步載入,以及ab檔案流的非同步載入,所有的ab檔案都在雲端儲存上,需要透過UnityWebRequest進行請求
  • 解決方法:
    • 業務上所有的資源載入都透過非同步載入進行,Lua上的非同步封裝可以參考我之前的文章Lua中最佳化的非同步封裝。如果必須使用同步介面進行載入,這邊有兩個解決方法:
      • 透過Resources.Load進行載入,將資源放到Resources資料夾下。WebGL在打包的時候。會將這部分的資源以二進位制原始檔的形式,一起編譯到WASM中,使得邏輯程式碼可以正常同步載入
      • 透過提前載入ab檔案,在確定資源所依賴的ab檔案都在記憶體中,透過ab去載入資源,當然可以使用同步介面。專案在處理Lua檔案的require介面,也是這樣處理的。專案啟動的時候,先將Lua所有的ab都載入到記憶體裡面,然後在啟動Lua虛擬機器進行Lua的呼叫
  • 原因:
    • Web端是沒有檔案IO系統,並且因為Web開箱即用的特性,所有的資源必須在遠端伺服器上

6. Lua虛擬機器 GC執行緒問題

  • 問題:

    • WebGL平臺對執行緒支援不完善,在Lua虛擬機器GC的時候,會看到Web端有個報錯:ArgumentException: Destination array was not long enough. Check destIndex and length, and the array's lower bounds
  • 解決方法:

    • XLua GitHub上有個兄弟有個解決方案:https://github.com/Tencent/xLua/issues/741,親測有效
  • 原因:

    • 具體原因連結上有討論,可以詳細看一看

7. InputField在手機瀏覽器無法輸入問題

  • 問題:
    • WebGL中無法識別InputField喚起鍵盤,進行輸入
  • 解決方法:
    • 引入第三方庫:WebGLInput,直接在掛載InputField的go上,新增WebGLInput即可
    • 這邊需要注意微信小遊戲平臺問題,新增宏定義,小遊戲平臺有自己的支援方式,在使用WebInput會衝突。可以透過宏定義進行不同平臺的相容處理

8. iOS瀏覽器記憶體限制

  • 問題:
    • iOS瀏覽器下執行會崩潰,但是Android平臺下正常執行
  • 解決方法:
    • 檢查ab大小和依賴關係,儘量能夠單資源單ab,減少單ab大小和ab之間的依賴關係。避免載入資源的時候,需要載入大量的ab包
    • 記憶體分析,看是否沒有對無用資源進行記憶體解除安裝。需要嚴格控制執行時的執行記憶體
  • 原因:
    • iOS裝置的瀏覽器記憶體限制取決於裝置型號和作業系統版本。一般情清況下,iPhone和iPad瀏覽器的記憶體限制分別為256MB和512MB。這意味著當頁面超出了記憶體限制時,瀏覽器將會崩潰。

總結

  • 當你能夠匯出Web端執行時,並且在Web網頁上正常執行,證明已經完成了微信小遊戲適配70%以上的工作
  • 特別關注iOS下,遊戲執行的記憶體限制。可以透過Unity匯出帶Debug資訊,結合XCode工具進行除錯。當iOS效能最佳化成功,Android基本不會有大問題

相關文章