Unity3D高階-AssetBundle使用

weixin_33890499發表於2017-04-03
0、遊戲套路

我們經常下載一些遊戲App,如果Wifi情況下,App大小就無關緊要。但是如果是流量呢?一看到App的大小,直接就不下了。但是遊戲公司怎麼會不知道?所以採用熱更新,或者直接下載完畢後,再進行載入的行為進行籠絡使用者。當然這也是產品要求的結果。這裡不說遊戲的好玩,只討論這種套路是什麼手段進行的。

642887-cb2a2d82f2cd62fb.png
紅線框中的遊戲你敢流量下載?
1、什麼是AssetBundle?

資源需要打包釋出,所以Unity提供的打包策略,也就是AssetBundle。這個的方式有自己的壓縮格式(LZMA\壓縮),其實就是精簡你的資源。簡稱AB,幾乎所有的資源都可以打包程AB,AB可以存放Unity可識別的任何資源型別,具體取決於檔案的副檔名。例如:匯入的檔案字尾為“.byte”,Unity會將這些檔案作為文字(TextAssets)匯入。匯入的檔案字尾為“.spine”,Unity會將這些檔案作為動畫匯入。


可以通過官方的Demo來進行學習:Unity5 AssetBundle Demo


2、AssetBundle的作用
642887-2f0c7938102e6efc.png
這就是打包並上傳伺服器的資源流程

如果將上述流程反過來,就是載入伺服器資料包

642887-7343cc13cd5bf992.png
載入伺服器的資源流程
3、如何打包成AssetBundle

由於我們在打包的時候,可能會重複打包,造成資源很大。所以將資源拆分細緻後,獨立打包。比如:一個模型的網格Mesh,材質Material,貼圖Texture.

3-1、手動打包:

圖片展示比較簡單


642887-671590becca3acd3.png
Paste_Image.png
642887-185a236c3ba24490.png
做完上面圖片的效果就執行這一步
  • Unity5.0之前舊版AB打包
    本先掃描所有要打包的資源,然後AssetDatabase.GetDespendencie獲得所有的依賴,自己記錄起來,由於怕資源名稱相同,所以使用AssetDatabase。AssetPathToGUID獲得資源的唯一id,然後存起來。
    獲得所有依賴關係之後,再使用BuildPipeline.PushAssetDependenciesBuildPipeline.PopAssetDependencies按照層級和順序來打包。這樣打出來的資源就完全保留了依賴關係,檔案會被拆散,不會重複打包。
  • Unity5.0之後,AB打包使用下面的Api
BulidPipeline.BuildAssetBundles(outputPath)

然後為每一個資源可以設定一個assetBundleName。只要呼叫這個方法,那麼所有已經設定過AB的資源,就會自動打包。

4、如何解壓AssetBundle

Unity5.0之後,預設就有MainAsset(可以指定也可以不指定),5.0之前是沒有的。
如果指定mainAsset那麼載入後可以通過下面來獲取

AssetBundle.mainAsset

如果不指定那麼可以通過

AssetBundle.LoadAsset 指定名字讀取出來

官方Demo
可以加群134688909 將Demo下載下來,因為接下來全部使用這個進行。

642887-a5dc03f63ec027a8.png
這就是Demo的東西,第一步找到載入場景,第二步進行依賴關係生成,執行後Unity會根據依賴關係進行載入
5、AssetBundle依賴關係

官方Demo稍微修改一下
可以加群134688909 將Demo下載下來,因為接下來全部使用這個進行。

642887-cecb208bb207b7e5.png
Paste_Image.png
642887-3afe6682a7c871b0.png
將材質球新增進預製物並且給預製物新增AB
642887-46a9d0a9587a8ab1.png
設定UI的AB
642887-61d522777c81a5f9.png
打包資源
642887-a929c57c9cebb131.png
這就是生成的依賴檔案
642887-08ac6c725f3e8a5e.png
Paste_Image.png
642887-58206a4e1aa6c81f.png
相關依賴

6、案例介紹

案例介紹:官方Demo
可以加群134688909 將Demo下載下來,因為接下來全部使用這個進行。

我們可以測試一下打包成程式

