Unity热更新之AssetBundle

目录

目录

一、概述

1、AB包是什么

AssetBundle简称AB包。

这是特定定于平台的资产压缩包,有点类是压缩文件。

资产包括:模型、贴图、预设体、音效、材质球等

2、AB包的作用

  • 相对于Resources下的资源,AB包能更好管理资源

  • AB包能够减小包体大小:可以压缩资源并减少初始包大小

  • AB包更适合热更新:即资源热更新(脚本热更新主要是Lua)

二、AB包的使用

1、导入

高版本Unity中,已经不能通过包管理器进行下载(高版本Unity用Addressables功能封装了AB包功能)

目前使用资料区下载AssetBundles-Browser-master包文件导入工程中。(可以网上找)

之后即可在window菜单栏中打开AssetBundle Browser面板。

2、生成AB包

(1)Configure页签:

可以对想要的资源或预设体进行打包,点击想要打包的资源,在Inspector窗口下方选择AssetBundle的文件名,之后即可在窗口Configure中看到打包的信息。

Configure页签

(2)Bulid页签(最重要)

  • Build Target:打包的目标平台

  • OutputPath:打包输出路径

  • CearFolders:是否清空文件夹

  • CopytoStreamingAssets:是否将打包复制到StreamingAssets文件夹

  • Compression:压缩方式

    • LZMA:此压缩格式是表示整个 AssetBundle 的数据流,这意味着如果您需要从这些存档中读取某个资源,就必须将整个流解压缩。压缩率比LZ4高,,但使用时需要解压全部资源。

    • LZ4:如果 Unity 需要从 LZ4 存档中访问资源,只需解压缩并读取包含所请求资源的字节的块,即要用哪个解压哪个。

  • Exclude Type Information:在资源包中不包含资源的类型信息。

  • Force Rebuild:重新打包时需要重新构建包,与ClearFolders不同,他不会删除不存在的包。

  • Ignore Type Tree Changes:增量构建检查时,忽略类型树的修改

  • Append Hash:将文件哈希值附加在资源包名上

  • Strict Mode:严格模式,如果打包时报错,则打包直接失败

  • Dry Run Build:运行时构建

Build页签

打包时,会在Assets同级目录里创建一个AssetBundles的文件夹,如果勾选了Copy to StreamingAssets,还会把资源拷贝到Assets文件夹下的StreamingAssets文件夹中。

打包后,可以查看资源文件内部:

主包:其中和目录名一样的包是主包,记录了AB包依赖的关键信息。

AB包文件:是用二进制存储的资源文件。

manifest文件:即一些配置文件信息,加载时提供关键信息,如资源信息,依赖关系,版本信息等等

打包后的文件

(3)Inspect页签

主要用于查看包文件的一些信息

(4)答疑解惑

AB包打包只对资源进行打包,而不打包代码,代码打包由Lua来实现。

但打包预制体时,物体身上的组件不也打包了吗?难道不是说明能打包代码吗?

其实打包物体时,身上的组件脚本是利用了反射的机制,在打包时,只是记录了这些组件脚本的引用,而不是代码本身,所以AB包其实是不能打包代码的。

我们可以打开预制体文件验证一下,可以发现预制体内部记录的组件只是脚本ID,并非真正的代码。

三、加载AB包资源

1、加载资源

  • 不使用协程
// 1、加载AB包(不可重复加载同一个AB包)
AssetBundle ab = AssetBundle.LoadFromFile(Application.streamingAssetsPath + "/" + "model");
// 2、加载AB包中的资源(有多种重载)
GameObject obj = ab.LoadAsset<GameObject>("Cube");
  • 使用协程
IEnumerator LoadABRes(string ABName,string resName)
{
    AssetBundleCreateRequest abcr = AssetBundle.LoadFromFileAsync(Application.streamingAssetsPath + "/" + "head");
    yield return abcr;
    AssetBundleRequest abr = abcr.assetBundle.LoadAssetAsync(resName,typeof(Sprite));
    yield return abr;
    image.sprite = abr.asset as Sprite;
}

2、卸载资源

// 1、卸载所有AB包
AssetBundle.UnloadAllAssetBundles(false);  //bool参数为true时,会把通过AB包加载的资源也卸载了。
// 2、只卸载自己的AB包
ab.Unload(true);  //参数一样

3、依赖

一个资源身上用到了别的AB包中的资源,那这两个包就产生了依赖,这个时候如果只加载它创建对象,会出现资源丢失的的情况,此时需要把依赖包一起加载了才能正常。

  • 实例:新建一个材质球,赋给Cube预制体,运行时可以发现Meterial被默认自动打包到依赖它的AB包中了(如model包)。
    如果手动设置,比如将Material设置到head包中,如果只加载了model包,则Cube的材质球会丢失。只有加载了材质球所在的head包,才能恢复正常。

4、加载依赖

如果知道依赖包,可以手动去加载,但是大部分情况下,资源的依赖是非常复杂的,此时就需要用到主包来获取依赖信息。

下图是主包的Manifest文件内容

主包的Manifest

加载依赖代码如下:

// 加载主包
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++)
{
    AssetBundle.LoadFromFile(Application.stre amingAssetsPath + "/" + strs[i]);                 
}

四、AB包管理器

