分享

Unity 热更新技术 | (二) AssetBundle - 完整系列教程学习

 敲代码的小Y 2023-03-15 发布于上海

请添加图片描述

  • 🎬 博客主页:https://xiaoy.blog.csdn.net

  • 🎥 本文由 呆呆敲代码的小Y 原创,首发于 CSDN🙉

  • 🎄 学习专栏推荐:Unity系统学习专栏

  • 🌲 游戏制作专栏推荐:游戏制作

  • 🌲Unity实战100例专栏推荐:Unity 实战100例 教程

  • 🏅 欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!

  • 📆 未来很长,值得我们全力奔赴更美好的生活✨

  • ------------------❤️分割线❤️-------------------------

请添加图片描述

Unity 热更新技术最新系列全面教程

在这里插入图片描述

Unity 热更新技术 | (二) AssetBundle - 完整系列教程学习

请添加图片描述


前言

  • 开始学习热更新方面系列知识,就从这一篇开始吧!
  • 本系列教程 会从热更新基本概念出发,详细介绍热更新相关的全套知识点。
  • 如热更新技术基本原理、热更新主流方案、AssetBundle系列教程、Lua语言编程、Lua与Unity交互教程、Xlua框架使用、热更新实战教程等方面。
  • 热更新作为一个开发者必备技能,如果想学习的话,可以从现在开始了哦~
  • 接下来从 AssetBundle - 完整系列教程学习 开始学习吧!

一、什么是AssetBundle

  AssetBundle(简称AB包)是一个资源压缩包,可以包含模型、贴图、音频、预制体等。如在网络游戏中需要在运行时加载资源,而AssetBundle可以将资源构建成 AssetBundle 文件。

在这里插入图片描述


二、AssetBundle作用

1、AssetBundle是一个压缩包包含模型、贴图、预制体、声音、甚至整个场景,可以在游戏运行的时候被加载;
2、AssetBundle自身保存着互相的依赖关系;
3、压缩包可以使用LZMA和LZ4压缩算法,减少包大小,更快的进行网络传输;
4、把一些可以下载内容放在AssetBundle里面,可以减少安装包的大小;


三、AssetBundle三种压缩格式

AssetBundle 提供了三种压缩格式:

  1. 不压缩(BuildAssetBundleOptions.UncompressedAssetBundle):优点是需要加载资源时速度非常快,缺点是构建的 AssetBundle 资源文件会比较大。
  2. LZMA压缩(BuildAssetBundleOptions.None):unity中默认的压缩方式,优点是会将文件压缩的非常小,缺点是每次使用都需要将压缩的文件全部解压,非常耗费时间,可能会造成游戏的卡顿,不推荐在项目中使用。
  3. LZ4压缩(BuildAssetBundleOptions.ChunkBasedCompression):是LZMA和不压缩之间的折中方案,构建的 AssetBundle 资源文件会略大于 LZMA 压缩,但是在加载资源时不需要将所有的资源都加载下来,所以速度会比 LZMA 快。建议项目中使用它。

四、AB打包流程

  1. 设置资源AssetBundle名称
  2. BuildPipeline,BuildAssetBundles打包
  3. 处理打包后的文件
  4. Ab包依赖描述

在这里插入图片描述


五、AB包具体使用方式

5.1 官方提供的打包工具:AssetBundle Browser

下载官方提供的打包工具,两种下载方式:

  1. git地址:https://github.com/Unity-Technologies/AssetBundles-Browser
  2. 在资源管理器中打开Packages的manifest.json文件,在"dependencies": {}中添加一行代码:“com.unity.assetbundlebrowser”: “1.7.0”,

下载之后导入Unity工程即可,如遇报错可以删掉Test文件夹即可。

打开方式:Windows -> AssetBundle Browser 启动打包除窗口。
在这里插入图片描述

5.2 将对象保存为预制体并为预制体设置AB包信息

在场景中新建几个游戏对象做测试,将其拖到Resources下当做预制体。

在这里插入图片描述

然后在监视器面板中设置AB包的信息,选中该物体,在右下角设置AB包名称。

在这里插入图片描述

这样就可以在面板中看到我们设置的AB包信息了。设置的时候会根据AB包不同名称分别打到不同的包中。

在这里插入图片描述

5.3 执行打包方法

选择对应的平台及输出路径,然后根据情况选择其他配置。

在这里插入图片描述

参数含义如下

  • Build Target:打包平台选择
  • Output Path :文件输出路径
  • Clear Folders:清空路径内容
  • Copy to StreamingAssets:将打包后的内容复制到Assets/StreamingAssets文件夹下
  • Advanced Settings
    • Exclude Type Infomation:在资源包中 不包含资源的类型信息
    • Force Rebuild:重新打包时需要重新构建包
      和ClearFolder不同,他不会删除不再存在的包
    • Ignore Type Tree Changes:增量构建检查时,忽略类型数的修改
    • Apped Hash:将文件哈希值附加到资源包名上
    • Strict Mode:严格模式,如果打包报错了,则打包直接失败无法成功
    • Dry Run Build:运行时构建

