Siki_Unity_3-7_AssetBundle從入門到掌握

Unity 3-7 AssetBundle從入門到掌握

任務1&2&3:課程介紹

AssetBundle -- 用於資源的更新html

爲了以後的xLua (Lua熱更新的框架)打下基礎git

任務4&5:AssetBundle的定義和做用

AssetBundle的學習 -- 一手學習資源:UnityManual -> AssetBundles -> 教程式的文檔github

AssetBundle是一個壓縮包(也能夠認爲是一個文件夾)
  包含模型Model、貼圖Texture、預製體Prefab、聲音AudioClip、甚至整個場景
  在遊戲運行的時候能夠被加載算法

這個壓縮包被打包出來存在硬盤中,裏面包含的文件能夠分爲兩類:serialized file和resource files
  serialized file:資源,如模型、預製體,被打碎放在一個對象中,最後統一被寫進一個單獨的文件
  resource file:某些二進制資源,如圖片、聲音,被單獨保存,方便快速加載
    -- 一個單獨的圖片或聲音就會被打包成一個.resource文件數組

壓縮包可使用LZMA和LZ4壓縮算法,便於更快的進行網絡傳輸 -- 區別見任務13瀏覽器

爲何會用到網絡傳輸呢?
  1. 把一些能夠下載的資源放在AssetBundle中,而不放在app中,而在須要的時候從網上直接加載資源
    -- 減小安裝包的大小
    安裝包過大:影響用戶體驗,或致使用戶不想下載
  2. 方便更新
    -- 對於某些模型,若是進行某些活動更新,用戶不須要更新安裝包
    進入遊戲後,檢查資源更新,從服務器上下載模型並加載便可
    第一次啓動後,或以後有更新時,下載更新包到本地,以後就不用下載了緩存

AssetBundle自身保存着互相的依賴關係
  好比一個包是專門保存模型的,而另外一個包是專門保存貼圖的,那這兩個包之間就會有依賴關係安全

AssetBundle在加載時,做爲一個AssetBundle對象,對象中包含的內容就是AssetBundle壓縮包中的內容服務器

任務6:AssetBundle使用流程

https://docs.unity3d.com/Manual/AssetBundles-Workflow.html網絡

1. 指定資源的AssetBundle屬性

2. 構建AssetBundle包
  AssetBundle包能夠有多個,每一個AssetBundle包中能夠有多個文件
  Unity會搜尋項目中的資源,若是資源被標記了須要打包,則會被打包到AssetBundle包中

3. 上傳AssetBundle包
  通常狀況下都會上傳到服務器
4. 加載AssetBundle包及其中的資源
  啓動遊戲以後,遊戲會在服務器上檢查更新,將更新包下載到本地,加載上面的資源

任務7&8:打包AssetBundle

1. 指定資源的AssetBundle屬性 -- 詳見任務10~12

從AssetStore上搜索Material資源

建立一個Cube,拉伸成一個牆壁的形狀,將mat資源附加上

製做成一個名爲wall的prefab

修改wall prefab的AssetBundle標籤,New -> Wall (不區分大小寫)
  前面也能夠寫成 aaa/bbb的格式,表示在aaa文件夾下生成名爲bbb的assetbundle文件
    好比:scene/wall
  後綴寫成assetbundle,不是必要的,隨意填寫都行

設置成相同的assetbundle屬性的不一樣物品,會被打包到相同的assetbundle文件中

2. 使用Unity的API進行構建AssetBundle包 -- 詳見任務13
  這個API是隻有在Editor模式下才運行的

建立Editor文件夾
新建CreateAssetBundles.cs腳本

using UnityEditor;
不繼承自MonoBehaviour

BuildPipeline.BuildAssetBundles(string outputPath, BuildAssetBundleOptions option, BuildTarget platform);

string path; // path是以工程的根目錄爲基礎的 -- 輸出目錄須要已存在,Unity不會自動建立路徑
  所以須要進行路徑是否存在的判斷
  using System.IO;
  if(!Directory.Exists(path)) {
    Directory.CreateDirectory(path);
  }
BuildAssetBundleOptions.None // 表示不去設置該選項 -- 選項解釋詳見任務13
BuildTarget.StandaloneWindows64 // 表示爲了window64平臺打包的 -- 不一樣平臺之間的ab包不能夠交叉使用

using UnityEditor; using System.IO; public class CreateAssetBundle {

    [MenuItem("Assets/BuildAssetBundles")] static void BuildAllAssetBundles() {
        string path = "AssetBundles";
        if (!Directory.Exists(path)){
            Directory.CreateDirectory(path);
        }
        BuildPipeline.BuildAssetBundles(path, 
            BuildAssetBundleOptions.None, BuildTarget.StandaloneWindows64);
}}

