oynix

于无声处听惊雷,于无色处见繁花

Unity Addressables 的使用

Addressables是Unity中管理和加载资源的一种管理形式,这里的资源包括所有类型,如预制体、贴图、音效等。同时,还支持动态更新,也就是常说的热更新,大概说说一般用法。

1. 简介

Addressables中,资源以组group为单位进行管理,一个资源归属一个group,一个group中可以有多个资源,一个group会被打包成一个bundle文件,bundle文件可以打包进安装包中,也可以放到远端资源服务器,资源和bundle的关联会记录在catalog文件中,当通过Addressables加载一个资源时,会先查找其所属的bundle文件,或者在本地找到,或者从远端下载,解压加载之后,从中获取所需资源。

关于热更新,是通过一个hash值来控制。生成每个版本的catalog的同时,会生成一个与其关联的hash值,Addressables在初始化时,会对比本地和远程的hash,如果不同,则将远端的hash和catalog文件同步到本地,此时,便会使用最新版本的资源和bundle从属关系的catalog文件。

简单来说,它的原理就是这些,剩下的便是实际操作中的一些说明。

2. 引入

Addressables已被Unity官方纳入,在Package Manager中直接搜索Addressables即可安装使用,目前最新的版本是1.21.8

通过以下菜单路径,可以打开它的主要设置页面,

1
Window -> Asset Management -> Addressables -> Groups

首次打开时,需要点击初始化按钮,之后便会在Assets下看到生成了一个名字是AddressableAssetData的目录,这里面放的就是Addressables本身的一些配置文件,以及各个group的配置文件,

1
2
3
4
5
6
7
8
9
10
11
- AddressableAssetSettings.asset
Addressables自身的配置

- AssetGroups:
这里面是所有group的配置

- AssetGroupTemplates:
group的配置模版

- DataBuilders:
Addressables的运行模式

3. 创建group

点设置窗口左上角的New按钮,便可以看到所有的group配置模版,选择一个便可以使用其创建一个group,等下再说配置模版是什么。如下图,一共有5个group,其中Built In Data是不可修改的,Default Local Group是缺省的group,剩下的3个是我新创建的。

关于如何划分group,可以有多种维度,可以分成两个大group,一个是本地group,随着一起打入安装包中,一个是远端group,用来放一些需要热更新的资源;也可以按照功能、按照页面分组,按需放到本地,或是远端,等等。

4. profile

Addressables中的profile是用来配置bundle文件的构建目录和加载目录,构建目录是针对打包阶段而言的,简言之便是生成的bundle放到哪里,而加载目录是针对运行阶段而言的,指的是程序运行时,从哪里读取需要的bundle文件。

之所以将其单独抽取出来,是为了便于管理,因为可能存在多个维度,比如Local的构建和加载、远程的构建和加载、Android的构建和加载、iOS的构建和加载,等等。

如上图,这是默认的profile配置,Dev是我添加的profile。profile的配置形式为key-value,key的名字有几个默认的,value需要在后面填写,此外,还可以添加新的key-value,有两种可选形式,一种是上图中的Local和Remote,有BuildPath和LoadPath两对值,另一种就是BuildTarget形式,只有一对key-value,这些配置值在打包阶段时都可以读取使用。

使用起来很灵活,对于多阶段(开发、测试、发布),多平台(GooglePlay等),多平台(Android、iOS、Windows等)的多维度而言,可以每个阶段创建一个profile,也可以每个阶段的每个平台创建一个profile,等等,只要配置方式和读取相匹配,即可。

5. group配置模版

既然有了上面profile的说明,那么group的配置就很好说明了,group的配置文件缺省值都是一样的,但是同一个group,在不同情况需要的配置是不同的,比如,发布到Android远端资源服务器是一个配置,而发布到iOS远端资源服务器是另一个配置。所以,为了不用每次创建group时,都按需设置一遍,就提前将可能需要的所有配置都设置成模版,等到创建group时按需选择即可。这些配置文件模版,都放在了前面提到的AssetGroupTemplates目录中。

首次初始化之后,只有一个叫做Packed Assets的配置模版文件。

6. 配置Addressables

上面有提到,在Assets下的AddressableAssetData中,有个叫做AddressableAssetSettings的文件,这便是Addressables的配置文件,点一下便可以在Inspector中看到所有的配置项:

如图,大致提供了10个可配置类别,这里面包括了打包阶段需要的配置,以及程序运行阶段需要的一些配置。绝大多数配置使用缺省值即可满足需求,只有少数几个需要修改配置。一个是profile,需要按需选择。另一个就是Catalog,需要打开其中的Build Remote Catalog,并正确配置Remote的构建和读取路径,这样才可以支持热更新。

7. 配置group

在AddressableAssetData中的AssetGroup下,可以看到创建的每个group都有一个与之名字相同的配置文件,每个group都可单独设置。设置的话,无非就是告诉Addressables这个group打包成bundle后放到哪里,当程序中需要时从哪里去读取,剩下的项使用缺省值即可。

刚刚在group管理窗口中,我们不难发现,对于每个资源后面,还有个叫做Labels的列,意思就是可以为资源设置标签,为资源添加标签前,需要先创建标签,创建的标签对所有资源都可用,没有group限制,同时,一个资源可以挂一个标签,也可以挂多个标签。

配置项中,有个叫做Bundle Mode的选项,当选择Pack Together的时候,挂不挂标签对生成bundle文件没有影响;当选择为Pack Together By Label时,标签便会起作用,它的作用就是按照标签的种类进一步细分bundle文件。

