一.简介

  AssetBundle简称AB包,特定平台的资产压缩包(包括模型、贴图、预设体、音效、材质球等资产)。

    作用:Resources下的资源只读且打包后不可修改,而AB包存储位置自定,后期可以动态更新;AB包压缩后节省空间;可以进行资源热更新和脚本热更新。

二.官方打包工具AssetBundleBrowser

  1.下载安装工具

热更新基础--AssetBundle学习笔记-LMLPHP

打开Package Manager窗口

热更新基础--AssetBundle学习笔记-LMLPHP

搜索找到打包工具,右下角install安装,这里已经安装。

热更新基础--AssetBundle学习笔记-LMLPHP

安装成功后可以在Windows中看到AssetBundle Browser。

热更新基础--AssetBundle学习笔记-LMLPHP

Project窗口中Packages文件夹下也会出现打包工具文件夹。

热更新基础--AssetBundle学习笔记-LMLPHP

打包工具窗口。

  2.使用打包工具打AssetBundle包

热更新基础--AssetBundle学习笔记-LMLPHP

点击Project窗口中的资源(可以同时选择多个资源),在Inspector窗口的最下方会出现AssetBundle打包选项,在输入下拉框中填写或选择当前选中的一个或多个资源要打包成的AB包文件的名称和后缀。

热更新基础--AssetBundle学习笔记-LMLPHP

再次打开打包工具后再Configure选项中出现了资源。

热更新基础--AssetBundle学习笔记-LMLPHP

在Build选项中即可进行打包相关设置(平台、路径、是否清空文件夹、是否打包时同步复制文件到StreamingAssets文件夹下、压缩方式等),压缩方式有LZMA(压缩率高,但是如果取用包中单个文件时会将整个包解压缩,资源使用时效率低)、LZ4(压缩率不如LZMA,但是如果是多个资源在同一个包中又取用单个资源时不需要解压缩整个包,资源使用时效率高)、不压缩3种,推荐LZ4。

热更新基础--AssetBundle学习笔记-LMLPHP

打包后包文件存储在工程中AssetBundles文件夹下。

  3.AB包文件查看

热更新基础--AssetBundle学习笔记-LMLPHP

和Unity项目中的其他文件相同,打包出来的包文件也有响应的manifest清单文件与之对应,打开清单文件:

热更新基础--AssetBundle学习笔记-LMLPHP

清单文件中包含包的一些信息,如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;
    }
}
04-02 11:17