熱更新基礎--AssetBundle學習筆記

movin2333 發表於 2021-04-02

一.簡介

  AssetBundle簡稱AB包,特定平臺的資產壓縮包(包括模型、貼圖、預設體、音效、材質球等資產)。

    作用:Resources下的資源只讀且打包後不可修改,而AB包儲存位置自定,後期可以動態更新;AB包壓縮後節省空間;可以進行資源熱更新和指令碼熱更新。

二.官方打包工具AssetBundleBrowser

  1.下載安裝工具

熱更新基礎--AssetBundle學習筆記

開啟Package Manager視窗

熱更新基礎--AssetBundle學習筆記

搜尋找到打包工具,右下角install安裝,這裡已經安裝。

熱更新基礎--AssetBundle學習筆記

安裝成功後可以在Windows中看到AssetBundle Browser。

熱更新基礎--AssetBundle學習筆記

Project視窗中Packages資料夾下也會出現打包工具資料夾。

熱更新基礎--AssetBundle學習筆記

打包工具視窗。

  2.使用打包工具打AssetBundle包

熱更新基礎--AssetBundle學習筆記

點選Project視窗中的資源(可以同時選擇多個資源),在Inspector視窗的最下方會出現AssetBundle打包選項,在輸入下拉框中填寫或選擇當前選中的一個或多個資源要打包成的AB包檔案的名稱和字尾。

熱更新基礎--AssetBundle學習筆記

再次開啟打包工具後再Configure選項中出現了資源。

熱更新基礎--AssetBundle學習筆記

在Build選項中即可進行打包相關設定(平臺、路徑、是否清空資料夾、是否打包時同步複製檔案到StreamingAssets資料夾下、壓縮方式等),壓縮方式有LZMA(壓縮率高,但是如果取用包中單個檔案時會將整個包解壓縮,資源使用時效率低)、LZ4(壓縮率不如LZMA,但是如果是多個資源在同一個包中又取用單個資源時不需要解壓縮整個包,資源使用時效率高)、不壓縮3種,推薦LZ4。

熱更新基礎--AssetBundle學習筆記

打包後包檔案儲存在工程中AssetBundles資料夾下。

  3.AB包檔案檢視

熱更新基礎--AssetBundle學習筆記

和Unity專案中的其他檔案相同,打包出來的包檔案也有響應的manifest清單檔案與之對應,開啟清單檔案:

熱更新基礎--AssetBundle學習筆記

清單檔案中包含包的一些資訊,如version、CRC、Hash、Assets、Dependencies等。

三.AB包相關API

  1.載入AB包及使用包中檔案的相關API

void Start()
    {
        //載入AB包,AssetBundle類中有多個不同的靜態方法用於載入AB包,這裡使用從檔案載入(根據檔案地址載入)
        //在AB包打包時勾選StreamingAssets同步,在Assets資料夾下會出現一個StreamingAssets資料夾,AB包會同步複製到資料夾中,這裡從這個資料夾下載入AB包
        AssetBundle bundle = AssetBundle.LoadFromFile(Application.streamingAssetsPath + "/model");

        //載入AB包中的資源
        //AssetBundle類中提供了多個不同的成員方法載入AB包中的資源,這裡使用泛型載入,也推薦使用泛型載入
        GameObject obj = bundle.LoadAsset<GameObject>("Sphere");

        //使用資源
        Instantiate(obj).transform.position = Vector3.zero;

        //因為AB包不能重複載入,所以載入完成後需要將其解除安裝
        //引數是一個bool值,代表解除安裝AB包時是否一同解除安裝通過AB包中載入的資源
        //如剛才將資源例項化在場景中,如果這裡引數給true,解除安裝AB包後剛才例項化出來的Sphere會丟失,如果引數給false,解除安裝AB包後剛才例項化出來的Sphere不會受到影響

        //解除安裝當前AB包
        bundle.Unload(false);
        //解除安裝所有AB包
        AssetBundle.UnloadAllAssetBundles(false);
    }

  Unity提供了AssetBundle類,使用其中的AB包載入的靜態方法載入AB包,返回值也是AssetBundle型別,使用載入具體資源的成員方法載入包種具體的資源,推薦使用泛型方法,否則需要進行型別轉換。

  注意:AB包不能重複載入,會報錯,所以使用完成後記得解除安裝方便下次使用。

  2.AB包依賴關係

    在打包時,如果某個資源用到了一些依賴資源(Meterial、Sprite等),需要將這個資源依賴的資源一併打包起來,否則會出現資源丟失的問題。但是如果一個資源同時被多個資源使用,會出現重複打包的問題,所以可以將這些依賴資源單獨打包,利用主包獲取依賴資訊。主包名稱和資料夾名稱相同,如上面的PC包。

