1. 程式人生 > >Unity3D 5.3 新版AssetBundle使用方案及策略

Unity3D 5.3 新版AssetBundle使用方案及策略

Unity3D 5.3 新版AssetBundle使用方案及策略

    <div class="postBody">
        <div id="cnblogs_post_body" class="cnblogs-markdown"><p><img src="http://images2015.cnblogs.com/blog/686199/201606/686199-20160630112357812-722253466.jpg" /></p>

1.概覽

Unity3D 5.0版本之後的AssetBundle機制和之前的4.x版本已經發生了很大的變化,一些曾經常用的流程已經不再使用,甚至一些老的API已經被新的API所取代。
因此,本文的主要內容就是分析5.X版本的AssetBundle機制(包括建立資源包、壓縮資源包、載入資源包和從資源包中載入/解除安裝資源

等幾個方面)及其關鍵的API使用方式並總結一些對專案的建議(例如根據不同的情景,選擇不同的包體載入方案等等)。

2.AssetBundle系統的新功能

本小節包括:

  • AssetBundle系統的新功能
  • 新的AssetBundle系統的優勢

2.1.AssetBundle系統的新功能

在新的AssetBundle系統中,出現了以下的新功能:

  • 通過Editor中的UI即可方便的為AssetBundle標記資源。而且一個資源和對應的AssetBundle的對映將會在資源資料庫(AssetDatabase)中被建立。

    在箭頭處即可指定該資源所述的AssetBundle,第一個選項為AssetBundle的名字,而後一個選項則是為AssetBundle建立變體,例如一些素材需要區分為高清或普通存放在不同的AssetBundle中,那麼第二選項就可以以hd和normal來區分。
  • 提供了新的API用來設定資源所屬的AssetBundle:
  • 設定AssetImporter.assetBundleName的值,即可為該資源指定它所屬的AssetBundle。上文中在UI中設定的AssetBundle的名字便是為該值賦值,在資源有了assetBundleName之後,實際上它的資訊就已經存在於AssetDataBase裡面了。
  • 新版本中,建立AssetBundle檔案的API變得十分簡單了:
  • BuildPipeline.BuildAssetBundles():我們只需要提供一個輸出AssetBundle的地址即可。引擎將自動根據資源的assetbundleName屬性(即在上文中UI中設定的值)批量打包,自動建立Bundle以及資源之間的依賴關係。
  • 新增了一些打包策略/選項,且一些4.x中的舊有策略被預設開啟。
  • CompleteAssets ,用於保證資源的完備性,預設開啟;
  • CollectDependencies,用於收集資源的依賴項,預設開啟;
  • DeterministicAssetBundle,用於為資源維護固定ID,預設開啟;
  • ForceRebuildAssetBundle,用於強制重打所有AssetBundle檔案,新增;
  • IgnoreTypeTreeChanges,用於判斷AssetBundle更新時,是否忽略TypeTree的變化,新增;
  • AppendHashToAssetBundleName,用於將Hash值新增在AssetBundle檔名之後,開啟這個選項可以直接通過檔名來判斷哪些Bundle的內容進行了更新(4.x下普遍需要通過比較二進位制等方法來判斷,但在某些情況下即使內容不變重新打包,Bundle的二進位制也會變化),新增。
  • ChunkBasedCompression,用於使用LZ4格式進行壓縮,5.3新增。
  • Manifest檔案。在4.x版本中,我們通常需要自行維護配置檔案,以記錄AssetBundle之間的依賴關係,並供執行時使用。而在5.x版本中,使用Manifest檔案可以免去4.x版本中的這一過程。而Manifest檔案分為兩種:
  • 單個bundle的Manifest檔案,一旦一個新的AssetBundle檔案被建立匯出,便會對應生成一個.manifest檔案,其中包含了校驗、依賴檔案等資訊。所以可以用來做增量更新。
  • 實際上在打包的時候,在輸出的bundle所在的資料夾內還會生成一個總的manifest檔案,叫做[資料夾名].manifest。它包含了該資料夾內所有的bundle的資訊,以及它們之間互相依賴的資訊。所以在我們載入bundle的時候,需要先把總的manifest檔案載入進來,以確認各個bundle之間的依賴關係。
  • 一些在執行時動態載入AssetBundle的API被新的API代替。
  • 4.x版本中的AssetBundle.CreateFromFile方法,在5.x版本中變成了AssetBundle.LoadFromFile方法。
  • 4.x版本中的AssetBundle.CreateFromMemory方法,在5.x版本中變成了LoadFromMemoryAsync方法。
  • 4.x版本中的AssetBundle.CreateFromMemoryImmediate方法,在5.x版本中變成了LoadFromMemory方法。