[MenuItem("Assets/BuildAssetBundles")]  // 表示這個方法在菜單欄Assets下建立子菜單BuildAssetBundles

Unity中,Assets -> 點擊BuildAssetBundles,就會進行AssetBundle的打包了

Unity中的Project下沒有任何變化,
  在系統中打開工程根目錄,會發現新建了一個AssetBundles文件夾

wall.assetbundle就是wall的prefab文件
wall.assetbundle.manifest記錄了wall.assetbundle的依賴項

AssetBundles是爲當前目錄
AssetBundles.manifest當前目錄生成了哪一些assetbundle文件

.manifest是文本文件,一個assetbundle文件對應一個manifest文件
  記錄了對應的assetbundle的依賴,詳見任務14

如何刪除不用的assetbundle名字
  好比如今發現咱們的assetbundle名寫錯了,從新生成一個assetbundle
  可是設置assetbundle屬性的菜單中,會發現原來的assetbundle名字沒有被替換,依然存在

  在設置資源的AssetBundle屬性的地方,選擇remove unused names
  如今,在設置assetbundle屬性的菜單裏就找不到不用的assetbundle名了

任務9:AssetBundle的加載和使用

3. 上傳AssetBundle包
  由於在開發階段,咱們要重複對AssetBundle包進行修改,所以不會進行上傳到服務器這一操做

  開發階段直接將AssetBundle包放在本地,再進行加載

4. 加載AssetBundle包  -- 詳見任務16

這裏先講解加載本地的AssetBundle包
加載本地的AssetBundle包和加載服務器上的AssetBundle包所用的API是不一樣的

如今,將咱們的場景下的物品和Project中的prefab清空
  爲了更清楚地看到物體是從AssetBundle中加載出來的

建立一個空物體,命名LoadAssetBundle
  添加腳本LoadAssetBundleFromLocal.cs

在Start()中進行加載操做

AssetBundle.LoadFromFile("AssetBundles/scene/wall.assetbundle");
// 須要加後綴

返回值爲AssetBundle類型的對象 -- 見任務4&5:
AssetBundle在加載時,做爲一個AssetBundle對象,對象中包含的內容就是AssetBundle壓縮包中的內容

AssetBundle assetBundle = ...;
GameObject wallPrefab = assetBundle.LoadAsset<GameObject>("Wall");
  // 對象類型爲GameObject,名爲"Wall",將加載到的資源賦值給GameObject對象
  // 這裏的名字"Wall"不能亂寫,須要和打包前的名字相同 -- Case-insensitive
Instantiate(wallPrefab);

另外一種方法是assetBundle.LoadAllAssets();
  返回的是Object[],
  能夠經過foreach將全部Object[]中包含的prefabs都實例化出來

任務10&11:AssetBundle的分組策略
任務12:依賴打包

以後進行每一個階段的詳解

1. 資源的AssetBundle屬性詳解

AssetBundle的分組策略(僅供參考,需結合實際項目使用)

邏輯實體分組:
  一個UI界面/ 全部UI界面 (這個界面裏面的貼圖和佈局信息)一個包
  一個角色/ 全部角色 (這個角色裏面的模型和動畫)一個包
  全部的場景共享的部分 (全部共享的貼圖和模型)一個包

類型分組:
  全部聲音資源 一個包
  全部shader 一個包
  全部模型 一個包
  全部材質 一個包
  全部動畫 一個包 等等

按照使用分組:
  在某一時間內使用的全部資源 一個包 -- 好比一個關卡中的全部資源
  在一個場景中所需的全部資源 一個包

總結:
  1. 把常常更新的資源放在一個單獨的包中,跟不常常更新的包分離
    -- 用戶更新的時候能夠減小下載量

  2. 把須要同時加載的資源放在一個包中
    -- 這些須要同時使用的資源,若是不在同一個包中,則須要加載多個包

  3. 把其餘包所共享的資源放在同一個包中
    -- 好比:如有CubeWall和CapsuleWall共用了MaterialA,MaterialA中用了貼圖TextureA
      那麼在打包CubeWall的時候,會將它所依賴的資源,如MaterialA和TextureA,和它打包到一塊兒
      對於CapsuleWall也是同樣的,這樣的話會致使MaterialA和TextureA被打包了屢次
      資源打包的重複會使得assetbundle包變大
      若是將MaterialA和TextureA打包到同一個包中,
      這兩個wall prefab去引用這個資源包中的material和texture,即依賴於這個資源包

  4. 若是對於一個同一個資源有兩個版本,能夠考慮經過後綴來區分
    -- 這兩個版本都會使用到,好比角色升級前和升級後的模型

  5. 把一些須要同時加載的小資源打包成一個包
    -- 若是同時使用,且資源很小,那麼就放在一個包中吧
      由於加載包也是須要時間的,在同一個包中,就一會兒都加載出來了
      注意是小資源
  -- 任務12:依賴打包
    以上述例子來解釋
    
    將共用的Material和Texture文件打入一個包,叫share
    將CapsuleWall和CubeWall分別打入兩個包,capsulewall和cubewall包
    Unity會自動進行依賴控制
    build asset bundle -->
      查看assetbundle的大小爲67.9kb
      按原來的方式打包出來的大小將近兩倍。

