關於 web service, unity, blogger 等軟體工程筆記

Unity AssetBundle Variants 機制研究筆記

Edit icon 沒有留言
Unity

最近使用 AssetBundle Variants 機制來打包遊戲專案,紀錄打包遇到的問題以及解決筆記。關於 AssetBnudles 機制可以參考系列文章

用過 AssetBundle Variants 機制將遊戲打包成數多個語言版本,根據語言設定載入對應語言的 AssetBundles,成功完成多語言遊戲功能後,就放棄自行開發語言切換功能,因為這機制實在太方便了,又可減少玩家所需下載資料量。

Note: 使用版本 Unity5.6.x or above

關於 AssetBundle Variants

在 Unity 中 AssetBundles 機制能將遊戲內容打包成數多包的資源包,製作更新遊戲內容的機制。

而 AssetBnudle Variants 機制提供另一種資源打包方式,將資源包根據需求打包成不同版本,在遊戲進行中根據需求載入不同版本的資源包,進而更換不同版本的遊戲。載入不同版本的 AssetBundles,其遊戲內部資源參考將不會遺失 (Resource reference)。

例如可以根據語言種類打包 UI 貼圖,使用 AssetBundle Variants 機制將資源打包成中文、英文與日文版本等三個 AssetBundles,遊戲過程中,根據玩家語言設定載入對應語言包的 AssetBundles,讓遊戲 UI 自動使用不同語言版本的貼圖。

亦可以根據現實節日,跟遊戲貼圖以及資源打包聖誕節包、新年節包以及端午節包,根據現實時間修改設定,讓遊戲端載入不同節日的資源包,使得玩家也能在遊戲中享受過節日的氣氛。

簡易的測試專案

測試專案打包架構,Player 表示主程式資源,包含場景 Main;level 為 assetbundle,包含場景 Level,並且使用到 ~/Spring/Main.png;data.spring & data.fall 使用 variant 機制打包的兩個 assetbundles,包含相同檔名的 Main.png,但圖片內容不同

測試專案打包架構,Player 表示主程式資源,包含場景 Main;level 為 assetbundle,包含場景 Level,並且使用到 ~/Spring/Main.png;data.spring & data.fall 使用 variant 機制打包的兩個 assetbundles,包含相同檔名的 Main.png,但圖片內容不同

建立一個 AssetBundleVairants 的測試專案,其架構請參考上圖,進入場景為 Main (Entry scene),其場景內的程式會將 AssetBundle level 載入,並根據設定載入 data.spring 或是 data.fall,場景 Level 會根據載入資源不同,而顯示不同的圖片。

載入 data.spring 的結果

載入 data.spring 的結果

載入 data.fall 的結果

載入 data.fall 的結果

完整測試專案程式碼請參考 Github。

Github

AssetBundle Variants 運作原理

專案中資源的參考,場景 Level 使用 [GUID: c9ad] 來連結使用專案中的一張貼圖 ~/Spring/Main.png,Unity 會為每一個專案資源建立唯一的 GUID 來辨識以及連結資源

專案中資源的參考,場景 Level 使用 [GUID: c9ad] 來連結使用專案中的一張貼圖 ~/Spring/Main.png,Unity 會為每一個專案資源建立唯一的 GUID 來辨識以及連結資源

使用 AssetBundle Variants 打包後的資源參考,把包成兩個 AssetBundles,data.spring & data.fall,Unity 會根據資源檔名來建立一組新的 ID,場景 Level 改用該組新 ID 來連結資源。若載入 data.spring,則使用 ~/Spring/Main.png;反之載入 data.fall,則使用 ~/Fall/Main.png

使用 AssetBundle Variants 打包後的資源參考,把包成兩個 AssetBundles,data.spring & data.fall,Unity 會根據資源檔名來建立一組新的 ID,場景 Level 改用該組新 ID 來連結資源。若載入 data.spring,則使用 ~/Spring/Main.png;反之載入 data.fall,則使用 ~/Fall/Main.png

關於 AssetBundle 檔名

自行使用 Unity 底層 API 打包 AssetBundle 時,須設定此結構 AssetBundleBuild

strcut AssetBundleBuild
{
   public string assetBundleName;
   public string assetBundleVariant
   public string[] assetNames;
}

若 assetBundleVariant 設定為空字串或是 null,其打包出來的 AssetBundle 檔名為 assetBundleName,該名稱可設定 name.extension 的形式,例如設定 assetBundleName 為 scenes.assetbundle。