void Start()
    {
        //載入主包
        AssetBundle abMain = AssetBundle.LoadFromFile(Application.streamingAssetsPath + "/PC");
        //載入主包中的固定檔案
        AssetBundleManifest abManifest = abMain.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
        //從固定檔案中得到依賴資訊
        string[] strs = abManifest.GetAllDependencies("model");
        //得到依賴包名字
        for (int i = 0; i < strs.Length; i++)
        {
       //載入AB包,需要儲存下來,可以宣告一個字典將載入好的AB包儲存下來 AssetBundle.LoadFromFile(Application.streamingAssetsPath
+ "/" + strs[i]); } abMain.Unload(false); }

  3.AB包管理類,提供載入AB包中資源的同步和非同步方法,各有3種過載(根據包名和資源名載入、根據包名和資源名和資源型別載入、根據包名和資源名和泛型資源型別載入),還提供了解除安裝指定ab包和解除安裝所有包的方法

public class AssetBundleManager : MonoBehaviour
{
    //單例模組
    private static AssetBundleManager instance;
    public static AssetBundleManager Instance
    {
        get
        {
            if (instance == null)
            {
                GameObject obj = new GameObject("AssetBundleManager");
                DontDestroyOnLoad(obj);
                instance = obj.AddComponent<AssetBundleManager>();
            }
            return instance;
        }
    }

    //儲存所有載入過的AB包的容器
    private Dictionary<string, AssetBundle> abDic = new Dictionary<string, AssetBundle>();
    //主包,只會載入一次
    private AssetBundle mainAB = null;
    //獲取依賴包的配置檔案
    private AssetBundleManifest manifest = null;

    //ab包存放的路徑,方便修改
    private string PathUrl
    {
        get
        {
            return Application.streamingAssetsPath + "/";
        }
    }
    //主包名,根據平臺不同而不同
    private string MainABName
    {
        get
        {
#if UNITY_IOS
            return "IOS";
#elif UNITY_ANDROID
            return "Android";
#else 
            return "PC";
#endif
        }
    }

    /// <summary>
    /// 載入資源,同步載入,3種方法過載
    /// </summary>
    /// <param name="abName"></param>
    /// <param name="resName"></param>
    public Object LoadRes(string abName,string resName)
    {
        LoadAB(abName);

        Object obj = abDic[abName].LoadAsset(resName);
        //如果是GameObject,載入後基本都是建立遊戲物體,所以這裡判斷一下如果是GameObject,直接返回建立好的遊戲物體
        if (obj is GameObject)
            return Object.Instantiate(obj);
        else
            return obj;
    }
    public Object LoadRes(string abName,string resName,System.Type type)
    {
        LoadAB(abName);

        Object obj = abDic[abName].LoadAsset(resName, type);
        //如果是GameObject,載入後基本都是建立遊戲物體,所以這裡判斷一下如果是GameObject,直接返回建立好的遊戲物體
        if (obj is GameObject)
            return Object.Instantiate(obj);
        else
            return obj;
    }
    public T LoasRes<T>(string abName,string resName) where T : Object
    {
        LoadAB(abName);

        T t = abDic[abName].LoadAsset<T>(resName);
        //如果是GameObject,載入後基本都是建立遊戲物體,所以這裡判斷一下如果是GameObject,直接返回建立好的遊戲物體
        if (t is GameObject)
            return Object.Instantiate(t);
        else
            return t;
    }

