-
🎬 博客主页:https://xiaoy.blog.csdn.net
-
🎥 本文由 呆呆敲代码的小Y 原创,首发于 CSDN🙉
-
🎄 学习专栏推荐:Unity系统学习专栏
-
🌲 游戏制作专栏推荐:游戏制作
-
🌲Unity实战100例专栏推荐:Unity 实战100例 教程
-
🏅 欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!
-
📆 未来很长,值得我们全力奔赴更美好的生活✨
-
------------------❤️分割线❤️-------------------------
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 提供了三种压缩格式:
- 不压缩(BuildAssetBundleOptions.UncompressedAssetBundle):优点是需要加载资源时速度非常快,缺点是构建的 AssetBundle 资源文件会比较大。
- LZMA压缩(BuildAssetBundleOptions.None):unity中默认的压缩方式,优点是会将文件压缩的非常小,缺点是每次使用都需要将压缩的文件全部解压,非常耗费时间,可能会造成游戏的卡顿,不推荐在项目中使用。
- LZ4压缩(BuildAssetBundleOptions.ChunkBasedCompression):是LZMA和不压缩之间的折中方案,构建的 AssetBundle 资源文件会略大于 LZMA 压缩,但是在加载资源时不需要将所有的资源都加载下来,所以速度会比 LZMA 快。建议项目中使用它。
四、AB打包流程
- 设置资源AssetBundle名称
- BuildPipeline,BuildAssetBundles打包
- 处理打包后的文件
- Ab包依赖描述
五、AB包具体使用方式
5.1 官方提供的打包工具:AssetBundle Browser
下载官方提供的打包工具,两种下载方式:
- git地址:https://github.com/Unity-Technologies/AssetBundles-Browser
- 在资源管理器中打开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:严格模式,如果打包报错了,则打包直接失败无法成功
-
点击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包加载方式,简单记录一下:
- 异步加载: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);
}
- 同步加载,将等待 AssetBundle 对象创建完毕才返回。
AssetBundle.LoadFromMemory
AssetBundle.LoadFromMemory由AssetBundle.LoadFromMemoryAsync变化而来,与 LoadFromMemoryAsync 相比,该版本是同步的,将等待 AssetBundle 对象创建完毕才返回。
AssetBundle bundle = AssetBundle.LoadFromMemory(File.ReadAllBytes(ABPath));
var prefab = bundle.LoadAsset<GameObject>("bullet");
Instantiate(prefab);
- 同步加载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);
}
- 异步加载: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);
}
- 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间的依赖关系越来越复杂时,足够让开发者们搞的头皮发麻。
分组策略可根据自己的项目规划进行划分,一般有下面几种分组参考:
- 逻辑实体分组
a,一个UI界面或者所有UI界面一个包(这个界面里面的贴图和布局信息一个包)
b,一个角色或者所有角色一个包(这个角色里面的模型和动画一个包)
c,所有的场景所共享的部分一个包(包括贴图和模型) - 按照类型分组
所有声音资源打成一个包,所有shader打成一个包,所有模型打成一个包,所有材质打成一个包 - 按照使用类型分组
把在某一时间内使用的所有资源打成一个包。可以按照关卡分,一个关卡所需要的所有资源包括角色、贴图、声音等打成一个包。也可以按照场景分,一个场景所需要的资源一个包 - 按更新频率分组
不经常更新的放在一个包,经常更新的放在一个包分别管理。
总结
- 本文讲解了Unity中的AssetsBundle(AB 包)相关内容知识点。
- 包括AB包概念、压缩格式、Ab包使用方式等几个方面
- 其中Ab包使用方式这块建议仔细看一下,后续热更新方面打包的时候用到这个地方会多一点。
- 本篇文章介绍了 AssetBundle的相关内容,后续完整的热更新方案及教程请看专栏其他文章。