(轉)Unity資源打包學習筆記(一)、詳解AssetBundle的流程

轉載請標明出處:http://www.cnblogs.com/zblade/

  本文參照unity官網上對於assetBundle的一系列講解,主要針對assetbundle的知識點做一個梳理筆記,也爲後續的資源打包設計做一個基礎學習,本文的代碼和圖片均來自unity官網,詳情可以查看Unity的DOCUMENTATION。

一、什麼是AssetBundle

  AssetBundle就像一個ZIP壓縮文件,裏面存儲着不同平臺的特殊資源(models/texture/prefabs/materials/audio clip/scenes...), 這些資源都可以在運行時進行加載。具體的assetBundle中主要包含什麼?主要包含兩種互相關聯的東西:

1. 磁盤上的文件:也就是assetbundle文檔,可以將其視爲一個容器或者文件夾,其中包含兩類文件:序列化文件和資源文件,序列化文件就是資源在打包後對應的各個平臺的序列化操作後的文件,資源文件主要是針對textures/audio等較大的文件打包的二進制文件,這類文件在加載的時候是在其他線程執行的(效率更高)。

2. 就是實際的assetbundle對象了,可以通過代碼來進行資源加載,其中主體是各個資源在進行加載的時候的存儲路徑圖。用圖表示:

二、如何對資源進行分組便於打包AssetBundle

  雖然可以對AssetBundle自由的進行規劃,但是在進行項目的資源管理的時候,還是有一些規劃建議可以採用:

1.依據邏輯實體進行分組

  這種資源分類方式是依據資源的功能進行分類,例如 UI/角色/場景/code等具體各自功能規劃的部分來進行資源分組,可以將所有的textures/layout data都打入UI相關分類中,可以將所有的模型和動畫都打入角色相關的資源中,將場景相關的貼圖和模型都打入場景資源中。

  採用邏輯實體分組,對於資源的下載更新更爲有利,由於資源的分類,可以在進行資源更新的時候,只更新對應的資源,而無需更新冗餘的其他資源。

  使用這種分類方式最合適的策略,就是將資源進行詳細的分類(when and where will be used)

2.類型分組

  主要依據資源的類型來進行分組,這樣對於不同的應用平臺都具有一定的適用性。比如對於audio文件的壓縮設置,在mac和windows上都是一致的,那麼可以將audio文件都歸類爲一類文件,實現文件資源的複用(不同平臺的打包設置),對於shaders而言,對於不同平臺需要不同的編譯設置,那麼就需要分類處理。這類分類方法,對於在不同的版本中變動頻率較低的代碼文件和prefabs顯得更有優勢。

3.相互關聯的內容分組

  這種策略的,就是將需要同時進行加載的資源都歸類爲一個分組,例如將不同場景中的角色都依據場景來進行分組,這就要求單獨一個場景中的資源只能用於該場景,各個分組之間沒有互相關聯的關係。這種分類方式,對於資源的加載時間有較大的縮減,這種分類方式的使用場合主要在場景資源中,在不同的場景資源中,其包含的資源各自互相不關聯。

在一個項目中,可以將上述的幾種策略都交互使用,對應具體的應用需求來靈活的採用分組策略,當然unity也提供了一些資源分組的tips:

  • 分離高頻和低頻更新的資源;
  • 將需要同時下載的資源合併進一個組,例如Model以及其關聯的animations;
  • 如果出現多個bundle中的多個object都依賴於另一個完全不同的bundle,那麼將這些依賴關係都移動到一個單獨的bundle,這樣可以降低依賴關係的複雜度;多個bundle均依賴於另一個bundle中的資源,那麼將這些bundle以及其依賴的資源歸類到一個資源,這樣可以降低資源的重複率(避免一份資源被拷貝到多個不同的bundle中);
  • 不可能同時加載的資源,需要歸類的各自的assetbundle中,例如標準和高配的資源;
  • 如果一個assetbundle中資源在加載的時候低於50%需要被加載,那麼可以考慮將這些需要被加載的資源單獨分類爲一個資源(避免冗餘的加載);
  • 如果一組Objects對應的是一個資源的不同版本,那麼可以考慮assetbundle variants

 

三、如何打包AssetBundle

  unity資源打包的接口,就是BuildPipeline.BuildAssetBundles函數,其對應具體的三個參數,第一個是bundle的輸出路徑,下面來詳細分析一下剩下的2個參數:

1、BuildAssetBundleOptions

  具體的參數設置,可以參看API當中的詳細信息,下面主要集中說三個參數,分別對應三種壓縮格式的選擇。

1)BuildAssetBundleOptions.None :

  採用LZMA的壓縮格式, 這種壓縮格式要求資源在使用之前需要全部被解壓,這就會帶來在使用一個極小的文件的時候會額外帶來較長的解壓時間消耗。比較蛋疼的是,一旦這個bundle被解壓之後,在磁盤上又會以LZ4的格式重新壓縮,LZ4的壓縮格式,在使用資源的時候不需要全部解壓。

