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.BuildAssetBundlen對除Scene以外的資源打包,支持單個和多個資源,需要在方法的參數中指明需要被打入AssetBundle的資源;
- BuildPipeline.BuildStreamedSceneAssetBundlen對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因此我們可能需要做的是:- 提供腳本批量對資源設置assetbundleName
- 規劃好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對象,再通過http://WWW.assetBundle獲取AssetBundle對象
- 直接獲取AssetBundle
下面我們就具體分析一下這兩種途徑:
先獲取WWW對象,再通過http://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格式壓縮。
- 使用http://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#)?