任務13:打包選項 -- AssetBundle的壓縮方式

2. 構建AssetBundle包的詳解

BuildPipeline.BuildAssetBundles(path, BuildAssetBundleOptions, BuildTarget);

BuildAssetBundleOptions: 有不少選項
  None:使用LZMA算法壓縮 -- 高壓縮比,可是加載的時間更長
    使用以前須要總體解壓,假如包中有三個資源,其中一個資源是要用的,可是必須所有解壓才能使用
    一旦被解壓,這個包會使用LZ4從新壓縮,這個時候使用資源時就不須要總體解壓了。
      LZ4算法 -- 使用資源時不須要總體解壓,使用什麼資源就解壓哪個,加載速度快,可是壓縮程度不大
    即,在下載的時候使用LZMA算法--由於包小;以後會使用LZ4算法保存到本地--由於加載快
      這個機制是Unity內置的優化策略
  UncompressedAssetBundle:不壓縮,包大,加載快
  ChunkBasedCompression:使用LZ4算法壓縮
    壓縮率沒有LZMA高,但解壓速度塊,並且能夠加載指定資源而不用總體解壓
    優勢:能夠得到跟不壓縮相媲美的加載速度,並且又將包大小壓縮了

實例對比:
  使用None,AssetBundle大小爲67.9KB
  使用ChunkBasedCompression,AssetBundle大小爲97.2KB
  使用UncompressedAssetBundle,AssetBundle大小爲151KB

任務14&15:Manifest文件詳解 && 資源的依賴關係

以share.assetbundle.manifest爲例

ManifestFileVersion: 0
CRC: 1445635609    -- CRC: 用於校驗文件是否完整
Hashes:
  AssetFileHash:
    serializedVersion: 2
    Hash: c009e7bf6581a024ac7d30c2196142f6
  TypeTreeHash:
    serializedVersion: 2
    Hash: cc983a3149e5fb03ce027e70c7a1a559
HashAppended: 0
ClassTypes:
- Class: 21
  Script: {instanceID: 0}
- Class: 28
  Script: {instanceID: 0}
- Class: 48
  Script: {instanceID: 0}
Assets:    -- 包中包含的文件
- Assets/Materials/Wood texture floor_01/Wood texture floor_01.png
- Assets/Materials/Wood texture floor_01/Wood texture floor_01.mat
Dependencies: []    -- share包沒有依賴其餘包,所以爲空

以capsulewall.assetbundle.manifest爲例

ManifestFileVersion: 0
CRC: 429711530
Hashes:
  AssetFileHash:
    serializedVersion: 2
    Hash: e312559590875437e4b50f7f082675eb
  TypeTreeHash:
    serializedVersion: 2
    Hash: 32a31884022770668140c4ee94dc0ded
HashAppended: 0
ClassTypes:
- Class: 1
  Script: {instanceID: 0}
- Class: 4
  Script: {instanceID: 0}
- Class: 21
  Script: {instanceID: 0}
- Class: 23
  Script: {instanceID: 0}
- Class: 33
  Script: {instanceID: 0}
- Class: 43
  Script: {instanceID: 0}
- Class: 136
  Script: {instanceID: 0}
Assets:
- Assets/Prefabs/CapsuleWall.prefab
Dependencies:    -- 由於capsulewall依賴了share包中的材質和貼圖,所以這裏有了對share的依賴
- "D:/Programming/SikiUnityLearning/3-7AssetBundle/AssetBundleProject/AssetBundles/share.assetbundle"

對於資源的加載而言,若是一個資源有依賴,那麼必須先去加載它的依賴,不然會出現資源 (好比貼圖) 丟失的狀況
  就算當前Project中是存在那些材質資源的,也不行,須要加載匹配的依賴包中的資源才行

好比直接加載cubewall並實例化:
  