這種壓縮格式主要用於一個bundle中資源都需要被加載的時候,例如打包角色或者場景資源的時候,這種壓縮格式在初始化下載的時候被推薦(更小的包體),這些資源在被解壓後,又會以LZ4的格式被緩存。

2) BuildAssetBundleOptions.UncompressedAssetBundle:

  無壓縮的打包,加載的文件更大,但是時間更快(省去解壓的時間)

3)BuildAssetBundleOptions.ChunkBasedCompression:

  採用LZ4的壓縮格式,相比於LZMA而言文件體積更大,但是不要求在使用之前整個bundle都被解壓。LZ4使用chunk based 算法,這就運行文件以chunk或者piece的方式加載,只解壓一個chunk文件,而無需解壓bundle中其餘不相關的chunk。

2、BuildTarget

  也就是當前資源需要被使用的平臺的分類,在打完資源後,會發現文件夾中有更多的文件,一般是2*(n+1), 對於各個不同的資源,都會有一個manifest文件,一般是 bundlename+".manifest",除此之外,還會有一個額外的manifest文件,對應不同的平臺會有不同的額外的manifest(這是一個總體的manifest文件)。

對於manifest中內容,可以在文本文件中打開,一般舉例:

複製代碼
ManifestFileVersion: 0
CRC: 2422268106
Hashes:
  AssetFileHash:
    serializedVersion: 2
    Hash: 8b6db55a2344f068cf8a9be0a662ba15
  TypeTreeHash:
    serializedVersion: 2
    Hash: 37ad974993dbaa77485dd2a0c38f347a
HashAppended: 0
ClassTypes:
- Class: 91
  Script: {instanceID: 0}
Assets:
  Asset_0: Assets/Mecanim/StateMachine.controller
Dependencies: {}
複製代碼

  首先是版本,然後是CRC校驗碼,hash碼,classtypes,包含的資源及其路徑,依賴關係(dependencies)。

PS:

  對於依賴關係就多詳細講解一下,所謂的依賴關係,指的是一個bundle中的一個或者多個UnityEngine.Objects包含對另一個bundle中的一個UnityEngine.Object的引用,如果一個bundle中的object對包含對其他非該bundle的object的引用,但是該object不屬於任何一個bundle,那麼這種依賴關係就不存在(這兒會帶來一個問題,如果多個bundle均引用一個不屬於bundle的object,那麼在加載的時候,各個bundle均會將該object進行copy到其內存中,這就帶來內存的額外冗餘佔用),所以依賴關係就是兩個或者多個bundle之間的object的引用關係。

  對於依賴資源的加載,需要在該資源被加載前加載,unity不會自動的加載依賴資源,這需要在代碼中實現依賴資源的加載。

 

四、如何使用AssetBundle

  在說完bundle的分類,打包後,接下來就是如何在實際的遊戲中加載使用這些bundle。在unity5以後,提供了4種不同類型的加載接口,下面逐一分析一下這四種不同接口的使用:

1、AssetBundle.LoadFromMemoryAsync(byte[] binary, unit crc = 0)

  這個方法用來加載ab數據的bytes數組,如果數據是使用LZMA的壓縮格式,那麼在加載的時候會進行解壓的操作,LZ4格式的數據則會保持其壓縮的狀態,使用示例:

複製代碼
using UnityEngine;
using System.Collections;
using System.IO;

public class Example : MonoBehaviour
{
    IEnumerator LoadFromMemoryAsync(string path)
    {
        AssetBundleCreateRequest createRequest = AssetBundle.LoadFromMemoryAsync(File.ReadAllBytes(path));
        yield return createRequest;
        AssetBundle bundle = createRequest.assetBundle;
        var prefab = bundle.LoadAsset<GameObject>("MyObject");
        Instantiate(prefab);
    }
}
複製代碼

  當然,對於bytes數組,也可以使用File.ReadAllBytes(path)的方式來加載數組。

2、 AssetBundle.LoadFromFile

  在加載非壓縮文件或者LZ4壓縮類型文件的時候,該接口效率極高,對於LZMA壓縮格式的文件,也會在加載的時候執行解壓的操作,使用示例:

複製代碼
public class LoadFromFileExample extends MonoBehaviour {
    function Start() {
        var myLoadedAssetBundle = AssetBundle.LoadFromFile(Path.Combine(Application.streamingAssetsPath, "myassetBundle"));
        if (myLoadedAssetBundle == null) {
            Debug.Log("Failed to load AssetBundle!");
            return;
        }
        var prefab = myLoadedAssetBundle.LoadAsset.<GameObject>("MyObject");
        Instantiate(prefab);
    }
}
複製代碼

ps: 在unity5.3及更早的版本中,在安卓平臺上如果從streaming assets路徑中加載文件會失敗(路徑文件夾中會額外包含.jar文件)。

3、WWW.LoadFromCacheOrDownload

  這個接口會被淘汰(被UnityWebRequest替換),那麼就不過多的講解這個接口(注意這個接口會進行存儲分配的操作以容納資源,如果分配不足以存儲會使得加載失敗)。

