大家好,我是阿赵。这次学习一下Unity的Addressables资源热更新用法。
一、 什么是Addressables
使用过Unity引擎做项目的朋友应该对AssetBundle都不陌生。
AssetBundle是长期以来,Unity项目资源热更新主要手段。AssetBundle可以把Unity项目里面的资源打包成ab包,我们需要做的事情有:
1、编辑器打包资源
- 指定压缩方式和打包方式
- 给主资源设置assetBundleName
- 判断哪些依赖需要打包,也设置assetBundleName
- 打包AssetBundle
- 记录AssetBundle文件的md5生成配置文件
- 把AssetBundle文件放到服务器
2、 运行时加载资源
- 下载配置文件对比md5确认哪些文件有变化
- 通过资源名找到具体的文件路径,判断文件是否需要下载
- 判断文件是否存在依赖,依赖文件是否需要下载
- 下载文件到本地指定路径
- 加载资源使用
- 管理资源计数器和依赖计数器
- 判断资源是否还有在使用,把没用的资源卸载,并相应减去计数器。
Addressables的官方介绍是:
Addressable Asset System
允许开发者通过资源的地址来请求资源。资源(例如预制件)标记为“可寻址”后,就会生成一个可从任何地方调用的地址。无论资源位于何处(本地还是远程),系统都会找到资源及其依赖项,然后将其返回。
Addressables,我个人感觉是AssetBundle的一个升级版本。先说一下它的好处:
1、 可视化的界面操作和打包
2、 可视化的资源列表分组关系
3、 自动处理资源映射和依赖关系
4、 打包的时候会自动生成哈希文件和配置文件
5、 自动对比文件是否需要更新
6、 加载使用的时候直接加载文件名即可得到资源
7、 加载使用的时候有自动的依赖计数器
8、 卸载资源的时候,会自动减去依赖的计数器
总结一句话,就是Addressables是Unity官方推出的一套AssetBundle管理框架,包含了我上面说的本来我们使用AssetBundle时应该做的各种加载卸载判断和工具链,就不用自己写了。
二、 Addressables的用法
1、 安装Addressables插件
Addressables是一个插件,需要先下载才能使用。
打开Package Manager,然后搜索Addressables,就可以找到这个插件。
需要注意的是,不同的Unity版本的Addressables版本也不一样,比如:
Unity2019:

Unity6:

选择Install安装之后,就可以使用了。
如果安装正确,在Window——Asset ManageMent菜单里面会找到Addressables的选项。不同版本的Addressables菜单也不一样,比如2019的是这样的:

Unity6的是这样的:

可以看到,新版本的Addressables反而菜单少了。下面我会用Unity6的新版本Addressables使用。
为了接下来的测试,我先准备了一个小小的例子。
准备了2个模型预设,一个是立方体Cube,一个是球体Sphere:


然后这两个模型都是使用同一个材质球,叫做cubeMat,然后都是使用同一张贴图,叫做uv。立方体上面挂了一个RotaSelf的脚本,会让模型自己自转。
然后建了个场景叫做TestScene,里面把立方体和球都摆上去。
接下来就用这个有共用资源的模型预设、还有场景,来测试一下Addressables的用法。
2、 编辑器打包资源
1. 设置Addressable

选择我们想打包的资源,会发现在安装了Addressables之后,在Inspector面板上会多了一个Addressable的选项。
把它勾上之后:

会出现一个资源路径输入框,默认的内容就是当前资源的路径。
这个就是我们需要加载资源时用到的资源名称了,这个很重要,如果不喜欢默认的名称,可以按照自己的想法去修改。
在资源路径下面有一个Group组的选项:

有一个默认值Default Local Group。这个组也是很重要的,关系到打包之后,哪些资源会打成同一个包。
接下来,我把剩下几个资源,包括Sphere、cubeMat和uv都勾上Addressable,把TestScene场景也勾上。

2. Addressables Groups
通过菜单Window——Asset ManageMent——Addressables可以找到Groups菜单