須要在加載cubewall以前加載share包 -- 不必定,詳見下
AssetBundle sharedAssetBundle = AssetBundle.LoadFromFile("AssetBundles/share.assetbundle");
以後再加載cubewall .....
就能夠獲得完整的cubewall了

資源的依賴關係:

上面講到假如MaterialA中使用到了TextureB,即MaterialA包依賴TextureB包;
  被依賴的包TextureB必須先加載,才能進行加載MaterialA

其實否則:
  正確的順序爲,包的加載並無嚴格的前後順序
  只要保證在使用到MaterialA資源以前,TextureB的包被加載完成,就行

以上述cubewall的例子爲例
  在使用cubewall資源以前
    即在GameObject wallPrefab = assetBundle.LoadAsset<GameObject>("cubewall");以前
  加載share包便可

可是在測試過程當中
  同在Start()裏,不管誰先誰後,均可以加載出帶有mat的cubewall

很奇怪???和Documentation中說的不一樣?

Consider the following example, a Material in Bundle 1 references a Texture in
Bundle 2:

In this example, before loading the Material from Bundle 1, you would need to
 load Bundle 2 into memory. It does not matter which order you load Bundle 1 
and Bundle 2, the important takeaway is that Bundle 2 is loaded before loading
 the Material from Bundle 1. 

作個測試:
  在Start()中load cubewall,並instantiate一個cubewall
    這時,會出現material丟失的狀況
  可是在Update()中經過Input.GetKeyDown()按下按鈕後進行load share包
    在按下按鍵以前,material仍是丟失的
    可是按下按鍵以後,material就出如今被instantiate的cubewall上

嗯,很奇怪

這麼作居然能夠,可是保險起見,仍是按上面的前後順序原則進行load吧

任務16&17:AssetBundle四種加載方式的前三種

AssetBundle的加載分爲四種:
  AssetBundle.LoadFromMemoryAsync() -- 從內存中異步進行加載
  AssetBundle.LoadFromFile() -- 從本地進行加載
  WWW.LoadFromCacheOrDownLoad() -- 從緩存中(本地)或服務器上加載
  UnityWebRequest.DownloadHandlerAssetBundle() -- 以前用WWW方法,但更新後推薦用這個方法從服務器加載
什麼狀況下用哪一個方法?
  主要基於assetbundle是以什麼形式提供的

AssetBundle.LoadFromMemoryAsync(byte[] binary, uint crc = 0);

將byte[]轉換成AssetBundle對象
byte[] binary -- 如何獲得byte[]呢?
  能夠經過http/tcp等協議獲得
    假如經過tcp協議下載獲得了服務器端的assetbundle,這時獲得的就是byte數組
    直接使用LoadFromMemoryAsync()便可
    或者也能夠先保存到本地,再經過LoadFromFile()獲得
  也能夠經過File.ReadAllBytes(path)獲得

官方實例:

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);
}

但這個實例的AssetBundle.LoadFromMemoryAsync(File.ReadAllBytes(path));
  就是實現了LoadFromFile(path)的從本地文件讀取包的狀況

AssetBundleCreateRequest 表示作了一個異步請求
須要等待Load...()加載完成

實例:

using UnityEngine;
using System.IO;
using System.Collections;
public class LoadAssetBundleFromMemory : MonoBehaviour {

    IEnumerator Start () {

        // assetbundle的加載
        string path = "AssetBundles/cubewall.assetbundle";
        AssetBundleCreateRequest request = AssetBundle.LoadFromMemoryAsync(File.ReadAllBytes(path));
        // 異步方式的請求,須要等待Load完成
        yield return request;

        AssetBundle assetBundle = request.assetBundle;

        // assetbundle的使用
        GameObject wallPrefab = assetBundle.LoadAsset<GameObject>("cubewall");
        Instantiate(wallPrefab);
    }
}

LoadFromMemoryAsync()的變形
LoadFromMemory() -- 同步的方式
  不須要進行yield暫停,由於同步的方式會等待Load的方法加載完才進行返回
  使用方法和LoadFromFile類似 -- 
    AssetBundle assetBundle = AssetBundle.LoadFromMemory(File.ReadAllBytes(path));
    不須要進行yield return操做

void Start() {
    // assetbundle的加載
    string path = "AssetBundles/cubewall.assetbundle";
    AssetBundle assetBundle = AssetBundle.LoadFromMemory(File.ReadAllBytes(path));

    // assetbundle的使用
    GameObject wallPrefab = assetBundle.LoadAsset<GameObject>("cubewall");
    Instantiate(wallPrefab);
}

