Unity資源熱更之AssetBundle(2)———舊版本AssetBundle

尊重原創,轉載請註明出處,謝謝! 
http://blog.csdn.net/y1196645376/article/details/52567171

這篇文章主要講的是舊版的AssetBundle打包一系列方案。


1.API介紹:

a.BuildPipeline.BuildAssetBundle :打包常規資源生成AssetBundle.

這裏寫圖片描述

  • Object mainAsset:             指定mainAsset,這樣解析該AB包的時候可以通過assetBundle.mainAsset得到。
  • Object[] assets :                   指定所有asset,傳入對象數組.解析的時候可以通過LoadAsset(“name”)得到。
  • pathName :                          指定生成的AB包的存儲路徑
  • BuildAssetBundleOptions:打包時候的一些特定屬性選項。
      CompleteAssets : 用於保證資源的完備性,默認開啓;
      CollectDependencies:用於收集資源的依賴項,默認開啓;
      DeterministicAssetBundle:用於爲資源維護固定ID,默認開啓;
      ForceRebuildAssetBundle:用於強制重打所有AssetBundle文件,新增;
      IgnoreTypeTreeChanges:用於判斷AssetBundle更新時,是否忽略TypeTree的變化,新增;
      AppendHashToAssetBundleName:用於將Hash值添加在AssetBundle文件名之後,開啓這個選項可以直接通過文件名來判斷哪些Bundle的內容進行了更新(4.x下普遍需要通過比較二進制等方法來判斷,但在某些情況下即使內容不變重新打包,Bundle的二進制也會變化),新增。
      ChunkBasedCompression:用於使用LZ4格式進行壓縮,5.3新增。
  • targetPlatform:適用平臺。選擇該AB應該使用哪些平臺。
      StandaloneWindows:   打包32位windows平臺上的包。
      StandaloneWindows64: 打包64位windows平臺上的包。
      iOS:                    打包ios平臺上的包。
      Android :                 打包Android平臺上的包。
      當然遠不止支持這幾個平臺格式,但是其他的平臺不是常用就不介紹了。

b.BuildStreamedSceneAssetBundle:將場景打包成AsssetBundle包。

這裏寫圖片描述

  • levels:欲打包的所有場景文件路徑。
  • localtionPath : 生成AssetBundle包保存的相對路徑。
  • target : 同上。
  • options : 同上。

          www下載好場景AB包之後通過獲取成員屬性assetBundle就可以吧場景信息讀入到內存中了。然後只需要通過Application.LoadLevel(name)就可以加載對應的場景。
c.WWW:通過一個路徑進行下載(無論是服務器路徑還是本地路徑下載操作都一樣)。

這裏寫圖片描述

  • Url:下載路徑;請注意:如果是本地鏈接請加上前綴”file://”,如果是http鏈接請加上前綴”http://”。

           www下載後的得到的對象可以通過獲取成員屬性assetBundle,即可得到下載文件的AB包。不過該方法下載得到的資源統統是放在內存裏面的,也就是說每次打開遊戲都需要重新下載。
d.WWW.LoadFromCacheOrDownload : 通過路徑和版本號下載。

這裏寫圖片描述

  • Url:       下載路徑;請注意:如果是本地鏈接請加上前綴”file://”,如果是http鏈接請加上前綴”http://”。
  • Version:版本號;請注意這個版本號不需要你自己去維護,每次Unity從服務器下載資源的到本地的時候都會維護這個資源的版本號。當下次調用該API下載該資源會先對比本地版本號和參數版本號是否匹配,如果不匹配就從服務器重新下載,否則就從磁盤緩存文件中讀取。

          www下載後的得到的對象可以通過獲取成員屬性assetBundle,即可得到下載文件的AB包。該方法下載的資源是緩存到電腦緩存文件中的。
e.BuildPipeline.PushAssetDependencies: 資源入棧。
   BuildPipeline.PopAssetDependencies: 資源出棧。

這裏寫圖片描述

      當我們對場景中多個複雜的物體進行打包的適合涉及到資源依賴關係。可以看到,Push和Pos都是成對使用,一個Push/Pop對就相當於一個Layer(層),層可以嵌套,內層可以依賴外層的資源。也就是說內層某資源在打包時,如果其引用的某個資源已經在外層加載了,那麼內層的這個資源包就會包含該資源的引用而不是資源本身。Push/Pop實際上維持了一個依賴的堆棧。那麼,在加載依賴資源包時,需要注意的是:先加載依賴的資源,然後加載其他資源,需要確保這個順序。


