Unity热更新之Addressables——基础(壹)
一、概述
1、学习前置知识点
- C#委托
- AssetBundle
- Resources同步异步加载
- ScriptableObject
- 协同程序基本原理
2、Addressables概述
Addressables是可寻址的意思,这是可寻址资源管理系统。
从Unity2018.2版本开始,建议用于替代AssetBundle的高阶资源管理系统,后面版本中AssetBundle将渐渐被淘汰。
3、主要作用
Addressables和AssetBundle主要作用是一样的:
- 管理资源
- 热更新
- 减小包的体积
4、Addressables优点
- 自动化管理AB包的打包、发布、加载
- 可以更方便进行本地和远程资源的加载
- 系统会自动处理资源关联性
- 内存管理更方便
- 迭代更方便
5、导入
(1)在Unity中,打开Window——Package Manager
(2)设置包为Unity Registry
(3)安装Addressables包
二、基础
(一)创建配置相关文件
- 方法一:
- Window——>Asset Maangerment ——> Addressables ——> Groups
- 窗口中点击Create Addressables Settings按钮,创建配置文件
- 方法二:
- Insperctor窗口中为资源勾选Addressabe,若没有配置文件,则会自动创建相关文件
(二)寻址资源设置
1、设置可寻址资源
-
方式一:选中资源,勾选Inspector窗口的Addressable
-
方式二:选中资源,拖入Addressables Groups窗口中
注意:
- C#代码无法作为可寻址资源
- Resources文件夹下的资源如果变为可寻址资源,会被移入Resources_moved文件夹中(因为不需要再通过Resources方式去打包加载了,避免重复打包)
2、右键选择资源的菜单内容
- Move Addressables to Group:将该资源放入到现有的另一个组中
- Move Addressables to New Group:使用与当前组相同设置创建一个新租,并将该资源放入该新组中
- Rmove Addressables:移除资源,该资源会变为不可寻址资源
- Simplify Addressable Names:简化可寻址资源名,会删除名称中的路径和拓展,简化缩短名称
- Copy Address to Clipboard:将地址复制到剪贴板
- Change Address:改名
- Create New Group:创建新租

3、资源信息
-
GroupName\Addressable Name:分组名 \ 可寻址名(可重名,描述资源)
-
Path:路径(不可重复,资源定位)
-
Labels:标签(可重复、可用于区分资源种类,例如青铜装备、黄金装备)
4、创建分组
创建:Create—>Group
分类:
-
Packed Assets:打包资源分组,默认自带默认打包加载相关设置信息
-
Blank (no schema):空白(无架构),没有相关信息需要自己关联
组对于我们来说意义重大,之后在资源打包时,一个组可以作为一个或多个 AB 包
5、右键选择某一组的菜单内容
-
Remove Group (s):移除组,组中所有资源恢复为不可寻址资源
-
Simplify Addressable Names:简化可寻址名称,会删除名称中的路径和拓展,简化缩短名称
-
Set as Default:设置为默认组,当直接勾选资源中的 Addressable 时,会自动加入该组
-
Inspect Group Setting:快速选中关联的组相关配置文件
-
Rename:重命名
-
Create New Group:创建新组