比如,名叫remote的group,Pack Together时只会生成一个remote_all_hash.bundle的文件;Pack Together By Label时,会把下面资源所有的标签相同的资源打到同一个bundle里,挂了A标签的会被打到remote_a_hash.bundle里,挂了B标签的会被打到remote_b_hash.bundle中,挂了A和B标签的,会被打到remote_a_b_hash.bundle文件中。

8. 打包资源

在打包之前,需要先把资源放到group中,点击一下资源,便可以在Inspector最上面看到一个Addressable的复选框,钩上之后就代表将其加到了group中,默认是加到上面提到的缺省group中,复选框后面的输入框里便是这个资源的名字,默认是这个资源的相对路径,也可以手动修改,点击后面的select可以打开group管理窗口,直接拖动资源,就可以为其更改group。

注意,Addressables是根据名字加载资源的,当存在资源名字重复的情况,只会加载第一个,具体是哪个则不可控,所以要避免设置相同名字,一般使用相对路径加文件名,因为同一目录下不可存在相同名字的文件,以此可保证资源名称唯一。

将所有资源都添加到group后,在group管理窗口的Build菜单中,选择New Build即可开始打包,每个group对应的bundle文件会生成到配置的路径下,对于远程的bundle,需要将资源和catalog以及hash文件上传到远端资源服务器。

9. 运行模式

Addressables有3种运行模式,在group管理窗口的Play Mode菜单中可以切换

  • Use Asset Database:直接读取
  • Simulate Groups:直接读取,但模拟加载过程
  • Use Existing Build:使用构建的bundle文件,即真机模式

当时使用Use Existing Build时,必须有构建好的Build。

10. 加载资源

上面一切就绪后,就可以通过Addressables来加载资源,代码很简单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class Loader : MonoBehaviour  
{
private const string LocalImagePath = "Assets/Textures/1.JPG";
private const string RemoteImagePath = "Assets/Textures/2.JPG";

public RawImage localImage;
public RawImage remoteImage;

void Start()
{
StartCoroutine(Load());
}

private IEnumerator Load()
{
var handle = Addressables.LoadAssetAsync<Texture2D>(LocalImagePath);
yield return handle;
localImage.texture = handle.Result;

var handle2 = Addressables.LoadAssetAsync<Texture2D>(RemoteImagePath);
while (!handle2.IsDone)
{
Debug.Log("progress:" + handle2.PercentComplete);
yield return null;
}

remoteImage.texture = handle2.Result;
}
}

其中,handle2显示了加载进度,一般在下载一个大的bundle包时耗时较长,此时显示一个进度条,体验更佳。

不需要的资源,要及时释放,减小占用的内存,传入获取到的资源,或者获取资源时用的Handle都可以,

1
2
3
Addressables.Release(Handle);

Addressables.Release(TObject);

11. 更新资源

前面有提过,热更的过程很好理解,举个例子,一个资源名字为Image1的图片,因为需求变更,换成了另一个图,注意,这里只是换图,资源名不能变。重新打bundle包之后,所在的bundle包名字就变了,同时,还会更新catalog文件,这样当再次请求Image1资源时,就会根据最新的catalog中的对应关系,去新的bundle中加载最新的资源了。

如上图,在第8步打包资源中,选择的是New Build选项,打出来的新的bundle文件,如果选择Update a Previous Build则是在上一次New Build的基础上,生成bundle文件。这两种方式的区别在于,catalog和hash文件的名字,New Build会根据当前时间创建生成新的catalog和hash文件,文件名中包含时间,而Update虽然更新了catalog则是和hash文件里的内容,但文件的名字不变。

这样做的原因在于,无法更改一起打进安装包里的catalog文件,意思就是,这个安装包只认远程资源服务器上固定名字的catalog和hash文件,如果这两个文件的文件名发生改变,则应用端无法检测到。

那么,Addressables是如何做到呢?

在每次New Build时,会生成一个名字是addressables_content_state.bin的文件,这个文件存储了必要的信息,在每次Update时,都是根据这个文件生成的,以此来保证catalog和hash文件的文件名不变。每次Update生成的bundle文件,放到远程资源服务器,名字相同的选择覆盖,剩下的新增。

等到客户端再次初始化Addressables时,发现远程同名的hash文件里的值和自己的不同,就会将远程的catalog和hash都同步到本地,这样就会自动按需加载最新的资源了。

旧版本的Addressables每次Update时还需要手动选择addressables_content_state.bin文件的位置,现在的最新版本,可以配置这个文件的默认的位置了,这样每次New Build时会自动生成一份到该路径下,等到Update时会自动去这个路径下读取。第6步中,有一项就是配置这个路径的,叫做Update a Previous Build,默认的路径在:

1
2
3
4
5
6
Assets/Addressables/[Platform]/addressables_content_state.bin

# Android
Assets/Addressables/Android/addressables_content_state.bin
# iOS
Assets/Addressables/iOS/addressables_content_state.bin

默认情况下,这个文件只会保留一个,每次点击New Build之后,这个文件都会重新生成,覆盖掉已经存在的。

12. 资源重复问题

当一个资源A被group组中的资源引用时,即便这个资源A没有被加到group中,那么A也会被打到被引用的资源所在的group中。当A被两个group中的资源引用时,那么A会被两个包都打进去,也就是会多个group引用,会有资源重复的问题。但是,如果资源A也是被Addressables管理,即也存在于某个group中,那么便不会有这个问题,所有引用A的地方,都会去A所在的bundle包中加载。

换句话说,对于一个通用资源,比如图片、材质等,最好合理分组,并都添加到group中,这样就可以避免被多处引用时所造成的重复存在多个bundle包中。

------------- (完) -------------
  • 本文作者: oynix
  • 本文链接: https://oynix.com/2023/02/746a488dd815/
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

欢迎关注我的其它发布渠道