2.實戰使用:

1.單個打包 && 合併打包。 

  • 首先,打開工程,在場景中創建兩個Cube分別命名爲Cube1,Cube2。然後製作成預製體。 
    這裏寫圖片描述

  • 然後編寫下面一段腳本:(腳本源自雨鬆MOMO大神,本人可能根據實際情況略有改動)

using UnityEngine;
using System.Collections;
using UnityEditor;

public class OldAssetBundleEditor : MonoBehaviour {

    [MenuItem("AB Editor/Create AssetBunldes Main")]
    static void CreateAssetBunldesMain()
    {
        //獲取在Project視圖中選擇的所有遊戲對象
        Object[] SelectedAsset = Selection.GetFiltered(typeof(Object), SelectionMode.DeepAssets);

        //遍歷所有的遊戲對象
        foreach (Object obj in SelectedAsset)
        {
            string sourcePath = AssetDatabase.GetAssetPath(obj);
            //本地測試:建議最後將Assetbundle放在StreamingAssets文件夾下,如果沒有就創建一個,因爲移動平臺下只能讀取這個路徑
            //StreamingAssets是隻讀路徑,不能寫入
            //服務器下載:就不需要放在這裏,服務器上客戶端用www類進行下載。
            string targetPath = Application.dataPath + "/StreamingAssets/" + obj.name + ".assetbundle";
            if (BuildPipeline.BuildAssetBundle(obj, null, targetPath, BuildAssetBundleOptions.CollectDependencies))
            {
                Debug.Log(obj.name + "資源打包成功");
            }
            else
            {
                Debug.Log(obj.name + "資源打包失敗");
            }
        }
        //刷新編輯器
        AssetDatabase.Refresh();

    }

