1. 程式人生 > >Unity資源解決方案之AssetBundle

Unity資源解決方案之AssetBundle

保留 裝包 方法 bundle 以及 pipe 用法 遊戲 cnblogs

1、什麽是AssetBundle

AssetBundle是Unity pro提供的一種用來存儲資源的文件格式,它可以存儲任意一種Unity引擎能夠識別的資源,如Scene、Mesh、Material、Texture、Audio、noxss等等,同時,AssetBundle也可以包含開發者自定義的二進制文件,只需要將自定義文件的擴展名改為.bytes,Unity就可以把它識別為TextAsset,進而就可以被打包到AssetBundle中。Unity引擎所能識別的資源我們稱為Asset,AssetBundle就是Asset的一個集合。

AssetBundle的特點:

壓縮(缺省)、動態載入、本地緩存;

2、AssetBundle VS Resource

AssetBundle作為Unity官方推崇的資源更新方案,與傳統的Resource差異如下:

a、Resource放在Resources目錄下,resources.assets文件,單個文件有2GB限制,首次必須全部下載;

b、AssetBundle創建需要通過Editor腳本創建,支持動態下載,是Unity Web Caching License唯一可以緩存的內容;

3、AssetBundle的適用平臺與跨平臺性

AssetBundle適用於多種平臺,包括網頁應用、移動應用、桌面應用等,可以動態更新,但不同平臺所使用的AssetBundle並不相同,在創建離線AssetBundle的時候需要通過參數來指定目標平臺,相容關系如表所示

技術分享


4、AssetBundle的工作流程(與flash加載swf類似)

a、創建AssetBundle;

b、上傳到Server;

c、遊戲運行時根據需要下載(或者從本地cache中加載)AssetBundle文件;

d、解析加載Assets;

e、使用完畢後釋放;

5、創建AssetBundle

5.1、如何創建AssetBundle

Unity引擎提供了創建AssetBundle的API,通過編譯管線BuildPipeline來創建AssetBundle文件,總共有三種方法:

a、BuildPipeline.BuildAssetBundle(mainAsset : Object, assets : Object[], pathName : string, options : BuildAssetBundleOptions = BuildAssetBundleOptions.CollectDependencies | BuildAssetBundleOptions.CompleteAssets, targetPlatform : BuildTarget = BuildTarget.WebPlayer) : bool

該API將編輯器中的任意類型的Assets打包成一個AssetBundle,適用於對單個大規模場景的細分;

b、BuildPipeline.BuildStreamedSceneAssetBundle(level : string[], locationPath : string, target : BuildTarget) : String

該API將一個或多個場景中的資源及其所有依賴以流加載的方式打包成AssetBundle,一般適用於多單個或多個場景進行集中打包;

c、BuildPipeline.BuildAssetBundleExplicitAssetNames(assets : Object[], assetNames : string[],pathName : string, options : BuildAssetBundleOptions = BuildAssetBundleOptions.CollectDependencies | BuildAssetBundleOptions.CompleteAssets, targetPlatform : BuildTarget = BuildTarget.WebPlayer) : bool

該API功能與a相同,但創建的時候可以為每個Object指定一個自定義的名字。(一般不太常用)

5.2、關於BuildAssetBundleOptions

a、CompleteAssets

使每個Asset自身完備,包含所有的Components;

b、CollectDependencies

包含每個Asset依賴的所有其他Asset;

c、DisableWriteTypeTree

在AssetBundle中不包含類型信息。需要註意的是,如果將AssetBundle發布到web平臺上,則不能使用這個選項;

d、DeterministricAssetBundle

使每個Object具有唯一的、不變的HashID,便於後續查找可以用於增量發布AssetBundle;

e、UncompressedAssetBundle

不進行數據壓縮。如果使用這個選項,因為沒有壓縮/解壓的過程,AssetBundle發布和加載會更快,但是AssetBundle也會更大,導致下載變慢。

5.3、AssetBundle之間的依賴

如果遊戲中的某個資源被多個資源引用(例如遊戲中的Material),單獨創建AssetBundle會使多個AssetBundle都包含被引用的資源(這裏跟flash編譯選項中的鏈接選項有些像),從而導致資源變大,這裏可以通過指定AssetBundle之間的依賴關系來減少最終AssetBundle文件的大小(把AssetBundle解耦)。

具體方法是在創建AssetBundle之前調用BuildPipeline.PushAssetDependencies和BuildPipeline.PopAssetDependencies來創建AssetBundle之間的依賴關系,它的用法就是一個棧,後壓入棧中的元素依賴棧內的元素。(記得要pop!)