642887-6dbe2729aa9c137b.png
選中Build Player

會輸出一個檔案,你可以選擇一個資料夾進行輸出。


642887-c0bb9b7e0b9d8228.png
Paste_Image.png

我們實際這樣做了,發現輸出控制檯有如下輸出

642887-699dcee22d861814.png
這個表示沒有什麼可以打包的

我們可以將尋找一下是那個程式碼中輸出的這個話

642887-e4f688f595709f3a.png
Paste_Image.png
642887-ad139aabc628dce7.png
解釋一下
642887-56c1715d0218427f.png
順藤摸瓜

順藤摸瓜之後其實我們發現就是我們的當前專案中的場景沒有載入進去

642887-e5c65da20df93b40.png
將場景拖拽進去
642887-acecd0d688575807.png
這個時候就進行打包了
642887-e4f9159b38c4a6c7.png
打包後發現成為了一個PC客戶端
執行後,發現與我們手動打包出來的程式一模一樣,也就是說我們可以程式的手段進行編譯打包。那麼這個程式碼在哪?我們是不是可以直接使用呢?
642887-9b20b4122f8974c9.png
這就是內部的判斷平臺,根據平臺打包

642887-224586a30bee52d7.png
打完包後,返回工程發現裡面中多了一個資料夾

StreamingAssets它下面的所有資源不會被加密,然後是原封不動的打包到釋出包中
然後我們就可以開始玩耍遊戲中的所有場景了,因為資源已經OK了。可以尋找並載入了。

7、批量命名打包

專案中的資源涉及方方面面,我們不可能每個都進行修改名字並打包,所以需要一個工具,,

642887-b179c0c7d92ccb93.png
Paste_Image.png

這裡是核心程式碼塊,也是遊戲核心開發人員的必備技能涉及自定義編輯器工具模組。如果你需要可以加群獲取134688909。群檔案下載就ok!

8、

由於我們要將模型資源放在遠端的伺服器端,但如果直接放fbx模型是不可以載入的,所以我們可以將fbx做成預設或者是直接將其打包成assetbundle格式的,然後通過www來載入獲取。
說下使用方法:

1、把附件指令碼放到工程資料夾下的...\Assets\Editor資料夾下。

2、在工程的Project檢視裡點選想要儲存的資源,網路上推薦的是Prefab,右鍵點選,選擇選單裡最下面的兩個選項任意一個都可以,第一個選項對應的自定義屬性有一個過期了,但是不影響使用。

3、指定檔案的構建路徑和檔案字尾,字尾無所謂。

4、推薦製造做法:

任何形式的資源都可以,包括集合資源,比如建立一個空的GameObject,把所有想要關聯的其他GameObject都拖進去,然後在project檢視裡建立一個prefab,將這個集合資源GameObject拖進去作為一個prefab。執行上面的第2、3步驟。

官方的講解
1.首先要講一下不同平臺下的一個StreamingAssets路徑,這是不同的。

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

這就獲取到了不同平臺的一個路徑,我們可以將打包的檔案放在這些路徑下,然後再從這路徑去讀取資源。
2.關於打包assetbundle的指令碼

using UnityEngine;
using System.Collections;
using UnityEditor;
 
public class Test : Editor
{
    //打包單個
    [MenuItem("Custom Editor/Create AssetBunldes Main")]
    static void CreateAssetBunldesMain ()
    {
        //獲取在Project檢視中選擇的所有遊戲物件
        Object[] SelectedAsset = Selection.GetFiltered (typeof(Object), SelectionMode.DeepAssets);
 
        //遍歷所有的遊戲物件
        foreach (Object obj in SelectedAsset) 
        {
            //本地測試:建議最後將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("Custom 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 ();
        } else {
            
        }
    }
    
    [MenuItem("Custom Editor/Create Scene")]
    static void CreateSceneALL ()
    {
        //清空一下快取
        Caching.CleanCache();
        string Path = Application.dataPath + "/MyScene.unity3d";
        string  []levels = {"Assets/Level.unity"};
        //打包場景
        BuildPipeline.BuildPlayer( levels, Path,BuildTarget.WebPlayer, BuildOptions.BuildAdditionalStreamedScenes);
        AssetDatabase.Refresh ();
    }
    
}