AssetBundle.LoadFromFile(string path)

從本地讀取 -- 以前使用過

LoadFromFile()的變形
LoadFromFileAsync() -- 異步方式

Enumerator Start() {
    string path = "AssetBundles/cubewall.assetbundle";
    AssetBundleCreateRequest request = AssetBundle.LoadFromFileAsync(path);
    // 等待Load加載完畢
    yield return request;
    AssetBundle assetBundle = request.assetBundle;

    // 加載資源
    GameObject wallPrefab = assetBundle.LoadAsset<GameObject>("cubewall");
    Instantiate(wallPrefab);
}

WWW.LoadFromCacheOrDownload()

將文件從服務器下載到本地的緩存目錄中

This API is useful for downloading AssetBundles from a remote server or loading local AssetBundles.

Loading an AssetBundle from a remote location will automatically cache the AssetBundle.
If the AssetBundle is compressed, a worker thread will spin up to decompress the bundle and write it to the cache.
  -- LZMA壓縮算法時 (BuildAssetBundleOptions.None)
Once a bundle has been decompressed and cached, it will load exactly list AssetBundle.LoadFromFile()

官方實例:

public class LoadFromCacheOrDownloadExample : MonoBehaviour {
    IEnumerator Start () {
        while (!Caching.ready)  // 等待Caching是否準備完成
            yield return null;  -- 表示暫停一幀
        // Cache準備完成,則開始進行下載
        // 5爲版本號
        // 若是是全新版本,會所有下載;
        // 若是版本相同,會進行檢查,若是已經下載了,會從cache中load
        // 若是沒有下載,則會從服務端進行下載
        var www = WWW.LoadFromCacheOrDownload
            ("http://myserver.com/myassetBundle", 5);
        // 等待下載完成
        yield return www;
        if(!string.IsNullOrEmpty(www.error)) { -- 記得進行錯誤檢測
            Debug.Log(www.error);
            yield return null;
        }
        var myLoadedAssetBundle = www.assetBundle;
        var asset = myLoadedAssetBundle.mainAsset;
    }
}  

實例:

public class LoadAssetBundleFromCacheOrDownload : MonoBehaviour {
    IEnumerator Start() {
        while (!Caching.ready)  // 等待Caching是否準備完成
            yield return null;
        // 本地文件的前綴 file:// 或 file:///
        WWW www = WWW.LoadFromCacheOrDownload 
            (@"file:///D:\...\AssetBundles\cubewall.assetbundle", 1);
        // 等待下載完成
        yield return www;
        if (!string.IsNullOrEmpty(www.error)) {
            Debug.Log(www.error);
            yield return null;
        }
        AssetBundle assetBundle = www.assetBundle;
        // var asset = assetBundle.mainAsset;
        // Debug.Log(asset);
        GameObject wallPrefab = assetBundle.LoadAsset<GameObject>("cubewall");
        Instantiate(wallPrefab);
    }
}

如何從服務端下載,詳見任務19

任務18&19:搭建簡單Server服務器 && 從服務器端下載AssetBundle

經過NetBox2.exe搭建簡單Server服務器

在素材文件夾中找到NetBox2.exe

使用的是http協議

建立頁面index.html做爲首頁,隨意寫一個內容
  此時打開NetBox2.exe就會跳轉到index.html頁面
本地的Server端就開啓了

將AssetBundles文件夾拷貝到NetBox2.exe所在目錄
NetBox2.exe所在目錄能夠視做服務器端
那麼如今就能夠經過Unity到Server端下載AssetBundle了

經過WWW.LoadFromCacheOrDownload()從服務器端下載

經過http協議能夠訪問剛纔搭建的服務器

訪問到服務器就至關於訪問到根目錄,再經過url獲得assetbundle的位置

WWW www = WWW.LoadFromCacheOrDownload(@"http://localhost/cubewall.assetbundle", 1);

測試:

第一次:打開Server,運行Unity project,獲得實例化後的cubewall
第二次:關閉Server,運行Unity project,依然能獲得實例化後的cubewall
  可是從瀏覽器中訪問不到localhost了
第三次:關閉Server,修改WWW.LoadFromCacheOrDownload()的版本號爲2
  此次就在www.error處輸出了404 Not Found

總結:第二次能獲得結果的緣由是,版本號相同的狀況下,會進行檢查,已經下載了,因此從cache目錄load
  第三次的版本號不一樣,所以須要到服務器端下載,可是Server端關閉,所以404 Not Found

任務20:使用UnityWebRequest下載AssetBundle -- 第四種

重點方法
  取代了WWW.LoadFromCacheOrDownload(),並作了優化