    /// <summary>
    /// 載入資源,非同步載入,3種方法過載
    /// </summary>
    /// <param name="abName"></param>
    /// <param name="resName"></param>
    /// <returns></returns>
    public void LoadResAsync(string abName,string resName, System.Action<Object> callBack)
    {
        StartCoroutine(ReallyLoadResAsync(abName, resName, callBack));
    }
    private IEnumerator ReallyLoadResAsync(string abName, string resName, System.Action<Object> callBack)
    {
        StartCoroutine(abName);

        AssetBundleRequest abr = abDic[abName].LoadAssetAsync(resName);
        yield return abr;

        //如果是GameObject,載入後基本都是建立遊戲物體,所以這裡判斷一下如果是GameObject,直接建立好遊戲物體
        if (abr.asset is GameObject)
            callBack(Instantiate(abr.asset));
        else
            callBack(abr.asset);
    }
    public void LoadResAsync(string abName, string resName, System.Type type, System.Action<Object> callBack)
    {
        StartCoroutine(ReallyLoadResAsync(abName, resName, type, callBack));
    }
    private IEnumerator ReallyLoadResAsync(string abName, string resName, System.Type type, System.Action<Object> callBack)
    {
        StartCoroutine(abName);

        AssetBundleRequest abr = abDic[abName].LoadAssetAsync(resName, type);
        yield return abr;

        //如果是GameObject,載入後基本都是建立遊戲物體,所以這裡判斷一下如果是GameObject,直接建立好遊戲物體
        if (abr.asset is GameObject)
            callBack(Instantiate(abr.asset));
        else
            callBack(abr.asset);
    }
    public void LoadResAsync<T>(string abName, string resName, System.Action<T> callBack) where T : Object
    {
        StartCoroutine(ReallyLoadResAsync<T>(abName, resName, callBack));
    }
    private IEnumerator ReallyLoadResAsync<T>(string abName, string resName, System.Action<T> callBack) where T : Object
    {
        StartCoroutine(abName);

        AssetBundleRequest abr = abDic[abName].LoadAssetAsync<T>(resName);
        yield return abr;

        //如果是GameObject,載入後基本都是建立遊戲物體,所以這裡判斷一下如果是GameObject,直接建立好遊戲物體
        if (abr.asset is GameObject)
            callBack(Instantiate(abr.asset) as T);
        else
            callBack(abr.asset as T);
    }

    /// <summary>
    /// 同步載入依賴包和資源包
    /// </summary>
    /// <param name="abName"></param>
    private void LoadAB(string abName)
    {
        //先載入依賴包,再載入AB包,最後載入檔案
        if (mainAB == null)
        {
            mainAB = AssetBundle.LoadFromFile(PathUrl + MainABName);
            manifest = mainAB.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
        }

        string[] strs = manifest.GetAllDependencies(abName);
        for (int i = 0; i < strs.Length; i++)
        {
            if (!abDic.ContainsKey(strs[i]))
                abDic.Add(strs[i], AssetBundle.LoadFromFile(PathUrl + strs[i]));
        }

        //沒有包載入包,有包直接取出來使用
        if (!abDic.ContainsKey(abName))
            abDic.Add(abName, AssetBundle.LoadFromFile(PathUrl + abName));
    }
    /// <summary>
    /// 非同步載入依賴包和資源包
    /// </summary>
    /// <param name="abName"></param>
    /// <returns></returns>
    private IEnumerator LoadABAsync(string abName)
    {
        //先載入依賴包,再載入AB包,最後載入檔案
        if (mainAB == null)
        {
            AssetBundleCreateRequest createRequest = AssetBundle.LoadFromFileAsync(PathUrl + MainABName);
            yield return createRequest;
            mainAB = createRequest.assetBundle;

            AssetBundleRequest request = mainAB.LoadAssetAsync<AssetBundleManifest>("AssetBundleManifest");
            yield return request;
            manifest = request.asset as AssetBundleManifest;
        }

        string[] strs = manifest.GetAllDependencies(abName);
        for (int i = 0; i < strs.Length; i++)
        {
            if (!abDic.ContainsKey(strs[i]))
            {
                AssetBundleCreateRequest createRequest = AssetBundle.LoadFromFileAsync(PathUrl + strs[i]);
                yield return createRequest;
                abDic.Add(strs[i], createRequest.assetBundle);
            }
        }

        //沒有包載入包,有包直接取出來使用
        if (!abDic.ContainsKey(abName))
            abDic.Add(abName, AssetBundle.LoadFromFile(PathUrl + abName));
    }

    /// <summary>
    /// 解除安裝單個包
    /// </summary>
    /// <param name="abName"></param>
    public void UnLoad(string abName)
    {
        if (abDic.ContainsKey(abName))
        {
            abDic[abName].Unload(false);
            abDic.Remove(abName);
        }
            
    }

    /// <summary>
    /// 解除安裝所有包
    /// </summary>
    public void ClearAssetBundles()
    {
        AssetBundle.UnloadAllAssetBundles(false);
        abDic.Clear();
        mainAB = null;
        manifest = null;
    }
}