舉個例子:

技術分享

現在遊戲內有mat1和mat2兩個Material,他們使用了相同的Texture zhuanqiang

不使用依賴

技術分享

使用依賴

技術分享

6、遠端Server的AssetBundle下載

Unity引擎提供了兩種方式從服務器下載AssetBundle文件,分別是緩存機制和非緩存機制。

6.1、緩存機制

通過WWW.LoadFromCacheOrDownload (url : string, version : int)接口來下載AssetBundle,下載後的AssetBundle會自動被保存到Unity引擎的緩存區內,該方法是Unity推薦的AssetBundle下載方式。下載AssetBundle的時候,該接口會先在本地緩存中查找該文件,看其之前是否被下載過,如果下載過,則直接從緩存中加載,如果沒有,則從服務器盡享下載。這樣做的好處是可以節省AssetBundle文件的下載時間,從而提高遊戲資源的載入速度(還可以節省下載流量)。同時開啟多個Coroutine進行WWW的LoadFromCacheOrDownload操作(緩存中),經測試開啟的WWW現成越多,速度會越快,但是需要考慮時機的機器火平臺的承載能力。如果一定要從網上下載資源的話,線程數最好設為5個(別人的經驗),很多平臺也有自己的限制,例如有的瀏覽器只能同事加載6個等等。

需要註意的是,Unity提供的默認緩存大小是根據發布平臺不同而不同的(可以向Unity購買Caching license支持)。目前對於web player的網頁遊戲,默認緩存大小為50M;對於PC上的客戶端或者IOS¥Android上的移動遊戲,默認緩存大小為4GB。

代碼:

WWW www = WWW.LoadFromCacheOrDownload (Url, 1);

yield return www;

AssetBundle ab = www.assetBundle;

6.2、非緩存機制

通過創建一個WWW實例來對AssetBundle文件下載,下載後的AssetBundle文件將不會進入Unity的緩存區。使用這種方法每次都會從遠端服務器下載。

代碼:

WWW www = new WWW(Url);

yield return www;

AssetBundle ab = www.assetBundle;

7、載入AssetBundle對象

7.1、通過WWW類方法和屬性

直接通過WWW.assetBundle屬性來創建AssetBundle,註意:通過WWW加載的AssetBundle在解析Asset之前一定要先調用WWW.assetBundle;

7.2、通過API動態創建

AssetBundle.CreateFromFile接口從磁盤創建一個AssetBundle文件的內存對象,該方法僅支持非壓縮格式的AssetBundle。

7.3通過API動態創建

AssetBundle.CreateFromMemory接口可以從內存數據流創建一個AssetBundle內存對象。主要用於對數據的加解密上。

例如:

WWW www = new WWW(url);

yield return www;

byte[] encrypedData = www.bytes;

byte[] decryptedData = YourDecryptionMethod(encrypedData);//解密函數

AssetBundle ab = AssetBundle.CreateFromMemory(decrypedData);

8、從AssetBundle中加載Assets

8.1、Assets的加載

AssetBundle.Load (name : string) : Object 從bundle中加載名為name的對象;

AssetBundle.Load (name : string, type : Type) : Object 從bundle中加載被指定類型的名為name的對象;

AssetBundle.LoadAsync (name : string, type : Type) : AssetBundleRequest 異步地從bundle中加載被指定類型的名為name的對象(異步加載需要Unity Pro專業版);

AssetBundle.LoadAll (type : Type) : Object[] 加載所有包含在asset bundle中且繼承自type的對象;

AssetBundle.LoadAll () : Object[] 加載所有包含在asset bundle中的對象;

AssetBundle.mainAsset 主資源在構建資源boundle時指定(只讀),該功能可以方便的找到bundle內的主資源。例如,你也許想有一個角色的預制體並包括所有紋理、材質、網格和動畫文件。但是完全操縱角色的預設體應該是你的mainAsset並且易於被訪問;

例如:

//開始下載
WWW www = new WWW(url);
//等待下載完成
yield return www;
//獲取指定的主資源並實例化
Instantiate(www.assetBundle.mainAsset);

8.2、AssetBundle中加載Level

Application.LoadLevel 該接口可以通過名字或者索引載入AssetBundle文件中包含的對應場景,當夾在新場景時,所有之前加載的GameObject都會被銷毀;

Application.LoadLevelAsync 該接口的作用與Application.LoadLevel相同,不同的是該接口是異步加載,即加載時主線程可以繼續執行;