若 assetBundleVariant 有設定 (啟用 variant 方式),其打包出來的 AssetBundle 檔名為 assetBundleName.assetBundleVariant,注意 assetBundleVariant 名稱有限制不得設定 Dot (.) 符號

如果要客製化打包出的 AssetBundles 擁有副檔名,最好的方式是打包完後,再使用 System.IO.File.MoveFile 修改其檔案名稱。

設定 Variants 打包機制遇到的錯誤整理

Error: Variant folder path cannot be empty

AssetBundleBuild 有設定 AssetBundleVariant,但其欲打包 Assets 卻沒有在 Importer 中設定 AssetBundleName 所造成出現錯誤,參考以下解決方案:

  • 人工解法:在 Project View 選擇欲打包的 Assets,在 Inspector 編輯器下面設定 AssetBundleName
  • 程式解法:讀取 Assets 的 AssetImporter 並且設定其值
    var importer = AssetImporter.GetAtPath(assetpath);
    importer.assetBundleName = "example";
    importer.assetBundleVariant = "example";
    importer.SaveAndReimport();

注意:Asset Importer 設定的 AssetBundleName & AssetBundleVariant 不一定要與 AssetBundleBuild 中的 AssetBundleName & AssetBundleVariant 一樣

Error: AssetBundle names “data.spring” and “data” can’t exist in the same build as “data.spring” has the variant.

若 AssetBundleName ‘data’ 已經確定有使用到 Variant 機制,便不能再打包一個相同名字的 AssetBundle 卻沒有設定 Vairant。例如以下建置方式便會發生此錯誤:

var assetBundleBuilds = new AssetBundleBuild[]
{
   new AssetBundleBuild
   {
      assetBundleName = "data",
      assetBundleVariant = "spring",
      assetNames = new string[] { "Assets/Data/Spring/Main.png" }
   },

   new AssetBundleBuild
   {
      assetBundleName = "data",
      assetBundleVariant = "",   // <- Cause error
      assetNames = new string[] { "Assets/Data/Fall/Main.png" }
   }
};

BuildPipeline.BuildAssetBundles(outputPath, assetBundleBuilds, BuildAssetBundleOptions.None, BuildTarget.Android);

Error: Building AssetBundle failed because hash collision was detected in the deterministic id generation.

AssetBundle Variants 機制中,Unity 採用 Assets 檔名來產生新的 id,該 id 必定唯一,其用來內部資源的參考 (Reference)。若一包使用 Variant 機制的 AssetBundle,存在相同檔名的 Assets 時便會發生此錯誤。解決方法也很容易,需要修改檔名即可。

以下會發生此建置錯誤的範例:

var assetBundleBuilds = new AssetBundleBuild[]
{
   new AssetBundleBuild
   {
      assetBundleName = "data",
      assetBundleVariant = "spring",
      assetNames = new string[] {
         "Assets/Data/Spring/Main.png",
         "Assets/Data/Fall/Main.png",   // <- Cause error
      }
   },
};

BuildPipeline.BuildAssetBundles(outputPath, assetBundleBuilds, BuildAssetBundleOptions.None, BuildTarget.Android);

Error: Inconsistent asset when sorting preload assets (Same path & file ID)

考慮以下 AssetBundle 打包:

var assetBundleBuilds = new AssetBundleBuild[]
{
   new AssetBundleBuild
   {
      assetBundleName = "data",
      assetBundleVariant = "spring",
      assetNames = new string[] { "Assets/Data/Spring/Main.png" }
   },

   new AssetBundleBuild
   {
      assetBundleName = "data",
      assetBundleVariant = "fall",
      assetNames = new string[] { "Assets/Data/Fall/Main.png" }
   }
};

在專案中同時引用到 Assets/Data/Spring/Main.png 以及 Assets/Data/Fall/Main.png 的貼圖資源所造成的錯誤,修改專案來修正這個問題。

小結

打包 AssetBundles with variant 應注意以下事項:

  • 同一包內的資源檔名不能重複,因此將欲打包在同一包的資源,都放在同一層的資料夾最為穩妥,讓作業系統為我們檢查檔案必須唯一
  • Prefab 不是一個正常的 Asset 資源,沒辦法使用 AssetBundleVariant 機制自動切換資源參考 * 得使用 AssetBundle.LoadAsset 來載入 Prefab
  • 應該根據 Assets 所設定的 AssetBundleName & Variant 來建立 AssetBundleBuilds,讓編輯者根據需求來設定打包,而不要像測試範例寫死
    • 讓編輯者承擔設定錯誤的風險 (?)
  • 之後想到再補上……

系列文章

Reference

沒有留言: