# 使用 Addressable 进行资源按需加载
对于小游戏情况而言,需做到即点即玩,即尽可能缩短玩家进入首场景之前的等待时间,对于Unity WebGL转换的小游戏启动耗时,资源下载通常是贡献最大的部分。本文将介绍Addressable资源管理系统 (opens new window)进行资源的按需加载。
# 一. Addressable概述
Addressable是由Unity官方在2019版本,正式发布的一种资源管理系统,Addressable是Unity官方对AssetBundle的一种封装,目的是为了填补一些AssetBundle缺失的功能,封装一些资源加载的固有功能,从而降低资源加载的使用门槛和后期维护所消耗的精力。
Addressable提供了以下能力:
- 低使用门槛:使用Addressable在开发前期就进入快速开发的阶段,使用任何你喜欢的资源管理技术,你都能快速的切换来Addressable系统中,几乎不需要修改代码。
- 依赖管理:Addressable系统不仅仅会帮你管理、加载你指定的内容,同时它会自动管理并加载好该内容的全部依赖。在所有的依赖加载完成,你的内容彻底可用时,它才会告诉你加载完成。
- 内存管理:Addressable不仅仅能记载资源,同时也能卸载资源。系统自动启用引用计数,并且有一个完善的Profiler帮助你指出潜在的内存问题。
- 内容打包:Addressable系统自动管理了所有复杂的依赖连接,所以即使资源移动了或是重新命名了,系统依然能够高效地找到准确的依赖进行打包。当你需要将打包的资源从本地移到服务器上面,Addressable系统也能轻松做到,几乎不需要任何代价。
相较于AssetBundle而言,Addressable有以下优势:
- 可视化UI界面,关于资源的分组及其他情况更加直观
- 自动依赖管理,相较于AssetBundle手动保证某资源的依赖需提前加载进内存,Addressable在开发时无需关注资源依赖情况,当作所有依赖都已经被加载进内存处理,在运行时会自动检测其依赖并自动加载,从而保证每次资源加载都能成功。
- 封装网络请求接口,相较于AssetBundle需要手动实现远程bundle的网络请求,Addressable在开发时无需关注资源在本地或者在cdn,统一调用LoadAssetAsync等异步接口处理处理,在运行时会自动下载。
- 资源分析工具,相较于AssetBundle需要手动检测多依赖下的资源重复打包问题,Addressable提供了资源检测工具自动分析重复情况。同时还提供Event Viewer来检测资源加载情况,供优化使用。
如今而言,Addressable是现在游戏作为分包的首选方案。
# 二. Addressables的加载流程
Addressable一般在构建之后会出现几个文件
- catalog.json:资源表,存放了addressable的所有资源
- setting.json:运行时addressable参数
- {bundle_name}.bundle:bundle包文件
对于所有Asset都是由ResourceManager来管理的
对于每一个资源都分别继承了IResourceLocation和IResourceProvider
IResourceLocation
:包含了该资源的所有信息,包括依赖信息,location哈希等。IResourceProvider
:包含资源的加载和卸载方式,主要是为了统一本地和远程的区别。
在刚进入游戏时,Addressable会做以下操作:
- 加载
Setting.json
,并根据哈希值判断catalog.json
是否有更新 - 加载最新
catalog.json
文件,并替换本地catalog缓存 - 解析
catalog.json
文件,读取其中所有的IResourceLocation
信息,并保存到ResourceManager
中
**一次运行时资源加载会做以下操作:*8
- 通过
IResourceLocation
获取该资源的依赖信息及文件hash信息 - 通过
IResourceProvider
获取该资源所属bundle包的加载方式 - 从cdn下载bundle包,下载完成后再加载
- 将加载的资源赋值给
AsyncOperationHandle.Result
并设置AsyncOperationHandle.Statue
并执行Completed事件
# 三. Addressables简单例子
AssetReferences 支持拖放和对象选择器分配,这可以使它们更方便地在 Editor Inspector 中使用。
public class LoadAssetScript : MonoBehaviour
{
public AssetReference somePrefab;
private void Start()
{
somePrefab.InstantiateAsync().Completed += (obj) =>
{
// 加载完成回调
if (obj.Status == AsyncOperationStatus.Succeeded)
{
Debug.Log("successful");
}
};
}
}
//协程写法
public class LoadAssetScript : MonoBehaviour
{
public AssetReference somePrefab;
private IEnumerator spawnSomathing()
{
var hanle = somePrefab.InstantiateAsync();
while(!handle.IsDone) {yield return null;}
// 加载完成回调
var gameObject = hanle.Result;
}
}
简单的异步加载接口Addressables.LoadAssetAsync
例子
void Start()
{
StartCoroutine(Load());
Addressables.LoadAssetAsync<GameObject>("Assets/Image.prefab").Completed += (handle) => {
if(handle.Status == AsyncOperationStatus.Succeeded){
Instantiate(handle.Result);
}
};
}
//协程写法
IEnumerator Load()
{
var handle = Addressables.LoadAssetAsync<GameObject>("Assets/Image.prefab");
if (!handle.IsDone)
{
yield return handle;
}
if (handle.Status == AsyncOperationStatus.Succeeded)
{
Instantiate(handle.Result);
}
}
# 四. 资源卸载
可以使用Addressables.Release
方法对某一个资源进行卸载,此时该资源不会立刻从内存退出
- 调用后引用记数会减1,当资源的引用记数为0时,此时该资源仍然不会从内存退出
- 等到该资源所属bundle的所有资源的引用计数全为0时,该bundle及包含的所有资源从内存退出
//这是一个Addressable handle卸载的简单例子
Addressables.LoadAssetAsync<GameObject>("Assets/Image.prefab").Completed += (handle) => {
if(handle.Status == AsyncOperationStatus.Succeeded){
Instantiate(handle.Result);
}
Addressables.Release(handle);
};
# 五. Addressable配置简介
# Group
Addressable在打包时是以Group为单位的,一个Group会打包成一个Bundle,同时在资源加载时,当需要加载某一个资源,会一次性将该资源所属的整个bundle包读取进内存,对于某一个资源的依赖资源,也会读取依赖资源所属的bundle包,因此,做好合理的资源分组非常重要
# Profiles
Profiles中记录了构建和加载的路径,包括本地和远程。在运行时调用Addressables.LoadAssetAsync
会自动请求此处设置的地址
# Analyzer
Analyzer会通过一个模拟构建,扫描所有Addressable的group,从而分析其中的资源冗余及共享依赖问题,关于具体实现可以参考Unity手游实战:从0开始SLG——资源管理系统-Addressable中文手册(五)分析器(完) (opens new window)
# Event Viewer
该项可以监视Addressables资源的内存管理情况,供性能优化及调试时使用。详细信息可以参考https://zhuanlan.zhihu.com/p/502115922 (opens new window)
# 六、Addressable编译与部署
默认情况下,当编译Addressable资源时会输出到Library/com.unity.addressables/,项目转换为小游戏时会自动拷贝Bundle文件到最终的生成目录下。我们只需要将对应的webgl/StreammingAssets上传到对应的CDN服务器即可。