Application.LoadLevelAdditive 該接口不同與Application.LoadLevel的是並不銷毀之前加載的GameObject;

Application.LoadLevelAdditiveAsync 該接口的作用與Application.LoadLevelAdditive相同,不同的是該接口是異步加載,即加載時主線程可以繼續執行;

例如:

WWW www = new WWW(url);

yield return www;

AssetBundle ab = www.assetBunlde;

Application.loadLevel("Level1");

9、AssetBundle與內存

內存一直都是開發者關註的一個重點,如果要了解清楚內存的使用情況。

技術分享

9.1、加載AssetBundle對內存的影響

Unity引擎在使用WWW方法時會分配一系列的內存空間來存放WWW實例對象、WebStream數據。該數據包括原始的AssetBundle數據、解壓後的AssetBundle數據以及一個用於解壓的Decompression Buffer。一般情況下,Decompression Buffer會在原始的AssetBundle解壓完成後自動銷毀,但需要註意的是,Unity會自動保留一個Decompression Buffer,不被系統回收,這樣做的好處是不用過於頻繁的開辟和銷毀解壓Buffer,從而在一定程度上降低CPU的消耗。

當把AssetBundle解壓到內存後,開發者可以使用WWW.assetBundle屬性來獲取AssetBundle對象,從而可以得到各種Assets,進而對這些Assets進行加載或者實例化操作。加載過程中,Unity會將AssetBundle中的數據流轉變為引擎可以識別的信息類型(紋理、材質、對象等)。加載完成後,開發者可以對其進行進一步的操作,比如對象的實例化、紋理和材質的復制和替換等。

9.2、AssetBundle的卸載

1)AssetBundle.Unload(true) 該接口會強制卸載掉所有AssetBundle中加載的Asset,包括AssetBundle的映射結構、自身對Web Stream的引用以及從AssetBundle創建出來的所有資源,該接口不推薦使用。

2)AssetBundle.Unload(false) 該接口會釋放AssetBundle內的序列化數據,但是任何從這個AssetBundle中實例化的物體都將完好。當然,你不能從這個AssetBundle中加載更多物體。

Resources.UnloadUnusedAssets該接口會卸載掉沒有使用的Assets,作用範圍是整個系統。

3)對於實例化出來的GameObject,可以調用GameObject.Destory()接口來卸載。該接口會延後到一個合理的時機進行處理。

註意:這是U3D沒有處理好的一個環節。在WWW加載資源完畢後,對資源進行instantiate後,對其資源進行unload,這時問題就發生了,instantiate處理渲染需要一定的時間,雖然很短,但也是需要1,2幀的時間。此時進行unload會對資源渲染造成影響,以至於沒有貼圖或 者等等問題發生。解決辦法:自己寫個時間等待代碼,等待個0.5秒到1秒之後再進行Unload。這樣就不會出現instantiate渲染中就運行unload的情況了。

10、關於其他

10.1、在AssetBundle中嵌入腳本

AssetBundle中的資源上如果Attach了腳本,打包的時候該腳本是不會被打到AssetBundle中的,其實這裏只是保存了一個類似於指針的關聯,如果需要把腳本也動態打到AssetBundle中,還需要做一番工作。

首先,將腳本預先編譯成assembly,把assembly保存成.bytes文件,這樣Unity會把它識別為TextAsset,就可以將這個TextAsset打包到AssetBundle中了,載入後可以通過反射機制使用該腳本,代碼如下:

AssetBundle bundle = WWWW.assetBundle;

TextAsset txt = bundle.load("MyBinaryAsText", typeof(TextAsset)) as TextAsset;

byte[] bytes = txt.bytes;

var assembly = System.Reflection.Assembly.Load(bytes);

需要註意的是,IOS平臺不支持動態載入腳本。

10.2、AssetBundle的版本控制

AssetBundle使用WWW.LoadFromCacheOrDownload(string url, int version, uint crc)加載,其中的第二個參數-version可以用來做版本控制,該參數強制用戶從服務器下載一個更高版本的AssetBundle。我們可以通過第三個參數crc來實現AssetBundle的內容校驗,當crc不為0的時候,Unity會校驗AssetBundle的CRC碼,如果不等,則說明文件損壞,Unity會重新下載該文件。對於crc的獲取,(老版本沒有提供方法,只能通過LoadFromCacheOrDownload傳一個錯誤的crc,從log中獲取),新版本在BuildAssetBundle的時候增加了一個out類型的參數,該參數會返回正確的crc碼,打包的時候可以記錄下來以供後面使用。

10.3、關於Editor和Runtime之間共享資源