或者随便找一个勾选了Addressable的资源,点击Select按钮

都可以打开Groups菜单:

如果之前完全没有使用过Addressables,这个菜单会是空的:

可以点击中间的Create Addressables Settings按钮,生成对应的文件夹:

这是Addressables系统很重要的一个菜单,刚才设置过Addressable的资源,都会列出在这里。然后可以看到,现在它们都是属于Default Local Group这个组的,然后Labels标签都是空的。
选择这个组,会看到在Inspector面板上出现了这个组的属性:

接下来我新建几个组,让资源分类。

选择New菜单,看到可以新增一个Packed Assets

然后右键点击它,可以给它命名。
我根据需要先设置几个组:

然后把刚才在默认组里面的资源拖到对应的组里面:

现在的情况是这样:

同一个组里面的资源会打包成一个bundle,所以这里需要根据自己的实际情况去规划。我这里只是为了展示,所以就按照用途去简单分类了。
3. Label标签
接下来看看后面的Labels。

这个标签的作用是可以把某些资源给标记成一类,加载的时候可以一起加载。

默认只有一个default的标签可以选择,可以通过下面的Manage Labels来新增标签。

点击加号可以添加新的标签:

比如我这里添加一个level1的标签,代表这些资源都是第一关需要用的。
这时候,就可以回到Groups界面,给资源分配标签了,比如我可以把某些资源勾上level1标签:

现在标签已经设置好了:

4. 资源加载方式

在Addressables Groups界面上有一个Play Mode Script的选项。这个选项代表的含义是在编辑器运行的时候,使用哪种资源加载方式来测试功能。
这个功能在2019的旧版上面是有3个选项的:

但在最新版,就变成只有2个选项了:

就以最新版来理解吧,比较简单一点,因为只有2选1:
(1) Use Asset Database
简单理解,就是使用本地的资源进行测试,在写测试代码加载资源的时候,对应的是编辑器里面的原始资源。
(2) Use Existing Build
简单理解,就是使用打包出来的Bundle进行测试,在写代码加载资源的时候,对应正式环境里面的本地资源和远程资源来加载。
如果下面我们要测试打包的资源是否能正常加载,我们就应该选择第二个选项Use Existing Build。
5. 打包资源
(1) 打包测试
接下来需要把设置了Addressable的资源给打包成bundle了。

在Addressables Groups界面,有一个Build的选项,第一次打包可以选择New Build——Default Build Script。
如果没有报错,成功打包后会出现一个Addressables Report的界面:

这里显示的是之前打包的记录,由于我之前已经打包过多次,所以这里会显示多次结果。
注意看这两项:

这里代表的是打包的时候,本地资源和需要放到网络的资源分别的打包路径。
由于现在还没设置远程打包,所以所有资源都会打在local本地资源,它的路径是在Library/com.unity.addressables/aa/目标平台。
所以我们可以先去看看这个目录:

可以看到这里面有很多bundle文件,其中我们之前设置了Group的prefab、mat、tex2d和scene都在这里找到了。然后还有一些事Unity内置的资源,在unitybuiltinassets里面,还有一些mono脚本,在monoscripts里面。
(2) 设置远程打包资源
先来看看Addressable Groups里面的属性:

每一个bundle包都可以指定它是否需要放到网络的

里面的选项有Local和Remote两种。
如果选择了Local,那么打包将会出现在刚才Library文件夹里面,然后在打包的时候,会自动拷贝到StreamingAssets文件夹里面,变成了包内资源。
如果选择了Remote,那么在打包的时候,会把资源根据设置好的远程路径打包,这些资源不会出现在StrreamingAssets文件夹里面,在使用这些资源的时候,会通过网络下载。
(3) 设置打包选项

选择Settings,会跳转到:

这个设置是可以有多组选择的,默认只有一组Default

所以我们可以新增几组不同用途的,点击Manage Profiles按钮:

在Addressables Profiles页面新增一个Profile

然后重命名,比如我这里命名为azhaoOnline:

接着右键,选择SetActive,这样这个Profile就是正在使用中了。