2.2.新的AssetBundle系統的優勢

由於引擎提供的這些新功能,我們就不再需要像4.x時代那麼複雜的用來打包的指令碼了。
同時,資源之間的互相依賴關係不再需要開發者手動維護了,曾經由於不當使用PushAssetDependencies/PopAssetDependencies而可能會造成依賴出現的問題,現在Unity3D已經為我們解決了。
而且由於引入了清單檔案manifest,因此我們可以實現增量更新,即只需要更新有變化的部分,而沒有變化的則不必更新。
舉一個例子:
假設我們有一個cube,它的material有一個材質,我們分別將cube和material打包成cubeBundle和materialBundle,之後我們修改material上的材質。在過去,我們需要分別重新為cube和material打包,而現在只需要對material重新打包即可,cube不受影響。

3.AssetBundle檔案的建立

本小節包括:

  • 舊有建立AssetBundle檔案的API
  • 新的建立AssetBundle檔案的API
  • 針對專案的建議

3.1.舊有建立AssetBundle檔案的API

在4.x時代,最常用的AssetBundle打包方法主要包括以下兩個:

  • BuildPipeline.BuildAssetBundle
    對除Scene以外的資源打包,支援單個和多個資源,需要在方法的引數中指明需要被打入AssetBundle的資源;
  • BuildPipeline.BuildStreamedSceneAssetBundle
    對Scene檔案打包,也支援單個和多個。

且在4.x時代,打包還需要注意資源之間互相依賴的問題。為了避免資源冗餘,同時提高資源載入和解除安裝的靈活性,因此依賴性打包的重要性不言而喻。老版本中,我們可以使用以下兩個方法來實現這種依賴性:

  • BuildPipeline.PushAssetDependencies
  • BuildPipeline.PopAssetDependencies

這種機制並不難理解,簡單的說PushAssetDependencies是將資源進棧,PopAssetDependencies是讓資源出棧,每打一個包,引擎都會檢查當前棧中所有的依賴項,檢視是否有相同資源已經在棧中。如有,則與其相關的AssetBundle建立依賴關係。

3.2.新的建立AssetBundle檔案的API

在新版本中,Unity3D為我們提供了唯一的API用來打AssetBundle包。即:

  • BuildPipeline.BuildAssetBundles

在指令碼中呼叫BuildPipeline.BuildAssetBundles,U3D將自動根據資源的assetbundleName屬性批量打包,自動建立Bundle和資源之間的依賴關係。
在資源的Inpector介面最下方可設定該資源的assetbundleName,每個assetbundleName對應一個Bundle,即assetbundleName相同的資源會打在一個Bundle中。
如果所依賴的資源設定了不同的assetbundleName,則會自動與之建立依賴關係,避免出現冗餘,從而減小Bundle包的大小。
當然,除了可以指定assetbundleName,我們還可以在Inpector中設定另一個名字,即variant。在打包時,variant會作為字尾新增在assetbundleName之後。相同assetbundleName,不同variant的Bundle是可以相互替換的。

設定好之後,我們只需要建立一個新的指令碼,通過編輯器拓展呼叫BuildPipeline.BuildAssetBundles方法即可:

using UnityEditor;

public class CreateAssetBundles
{
  [MenuItem ("Assets/Build AssetBundles")]
  static void BuildAllAssetBundles ()
    {
      BuildPipeline.BuildAssetBundles ("Assets/AssetBundles");
    }
}

BuildPipeline.BuildAssetBundles方法的引數為bundle的匯出目錄。當然它有很多過載的版本,可以提供額外的引數來定製符合自己需求的AssetBundle。

3.3.針對專案的建議

雖然新的AssetBundle簡化了打包和處理資源依賴的過程,但是卻引入了一個新的複雜度,即需要設定資源的assetbundleName以實現打包的功能。
因此我們可能需要做的是:

  1. 提供指令碼批量對資源設定assetbundleName
  2. 規劃好assetBundle所對應的資源型別,規劃好assetBundle的數量

4.AssetBundle的壓縮

本小節包括:

  • AssetBundle的壓縮型別
  • 針對專案的建議

4.1.AssetBundle的壓縮型別

Unity3D引擎為我們提供了三種壓縮策略來處理AssetBundle的壓縮,即:

  • LZMA格式
  • LZ4格式
  • 不壓縮