点击Build后会执行打包方法,等待打包完成即可获得对应的AB包文件。

若是上面选择了 Copy to StreamingAssets,则会打包出来两份资源。
一个与Asset同级目录,另一个则是在Assets/StreamingAssets文件夹下。

在这里插入图片描述

其中有一个主包文件和对应的AB包资源文件。

内容大致为以下几个部分:

  • AB包文件:资源文件
  • manifest文件:AB包文件信息(资源信息,依赖关系,版本信息等等)
  • 关键AB包(与打包目录名相同的包):主包文件,包含AB包依赖的关键信息

在这里插入图片描述
在这里插入图片描述

5.4 加载AB包,并使用其中的资源文件

上面已经讲到了打包AB包的方法,下面就是学习怎样加载我们打包好的AB包,并使用其中的资源。

在这里插入图片描述

下面直接使用LoadFromFile()方法进行AB包的加载及使用,代码如下:

1.使用同步加载方法 LoadFromFile()

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class ABLoadDemo : MonoBehaviour
{
    public Button LoadAb_Btn;
    private string LoadPath;//AB包路径

    private void Awake()
    {
        LoadPath = Application.streamingAssetsPath;
        LoadAb_Btn.onClick.AddListener(LoadAB);
    }

    /// <summary>
    /// 同步加载
    /// </summary>
    private void LoadAB()
    {
        //第一步:加载AB包
        AssetBundle ab = AssetBundle.LoadFromFile(LoadPath + "/"+"module");
        //第二步:加载AB包中的资源
        //GameObject abGO = ab.LoadAsset<GameObject>("bullet");//方法一:使用LoadAsset<>泛型加载
        //GameObject abGO = ab.LoadAsset("bullet") as GameObject;//方法二:使用LoadAsset名字加载(不推荐,会出现同名不同类型的对象无法区分的问题)
        GameObject abGO = ab.LoadAsset("bullet", typeof(GameObject)) as GameObject;//方法三:使用LoadAsset(Type)指定类型加载
        Instantiate(abGO);
    }
}

2.使用异步加载方法 LoadFromFileAsync(),使用协程辅助异步加载。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class ABLoadDemo : MonoBehaviour
{
    public Button LoadAbAsync_Btn;
    public Image image;//场景测试图片
    private string LoadPath;//AB包路径

    private void Awake()
    {
        LoadPath = Application.streamingAssetsPath;
        LoadAbAsync_Btn.onClick.AddListener(()=> 
        {
        	//启动协程完成异步加载
            StartCoroutine(LoadABAsync()); 
        });
    }
    /// <summary>
    /// 异步加载
    /// </summary>
    /// <returns></returns>
    IEnumerator LoadABAsync()
    {
    	//第一步:加载AB包
        AssetBundleCreateRequest abcr = AssetBundle.LoadFromFileAsync(LoadPath + "/" + "test");
        yield return abcr;
        //第二步:加载AB包中的资源
        AssetBundleRequest abr = abcr.assetBundle.LoadAssetAsync("head",typeof(Sprite));
        yield return abr;
        image.sprite = abr.asset as Sprite;
        Debug.Log("加载AB包赋值图片完成");
    }
}

同步加载实例化一个球体异步加载一张图片赋值给Image组件 的示例如下:

在这里插入图片描述

这样我们就学会最基本的Ab包加载和使用其中资源的方法了。

其中有个点需要注意:

  • 同一AB包不能重复加载多次,否则会报错(卸载后可重新加载)
    在这里插入图片描述

AB包卸载方式如下:

AssetBundle ab = AssetBundle.LoadFromFile(LoadPath + "/"+"module");

//卸载所有AB包资源。若参数为true表示将所有使用该AB包中的资源全部卸载,反之则不会
AssetBundle.UnloadAllAssetBundles(false);

//卸载某个指定AB包的方法。若参数为true表示将会把使用该AB包的场景资源也全部卸载,反之则不会
ab.Unload(false);

下面是几种常用的AB包加载方式,简单记录一下:

  1. 异步加载:AssetBundle.LoadFromMemoryAsync
    从内存区域异步创建 AssetBundle。
 /// <summary>
    /// 从本地异步加载AssetBundle资源,Path是AB包路径+AB包名称
    /// </summary>
    /// <param name="path">路径</param>
    /// <returns></returns>
    IEnumerator LoadFromMemoryAsync(string path)
    {
        AssetBundleCreateRequest createRequest = AssetBundle.LoadFromMemoryAsync(File.ReadAllBytes(path));
        yield return createRequest;
        AssetBundle bundle = createRequest.assetBundle;
        var prefab = bundle.LoadAsset<GameObject>("bullet");
        Instantiate(prefab);
    }
  1. 同步加载,将等待 AssetBundle 对象创建完毕才返回。
    AssetBundle.LoadFromMemory
    AssetBundle.LoadFromMemory由AssetBundle.LoadFromMemoryAsync变化而来,与 LoadFromMemoryAsync 相比,该版本是同步的,将等待 AssetBundle 对象创建完毕才返回。