我们可以根据需要新增多几个Profiles,然后对应不同的平台或者发布情况。
接下来可以配置我们新建的这个Profile,

Remote.BuildPath是生成需要放在网络上的热更新资源的目录,默认是ServerData/发布的平台,比如我发布的目标平台是PC的,那么目录就会是ServerData/StandaloneWindows64
Remote.LoadPath是读取资源的网络地址。Addressables为了让加载的时候不需要写更多代码,所以连这个远程地址都是配置的,比如我现在在本地搭建了一个服务器,然后把地址放在服务器里,那么这里读取的路径就应该是:
http://127.0.0.1/addressable/StandaloneWindows64/

再次回到Settings,会看到Profile In Use会变成:

接下来,我们需要勾选2个地方:

Build Remote Catalog:生成远程文件的Catalog文件,Catalog文件就相当于我们自己做AssetBundle管理时的对比更新的配置文件
Enable Json Catalog:生成json字符串内容格式的配置文件。
然后再把Build&Load Paths改成Remote

这样,在打包和加载的时候,就会使用我们建立的Profile里面的Remote设置了。
回去看看Addressables Groups界面:

如果Group设置了Remote,就看到下面的BuildPath和LoadPath都变成了Profile里面设置的地址了。
(4) 测试打包
我这次先只设置了prefab一个组为Remote,试试打包。

由于之前我已经生成过一次了,所以这次只需要更新就行了。所以选择Update a Previous Build。
打包完成后,看看ServerData文件夹,可以看到有对应平台的文件生成了:

由于我刚才只把prefab组设置了Remote,所以这里只生成了prefab 的bundle。bundle文件的名字好像很长,其实可以理解成之前AssetBundle的使用哈希作为文件名的做法而已。
除了prefab的bundle文件,还有另外2个文件,第一个是hash文件,里面有一个md5码,用于判断文件是否需要更新。然后是json文件,里面记录了Addressables生成的bundle的具体情况。
这两个Catalog文件,在热更新的过程中就是用于对比和查找资源的作用了。
再回头看Library里面的Local文件地址:

发现prefab的bundle已经不再在本地文件夹里面了。
到这一步,打包算是成功了。接下来我把例子里面的几个文件全部都设置了Remote,然后打包,并把生成的bundle文件、hash文件和json文件拷贝到我本地的wamp服务器上面。

3、 运行时加载资源
打包完之后,就可以尝试用代码加载这些网络上的资源了。在写代码之前,必须确认2个事情,保证现在系统是读取网络资源而不是本地资源:
第一个是Play Mode Script里面选择了Use Existing Build

第二个是设置里面Build &Load Paths选择了Remote。

接下来开始测试加载:
1 加载单个文件
先来试一下加载Cube。
加载的代码大概是这样:
AsyncOperationHandle<GameObject> handle = Addressables.LoadAssetAsync<GameObject>("Assets/res/Cube.prefab");
handle.Completed += (handleResult) =>
{
if (handleResult.Status == AsyncOperationStatus.Succeeded)
{
handleCube = handleResult;
Instantiate(handleResult.Result);
}
};
说明:
Addressables加载资源的API很简单,就是LoadAssetAsync,指定需要加载资源的类型,然后输入给资源Addressable打勾时的资源名字,就可以把资源加载出来了。
看着代码好像很复杂,是因为使用Addressables基本都是异步加载,原因是你不知道你这个资源是在本地还是在网络。而异步加载就需要一个异步加载的Handle,这个Handle很重要,因为卸载的时候也是要用到它的,所以我在前面声明了一个handleCube的变量把这个Handle存起来。
给这个Handle添加一个Completed的回调,意思就是在加载资源完成了之后,会异步回调完成方法,回调的参数里面的Result就是我们想要的资源对象。我们可以在回调方法里面写需要怎样用这个资源,比如实例化GameObject。
在这个过程中,我们是没有关心过这个Cube资源依赖了其他的资源,比如这个Cube是依赖了一个材质球和一张贴图,这里都不需要去获取依赖或者加载依赖。只需要直接获取主资源,剩下的事情Addressables就会自动做完,包括了查找依赖、下载依赖,读取到内存,返回可以使用的资源。
所以这时候,Cube已经加载出来了:

如果加载的不是模型,而是资源,比如贴图:
var handle = Addressables.LoadAssetAsync<Texture2D>("Assets/res/uv.png");
handle.Completed +=(handleResult)=>
{
meshRender.material.mainTexture = handleResult.Result;
};

成功的加载出贴图,并赋予给了想要的材质。
2. 按标签加载文件

还记得上面设置资源Group的时候,我给一部分资源设置了level1的标签,我们可以根据这些标签来选择下载资源。
var handle = Addressables.LoadResourceLocationsAsync("level1");
handle.Completed += (handleResult) =>
{
if (handleResult.Status == AsyncOperationStatus.Succeeded)
{
var locations = handleResult.Result;
foreach (var location in locations)
{
Debug.Log(location.PrimaryKey);
if (location.ResourceType == typeof(GameObject))
{
var subHandle = Addressables.LoadAssetAsync<GameObject>(location.PrimaryKey);
subHandle.Completed += (subHandleResult) =>
{
Instantiate(subHandleResult.Result);
};
}
}
}
};
说明:
通过 Addressables.LoadResourceLocationsAsync可以获得关于level1标签的所有资源列表,在回调函数里面得到的是一个locations列表,里面包含了设置了level1标签的所有资源的情况,但这只是资源的信息,并不是资源本身。
所以根据locations里面的每一个location,判断是否我们需要的类型,然后做出相应的处理。比如我的例子里面就是一线判断资源是否GameObject,如果是,则通过正常的LoadAssetAsync去异步加载这个资源,并且实例化。
由于我打印了每个location的PrimaryKey,所以可以检查一下,是否是我们之前设置了level1标签的那些资源:

3. 加载场景
加载场景的API是:
Addressables.LoadSceneAsync
参数直接输入场景的Addressable名字就行了:
var handle = Addressables.LoadSceneAsync("Assets/Scenes/TestScene.unity");
handle.Completed += handleresult =>
{
handleScene = handleresult;
};
这样在handle的Completed回调时,场景其实已经加载完了,我增加一个handleScene的存储,只是为了后面的释放。
4. 卸载测试
卸载使用的API是
Addressables.Release
参数就是之前通过Addressables.LoadAssetAsync或者Addressables.LoadSceneAsync加载时返回的handle了。
比如我在加载完Cube之后,把它给释放了:
Addressables.Release(handleCube);
这时候,会看到:

这是因为调用Addressables.Release,会把该handle加载的所有资源的引用减1,当1个资源的引用都为0时,就会被释放掉。这个Cube,包括它使用的材质球和贴图,在Release之后,都没有引用了,所以虽然场景里面还存在这个Cube,但实际上资源已经被释放掉了。
在释放掉之后,再次加载一份新的进来,会这样:

旧的那份依然是丢失的,但新读取的那份是完整的。
下面看看这种情况,我加载了Cube和Sphere,然后再把Cube给Release了:

这时候发现,2个物体的材质和贴图都是没有丢失的。这是因为虽然Cube把依赖的材质和贴图的引用减一了,但Sphere上面还有它们的引用,所以材质球和贴图这时候是不会被释放的。如果把Sphere也Release了,那么它们才会全部被回收。

这一个过程对于使用AssetBundle的朋友应该有点似曾相识的感觉。AssetBundle的unload也会存在这样的情况。不过也有点不一样。AssetBundle的unload(true),会把该AssetBundle直接卸载,在内存里面的资源也清空,所以会出现正在使用在场景的资源也丢失了。但Addressables在这一点上不太一样,它不会直接就强制释放掉所有资源,而是会判断是否还有引用。释放了之后再读取进来,变成了2份不同的内存,这一点就和AssetBundle的unload(false)有点类似。
5. 动态修改url
在使用了Addressables这么久,我们都是直接通过API读取资源,但资源是从哪里下载的,这一点似乎没有太多的感觉。
这是因为我们已经把下载的地址设置好了:

我们可能还要根据不同平台,设置更多的Profile,对应Android、iOS等,然后通过这些Profile来打包,生成对应平台的资源。
如果需要动态修改这个地址,我暂时是没有找到直接能改根目录url 的API,但可以通过一个Addressables.WebRequestOverride方法来修改url,先看看这个是怎样用的:
void Start()
{
DontDestroyOnLoad(gameObject);
Addressables.WebRequestOverride = EditWebRequestURL;
}
private void EditWebRequestURL(UnityWebRequest request)
{
Debug.Log(request.url);
}
我这里只是简单的打印一下需要下载的地址:

可以看到,加载资源时,第一次会先下载hash文件,对比释放有差异,然后再下载需要的主资源和依赖资源。
打印的request.url,是可读性的,除了可以打印出来看,还可以修改。所以这个时候,我们可以通过replace等手段,把url改成自己想要的,然后再赋值回去。
6. 判断本地是否有文件
想知道本地的资源下载到哪里了,可以使用这个方法:
void Start()
{
DontDestroyOnLoad(gameObject);
Addressables.InternalIdTransformFunc = InternalIdTransformFunc;
}
private string InternalIdTransformFunc(IResourceLocation location)
{
if(location.Data is AssetBundleRequestOptions)
{
var path = Path.Combine(Addressables.RuntimePath, "Bundles", location.PrimaryKey);
Log("path:" + path);
Log("internalId:" + location.InternalId);
if(File.Exists(path))
{
return path;
}
}
return location.InternalId;
}

在调用Addressables的加载方法时,会打印出path和internalId,可以看到,path就是本地的文件路径,可以判断文件是否存在,而InternalId就是远程上的下载地址了。
三、 个人对Addressables的看法
通过对Addressables的简单学习,可以总结出Addressables的几个特点:
1、 不用使用者考虑依赖问题,只关心需要使用的资源的加载。
2、 不需要使用者考虑网络下载、对比差异等问题,打包后扔服务器就行了。
3、 资源的设置有完整的界面工具可以操作。
其他方面,比如资源加载之后怎样保存在内存,什么时候卸载,这些问题还是需要使用者自己去规划的。
阿赵我对Addressables的看法是这样的:
1、 Addressables代表了Unity对于AssetBundle使用的一种思路。
资源该怎样分组、怎样分类,怎样判断它们之间的依赖。然后下载的时候,使用怎样的加载策略去加载资源。资源用完之后,怎样去卸载和调整计数器。这些东西,虽然在Addressables里面都不需要使用者去操心,是自动完成的。但实际上也是可以给我们一个思路的参考。
2、 Addressables的适用群体
Addressables适合什么人使用呢?当然所有人都可以使用Addressables。我个人觉得,Addressables适合一些定制性不太强的项目,在一般通用的情况下,Addressables可以解决大部分的问题,对于团队来说,不需要重新写一套资源框架,是减少了很多工作量的。
3、 Addressables的一些缺点
如果说Addressables有什么缺点,那么应该就是灵活性稍微差点。这种问题往往在通用型插件里面都经常会出现。为了让插件工具使用起来更简单,插件本身往往会制定了一些规则,使用者根据规则来使用就行了。比如资源的分组、远程URL的设置等等,都是Addressables固定好的规则。如果按照规则内使用,会觉得很好用。
这个时候如果想特殊定制一些规则,比如可以同时使用多个CDN进行切换下载,比如在Group和Label之外再组织多一层归属。要么就是使用插件本身提供的功能迂回的实现,要么就是实现不了。
所以对于定制性很强的项目,使用者都不太喜欢用第三方的工具,而是喜欢自己写适合项目自身的工具链。
总的来说Addressables是一个比较不错的插件,可以省去很多麻烦,觉得适合自己使用的,不妨可以试试。
241

被折叠的 条评论
为什么被折叠?



