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

1.概覽

Unity3D 5.0版本之後的AssetBundle機制和之前的4.x版本已經發生了很大的變化,一些曾經常用的流程已經不再使用,甚至一些老的API已經被新的API所取代。

n因此,本文的主要內容就是分析5.X版本的AssetBundle機制(包括創建資源包、壓縮資源包、載入資源包和從資源包中載入/卸載資源等幾個方面)及其關鍵的API使用方式並總結一些對項目的建議(例如根據不同的情景,選擇不同的包體載入方案等等)。

2.AssetBundle系統的新功能

本小節包括:

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

2.1.AssetBundle系統的新功能

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

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

    在箭頭處即可指定該資源所述的AssetBundle,第一個選項為AssetBundle的名字,而後一個選項則是為AssetBundle創建變體,n例如一些素材需要區分為高清或普通存放在不同的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文件名之後,開啟這個選項可以直接通過n文件名來判斷哪些Bundle的內容進行了更新(4.x下普遍需要通過比較二進位等方法來判斷,但在某些情況下即使內容不變重新打包,Bundle的二進n制也會變化),新增。
  • ChunkBasedCompression,用於使用LZ4格式進行壓縮,5.3新增。
  • Manifest文件。在4.x版本中,我們通常需要自行維護配置文件,以記錄AssetBundle之間的依賴關係,並供運行時使用。而在5.x版本中,使用Manifest文件可以免去4.x版本中的這一過程。而Manifest文件分為兩種:
  • 單個bundle的Manifest文件,一旦一個新的AssetBundle文件被創建導出,便會對應生成一個.manifest文件,其中包含了校驗、依賴文件等信息。所以可以用來做增量更新。
  • 實際上在打包的時候,在輸出的bundle所在的文件夾內還會生成一個總的manifest文件,叫做[文件夾名].manifest。它包含了n該文件夾內所有的bundle的信息,以及它們之間互相依賴的信息。所以在我們載入bundle的時候,需要先把總的manifest文件載入進來,以確n認各個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時代那麼複雜的用來打包的腳本了。

n同時,資源之間的互相依賴關係不再需要開發者手動維護了,曾經由於不當使用PushAssetDependencies/PopAssetDependencies而可能會造成依賴出現的問題,現在Unity3D已經為我們解決了。

n而且由於引入了清單文件manifest,因此我們可以實現增量更新,即只需要更新有變化的部分,而沒有變化的則不必更新。

n舉一個例子:

n假設我們有一個cube,它的material有一個材質,我們分別將cube和material打包成cubeBundle和nmaterialBundle,之後我們修改material上的材質。在過去,我們需要分別重新為cube和material打包,而現在只需要對nmaterial重新打包即可,cube不受影響。

3.AssetBundle文件的創建

本小節包括:

  • 舊有創建AssetBundle文件的API
  • 新的創建AssetBundle文件的API
  • 針對項目的建議

3.1.舊有創建AssetBundle文件的API

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

  • BuildPipeline.BuildAssetBundle

    n對除Scene以外的資源打包,支持單個和多個資源,需要在方法的參數中指明需要被打入AssetBundle的資源;
  • BuildPipeline.BuildStreamedSceneAssetBundle

    n對Scene文件打包,也支持單個和多個。

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

  • BuildPipeline.PushAssetDependencies
  • BuildPipeline.PopAssetDependencies

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

3.2.新的創建AssetBundle文件的API

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

  • BuildPipeline.BuildAssetBundles

在腳本中調用BuildPipeline.BuildAssetBundles,U3D將自動根據資源的assetbundleName屬性批量打包,自動建立Bundle和資源之間的依賴關係。

n在資源的Inpector界面最下方可設置該資源的assetbundleName,每個assetbundleName對應一個Bundle,即assetbundleName相同的資源會打在一個Bundle中。

n如果所依賴的資源設置了不同的assetbundleName,則會自動與之建立依賴關係,避免出現冗餘,從而減小Bundle包的大小。

n當然,除了可以指定assetbundleName,我們還可以在Inpector中設置另一個名字,即variant。在打包時,variant會作為n後綴添加在assetbundleName之後。相同assetbundleName,不同variant的Bundle是可以相互替換的。

設置好之後,我們只需要創建一個新的腳本,通過編輯器拓展調用BuildPipeline.BuildAssetBundles方法即可:

using UnityEditor;nnpublic class CreateAssetBundlesn{n [MenuItem ("Assets/Build AssetBundles")]n static void BuildAllAssetBundles ()n {n BuildPipeline.BuildAssetBundles ("Assets/AssetBundles");n }n}n

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

3.3.針對項目的建議

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

n因此我們可能需要做的是:

  1. 提供腳本批量對資源設置assetbundleName
  2. 規劃好assetBundle所對應的資源類型,規劃好assetBundle的數量

