Unity Addressable Asset System介绍、教程和原理讲解
Unity Addressable Asset System介绍、教程和原理讲解
1. 背景(怎么来的)
Unity Addressable Asset System是官方推出的资源加载和资源管理的一套解决方案。
它使用异步加载的方式加载资源,提供统一的加载接口,不管资源放在哪个目录,不管资源的依赖有多复杂。
传统的资源管理方式严重将组织、构建、加载Asset的逻辑捆绑在一起,比如
- 资源存放在Resource文件,那加载资源必须要用到Resources.Load方法,并且提供资源路径
- 加载ab中的asset时,加载代码需要和构建策略捆绑在一起,先找出资源对应的bundle加载完成后,再加载需要的资源。
- 以路径加载的代码,当资源路径发生改变往往要多次更改代码,这个更改可能涉及多处。
- 资源位于远程,需要写代码管理资源的下载、加载、卸载和缓存。
基于以上种种不便,Unity Addressable Asset System(下文称AA系统)的一个重要好处就是解决以上种种痛点。
2. 特点(有何用)
- 快速迭代:解耦组织、构建、加载Asset的逻辑,通过[address]和统一接口加载资源,不管资源放是否来自Resources目录,或者将来重命名或者路径变更,不管资源如何构建,都不用再修改加载代码。
- 依赖管理:当加载一个资源的时候,AA系统会先加载完这个资源的所有依赖,然后再返回这个资源。
- 内存管理:自动管理资源加载卸载的引用计数,并提供工具探测内存泄露问题。
- 内容打包:当资源变更时能够有效打包资源,更容易管理本地和远程打包资源和有效减小应用大小。
- 配置文件:统一管理资源如何打包的变量,避免配置分散多处修改问题。
3. 概念
Group
group定义一组在构建时可处理的Addressable Asset集合。
默认有两个group,如上图:
- Built In Data。内置的group,包含Resources文件夹,这个group不支持修改。
- Default Local Group。在Inspector标记Addresables的Asset会默认添加进这里,支持修改,包括改名和添加Schema。
Group Schema
group schema定义一组分配给group在构建使用的数据,可视为配置数据,决定group中Asset如何打包、打包粒度等等。
点击group,查看这个group中添加的schema,和schema的配置:
包括三种Schema:
- Content Updage Restriction 主要决定ab可不可以更改
Update Restriction:决定改组内容打包成ab后,可不可以动态更新。can change post release:允许发布后变更;cannot change post release:不允许发布后变更。 - Content Packing & Loading 决定打包方式和加载的配置,包括构建目录、压缩、打包模式等等
下面是几个重要的配置,鼠标移动到变量名上可以显示该配置作用介绍:
Build Path:打包后的发布路径。可分为本地和远程路径。
Load Path:加载ab的路径,可分为本地和远程路径。
这些的路径变量来自Profiles中定义的变量,Profiles的解析请看下面。
Compression:支持三种压缩方式,包括不压缩、LZ4和LZMA。
Include In Build:勾选才会将该group纳入构建进程中。
Use Asset Bundle Cache:勾选会将加载的ab加进缓存目录中,远程bundle才需要勾选。
Bundle Mode:打包模式,分别是Pack Separately单独打包,Pack Together全部达成一个包,Pack Together By Label根据label划分打包。
Bundle Naming:bundle的命名规则。
Asset Provider:加载和解析Asset的类,不同的资源类型对应不同的provider。
Asset bundle Provider:加载和解析Asset bundle的类,默认为AssetBundleProvider。 - Resources and Build In Scenes 是否将Resources和Build Setting Scenes包含进来打包
Include Resources Folders:是否将Resources文件夹下的Asset包含进group。Resources下的Asset会打包进包体,一般不需要勾选。
Include Build Setting Scenes:是否包含Build Setting添加的Scenes。
Addressable Asset Settings
作为AA系统全局的配置文件,在初始化的时候生成。以下介绍几个重要的变量:
属性介绍:
Disable Catalog Update on Startup:勾选则在AA系统初始化时候不检查catalog文件更新。
Build Remote Catalog: 勾选则生成远程ab的catalog文件,需要配置remote catalog文件的build path和load path。
Send Profiler Event:勾选则开启分析器。
Unique Bundle IDs:给bundle分配独一无二的ID。因为catalog文件可以在初始化以外的时候更新,当旧资源已经在内存中时,但unity无法下载两个同internal name的资源,这种情况资源无法及时更新。要么先卸载重新资源重新下载,要么分配给bundle单独的id来区分。开启这个选项,当资源更新时不仅仅是更新该资源,也会更新依赖链,带来一定的性能花销。
Contiguous Bundle:基于源资源的排序来排序Bundle,可以加快Asset加载速度。
Build and Play Mode Scripts
Use Asset Database (fastest):加载源文件模式,不用打ab和分析。
Simulate Groups(Advanced):模拟组模式,以不打ab方式来分析内容的布局和依赖。用类似加载ab的方式,通过ResourceManager从AssetDatabase加载。这个模式用于模拟加载策略,调整和平衡内容的分布。
Use Existing Build:使用构建好的ab加载,需要提前构建好ab。
Default Build Script:默认的构建脚本。
可选的Build Script都是ScriptableObject的Asset。假如需要扩展,可以新建脚本,继承IDataBuilder接口。添加可以选择左下角+,添加Build Script Asset。
前三个Play Mode Script分别对应Assetables Groups-》Play Mode Script下的三个模式。
Default Build Script对应Assetables Groups-》Build-》New Build-》Default Build Script。
Asset Group Template:group模板,Assetables Groups面板下Create→Group下,或者group右键菜单可以选择。在Project面板目录上右键菜单Create-》Addressables-》Group Templates-》Blank Group Template可以创建模板。
4. 使用流程(怎么用)
step1 安装
到Package Manager安装以下两个package:
- Addressables,基本包,本文章使用的版本是1.16.15
- Scriptable Build Pipeline package,依赖包
step2 初始化
安装包后,通过菜单栏Window->Asset Management->Addressables→Groups 可以打开AA系统的管理面板
未初始化会出现初始化按钮,点击后Asset目录下会出现一个AddressableAssetsData目录,存放着一些配置文件。
step3 标记可寻址
需要将Asset标记为Addressable,才能纳入AA系统的管理。标记为Addressable Asset有两个途径:
通过Inspector。勾选,会将Asset添加进默认的group(Default Local Group),以相对Assets目录的地址作为AA系统寻找该Asset的【address】,这个地址支持修改。
- 拖动Asset进group。
step4 配置远程打包路径
unity官方推荐新建一个profile为hosting service所用,配置正确的build path和load path,可以上可以填上Hosting Service 变量:
并在需要在AddressableAssetSettings上开启构建远程catelog,选用Editor Hosted Profile,选择build path 和 load path:
Addressables Group面板上选择Editor Hosted Profile
最后在group Inspector面板中选择内容构建输出的目录,若为远程资源则选择RemoteBuildPath。
这就配置完了Hosting Services的使用流程。step5 打包
前面已经了解到Group Schema控制打包方式,主要用到两个Schema:content update restriction 和 content packing & loading
打出的ab类型可分为远程和本地,静态和非静态(can change post release为非静态)。两两结合可以分为四个组,每个组下各包含两个资源:
点击Addressables Groups菜单栏build-》new build-》default build script,开始构建。
ab构建完成后,可以看到本地ab和远程ab分别打包到了各自的目录:
local和remote各自有一份catalog json文件,remote多了一份hash文件,记录catalog文件的hash值,用于判断catalog文件的更新。
catalog文件里记录了address到资源的映射关系,用于定位资源和决定如何加载资源(where和how),每当资源发生变动后构建完成时,catalog文件重新生成。step6 更新
再看看更新资源后,各组间的资源打包后如何组织?
每个组更改第一个资源后,点击Tools-》check for content update restriction,检查更新
这时候会打开窗口选择addressables_content_state.bin文件,文件位于Assets/AddressableAssetsData/[platform]下,发布哪个平台选择哪个平台,一般是Android和Windows。
为什么要选择这个bin文件?
使用过BuildPipeline构建ab都知道有个manifest文件记录资源间依赖关系,AA系统会将资源间依赖关系记录进这个bin文件,作为对比找出更改的资源。
这步过后,会检查出更改的资源:
为什么所有资源都有更改,只检查出两个有更新,并且两个资源都来自静态组?
unity推荐打包内容分两种类型:
- can change post release:期望更新的动态内容。一般打成小包,以控制精准更新,减少包中其他没有更新的资源的不必要更新。
- cannot change post release:不期望更新的静态内容。一般打到一个很少更新的大包中。
addressables_content_state.bin文件只是记录了静态资源的hash和依赖关系,动态资源的依赖关系动态计算没有记录到bin文件中,所以check for content update restriction只是作用于static组,对nonStatic组没有作用,也即是对设置了can change post release的组没有作用。
点击apply changes按钮,groups会出现变化:
static组中更改的资源会移动到一个新组content update group:
content update group属于remote non static组
点击build-》update a previous build,选择刚才的bin文件,增量更新
对比发现:
catalog json和hash文件会更新,被重写。
静态组资源bundle并不会被重写更改或者重生生成,静态组中更改的资源会被移动进content update group重新打包到remote目录。
非静态资源组的bundle包虽然不会被重写,但是会被重新生成,hash值也会更新。
旧的非静态资源组bundle会在catalog文件中失去连接和记录,成为无效资源(这个无效资源会保留在用户设备上成为垃圾资源,应该要有个机制及时清理)。
note:每次full build,bundle和catalog文件会重新生成,localBuildpath目录会清空,但是remoteBuildPath目录并不会清空,新生成的资源会跟旧资源混合。
用一张图来阐述官方推荐的AA系统资源更新生命周期:
5. 工具
Profiles
通过菜单栏Window->Asset Management→Addressables→Profiles 或者 group面板的菜单栏tools→Profiles 打开Profiles面板。
这个是AA系统用来管理一组组变量的解决方案。
默认有以上5个变量,见名知义。
从变量的值中发现有两种符号:[]和{}。
[]:定义编辑时的变量。比如UnityEditor.EditorUserBuildSettings.activeBuildTarget,在编辑时就可以得到的值。
{}:定义运行时的变量。比如UnityEngine.AddressableAssets.Addressables.RuntimePath,在运行时才知道值。
左上角可以添加变量和Profile,在变量上右键可以删除和更名变量。
在AddressableAssetSettings Inspector上可以选择使用哪一份Profilles,这里使用Default Profiles。
Hosting Services
AA系统提供本地资源服务器,模拟远程资源加载。被设计的目的是为了在测试打包内容时加快迭代速度,不需要另外写一套远程资源下载加载代码。它能够为本地或者远端的客户端提供内容。
Addressables Groups-》tools-》Hosting Services-》Create-》Local Hosting创建本地资源服务器:
勾选Enable会分配端口号启动服务器,也可以指定端口号。
开启后,hosting services会以每个group的build path为资源目录提供内容。
内存管理memory management
AA系统通过Addressables API来管理资源的引用计数,提供Event Viewer工具监测资源对象被引用和内存占用情况。
Addressables常见的资源加卸载API:
- Addressables.LoadAssetAsync & Addressables.Release
- Addressables.LoadSceneAsync & Addressables.UnloadSceneAsync
- Addressables.InstantiateAsync & Addressables.ReleaseInstance
写段测试代码观察资源的引用情况: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
30
31
32
33
34
35using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
public class Loader : MonoBehaviour
{
private void OnEnable()
{
InstByLoadAssetAsync("Assets/Local_static 1.prefab");
InstByLoadAssetAsync("Assets/Local_non_static.prefab");
InstByLoadAssetAsync("Assets/Remote_static 1.prefab");
InstByLoadAssetAsync("Assets/Remote_non_static.prefab");
InstByLoadAssetAsync("Assets/Local_static.prefab");
InstByInstantiateAsync("Assets/Local_static 1.prefab");
InstByInstantiateAsync("Assets/Local_non_static.prefab");
InstByInstantiateAsync("Assets/Remote_static 1.prefab");
InstByInstantiateAsync("Assets/Remote_non_static.prefab");
InstByInstantiateAsync("Assets/Local_static.prefab");
}
private void InstByLoadAssetAsync(string address)
{
AsyncOperationHandle<GameObject> handle = Addressables.LoadAssetAsync<GameObject>(address);
handle.Completed += OnLoadDone;
}
private void OnLoadDone(AsyncOperationHandle<GameObject> handle)
{
Instantiate(handle.Result);
}
private void InstByInstantiateAsync(string address)
{
Addressables.InstantiateAsync(address);
}
play mode script选择use existing build,因为use asset database 模式没有统计资源依赖数据(实际上只有fps和Monoheap),线上是ab加载模式所以数据更加准确。
通过tools-》Event Viewer打开面板:
- FPS: 帧率
- MonoHeap:内存占用
- Event Count:每帧事件数
- Instantiation Counts:通过Addressables.InstantiateAsync实例化的资源
- Resource Request:每个资源的引用计数
注意:当资源的引用计数为零时,并不代表该资源被回收,比如AB中的a资源引用未0,但AB自身的引用不为0,AB未被释放所以a也未释放。
6. 代码层加载资源过程(如何实现)
第一步初始化,加载catalog文件,解析出资源定位信息。
接下来的过程可以简单分为两步:寻址和加载。
- catalog文件会被加载并解析得出所有的Locator对象,Locator对象包括资源及其依赖的信息。
- 遍历所有的Locator对象,匹配address key,找出对应的Locator,Locator下包含了资源的Location对象,这个对象管理资源地址及其依赖信息。
- ResourceManager类根据Locations对象上记录的ProviderId找到Provider对象,调用返回自身和其依赖的异步操作对象AsyncOperation,执行异步操作加载资源通过回调函数返回。
Location对象:
可以看到上面记录的InternalId,Dependencies、ProviderId、LocatorId等信息。
catalog文件中记录有LocatorId,能被它解析出map信息:
参考资料:
Addressables官方文档
Addressable Asset System