AssetBundle bundle = AssetBundle.LoadFromMemory(File.ReadAllBytes(ABPath));
        var prefab = bundle.LoadAsset<GameObject>("bullet");
        Instantiate(prefab);
  1. 同步加载AssetBundle.LoadFromFile
    从磁盘上的文件同步加载 AssetBundle。
    该函数支持任意压缩类型的捆绑包。 如果是 lzma 压缩,则将数据解压缩到内存。可以从磁盘直接读取未压缩和使用块压缩的捆绑包。

与 LoadFromFileAsync 相比,该版本是同步的,将等待 AssetBundle 对象创建完毕才返回。
这是加载 AssetBundle 的最快方法。

/// <summary>
/// 从磁盘上的文件同步加载 AssetBundle。
/// </summary>
void LoadFromFile()
{
    AssetBundle ab = AssetBundle.LoadFromFile(Application.dataPath + "/StreamingAssets/"+ assetBundle);
    var go = ab.LoadAsset<GameObject>("ZAY");
    Instantiate(go);
}
  1. 异步加载:AssetBundle.LoadFromFileAsync
    LoadFromFileAsync AssetBundle 的异步创建请求。加载后使用 assetBundle 属性获取 AssetBundle。
    从磁盘上的文件异步加载 AssetBundle。

该函数支持任意压缩类型的捆绑包。 如果是 lzma 压缩,则将数据解压缩到内存。可以从磁盘直接读取未压缩和使用块压缩的捆绑包。

IEnumerator LoadFromFileASync(string path)
    {
        AssetBundleCreateRequest createRequest = AssetBundle.LoadFromFileAsync(path);
        yield return createRequest;
        AssetBundle bundle = createRequest.assetBundle;
        if (bundle == null)
        {
            Debug.Log("Failed to load AssetBundle!");
            yield break;
        }
        var parefab = bundle.LoadAsset<GameObject>("bullet");
        Instantiate(parefab);
    }
  1. UnityWebRequestAssetBundle 和 DownloadHandlerAssetBundle
    UnityWebRequestAssetBundle 此方法将 DownloadHandlerAssetBundle 附加到 UnityWebRequest。

DownloadHandlerAssetBundle.GetContent(UnityWebRequest) 作为参数。GetContent 方法将返回你的 AssetBundle 对象。

   IEnumerator WEB(string url)
    {
        UnityWebRequest request = UnityWebRequestAssetBundle.GetAssetBundle(url, 3, 0);
        //此方法将返回 WebRequestAsyncOperation 对象。在协程内部生成 WebRequestAsyncOperation 将导致协程暂停,
        //直到 UnityWebRequest 遇到系统错误或结束通信为止。
        yield return request.SendWebRequest();
        //如果加载失败
        if (request.result != UnityWebRequest.Result.Success)
        {
            Debug.Log(request.error);
            yield break;
        }
        //返回下载的 AssetBundle 或 null。
        AssetBundle bundle = DownloadHandlerAssetBundle.GetContent(request);
        var parefab = bundle.LoadAsset<GameObject>("ZAY");
        Instantiate(parefab);
    }

5.5 AB包的加载流程

再简单介绍下AB的加载流程,如下所示:

在这里插入图片描述


六、AssetBundle依赖关系

  上面讲了一下基本的 AssetBundle打包 和 加载 的方法。
在加载流程中也提到了依赖关系,下面就来讲一下AssetBundle的依赖关系,所谓依赖关系就是指某个AB包中的某个资源可能是依赖于另外一个AB包的。

比如我们打包的时候,一个AB包中的内容全是模型,而另外一个AB包中的资源都是材质,此时模型AB包中就可能需要使用到材质AB包中的资源,此时两个AB包就存在依赖关系。

下面用一个例子看一下具体效果:
首先新建一个Material材质球,改为黄色并将其赋给Player对象。

在这里插入图片描述

Player对象是勾选了AB包的,我们现在重新使用Build打包看一下AB包情况。

在这里插入图片描述

可以看到这个材质也被自动打包进了AB包中,而且Budle名是默认设置的auto。
在包中的一个资源如果使用了另外一个资源,那么打包的时候会把另外一个资源也默认打包进该包中。
此时我们可以手动修改该材质的AB包名称,然后重新打包一下。

在这里插入图片描述