4.AssetBundle的壓縮

本小節包括:

  • AssetBundle的壓縮類型
  • 針對項目的建議

4.1.AssetBundle的壓縮類型

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

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

LZMA格式:

n在默認情況下,打包生成的AssetBundle都會被壓縮。在U3D中,AssetBundle的標準壓縮格式便是LZMA(LZMA是一種序列化流文n件),因此在默認情況下,打出的AssetBundle包處於LZMA格式的壓縮狀態,在使用AssetBundle前需要先解壓縮。

n使用LZMA格式壓縮的AssetBundle的包體積最小(高壓縮比),但是相應的會增加解壓縮時的時間。

LZ4格式:

nUnity 5.3之後的版本增加了LZ4格式壓縮,由於LZ4的壓縮比一般,因此經過壓縮後的AssetBundle包體的體積較大(該演算法基於chunk)。但是,使用LZ4格式的好處在於解壓縮的時間相對要短。

n若要使用LZ4格式壓縮,只需要在打包的時候開啟BuildAssetBundleOptions.ChunkBasedCompression即可。

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

不壓縮:

n當然,我們也可以不對AssetBundle進行壓縮。沒有經過壓縮的包體積最大,但是訪問速度最快。

n若要使用不壓縮的策略,只需要在打包的時候開啟BuildAssetBundleOptions.UncompressedAssetBundle即可。

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

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中載入資源將在下一小節中分析。

n要在運行時載入AssetBundle對象主要可以分為兩大類途徑:

  • 先獲取WWW對象,再通過WWW.assetBundle獲取AssetBundle對象
  • 直接獲取AssetBundle

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

先獲取WWW對象,再通過WWW.assetBundle載入AssetBundle對象:

n在先獲取WWW對象,在獲取AssetBundle的這種方式中,我們可以使用以下兩個API來實現這個功能。

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

直接載入AssetBundle對象:

n在4.x時代,我們可以通過CreateFromFile或CreateFromMemory方法將磁碟上的文件或內存中的流構造成我們需要的nAssetBundle對象。但是在5.x版本中,曾經的這兩個方法已經被新的LoadFromFile、LoadFromMemory方法所代替(這兩n個方法還有非同步的版本),且機制上也有了一些區別。

  • public static AssetBundle LoadFromFile(string path, uint crc = 0):新n的從文件創建載入AssetBundle方法和4.x中的CreateFromFile方法在機制上有了一些分別,舊的CreateFromFile必須n使用未壓縮的Bundle文件才能在運行時動態創建AssetBundle對象。而新的LoadFromFile方法則沒有這個要求,它支持上一節中提到n的幾個壓縮格式,針對LZ壓縮格式和未壓縮的磁碟上的bundle文件,該方法會直接載入。針對使用默認的LZMA壓縮格式壓縮的bundle文件,該方n法會在幕後先將bundle文件解壓後再載入。這是最快的載入AssetBundle的方式。該方法是同步版本,還有非同步版n本:LoadFromFileAsync。
  • public static AssetBundle LoadFromMemory(byte[] binary, uint crc = 0):從n內存中獲取Bundle的二進位數據,同步地創建AssetBundle對象。該方法一般用在經過加密的數據上,經過加密的流數據經過解密之後我們可以調n用該方法動態的創建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對nAssetBundle進行壓縮,因此在打包時選擇開啟nBuildAssetBundleOptions.UncompressedAssetBundle。
  • 在運行時需要載入AssetBundle對象時,使用LoadFromFileAsync方法進行非同步載入。

6.資源的載入和卸載

本小節包括:

  • 從AssetBundle對象中載入資源
  • 資源的卸載

6.1.從AssetBundle對象中載入資源

新舊版的載入和卸載資源的API名稱發生了一些變化,但是機制變化不大。

n在舊有的4.X版本中,從AssetBundle對象中載入資源所使用的API主要包括以下幾個:

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

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

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

6.2.資源卸載

資源卸載部分的變化不大,使用的仍然是Unload方法。

  • Unload

該方法會卸載運行時內存中包含在bundle中的所有資源。

n當傳入的參數為true,則不僅僅內存中的AssetBundle對象包含的資源會被銷毀。根據這些資源實例化而來的遊戲內的對象也會銷毀。

n當傳入的參數為false,則僅僅銷毀內存中的AssetBundle對象包含的資源。

推薦閱讀:

從遊戲腳本語言說起,剖析Mono搭建的腳本基礎
Visual Studio 開發體驗究竟牛到什麼程度?真的只是拖拖控制項就能完成中小型項目開發?
【譯】介紹 .NET Standard
妥協與取捨,解構C#中的小數運算
怎樣考察有八年經驗程序猿的水平(C#)?

TAG:Unity游戏引擎 | C# | 游戏开发 |