这里加载资源时,如果是GameObject类型,则返回实例化后的对象(其实我觉得可以去掉)

using System.Collections;
using System.Collections.Generic;
using System.Security;
using UnityEngine;
using UnityEngine.Events;

public class ABMgr : SingletonAutoMono<ABMgr>
{
    private AssetBundle mainAB = null;
    private AssetBundleManifest manifest = null;
    private Dictionary<string,AssetBundle> abDic = new Dictionary<string,AssetBundle>();

    /// <summary>
    /// 路径名
    /// </summary>
    private string PathUrl
    {
        get
        {
            return Application.streamingAssetsPath + "/";
        }
    }

    /// <summary>
    /// 主包名
    /// </summary>
    private string MainABName
    {
        get
        {
#if UNITY_IOS
            return "IOS";
#elif UNITY_ANDROID
            return "Android";
#else
            return "PC";
#endif
        }
    }

    /// <summary>
    /// 加载AB包及其依赖
    /// </summary>
    /// <param name="abName">包名</param>
    public void LoadAB(string abName)
    {
        if (mainAB == null)
        {
            mainAB = AssetBundle.LoadFromFile(PathUrl + MainABName);
            manifest = mainAB.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
        }
        // 加载依赖包
        AssetBundle ab = null;
        string[] strs = manifest.GetAllDependencies(abName);
        for (int i = 0; i < strs.Length; i++)
        {
            if (!abDic.ContainsKey(strs[i]))
            {
                ab = AssetBundle.LoadFromFile(PathUrl + strs[i]);
                abDic.Add(strs[i], ab);
            }
        }
        // 加载目标资源包
        if (!abDic.ContainsKey(abName))
        {
            ab = AssetBundle.LoadFromFile(PathUrl + abName);
            abDic.Add(abName, ab);
        }
    }

    /// <summary>
    /// 同步加载资源
    /// </summary>
    /// <param name="abName">包名</param>
    /// <param name="resName">资源名</param>
    public Object LoadRes(string abName,string resName)
    {
        // 加载AB包
        LoadAB(abName);

        // 加载资源并返回
        Object obj = abDic[abName].LoadAsset(resName);
        if (obj is GameObject)
        {
            return Instantiate(obj);
        }
        else
        {
            return obj;
        }
    }

    public Object LoadRes(string abName, string resName,System.Type type)
    {
        // 加载AB包
        LoadAB(abName);

        // 加载资源并返回
        Object obj = abDic[abName].LoadAsset(resName,type);
        if (obj is GameObject)
        {
            return Instantiate(obj);
        }
        else
        {
            return obj;
        }
    }

    public T LoadRes<T>(string abName,string resName) where T : Object
    {
        // 加载AB包
        LoadAB(abName);

        // 加载资源并返回
        T obj = abDic[abName].LoadAsset<T>(resName);
        if (obj is GameObject)
        {
            return Instantiate(obj);
        }
        else
        {
            return obj;
        }
    }

    /// <summary>
    /// 异步加载资源(只是加载资源异步,AB包加载并没有使用异步)
    /// </summary>
    /// <param name="abName">包名</param>
    /// <param name="resName">资源名</param>
    /// <param name="callback">回调函数</param>
    public void LoadResAsync(string abName,string resName,UnityAction<Object> callback)
    {
        StartCoroutine(ReallyLoadResAsync(abName,resName,callback));
    }

    private IEnumerator ReallyLoadResAsync(string abName, string resName, UnityAction<Object> callback)
    {
        LoadAB(abName);
        AssetBundleRequest abr = abDic[abName].LoadAssetAsync(resName);
        yield return abr;

        if (abr.asset is GameObject)
            callback(Instantiate(abr.asset));
        else
            callback(abr.asset);
    }

    public void LoadResAsync(string abName, string resName, System.Type type, UnityAction<Object> callback)
    {
        StartCoroutine(ReallyLoadResAsync(abName, resName, type, callback));
    }

    private IEnumerator ReallyLoadResAsync(string abName, string resName, System.Type type, UnityAction<Object> callback)
    {
        LoadAB(abName);
        AssetBundleRequest abr = abDic[abName].LoadAssetAsync(resName,type);
        yield return abr;

        if (abr.asset is GameObject)
            callback(Instantiate(abr.asset));
        else
            callback(abr.asset);
    }

    public void LoadResAsync<T>(string abName, string resName, UnityAction<T> callback) where T : Object
    {
        StartCoroutine(ReallyLoadResAsync<T>(abName, resName, callback));
    }

    private IEnumerator ReallyLoadResAsync<T>(string abName, string resName, UnityAction<T> callback) where T : Object
    {
        LoadAB(abName);
        AssetBundleRequest abr = abDic[abName].LoadAssetAsync<T>(resName);
        yield return abr;

        if (abr.asset is GameObject)
            callback(Instantiate(abr.asset) as T);
        else
            callback(abr.asset as T);
    }

    public void UnLoad(string abName)
    {
        if (abDic.ContainsKey(abName))
        {
            abDic[abName].Unload(false);
            abDic.Remove(abName);
        }
    }

    public void ClearAB()
    {
        AssetBundle.UnloadAllAssetBundles(false);
        abDic.Clear();
        mainAB = null;
        manifest = null;
    }
}