官方實例:

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

1. using UnityEngine.Networking;

2. UnityWebRequest.GetAssetBundle(uri,0);
  // uri能夠是本地路徑也能夠是server路徑

3. unityWebRequest.SendWebRequest()
  // 開始進行下載

4. DownloadHandlerAssetBundle.GetContent(request);
  // 取得內容,返回一個AssetBundle的對象
  (request.DownloadHandler as DownloadHandlerAssetBundle).assetBundle
  request中的DownloadHandler強轉爲DownloadHandlerAssetBundle類型,自帶assetBundle

實例:

using UnityEngine.Networking;
public class LoadAssetBundleFromUnityWebRequest : MonoBehaviour {
    IEnumerator Start() {
        string uri = @"file:///D:\...\AssetBundles\cubewall.assetbundle";
        UnityWebRequest request = UnityWebRequest.GetAssetBundle(uri);
        // 這個時候還沒從服務器端下載
        // 調用了request.Send()以後,纔會開始下載
        yield return request.SendWebRequest();

        // 獲得AssetBundle -- 方法1
        AssetBundle assetBundle = DownloadHandlerAssetBundle.GetContent(request);
        // 獲得AssetBundle -- 方法2
        // 由於DownloadHandler有不少類型,轉換爲DownloadHandlerAssetBundle類型
        AssetBundle assetBundle2 = (request.downloadHandler as 
            DownloadHandlerAssetBundle).assetBundle;

        // 實例化
        GameObject wallPrefab = assetBundle.LoadAsset<GameObject>("cubewall");
        Instantiate(wallPrefab);
        GameObject wallPrefab2 = assetBundle2.LoadAsset<GameObject>("cubewall");
        Instantiate(wallPrefab2);
    }
}

總結:
  1. 經過UnityWebRequest.GetAssetBundle(uri)指定uri
  2. 經過request.SendWebRequest() 進行assetbundle下載
  3. 取得AssetBundle
    兩個方法
    a. DownloadHandlerAssetBundle.GetContent(request);
    b. request.DownloadHandler...assetBundle

將GetAssetBundle(uri)的路徑改成服務器端的地址
  UnityWebRequest.GetAssetBundle(@"http://localhost/AssetBundles/cubewall.assetbundle");

任務21:從AssetBundle裏面加載資源

上面講解了如何從本地/服務器獲得AssetBundle,這節講述如何從獲得的AssetBundle裏獲得所需資源

四種加載資源的方式:
  loadedAssetBundle.LoadAsset<T>(assetName);
  loadedAssetBundle.LoadAssetAsync<T>(assetName);
  loadedAssetBundle.LoadAllAssets();

T objectFromBundle = bundleObject.LoadAsset<T>(assetName);
  好比GameObject object = assetBundle.LoadAsset<GameObject>("prefabName");
    -- 和以前使用的方法相同

還有一種方式 -- LoadAllAssets()
  Unity.Object[] objectArray = assetBundle.LoadAllAssets();
  能夠獲得該包中的指定的全部資源
    好比:存入了一個prefab,該prefab所使用的material會自動打包進來,可是unity自動打包的
      這時,經過LoadAllAssets()就不會獲得這個material資源

上述兩種方法對應的異步方法:
  當加載的資源較大時就可使用異步加載的方式

loadedAssetBundle.LoadAssetAsync<T>(assetName);
  AssetBundleRequest request = loadedAssetBundle.LoadAssetAsync<GameObject>(assetName);
  yield return request;  // 等待加載完成
  GameObject object = request.asset; // 取得加載到的資源

loadedAssetBundle.LoadAllAssetsAsync();
  AssetBundleRequest request = loadedAssetBundle.LoadAllAssetsAsync();
  yield return request;  // 等待加載完成
  Object[] objects = request.allAsset; // 取得加載到的資源

任務22:經過Manifest文件獲得某個包的依賴

在任務15中,講述了包之間的依賴關係

對於經過BuildPipeline.BuildAssetBundles(path, ...);
  在path文件夾下存放那些assetbundle包
  在這個path文件夾下,也會自動生成一個與文件夾名字相同的包
  對於該包會有對應的一個manifest文件

接任務15,生成的AssetBundles.manifest的內容爲

ManifestFileVersion: 0
CRC: 29988270
AssetBundleManifest:
  AssetBundleInfos:
    Info_0:
      Name: capsulewall.assetbundle
      Dependencies:
        Dependency_0: share.assetbundle
    Info_1:
      Name: cubewall.assetbundle
      Dependencies:
        Dependency_0: share.assetbundle
    Info_2:
      Name: share.assetbundle
      Dependencies: {}

