1.概覽
Unity3D 5.0版本之后的AssetBundle機制和之前的4.x版本已經發生了很大的變化,一些曾經常用的流程已經不再使用,甚至一些老的API已經被新的API所取代。
因此,本文的主要內容就是分析5.X版本的AssetBundle機制(包括創建資源包、壓縮資源包、加載資源包和從資源包中加載/卸載資源等幾個方面)及其關鍵的API使用方式並總結一些對項目的建議(例如根據不同的情景,選擇不同的包體加載方案等等)。
2.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文件的創建
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 UnityEngine; using UnityEditor; /// <summary> /// 自動打包所有資源(設置了Assetbundle Name的資源) /// </summary> public class CreateAssetBundles : MonoBehaviour { [MenuItem("AssetBundle/Build AssetBundles")] static void BuildAllAssetBundles() { BuildPipeline.BuildAssetBundles("Assets/AssetBundles", BuildAssetBundleOptions.ForceRebuildAssetBundle, BuildTarget.StandaloneWindows); } }
BuildPipeline.BuildAssetBundles方法的參數為bundle的導出目錄。當然它有很多重載的版本,可以提供額外的參數來定制符合自己需求的AssetBundle。
3.3.針對項目的建議
雖然新的AssetBundle簡化了打包和處理資源依賴的過程,但是卻引入了一個新的復雜度,即需要設置資源的assetbundleName以實現打包的功能。
因此我們可能需要做的是:
- 提供腳本批量對資源設置assetbundleName
- 規划好assetBundle所對應的資源類型,規划好assetBundle的數量
4.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的加載和卸載
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對象,再通過http://WWW.assetBundle獲取AssetBundle對象
- 直接獲取AssetBundle
下面我們就具體分析一下這兩種途徑:
先獲取WWW對象,再通過http://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格式壓縮。
- 使用http://WWW.LoadFromCacheOrDownload方法下載並緩存AssetBundle包文件。
- 這樣做的好處是:獲得了最大的壓縮率,在下載過程中可以減少數據傳輸量。同時,在本地磁盤創建緩存之后,又可以兼顧之后的加載速度,且節約內存。
- 我們自己進行加密的AssetBundle:
- 在打AssetBundle包時,使用LZ4壓縮格式進行打包(開啟BuildAssetBundleOptions.ChunkBasedCompression即可)。
- 在運行時需要加載AssetBundle對象時,使用LoadFromMemory方法進行加載。(這也是從內存中使用流數據加載AssetBundle對象的僅有的使用場景。)
- 我們自己壓縮的AssetBundle:
- 我們自己也可以使用第三方庫或工具對生成的AssetBundle包文件進行壓縮,如果需要這樣做,則我們最好不要再使用Unity3D對 AssetBundle進行壓縮,因此在打包時選擇開啟 BuildAssetBundleOptions.UncompressedAssetBundle。
- 在運行時需要加載AssetBundle對象時,使用LoadFromFileAsync方法進行異步加載。
6.資源的加載和卸載
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對象包含的資源。