4、UnityWebRequest

  這個接口,會有兩步操作,首先是創建一個web request(調用UnityWebRequest.GetAssetBundle), 然後進行資源的獲取(調用DownloadHandlerAssetBundle.GetContent),unity提供的使用示例爲:

複製代碼
IEnumerator InstantiateObject()
    {
        string uri = "file:///" + Application.dataPath + "/AssetBundles/" + assetBundleName;       
         UnityEngine.Networking.UnityWebRequest request = UnityEngine.Networking.UnityWebRequest.GetAssetBundle(uri, 0);
        yield return request.Send();
        AssetBundle bundle = DownloadHandlerAssetBundle.GetContent(request);
        GameObject cube = bundle.LoadAsset<GameObject>("Cube");
        GameObject sprite = bundle.LoadAsset<GameObject>("Sprite");
        Instantiate(cube);
        Instantiate(sprite);
    }
複製代碼

  使用這種方式,可以使得開發者更爲靈活的操作下載數據,同時進行內存使用分配,會逐漸的被用來WWW接口。

  在加載完assetBundle後,接下來,就是如何從bundle中獲取資源(asset),其基本的接口模板爲:

T objectFromBundle = bundleObject.LoadAsset<T>(assetName);

  如果想獲取所有的assets則可以使用接口:

Unity.Object[] objectArray = loadedAssetBundle.LoadAllAssets();

  一旦獲取到asset,那麼就可以在遊戲中使用這些資源了(一般是實例化創建操作)。

5、加載AssetBundle Manifest

  除了加載assetbundle,一般還會加載其對應的manifest(與其存儲在同一個文件夾下的相同名字的manifest),一般加載manifest的操作示例:

AssetBundle assetBundle = AssetBundle.LoadFromFile(manifestFilePath);
AssetBundleManifest manifest = assetBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");

  在前文也提及到,如果一個assetbundle依賴於另一個assetbundle,那麼需要提前加載依賴相關的bundle,那麼依據manifest,可以加載其依賴的assetbundle:

複製代碼
AssetBundle assetBundle = AssetBundle.LoadFromFile(manifestFilePath);
AssetBundleManifest manifest = assetBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
string[] dependencies = manifest.GetAllDependencies("assetBundle"); //Pass the name of the bundle you want the dependencies for.
foreach(string dependency in dependencies)
{
    AssetBundle.LoadFromFile(Path.Combine(assetBundlePath, dependency));
}
複製代碼

  現在,已經加載了assetbundle, 也獲取了assetbundle的dependencies,以及其中assets,這樣就可以管理這些assetbundle了。

 

五、管理AssetBundle

  unity在場景中的Object被移除的時候不自動釋放objects,資源的清理需要再特定的時間觸發(場景切換)或者手動的管理。所以怎麼加載和卸載資源顯得尤爲重要,不合適的加載可能會導致資源的重複加載,不合適的卸載可能會帶來資源的缺失(比如丟失貼圖)。

  對於assetbundle的資源管理,最重要的是掌握什麼時候調用AssetBundle.Unload(bool)這個函數,傳入true/false會有不同的卸載策略。這個API會卸載對應的assetbundle的頭部信息,參數對應着是否同時卸載從該assetbundle中實例化的所有Objects。

  AssetBundle.Unload(true)會卸載assetbundle中的所有gameobjects以及其依賴關係,但是並不包括基於其Objects實例化(複製)的Object(因爲這些object不屬於該assetbundle,只是引用),所以當卸載貼圖相關的assetbundle的時候,場景中對其引用的實例化物體上會出現貼圖丟失,也就是場景中會出現紅色的區域,unity都會將其處理成貼圖丟失。

  舉例說明,假設材質M來自於assetbundle AB, 如果 AB.Unload(true), 那麼場景中任何M的實例都會被卸載和銷燬,如果AB.Unload(false), 那麼就會切斷材質M實例與AB之間的關係:

  那麼如果該assetbundle AB在後面再次被加載,unity不會重新關聯其關係,這樣在後續的使用中,就會出現一份材質的多個實例:

  所以通常情況下,AssetBundle.Unload(false) 並不能帶來較爲合理的釋放結果,AssetBundle.Unload(true)通常用來確保不會在內存中多次拷貝同一個資源,所以其更多的被項目所採納,此外還有兩個常用的方法用來確保其使用:

1)在遊戲中,對於場景的卸載有明確的規劃,比如在場景切換中或者場景加載中;

2)管理好對每個單獨的object的計數,只有在沒有引用的時候才卸載該assetbundle,這樣可以規避加載和卸載過程中的多份內存拷貝問題。

  如果要使用AssetBundle.Unload(false), 那麼這些實例化的對象可以通過2中途徑卸載:

1)清除對不需要物體的所有引用,場景和代碼中都需要清楚,然後調用Resources.UnloadUnusedAssets;

2) 在場景加載的時候採用非增量的方式加載,這會清楚當前場景中的所有Objects,然後反射自動調用Resources.UnloadUnusedAssets

如果你不想管理這些assetbundle,unity推出了AssetBundle Manager,可以學習瞭解一下,此外Unity還推出了一些AssetBundle Browser Tool, 也可以學習瞭解一下。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章