LZMA格式:
在預設情況下,打包生成的AssetBundle都會被壓縮。在U3D中,AssetBundle的標準壓縮格式便是LZMA(LZMA是一種序列化流檔案),因此在預設情況下,打出的AssetBundle包處於LZMA格式的壓縮狀態,在使用AssetBundle前需要先解壓縮。
使用LZMA格式壓縮的AssetBundle的包體積最小(高壓縮比),但是相應的會增加解壓縮時的時間。
LZ4格式:
Unity 5.3之後的版本增加了LZ4格式壓縮,由於LZ4的壓縮比一般,因此經過壓縮後的AssetBundle包體的體積較大(該演算法基於chunk)。但是,使用LZ4格式的好處在於解壓縮的時間相對要短。
若要使用LZ4格式壓縮,只需要在打包的時候開啟BuildAssetBundleOptions.ChunkBasedCompression即可。

BuildPipeline.BuildAssetBundles(Application.streamingAssetsPath, 
            BuildAssetBundleOptions.ChunkBasedCompression);

不壓縮:
當然,我們也可以不對AssetBundle進行壓縮。沒有經過壓縮的包體積最大,但是訪問速度最快。
若要使用不壓縮的策略,只需要在打包的時候開啟BuildAssetBundleOptions.UncompressedAssetBundle即可。

BuildPipeline.BuildAssetBundles(Application.streamingAssetsPath, 
                BuildAssetBundleOptions.UncompressedAssetBundle);

4.2.針對專案的建議

AssetBundle的壓縮策略不僅僅和包體的大小、包體的解壓速度相關,而且還會關係到AssetBundle在執行時動態載入的API使用。因此,針對不同型別資源的AssetBundle要指定出符合其使用特點的壓縮策略。

5.AssetBundle的載入和解除安裝

本小節主要包括:

  • 新版API
  • 動態載入方式對比
  • 針對專案的建議

5.1 新版API

在5.x版本中的新AssetBundle系統中,舊有的一些動態載入API已經被新的API所取代,具體內容如下:

  • 4.x版本中的AssetBundle.CreateFromFile方法,在5.x版本中變成了AssetBundle.LoadFromFile方法。
  • 4.x版本中的AssetBundle.CreateFromMemory方法,在5.x版本中變成了LoadFromMemoryAsync方法。
  • 4.x版本中的AssetBundle.CreateFromMemoryImmediate方法,在5.x版本中變成了LoadFromMemory方法。

因此,本小節之後的內容將使用新版API。

5.2.動態載入方式對比

使用AssetBundle動態載入資源首先要獲取AssetBundle物件,第二步才是從AssetBundle中載入目標資源。因此本小節將主要關注如何在執行時獲取AssetBundle的物件,關於如何從AssetBundle中載入資源將在下一小節中分析。
要在執行時載入AssetBundle物件主要可以分為兩大類途徑:

  • 先獲取WWW物件,再通過WWW.assetBundle獲取AssetBundle物件
  • 直接獲取AssetBundle

下面我們就具體分析一下這兩種途徑:

先獲取WWW物件,再通過WWW.assetBundle載入AssetBundle物件:
在先獲取WWW物件,在獲取AssetBundle的這種方式中,我們可以使用以下兩個API來實現這個功能。

  • public WWW(string url),直接呼叫WWW類的建構函式,目標AssetBundle所在的路徑作為其引數,構造WWW物件的過程中會載入Bundle檔案並返回一個WWW物件,完成後會在記憶體中建立較大的WebStream(解壓後的內容,通常為原Bundle檔案的4~5倍大小,紋理資源比例可能更大),因此後續的AssetBundle.LoadAsset可以直接在記憶體中進行。
  • public static WWW LoadFromCacheOrDownload(string url, int version, uint crc = 0),WWW類的一個靜態方法,呼叫該方法同樣會載入Bundle檔案同時返回一個WWW物件,和上一個直接呼叫WWW的建構函式的區別在於該方法會將解壓形式的Bundle內容存入磁碟中作為快取(如果該Bundle已在快取中,則省去這一步),完成後只會在記憶體中建立較小的SerializedFile,而後續的AssetBundle.LoadAsset需要通過IO從磁碟中的快取獲取。