包含了這個文件夾下全部的assetbundle包,和每一個包所依賴的包的信息
  好比這個文件夾下有capsulewall.assetbundle, cubewall.assetbundle, share.assetbundle
  依賴關係是capsulewall包依賴share包,cubewall包依賴share包,share包沒有依賴其餘包

想要知道包之間的依賴關係,只須要在程序中獲得AssetBundles文件便可 (不是AssetBundles.manifest)
  獲得AssetBundleManifest的方式和獲得其餘資源的方式同樣,有不少種

AssetBundle assetBundle = AssetBundle.LoadFromFile(manifestFilePath);  // 這裏即AssetBundles的路徑
AssetBundleManifest manifest = assetBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
  獲得的manifest對象,與AssetBundles.manifest文件內容是對應的,包含了全部資源的列表和依賴關係

// 如何獲得一個包的依賴關係呢
string[] dependencies = manifest.GetAllDependencies("assetName");  // 好比"cubewall"

// 經過這些依賴着的包名,加載這些包便可
foreach(string dependency in dependencies) {
  AssetBundle.LoadFromFile(Path.Combine(assetBundlePath, dependency));
}

實例,以任務15爲例

public class LoadAssetBundleWithDependencies : MonoBehaviour {
  private void Start() {
    string path = "AssetBundles/";
    string assetBundleName = "CubeWall.assetbundle";
    // 獲得AssetBundles文件
    AssetBundle manifestAssetBundle = AssetBundle.LoadFromFile(Path.Combine(path, "AssetBundles"));
    // 獲得AssetBundles.manifest內容
    AssetBundleManifest manifest = manifestAssetBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
    // 經過manifest中存儲的包名,獲得某個assetbundle的依賴
    string[] dependencies = manifest.GetAllDependencies(assetBundleName);
    // 遍歷依賴,並加載全部依賴
    foreach(string dependency in dependencies) {
      AssetBundle.LoadFromFile(Path.Combine(path, dependency));
    }
    // 加載assetbundle本dle
    AssetBundle assetBundle = AssetBundle.LoadFromFile(Path.Combine(path, assetBundleName));
    GameObject wallPrefab = assetBundle.LoadAsset<GameObject>("cubewall");
    Instantiate(wallPrefab);
}}

任務23:AssetBundle的卸載 Unload

卸載的優缺點:
  減小內存使用
  若是卸載不當,會致使丟失,好比卸載掉了某個正在運行的物體所依賴的資源時

Unity does not automatically unload Objects when they are removed from the active scene.
Asset cleanup is triggered at specific times, and it can also be triggered manually.

致使的問題: Improperly unloading an AssetBundle can bead to duplicating objects in memory or
  other undesirable circumstances, such as missing textures;

assetBundle.Unload(bool)
-- unloads the header information of the assetBundle.
 the bool arguments indicates whether to also unload all Objects instantiated from this assetBundle
  Unload(true): unload the objects loaded from the assetBundle, even if they're being currently used in the active scene
    -- this is what can cause textures or other things to go missing.

官方實例:assetbundle.Unload(true) 和assetbundle.Unload(false)的區別

MaterialM 在assetbundle中被加載

assetbundle.Unload(true):
  Any instance of M in the active scene will also be unload and destroyed.

assetbundle.Unload(false):
  break the chain of the current instances of M and assetbundle
    
  if assetbundle is reloaded later and M is loaded by assetbundle.LoadAsset()
  unity will not re-link the existing copies of M to the newly loaded Material.
  -- there will be two copies of M loaded.
    
    此時原來的M依舊被其餘物體使用
    若是它再也不被使用了,就會致使M依然佔用內存,並且經過Unload()也沒法卸載(由於Unload是卸載ab包的)

所以:using AssetBundle.Unload(false) does not lead to an ideal situation.
  Most projects should use AssetBundle.Unload(true) to keep from duplicating objects in memory.

Most projects should use AssetBundle.Unload(true) and adopt a method to ensure that Objects are not duplicated.
使用Unload(true)的兩種思路

1. Having well-defined points during the applications's lifetime at which transient Assetbundles are unloaded
  好比不一樣關卡的切換時,或正在加載新場景時調用unload(true)

2. Maintaining reference-counts for individual Objects, and unload only when all of their constituent Object are unused.
  監測資源的使用,當全部資源不被使用時能夠調用unload(true)

使用Unload(false)後,unload未卸載的資源(如MaterialM)的兩種思路

1. Eliminate all references to an unwanted Object, both in the scene and in code. -- 好比把引用設置爲null
  After the is done, call Resources.UnloadUnusedAssets()