前提要新手動建立一個StreamingAssets資料夾

3.讀取資源,這裡只舉例從本地讀取,跟從網路讀取是一樣的,可以參考官方文件:

本地讀取

using UnityEngine;
using System.Collections;
 
public class RunScript : 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 OnGUI()
    {
        if(GUILayout.Button("Main Assetbundle"))
        {
            //StartCoroutine(LoadMainGameObject(PathURL + "Prefab0.assetbundle"));
            //StartCoroutine(LoadMainGameObject(PathURL +  "Prefab1.assetbundle"));
        
            StartCoroutine(LoadMainCacheGameObject(PathURL + "Prefab0.assetbundle"));
            StartCoroutine(LoadMainCacheGameObject(PathURL +  "Prefab1.assetbundle"));
        }
        
        if(GUILayout.Button("ALL Assetbundle"))
        {
            StartCoroutine(LoadALLGameObject(PathURL + "ALL.assetbundle"));
        }
        
        if(GUILayout.Button("Open Scene"))
        {
            StartCoroutine(LoadScene());
        }
        
    }
    
    //讀取一個資源
    
    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.Load("Prefab0");
         Object  obj1 =  bundle.assetBundle.Load("Prefab1");
        
         //載入到遊戲中   
         yield return Instantiate(obj0);
         yield return Instantiate(obj1);
         bundle.assetBundle.Unload(false);
    }
    
    private IEnumerator LoadMainCacheGameObject(string path)
    {
         WWW bundle = WWW.LoadFromCacheOrDownload(path,5);
         
         yield return bundle;
         
         //載入到遊戲中
         yield return Instantiate(bundle.assetBundle.mainAsset);
         
         bundle.assetBundle.Unload(false);
    }
    
    
    private IEnumerator LoadScene()
    {
         WWW download = WWW.LoadFromCacheOrDownload ("file://"+Application.dataPath + "/MyScene.unity3d", 1);
            
          yield return download;
          var bundle = download.assetBundle;
          Application.LoadLevel ("Level");
    }
}

如果assetbundle檔案放在伺服器端,直接用www.loadfromcacheordownload()通過版本來控制是否從伺服器下載並儲存到本地。本人親自測試,這個方法是能下載到本地的,存在沙盒檔案下(移動開發者的朋友應該知道),當然也可以自己來做版本控制,那樣更靈活,並且擺脫www.loadfromcacheordownload()方法的束縛,貌似這個方法存貯檔案是有空間大小限制的,據說是50M,本人沒有親自考證,後期我會自己做一個版本控制!

9、使用UnityWebRequest載入AssetBundle

1.使用UnityWebRequest需要引用using UnityEngine.Networking.
2.UnityWebRequest中有幾個方法,UnityWebRequest.GetAssetBundle(URL)獲取assetBundle資源,有一個返回資源的函式SendWebRequest,用來下載資源,DownloadHandlerAssetBundle.GetContent()未獲取assetBundle資源包,接著就是LoadAsset<>()載入,例項化。
3.程式碼如下

void Start () { 
StartCoroutine(Load()); 
}
private IEnumerator Load()
{
    string url = "";//此為AssetBundle資源路徑,可為本地,也可以是服務端
   UnityWebRequest request=  UnityWebRequest.GetAssetBundle(url);
    yield return request.SendWebRequest ();
    // AssetBundle ab = DownloadHandlerAssetBundle.GetContent (request );

    AssetBundle ab = (request.downloadHandler as DownloadHandlerAssetBundle).assetBundle;
    GameObject ao = ab.LoadAsset<GameObject>("Man1");
    Instantiate(ao);
}
IEnumerator LoadABWeb()
    {
        UnityWebRequest request = UnityWebRequest.GetAssetBundle(path);
        yield return request.SendWebRequest();
        Debug.Log(request.error);
        AssetBundle bundle = DownloadHandlerAssetBundle.GetContent(request);
        audio.clip = bundle.LoadAsset<AudioClip>("music");
        audio.Play();
    }

相關文章