Unity3D 5.0版本以後的AssetBundle機制和以前的4.x版本已經發生了很大的變化,一些曾常常用的流程已經再也不使用,甚至一些老的API已經被新的API所取代。
所以,本文的主要內容就是分析5.X版本的AssetBundle機制(包括建立資源包、壓縮資源包、加載資源包和從資源包中加載/卸載資源等幾個方面)及其關鍵的API使用方式並總結一些對項目的建議(例如根據不一樣的情景,選擇不一樣的包體加載方案等等)。算法
本小節包括:數據庫
AssetBundle系統的新功能緩存
新的AssetBundle系統的優點異步
在新的AssetBundle系統中,出現瞭如下的新功能:socket
經過Editor中的UI便可方便的爲AssetBundle標記資源。並且一個資源和對應的AssetBundle的映射將會在資源數據庫(AssetDatabase)中被建立。
在箭頭處便可指定該資源所述的AssetBundle,第一個選項爲AssetBundle的名字,然後一個選項則是爲AssetBundle建立變體,例如一些素材須要區分爲高清或普通存放在不一樣的AssetBundle中,那麼第二選項就能夠以hd和normal來區分。編輯器
提供了新的API用來設置資源所屬的AssetBundle:函數
設置AssetImporter.assetBundleName的值,便可爲該資源指定它所屬的AssetBundle。上文中在UI中設置的AssetBundle的名字即是爲該值賦值,在資源有了assetBundleName以後,實際上它的信息就已經存在於AssetDataBase裏面了。工具
新版本中,建立AssetBundle文件的API變得十分簡單了:ui
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方法。
因爲引擎提供的這些新功能,咱們就再也不須要像4.x時代那麼複雜的用來打包的腳本了。
同時,資源之間的互相依賴關係再也不須要開發者手動維護了,曾經因爲不當使用PushAssetDependencies/PopAssetDependencies而可能會形成依賴出現的問題,如今Unity3D已經爲咱們解決了。
並且因爲引入了清單文件manifest,所以咱們能夠實現增量更新,即只須要更新有變化的部分,而沒有變化的則沒必要更新。
舉一個例子:
假設咱們有一個cube,它的material有一個材質,咱們分別將cube和material打包成cubeBundle和materialBundle,以後咱們修改material上的材質。在過去,咱們須要分別從新爲cube和material打包,而如今只須要對material從新打包便可,cube不受影響。
本小節包括:
舊有建立AssetBundle文件的API
新的建立AssetBundle文件的API
針對項目的建議
在4.x時代,最經常使用的AssetBundle打包方法主要包括如下兩個:
BuildPipeline.BuildAssetBundle
對除Scene之外的資源打包,支持單個和多個資源,須要在方法的參數中指明須要被打入AssetBundle的資源;
BuildPipeline.BuildStreamedSceneAssetBundle
對Scene文件打包,也支持單個和多個。
且在4.x時代,打包還須要注意資源之間互相依賴的問題。爲了不資源冗餘,同時提升資源加載和卸載的靈活性,所以依賴性打包的重要性不言而喻。老版本中,咱們可使用如下兩個方法來實現這種依賴性:
BuildPipeline.PushAssetDependencies
BuildPipeline.PopAssetDependencies
這種機制並不難理解,簡單的說PushAssetDependencies是將資源進棧,PopAssetDependencies是讓資源出棧,每打一個包,引擎都會檢查當前棧中全部的依賴項,查看是否有相同資源已經在棧中。若有,則與其相關的AssetBundle創建依賴關係。
在新版本中,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。
雖然新的AssetBundle簡化了打包和處理資源依賴的過程,可是卻引入了一個新的複雜度,即須要設置資源的assetbundleName以實現打包的功能。
所以咱們可能須要作的是:
提供腳本批量對資源設置assetbundleName
規劃好assetBundle所對應的資源類型,規劃好assetBundle的數量
本小節包括:
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);
AssetBundle的壓縮策略不只僅和包體的大小、包體的解壓速度相關,並且還會關係到AssetBundle在運行時動態加載的API使用。所以,針對不一樣類型資源的AssetBundle要指定出符合其使用特色的壓縮策略。
本小節主要包括:
新版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。
使用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的數據。
因爲以上分析的幾種加載手段各有各的使用情景和特色。所以建議在咱們的項目中按照如下情景使用這些方法:
隨遊戲一同發佈的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方法進行異步加載。
本小節包括:
從AssetBundle對象中加載資源
資源的卸載
新舊版的加載和卸載資源的API名稱發生了一些變化,可是機制變化不大。
在舊有的4.X版本中,從AssetBundle對象中加載資源所使用的API主要包括如下幾個:
Load:從資源包中加載指定的資源
LoadAll:加載當前資源包中全部的資源
LoadAsync:從資源包中異步加載資源
而在新版的AssetBundle中,加載資源的API已經變成了如下的幾個:
LoadAsset:從資源包中加載指定的資源
LoadAllAsset:加載當前資源包中全部的資源
LoadAssetAsync:從資源包中異步加載資源
資源卸載部分的變化不大,使用的仍然是Unload方法。
Unload
該方法會卸載運行時內存中包含在bundle中的全部資源。當傳入的參數爲true,則不只僅內存中的AssetBundle對象包含的資源會被銷燬。根據這些資源實例化而來的遊戲內的對象也會銷燬。當傳入的參數爲false,則僅僅銷燬內存中的AssetBundle對象包含的資源。