直接載入AssetBundle物件:
在4.x時代,我們可以通過CreateFromFile或CreateFromMemory方法將磁碟上的檔案或記憶體中的流構造成我們需要的AssetBundle物件。但是在5.x版本中,曾經的這兩個方法已經被新的LoadFromFile、LoadFromMemory方法所代替(這兩個方法還有非同步的版本),且機制上也有了一些區別。

  • public static AssetBundle LoadFromFile(string path, uint crc = 0):新的從檔案建立載入AssetBundle方法和4.x中的CreateFromFile方法在機制上有了一些分別,舊的CreateFromFile必須使用未壓縮的Bundle檔案才能在執行時動態建立AssetBundle物件。而新的LoadFromFile方法則沒有這個要求,它支援上一節中提到的幾個壓縮格式,針對LZ壓縮格式和未壓縮的磁碟上的bundle檔案,該方法會直接載入。針對使用預設的LZMA壓縮格式壓縮的bundle檔案,該方法會在幕後先將bundle檔案解壓後再載入。這是最快的載入AssetBundle的方式。該方法是同步版本,還有非同步版本:LoadFromFileAsync。
  • public static AssetBundle LoadFromMemory(byte[] binary, uint crc = 0):從記憶體中獲取Bundle的二進位制資料,同步地建立AssetBundle物件。該方法一般用在經過加密的資料上,經過加密的流資料經過解密之後我們可以呼叫該方法動態的建立AssetBundle物件。該方法是同步版本,還有非同步版本:LoadFromMemoryAsync。

以上便是在執行時動態載入AssetBundle物件的方法。下面,我們再從載入過程中記憶體消耗的角度來對比一下這幾種載入AssetBundle物件的方法,下表是Unity3D官方的一箇中文版總結。

注意:當使用WWW來下載一個bundle時,WebRequest還會有一個8*64KB的快取區用來儲存來自socket的資料。

5.3.針對專案的建議

由於以上分析的幾種載入手段各有各的使用情景和特點。因此建議在我們的專案中按照以下情景使用這些方法:

  • 隨遊戲一同釋出的AssetBundle(一般位於StreamingAssets資料夾中):
  • 在打AssetBundle包時,使用LZ4壓縮格式進行打包(開啟BuildAssetBundleOptions.ChunkBasedCompression即可)。
  • 在執行時需要載入AssetBundle物件時,使用LoadFromFile方法進行載入。
  • 這樣做的好處是:即可以將AssetBundle檔案壓縮,又可以兼顧載入速度,且節約記憶體。
  • 作為更新包,需要從服務端下載的AssetBundle:
  • 在打AssetBundle包時,使用預設的LZMA格式壓縮。
  • 使用WWW.LoadFromCacheOrDownload方法下載並快取AssetBundle包檔案。
  • 這樣做的好處是:獲得了最大的壓縮率,在下載過程中可以減少資料傳輸量。同時,在本地磁碟建立快取之後,又可以兼顧之後的載入速度,且節約記憶體。
  • 我們自己進行加密的AssetBundle:
  • 在打AssetBundle包時,使用LZ4壓縮格式進行打包(開啟BuildAssetBundleOptions.ChunkBasedCompression即可)。
  • 在執行時需要載入AssetBundle物件時,使用LoadFromMemory方法進行載入。(這也是從記憶體中使用流資料載入AssetBundle物件的僅有的使用場景。)
  • 我們自己壓縮的AssetBundle:
  • 我們自己也可以使用第三方庫或工具對生成的AssetBundle包檔案進行壓縮,如果需要這樣做,則我們最好不要再使用Unity3D對AssetBundle進行壓縮,因此在打包時選擇開啟BuildAssetBundleOptions.UncompressedAssetBundle。
  • 在執行時需要載入AssetBundle物件時,使用LoadFromFileAsync方法進行非同步載入。

6.資源的載入和解除安裝

本小節包括:

  • 從AssetBundle物件中載入資源
  • 資源的解除安裝

6.1.從AssetBundle物件中載入資源

新舊版的載入和解除安裝資源的API名稱發生了一些變化,但是機制變化不大。
在舊有的4.X版本中,從AssetBundle物件中載入資源所使用的API主要包括以下幾個:

  • Load:從資源包中載入指定的資源
  • LoadAll:載入當前資源包中所有的資源
  • LoadAsync:從資源包中非同步載入資源

而在新版的AssetBundle中,載入資源的API已經變成了以下的幾個:

  • LoadAsset:從資源包中載入指定的資源
  • LoadAllAsset:載入當前資源包中所有的資源
  • LoadAssetAsync:從資源包中非同步載入資源

6.2.資源解除安裝

資源解除安裝部分的變化不大,使用的仍然是Unload方法。

  • Unload

該方法會解除安裝執行時記憶體中包含在bundle中的所有資源。
當傳入的引數為true,則不僅僅記憶體中的AssetBundle物件包含的資源會被銷燬。根據這些資源例項化而來的遊戲內的物件也會銷燬。
當傳入的引數為false,則僅僅銷燬記憶體中的AssetBundle物件包含的資源。