1)Unity提供了一種可以公用的類——noxssableObject,適用於描述動態劃分場景;通過代碼劃分場景——>打包多個AssetBundle——>將劃分信息記錄在ScriptableObject中,並保存至Asset——>載入時先載入劃分信息,再根據這個劃分信息載入AssetBundle。

2)使用XML文件。

10.4、關於編輯器擴展

Unity3D可以通過事件觸發來執行你的編輯器代碼,但是我們需要一些編譯器參數來告知編譯器何時需要觸發該段代碼。 [MenuItem(XXX)]聲明在一個函數上方,告知編譯器給Unity3D編輯器添加一個菜單項,並且當點擊該菜單項的時候調用該函數。觸發函數裏可以編寫任何合法的代碼,可以是一個資源批處理程序,也可以彈出一個編輯器窗口。代碼裏可以訪問到當前選中的內容(通過Selection類),並據此來確定顯示視圖。與此類似,[ContextMenu("XXX")]可以向你的上下文菜單中添加一個菜單項。 當你編寫了一些Component腳本,當它被附屬到某個GameObject時,想在編輯視圖即可在Scene視圖觀察到效果,那麽你可以把[ExecuteInEditMode]寫在類上方來通知編譯器,該類的OnGUI和Update等函數在編輯模式也也會被調用。我們還可以使用[AddComponentMenu("XXX/XXX")]來把該腳本關聯到Component菜單中,點擊相應菜單項即可為GameObject添加該Component腳本。

為了避免不必要的包含,Unity3D的運行時和編輯器類分辨存儲在不同的Assemblies裏(UnityEngine和UnityEditor)。Editor目錄下的腳本會在其它腳本之後進行編譯,這方便了你去使用那些運行時的內容。而那些目錄下的腳本是不能訪問到Editor目錄下的內容的。所以,你最好把你的編輯器腳本寫在Editor目錄下。

10.5、關於差量發布

在5.2中介紹了創建AssetBundle的參數,其中的d選項在選中的時候可以使相同的內容兩次發布出來的文件是完全一樣的,我們在創建AssetBundle的時候選擇上這個參數,那麽就可以做差量了。測試數據如下:

沒有選擇該參數:

技術分享

選擇了該參數後:

技術分享

10.6、關於項目中應用

項目中使用AssetBundle做開發可以使用宏進行隔離,接口封裝盡量采用異步接口,通過引用計數cache機制,確定ab的釋放時機。大體流程如下:

a、確定加載ab次數(資源數)

b、加載ab

c、成功後根據資源url引用計數減去對應資源數

d、引用計數為0的時候調用AssetBundle的Unload(false)

代碼中使用的地方可以通過封裝的GetObject獲取已經加載的對象,使用完成後可以調用Resources的UnloadUnUsedAssets釋放資源。

10.7、關於AssetBundle的粒度控制

AB的粒度越小,差量更新的冗余就會越小,粒度越大,差量更新的冗余就會越大。但是,並不是說粒度越小就越好,粒度小了,(運行時)加載的時候會增加IO次數、解壓次數(AB一般選擇壓縮格式)和申請內存的次數,導致加載時長變長。因此粒度的控制是一個時間與空間平衡的選擇過程。經過實驗,大體有一個數據可以用來參考,1M左右的AssetBundle包加載性能最好,冗余也可以接受。

10.8、關於AssetBundle的壓縮選擇

AssetBundle壓縮與不壓縮的差異主要有兩方面:

a、外存(安裝包的大小或者安裝後占用磁盤空間的大小)

b、加載方式的選擇(能否使用同步方法)

這裏如果一些對性能要求特別高,資源又不大的AB可以采用非壓縮方式,通過CreateFromFile加載AB包,性能最高又不會產生大量的內存。對於其他的資源文件,建議壓縮處理,壓縮與非壓縮在磁盤占用上會有4倍左右的大小差別,如果都采用非壓縮格式,有可能會導致你的磁盤占用達到一個非常恐怖的量級。

10.9、AssetBundle在外存優化中的應用

安裝後的磁盤構成基本上是資源的內存值(Resources目錄下資源),舉個粒子,一張真彩色的1024*1024的圖片放到Resources目錄下安裝後占用的內存為4M(4*1024*1024)。如果你的遊戲是一個2D的,又包含很多的圖片資源,這樣會使你的安裝包在用戶機器上安裝需要大量的磁盤空間,這裏你可以把它們打成AB包放到用戶的手機上,這樣就磁盤占用就會小很多了。

Unity資源解決方案之AssetBundle