6、配置概述相关
在Addressables Groups窗口中,左上角的Profile可以打开Manage Profiles,用于配置相关打包信息,后续会详细讲解
7、Tools工具选项
在左上角Profile的右边,目前简单了解下,后续会详细讲解
-
Inspect System Settings:检查系统设置
-
Check for content Update Restrictions:检查内容更新限制
-
Window:打开 Addressables 相关窗口
-
Groups View:分组视图相关
-
Show Sprite and Subobject Addressable:显示可寻址对象的精灵和子对象,一般想要看到图集资源内内容时可以勾选该选项
-
Group Hierarchy with Dashes:带破折号的组层次结构
8、Play Mode Script 播放模式脚本
在窗口右上角,用于确定在编辑器播放模式下运行游戏时,可寻址系统如何访问可寻址资源
- Use Asset Database (fastest)
使用资源数据库(最快的),一般在开发阶段使用,使用此选项时,您不必构建打包可寻址内容,它会直接使用文件夹中的资源
在实际开发时,可不使用这种模式,因为没有测试的意义
- Simulate Groups (advanced)
模拟组(后期),一般在测试阶段使用,分析布局和依赖项的内容,而不创建资产绑定。通过 ResourceManager 从资产数据库加载资产,就像通过捆绑包加载一样。通过引入时间延迟,模拟远程资产绑定的下载速度和本地绑定的文件加载速度
在开发阶段可以使用这个模式来进行资源加载
- Use Existing Build (requires built groups)
正经的远端加载资源,使用现有构建(需要构建组),一般在最终发布测试阶段使用
从早期内容版本创建的捆绑包加载资产,在使用此选项之前,必须使用生成脚本(如默认生成脚本)打包资源
远程内容必须托管在用于生成内容的配置文件的 RemoteLoadPath 上
9、Build构建打包相关
- New Build:构建资源(相当于打包资源分组)
- Update a Previour Build:更新以前的版本(更新以前的AB包)
- Clean Build:清空之前构建的资源
10、资源注意事项
- 资源路径一定不能相同(后缀不同,名字相同可以)
- 资源名可以随意修改(Addressable Name)
- 之后加载资源时,可以使用名字和标签作为双标识加载指定资源
- 资源可以按照一定的规则进行分组,如角色、装备、怪物、UI等
(三)加载指定资源
1、Addressables中的资源标识类
如果设置为公开,可以在Inspector窗口中拖入可寻址资源。
这里提供了很多方法,就不一一列举了,都是AssetReference开头的
using UnityEngine.AddressablesAssets;
AssetReference // 资源标识类,可以装载任意类型资源
AssetReferenceT<> // 加载指定类型资源
AssetReferenceAtlasedSprite // 图集资源标识类
AssetReferenceGameObject // 游戏对象资源标识类
......
2、加载资源
- Addressables加载都用异步加载,同步加载已经弃用了。使用
AssetReference.LoadAssetAsync可以加载资源 - 加载资源后用异步加载的句柄
AsyncOperationHandle接收,里面提供了很多异步加载的方法 handle.Completed添加资源加载完成后的事件,handle.Status查看加载状态,handle.Result获取加载的资源
public AssetReference assetReference;
private void Start()
{
// 加载资源
AsyncOperationHandle<GameObject> handle = assetReference.LoadAssetAsync<GameObject>();
// 加载完成的回调事件
handle.Completed += Handle_Completed;
}
private void Handle_Completed(AsyncOperationHandle<GameObject> handle)
{
// 获取加载的资源并处理
if (handle.Status == AsyncOperationStatus.Succeeded)
{
Instantiate(handle.Result);
}
// 另一种获取资源的方式是assetReference,但不建议
if (assetReference.IsDone)
{
Instantiate(assetReference.Asset as GameObject);
}
}
句柄(Handle):
是一个由系统(如操作系统)提供的、用于间接引用其管理的内部资源的一个抽象标识符。
它的核心思想是:“你不要直接碰资源,给我一个凭证(句柄),我来替你操作。”
3、加载场景
public AssetReference sceneReference;
private void Start()
{
sceneReference.LoadSceneAsync().Completed += (handle) =>
{
Debug.Log("场景加载结束");
};
}
4、释放资源
assetReference.ReleaseAsset();
- 释放资源后,这一帧资源标识类
assetReference中的资源会置空,但是AsyncOperationHandle中的对象好像不为空(这里唐老狮说的不是很清楚,我自己测试了一下,然后写在了第(七)节资源释放那里)
5、直接实例化
使用提供的InstantiateAsync方法可以直接把对象实例化出来,方便使用,当然只适用于能够实例化的对象
public AssetReferenceGameObject gameObjectReference;
private void Start()
{
gameObjectReference.InstantiateAsync();
}
6、自定义标识类
了解即可:老版本(unity2020.1之前),是无法直接使用AssetReferenceT<>的,Inspector窗口会无法显示,需要自定义一个标识类,继承AssetReferenceT<>才可以使用
(四)Label标签的作用
1、关于指定资源加载方式
实际商业项目开发中,并不会经常使用这种方式,而是根据配置文件决定加载什么资源,是动态加载。因此需要学习根据名字或标签去加载对应资源,这样就可以读表进行加载
2、Label的设置
在Addressables Groups窗口中,可以为资源设置标签
3、Label的作用
相同作用的不同资源,可以让他们资源名相同,而通过Label区分他们来加载使用
如同样是一套UI,资源名一样,但是可能不同节日的UI样式不一样,这样资源名可以都相同,而只通过标签来区分
4、标签相关的特性
通过这个特性,可以限制AssetReference只能使用指定Label的资源
[AssetReferenceUILabelRestriction("Label1", "Label2", ...)]
public AssetReference assetReference;
(五)动态加载单个资源
1、通过资源名/标签名加载资源
Addressables.LoadAssetAsync<资源类型>("资源名/标签名");
AsyncOperationHandle<GameObject> handle = Addressables.LoadAssetAsync<GameObject>("Cube");
handle.Completed += (handle) =>
{
if (handle.Status == AsyncOperationStatus.Succeeded)
{
Instantiate(handle.Result);
}
};
- 如果存在同名或同标签的同类型资源,那么无法确定加载哪一个,会自动加载第一个满足条件的
- 如果存在同名或同标签的不同类型资源,可以通过泛型指定加载哪一个
2、释放资源
Addressables.Release(handle);
3、场景加载
- 参数1:场景名
- 参数2:加载模式(单独还是叠加)
- 参数3:加载完后是否激活(false则加载完后不会切换,需要自己使用返回的加载对象
SceneInstance的ActivateAsync()方法 - 参数4:场景加载异步操作的优先级
Addressables.LoadSceneAsync("SampleScene",
UnityEngine.SceneManagement.LoadSceneMode.Single,
true,
100);
(六)动态加载多个资源
1、根据单个信息动态加载多个资源
动态加载多个资源使用Addressables.LoadAssetsAsync,有多个重载,但常用的就两种方式:
方式一:加载完资源后,直接对多个资源进行处理
- 参数1:资源名/标签名
- 参数2:每个资源加载完后的回调函数
- 参数3(可选):资源加载失败时,是否自动释放已加载和依赖的资源。如果为false,则手动管理释放
Addressables.LoadAssetsAsync<Object>("Cube", (obj) =>
{
Debug.Log(obj.name); // 每个资源加载结束后的回调函数
});
方式二:使用handle(推荐)
AsyncOperationHandle<IList<Object>> handle = Addressables.LoadAssetsAsync<Object>("Cube",
(obj) => {
// 可不处理,交给handle处理
});
handle.Completed += (handle) =>
{
foreach (var obj in handle.Result)
{
Debug.Log(obj.name);
}
// 如果要释放资源,这种方式方便些,因为已经记录了handle
Addressables.Release(handle);
};
2、根据多个信息动态加载多个资源
之前只能通过资源名或标签名加载资源,现在可以多个资源或标签名加载了:
使用Addressables.LoadAssetsAsync的重载函数即可实现:
- 参数1:加载资源的条件列表(资源名/标签名)
- 参数2:每个资源加载完后的回调函数
- 参数3:可寻址合并模式,即资源结果的合并方式,使用
Addressables.MergeMode枚举 - 参数4(可选):资源加载失败时,是否自动释放已加载和依赖的资源。如果为false,则手动管理释放
List<string> strs = new List<string>() { "Cube","Red" };
Addressables.LoadAssetsAsync<Object>(strs, (obj) =>
{
Debug.Log(obj.name); // 每个资源加载结束后的回调函数
}, Addressables.MergeMode.Intersection);
当然这里也是推荐用handle去处理
3、可寻址合并模式
假设上面例子中两个条件Cube和Red,找到的资源分别为[1,2,3]和[1,3,4]:
- None:不发生合并,使用第一组结果
[1,2,3] - UseFirst:使用第一组结果
[1,2,3] - Union:合并所有结果(并集),结果为
[1,2,3,4] - Intersection:使用相交的结果(交集),结果为
[1,3](找出满足所有标签的)
(七)关于资源获取释放的补充
1、简单概述
在之前资源加载的学习中,我们发现资源可以从AssetReference中加载/释放,也可以从Addressables中加载/释放。
获取资源时,若使用AssetReference加载,则可以从assetReference.Asset获取,或从返回的句柄handle.Result中获取。
而使用Addressables加载,则可以从返回的句柄handle.Result中获取。
2、两者关系
那么AssetReference和Addressables有什么关系呢?实际上AssetReference资源加载和释放都是通过调用Addressables的相关API实现的。而assetReference.Asset其实获取的是handle.Result,只是包装成了属性。因此AssetReference可以看作是对Addressables的再一层封装。
3、关于第(三)节的小问题
- 如果看不太懂直接看总结
使用assetReference加载资源后会返回一个句柄handle,之后在加载完成的回调函数中,assetReference.ReleaseAsset()释放资源后,发现assetReference.Asset为null,但是handle.Result还有资源:
AsyncOperationHandle<GameObject> handle = assetReference.LoadAssetAsync<GameObject>();
handle.Completed += (handle) => {
GameObject cube = Instantiate(handle.Result); // 使用资源
assetReference.ReleaseAsset(); // 释放资源
Debug.Log(assetReference.Asset); // 打印 null
Debug.Log(handle.Result); // 打印 Cube (UnityEngine.GameObject)
};
此时就有个疑问,难道handle的资源没释放吗,是因为handle引用了这个资源?
其实不是的,看了源码之后,发现这个AsyncOperationHandle是struct,即值类型,也就是说应该没有资源引用的问题,资源是被卸载了的,然后看了下assetReference.ReleaseAsset()的源码,并且问了AI,这个资源释放大概是这样的流程,不讲太详细,知道会大概会发生什么就行:
- 调用
assetReference.ReleaseAsset()时:- 内部调用类似
handle.Release()方法,释放掉assetReference创建资源时的句柄 - 释放句柄时,资源引用计数-1,然后句柄引用的底层的
AsyncOperationBase类成员变量m_InternalOp会被置空,此时句柄的IsValid()会变成false,表示无效,而使用assetReference.Asset获取的资源其实就是从句柄中获取的资源,因此这时会返回null - 但是回调函数里的句柄目前还不受影响,因为该句柄是值类型,目前这个句柄还保留之前的状态,即底层的
m_InternalOp引用还在,因此此时IsValid()还是true,所以handle.Result可以返回资源。 - 但是到了帧末,
m_InternalOp被销毁,handle.Result就无法获取资源了
- 内部调用类似
4、总结
如果上面看不太懂,那么就看这个总结好了:
使用assetReference.ReleaseAsset()释放资源时:
assetReference.Asset立刻为null,它内部的句柄已经无效- 但当前帧内,资源还在,可以从加载完成回调的
handle.Result获取 - 等到下一帧,底层资源已被彻底销毁,
handle也变为无效状态,资源就完全获取不到了。