2. Load a scene non-additively. This will destroy all Objects in the current scene -- 使用非附加的方式切換場景時
  and invoke Resources.UnloadUnusedAssets() automatically.

任務24:關於文件校驗

從服務器傳輸過程當中,可能會出現字節傳輸丟失或出錯
因此須要在使用文件以前進行文件是否完整傳輸的校驗

文件校驗:
  經過文件的內容進行生成校驗值,校驗值的生成經過算法
  CRC  MD5  SHA1

  在服務器端生成校驗值後,進行文件的傳輸(with生成的校驗值);
  客戶端下載到文件後,使用相同算法進行校驗值的生成,將兩個校驗值進行對比便可

上述三種校驗算法的區別:
  1. 算法不一樣:CRC爲多項式除法;MD5和SHA1爲替換、輪轉等方法
  2. 校驗值的長度不一樣:CRC校驗位的長度和其多項式有關係,通常爲16或32位;MD5爲128位;SHA1爲160位
  3. 校驗值的稱呼不一樣:CRC通常叫作CRC值;MD5和SHA1通常叫作哈希值或散列值
  4. 檢測能力不一樣:CRC的安全性跟多項式有很大關係,比MD5和SHA1差不少;
    MD5安全性很高;SHA1安全性最高
  5. 效率不一樣:CRC效率最高,和檢測能力爲逆相關
  6. 用途不一樣:CRC通常用做通訊數據的校驗;MD5和SHA1通常用於安全領域,好比文件校驗、數字簽名等

任務25:AssetBundle中其餘須要注意的問題

Patching with AssetBundle -- 打補丁 -- 更新遊戲的一些資源

須要注意的點:
How to detect which assetbundles to replace.
A patching system requires two lists of information:
  1. A list of the currently downloaded AssetBundles, and their versioning information
  2. A list of the AssetBundles on the server, and their versioning information

依賴包重複的問題:

三種解決方式:
  1. 將須要共享的資源打包到一塊兒,即若兩個資源依賴於同一個資源,則將這兩個資源打包到一塊兒
    缺點:
      這些資源不必定同時使用;
      資源更新時若是有一個資源是常常更新的,則整個包都須要更新
  2. 分割包,這些包不在同一時間使用
    缺點:可是這種方法會致使assetbundle的大小變大
  3. 將被依賴的資源(須要被共享的資源)打包到一個包中 -- 推薦方法(具體問題具體分析)

圖集重複問題:Sprite Atlas Duplication

Texture Type爲Sprite 2D and UI下有一個屬性,Packing tag
Unity會自動將這些2D圖片打包到圖集中
  若是Packing tag不指定,則會打包到同一個圖集中
  這樣的結果是:
    若是將貼圖A打包到A包中,由於貼圖A在圖集中,則會將整個圖集打包到A包中
    若是將貼圖B打包到B包中,由於貼圖B也在圖集中,那麼整個圖集也會打包到B包中
    -- 圖集重複問題

解決方法:
  將屬於同一個圖集的圖片打包到同一個包中
  將圖片分屬於不一樣圖集,經過修改圖片的Packing Tag

任務26:AssetBundle Broswer Tool -- 第三方插件

Unity AssetBundle Broswer tool -- 瀏覽工具
  用於清楚地顯示BundleList、AssetList和對應的Details信息

https://github.com/Unity-Technologies/AssetBundles-Browser

在Release中下載,將Editor目錄拖入Unity Project面板

如今,在Window->AssetBundleBroswer就能打開瀏覽面板了

能夠顯示各個assetbundle,和assetbundle中的資源信息,之間的dependency等等

在Build選項卡中

實現了BuildPipeline的那些parameter選項
Clear Folders -- 將以前生成的assetbundle文件刪除,就不須要手動清空該文件夾了
StreamingAssets表示流資源,在build成安裝包的時候,會把這個文件夾中的資源原封不動地放入安裝包中
  其餘文件夾下的資源可能會自動進行壓縮等操做
CopyToStreamingAssets選項將assetbundle拷貝到StreamingAssets文件夾下
  爲何呢,這樣作會加大安裝包大小
  若是assetbundle不放在StreamingAssets下,那麼第一次加載的時候仍是須要去下載這部分資源的
  雖然安裝包小,可是仍是須要進行下載的。
  那麼,索性就將遊戲一開始運行就必須使用的資源放入
  結果就是,玩家安裝完遊戲就不須要再進行下載更新包了,除非有更新文件

點擊Build,就能夠生成了

相關文章
相關標籤/搜索