    [MenuItem("AB Editor/Create AssetBunldes ALL")]
    static void CreateAssetBunldesALL()
    {

        Caching.CleanCache();

        string Path = Application.dataPath + "/StreamingAssets/ALL.assetbundle";

        Object[] SelectedAsset = Selection.GetFiltered(typeof(Object), SelectionMode.DeepAssets);

        foreach (Object obj in SelectedAsset)
        {
            Debug.Log("Create AssetBunldes name :" + obj);
        }

        //這裏注意第二個參數就行
        if (BuildPipeline.BuildAssetBundle(null, SelectedAsset, Path, BuildAssetBundleOptions.CollectDependencies))
        {
            AssetDatabase.Refresh();
        }
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 這個時候在Unity工具欄處應該出現了一個AB Editor的選項按鈕。 
    這裏寫圖片描述

  • 我們在Project資源視圖欄中同時選中我們剛纔製作好的Cube1,Cube2的Prefab。然後在分別點擊上圖所示的AB Editor欄下的兩個按鈕。這個時候就會分別生成兩個Cube的單個打包和合並打包。不過請注意,一定要保證你的工程Assets目錄下有StreamingAssets文件夾。 
    這裏寫圖片描述

  • 以上我們就對Cube1,Cube2打包完成了,接下來我們來編寫代碼讀取這兩個AssetBundle資源。然後實例化到場景中去。
using UnityEngine;
using System.Collections;

public class OldAssetBundleLoad : MonoBehaviour {

        //不同平臺下StreamingAssets的路徑是不同的,這裏需要注意一下。
        public static readonly string PathURL =
#if UNITY_ANDROID
        "jar:file://" + Application.dataPath + "!/assets/";
#elif UNITY_IPHONE
        Application.dataPath + "/Raw/";
#elif UNITY_STANDALONE_WIN || UNITY_EDITOR
    "file://" + Application.dataPath + "/StreamingAssets/";
#else
        string.Empty;
#endif

    void Start()
    {
        //StartCoroutine(LoadMainGameObject(PathURL + "Cube1.assetbundle"));
        //StartCoroutine(LoadMainGameObject(PathURL + "Cube2.assetbundle"));

        StartCoroutine(LoadALLGameObject(PathURL + "ALL.assetbundle"));
    }

    //讀取一個資源
    private IEnumerator LoadMainGameObject(string path)
    {
         WWW bundle = new WWW(path);

         yield return bundle;

         //加載到遊戲中
         yield return Instantiate(bundle.assetBundle.mainAsset);
         bundle.assetBundle.Unload(false);
    }

    //讀取全部資源
    private IEnumerator LoadALLGameObject(string path)
    {
         WWW bundle = new WWW(path);

         yield return bundle;

         //通過Prefab的名稱把他們都讀取出來
         Object obj0 = bundle.assetBundle.LoadAsset("Cube1");
         Object obj1 = bundle.assetBundle.LoadAsset("Cube2");
         //加載到遊戲中   
         yield return Instantiate(obj0);
         yield return Instantiate(obj1);
         bundle.assetBundle.Unload(false);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 寫好腳本後,將該腳本掛在場景的Camera上面,然後把場景的Cube1,Cube2刪掉。運行遊戲,你會發現腳本通過解析AssetBundle包將Cube1,Cube2實例化到場景中。 
    這裏寫圖片描述

2.引入依賴關係的概念。 

  • 我們回到上面那個demo,打開StreamingAssets文件夾查看剛纔打包好的AB包。 
    這裏寫圖片描述

  • 仔細我們可以發現,分開打包的總大小居然是合併打包的2倍。這是爲什麼呢?其實是這樣的: 

        當選擇了BuildAssetBundleOptions.CollectDependencies時,果然你沒有將A依賴的資源B使用BuildPipeline.PushAssetDependencies() 和BuildPipeline.PopAssetDependencies()來進行依賴打包,那麼A會把其引用的資源都打包到自己的assetbundle 中。如果引用的資源是圖片、sprite或自定義的shader(內置的shader不會打包,這裏的自定義shader被unity看作是一種資源,打包處理的時候也是如同資源來處理的),那麼會打包到assetbundle 中。如果引用的是代碼,那麼會打包一個對工程中代碼的引用,也就是說引用的代碼必須存在於工程中,這樣當c被加載到本地的時候纔可以和本地的代碼進行關聯,如果本地沒有這個代碼,則會丟失對這個腳本的引用。

  • 那這麼說:是不是把所有Prefab都打包到一個AssetBundle裏面就是最佳方案麼?答案是並不:

      1. 更新麻煩:如果所有資源都在一個包下,那麼任意一個資源更改了都要把這個包整體更換。
      2. 維護麻煩:因爲有的物體是共用的某些被依賴項,所以如果涉及到修改這些被依賴項,就會牽一髮而動全身。
  • 所以說:我們需要找到一個解決方案來解決以上兩個問題:1.依賴資源共享2.可維護性。 
        ——而這個解決方案就是我們所說的依賴打包。

3.依賴打包的流程 

  • 依賴關係打包的實質就是將那些被依賴的資源先打包 ( 這裏我把這類資源用”底層資源”代替 )。對於那些引用了底層資源的資源 ( 這裏我把這類資源用”頂層資源”代替 )在打包的時候就不用再將底層資源打包到自己的包裏面,而是添加對底層資源的引用,這樣就避免了重複打包底層資源了。而且不同資源可以根據項目的分類分別打包到不同的AB包裏面,也方便了後期的更新和維護。

  • 廢話不多說,我們來了解如何使用依賴關係打包。這裏我提供兩個依賴關係的示例:

    • 和上面那個例子一樣,兩個Cube引用同一個materials。這樣的依賴關係應該是這樣: 
      這裏寫圖片描述
    所以,打包方式是從最底層開始:green->Cube1->Cube2,後面兩個可以交換順序。當然僅僅是體現在打包順序上是不夠的,我們這裏要引入一個資源棧的概念。一個Push-Pop對應一個資源棧,資源棧可以內部再嵌套多個資源棧。那麼我們試圖LoadAsset的時候,我們就需要將這個asset所在棧的外部棧的所有asset全部load出來。
    • BuildPipeline.PushAssetDependencies: 資源入棧,BuildPipeline.PopAssetDependencies: 資源出棧。 
      這裏我提供一個我總結的一個小技巧: 
      1.先把所有材料編號:A-green,B-Cube1,C-Cube2。 
      2.然後將每個編號兩邊加上括號,表示每個材料都在一個獨立的棧:(A),(B),(C) 
      3.如果X依賴Y,那麼X的棧加入Y的棧裏:(A(B)),(A(C))。 
      4.然後合併:(A(B)(C))。
  • 打包代碼:
using UnityEngine;
using System.Collections;
using UnityEditor;

public class OldAssetBundleDependence : MonoBehaviour
{
    [MenuItem("AB Editor/Create Dependence AssetBunldes")]
    public static void Pack()
    {
        string path = Application.dataPath + "/StreamingAssets";
        BuildTarget target = BuildTarget.StandaloneWindows64;
        BuildAssetBundleOptions op = BuildAssetBundleOptions.CollectDependencies | BuildAssetBundleOptions.CompleteAssets | BuildAssetBundleOptions.DeterministicAssetBundle;
        // (
        BuildPipeline.PushAssetDependencies();
        // A
        BuildPipeline.BuildAssetBundle(AssetDatabase.LoadMainAssetAtPath("Assets/Materials/green.mat"), null, path + "/green.assetbundle", op, target);
        // (
        BuildPipeline.PushAssetDependencies();
        // B
        BuildPipeline.BuildAssetBundle(AssetDatabase.LoadMainAssetAtPath("Assets/Prefab/Cube1.prefab"), null, path + "/Cube1.assetbundle", op, target);
        // )
        BuildPipeline.PopAssetDependencies();
        // (
        BuildPipeline.PushAssetDependencies();
        // C
        BuildPipeline.BuildAssetBundle(AssetDatabase.LoadMainAssetAtPath("Assets/Prefab/Cube2.prefab"), null, path + "/Cube2.assetbundle", op, target);
        // )
        BuildPipeline.PopAssetDependencies();
        // )
        BuildPipeline.PopAssetDependencies();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 清注意,若要使用以上代碼請保證:在你的工程目錄下也應有此幾個資源文件且路徑和名字應當一樣。

  • 保存代碼點擊AB Editor/Create Dependence AssetBunldes,就會在StreamingAssets文件夾下生成三個AB包。仔細觀察發現和我們最開始的單個打包得到的3個包的區別:

這裏寫圖片描述 
非依賴打包

很明顯可以看出差別了。所以使用依賴打包可以很大程度上減少資源的重複,減小包的大小。
  • 不過值得注意的是:通過依賴關係打包生成的AssetBundle不能像之前那樣加載,因爲有了依賴關係所以加載某個asset的時候需要先把它的所有依賴項都提前加載。例如對於剛纔的OldAssetBundleLoad腳本,我們在Start函數去解析AB包實例化Cube1。
void Start()
{
    StartCoroutine(LoadMainGameObject(PathURL + "Cube1.assetbundle"));
}
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4
  • 運行場景,發現成功實例化出Cueb1,然而身上的材質卻丟失了。 
    這裏寫圖片描述

  • 這時候,我們修改Start函數,在實例化Start函數之前先去Load材質資源:

void Start()
    {
        StartCoroutine(LoadAB(PathURL + "green.assetbundle"));
        StartCoroutine(LoadMainGameObject(PathURL + "Cube1.assetbundle"));
    }
    public IEnumerator LoadAB(string path)
    {
        bundle = new WWW(path);

        yield return bundle.assetBundle.mainAsset;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 這次運行發現材質已經成功找到了,所以在使用依賴打包的時候特別需要注意的是需要先Load依賴文件。對了,我們需要注意的是一個AB包不能被重複Load,也就是說假如我用以上的代碼生成了Cube1。但是如果我用同樣的方式再生成一個Cube2,就不能去加載材質了。因爲材質已經被Load過一次,所以就導致我們在創建一個物體的時候我們不知道它的依賴資源哪些已經被Load過的。這就必須要讓我們遵循一個原則,就是Load過的之後及時UnLoad(false)。不過,更好的解決方法應當是,在應該創建一個資源管理類,拿一個字典專門記錄所有的Load過的AB包。這樣我們就知道哪些Load過哪些沒Load過了。

4.依賴打包的擴展 

  • 對於依賴打包的AB包,我們解析的時候需要額外注意依賴包的Load,但是這個過程我們可以讓代碼自動化完成。因爲5.X的新AssetBundle採用的就是這個方案,所以我就簡單說一下思路不給出代碼了。我們可以在依賴關係打包的時候,生成依賴關係信息。然後實例化的時候就可以根據這個信息然後動態獲取一個AssetBundle的所有依賴包的Path.這樣解析起來就很方便了。

以上就是Unity老版本的AssetBundle打包攻略,接下來我會介紹Unity新版本的AssetBundle使用攻略。

Demo源碼地址: 點這裏

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