此时我们再去加载AB包module获取Player对象试一下效果:

		//加载AB包
        AssetBundle ab = AssetBundle.LoadFromFile(LoadPath + "/"+"module");
        //加载AB包中的资源
        GameObject abGO = ab.LoadAsset("player", typeof(GameObject)) as GameObject;
        //实例化对象
        Instantiate(abGO);

可以看到游戏对象被加载出来了,但是材质发生了丢失。
在这里插入图片描述

  原因就是因为该AB包module中的Player对象使用到了materials包中的材质球资源,但是我们没有加载materials包。所以出现了材质丢失。
这就是说module包中有资源对象依赖对materials包中的资源,所以他们存在AB包依赖关系。

出现这种有依赖关系的情况时,如果只加载自己的AB包,那么通过它创建的对象就会出现资源丢失的情况(比如上方的材质丢失等),此时就需要将依赖包一起进行加载,才能保证材质不丢失。

比如上方加载module包的代码多加一行,如下所示:

		//加载AB包
        AssetBundle ab = AssetBundle.LoadFromFile(LoadPath + "/"+"module");
        //加载依赖包
        AssetBundle abMaterials = AssetBundle.LoadFromFile(LoadPath + "/" + "materials");
        //加载AB包中的资源
        GameObject abGO = ab.LoadAsset("player", typeof(GameObject)) as GameObject;
        //实例化对象
        Instantiate(abGO);

此时运行项目就会发现,一切正常了,模型和材质都是正常显示了。
在这里插入图片描述

但问题是如果此时我们打包了很多的AB包,并且各个AB包中的依赖关系比较复杂时,我们就没办法上面那样根据依赖包的名称手动加载了。

此时我们就可以打开AB包中的主包的manifest文件查看具体的依赖关系:
在这里插入图片描述
可以看到manifest中有标志说 资源包info_0(module包) 对 Info_1(materials包)有依赖关系。

所以说我们在代码中就可以使用主包的manifest文件来对每个AB包的依赖包进行加载。
所以代码可以更改为如下所示:

        //加载AB包
        AssetBundle ab = AssetBundle.LoadFromFile(LoadPath + "/"+"module");
        //加载主包
        AssetBundle abMain = AssetBundle.LoadFromFile(LoadPath + "/" + "StandaloneWindows");
        //加载主包中的固定文件
        AssetBundleManifest abManifest = abMain.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
        //从固定文件中得到依赖信息
        string[] strs = abManifest.GetAllDependencies("module");
        //得到依赖包的名字并加载
        foreach (var s in strs)
        {
            Debug.Log("依赖包:"+s);
            AssetBundle.LoadFromFile(LoadPath + "/" + s);
        }
        //加载AB包中的资源 实例化对象 卸载所有AB包资源
        GameObject abGO = ab.LoadAsset("player", typeof(GameObject)) as GameObject;
        Instantiate(abGO);
        AssetBundle.UnloadAllAssetBundles(false);

在这里插入图片描述

此处注意点:在manifest文件中只能看到某个AB包依赖于哪些其他AB包,并不能看到某个AB包中资源依赖于哪个AB包中的具体资源。


七、AssetBundle分组策略

上面提到了AssetBundle的依赖关系,那么就不得不提一下AssetBundle的分组策略啦。

我们现在已经知道不同的AB包之间可能会存在各种依赖关系,那么此时对AB包的分组就显得尤为重要了。
不然的话等到项目资源越来越多、各个AB间的依赖关系越来越复杂时,足够让开发者们搞的头皮发麻。

分组策略可根据自己的项目规划进行划分,一般有下面几种分组参考:

  1. 逻辑实体分组
    a,一个UI界面或者所有UI界面一个包(这个界面里面的贴图和布局信息一个包)
    b,一个角色或者所有角色一个包(这个角色里面的模型和动画一个包)
    c,所有的场景所共享的部分一个包(包括贴图和模型)
  2. 按照类型分组
    所有声音资源打成一个包,所有shader打成一个包,所有模型打成一个包,所有材质打成一个包
  3. 按照使用类型分组
    把在某一时间内使用的所有资源打成一个包。可以按照关卡分,一个关卡所需要的所有资源包括角色、贴图、声音等打成一个包。也可以按照场景分,一个场景所需要的资源一个包
  4. 按更新频率分组
    不经常更新的放在一个包,经常更新的放在一个包分别管理。
    在这里插入图片描述

总结

  • 本文讲解了Unity中的AssetsBundle(AB 包)相关内容知识点。
  • 包括AB包概念、压缩格式、Ab包使用方式等几个方面
  • 其中Ab包使用方式这块建议仔细看一下,后续热更新方面打包的时候用到这个地方会多一点。
  • 本篇文章介绍了 AssetBundle的相关内容,后续完整的热更新方案及教程请看专栏其他文章

    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多