# 使用Addressable Assets System进行资源按需加载
## 一、概述
对于Unity WebGL转换的小游戏启动耗时,资源下载通常是贡献最大的部分。这是由于手游APP往往很少针对首包资源进行特殊优化。
那么,接下来的问题是:小游戏中多大的首包资源合适? 剩余的游戏资源如何加载?
在此,我们建议得优化原则是:
> 1. 首包资源量不超过5M
> 2. 资源按需延迟加载,拆分得尽量细
本文介绍如何使用Unity新的资源管理流程[Addressable Assets System](https://docs.unity3d.com/Packages/com.unity.addressables@1.1/manual/index.html)进行资源的按需加载。
附可参考的项目:
https://git.weixin.qq.com/wechat-minigame/unity-webgl-addressable
## 二、Addressable在小游戏中的应用
### 2.1 什么是Addressable Assets System
Unity在2018版本中推出了Addressable Assets System(以下简称Addressable)的预览版本,并在2019的版本中已经成为正式版本,可以用于生产(仅表示发布时间,实际上大部分Unity版本都可正常使用)。
Addressable提供了以下能力:
> 低使用门槛:使用Addressable在开发前期就进入快速开发的阶段,使用任何你喜欢的资源管理技术,你都能快速的切换来Addressable系统中,几乎不需要修改代码。
> 依赖管理:Addressable系统不仅仅会帮你管理、加载你指定的内容,同时它会自动管理并加载好该内容的全部依赖。在所有的依赖加载完成,你的内容彻底可用时,它才会告诉你加载完成。
> 内存管理:Addressable不仅仅能记载资源,同时也能卸载资源。系统自动启用引用计数,并且有一个完善的Profiler帮助你指出潜在的内存问题。
> 内容打包:Addressable系统自动管理了所有复杂的依赖连接,所以即使资源移动了或是重新命名了,系统依然能够高效地找到准确的依赖进行打包。当你需要将打包的资源从本地移到服务器上面,Addressable系统也能轻松做到,几乎不需要任何代价。
### 2.2 相对于AssetsBundles的优势
Unity中资源按需加载也可以使用老的AssetBundle,然而使用AB需要做不少的工作:标识Asset、组织Bundle、编译、Load/Unload、依赖关系以及后期维护的复杂工作。新一代的Addressable正是对这些痛点做了不少改进,开发者只需要将Asset设置为addressable然后加载即可,[功能强大并且学习曲线变得平滑](https://docs.google.com/document/d/1hPLNLdrF0qAvjEJTpKf-cuO_d4FCV0H2cqBeP1Zo6mA/edit)。
### 2.3 在小游戏中使用Addressable Assets System
无论是Addressable还是AssetBundle在微信小游戏底层都使用XHR进行远程资源访问,并使用微信小游戏文件存储系统进行缓存。对于已有的游戏资源,如果我们需要尽量少的工作量去做到像H5游戏按需加载,使用Addressable是最佳做法。
## 三、启动优化与资源优化实战
### 3.1 从首包开始
首包资源应该只包含首屏所需资源,比如Splash界面以及对应文案。
精简场景使用zip压缩后3M左右最佳,不应超过5M。
首屏资源需要注意:
> 1. 导出场景不要勾选任何其他场景
> 2. 不要打包字体文件,字体往往压缩率很低。
> 3. 通过Addressable检查Bultin分组,特别注意不要随意放置资源到Resources目录,该目录将无条件被打包到首包中。
> 4. 上线前使用专业版剔除Unity Splash资源
### 3.2 资源按需加载
#### 3.2.1 场景动态加载
如前所述,我们构建时仅选择了splash场景,那么主场景(如大厅/战斗等)该如何加载?此时我们可以**将每个场景单独作为Addressable分组**,在用到的时候才下载该场景包。
使用Addressables.LoadSceneAsync可以动态加载场景与获取加载进度:
```
IEnumerator LoadMain()
{
var handle = Addressables.LoadSceneAsync("Assets/RPGPP_LT/Scene/rpgpp_lt_scene_1.0.unity", LoadSceneMode.Single, true);
handle.Completed += (obj) =>
{
Debug.LogWarning($"Load async scene complete{obj.Status}");
};
while (!handle.IsDone)
{
// 在此可使用handle.PercentComplete进行进度展示
yield return null;
}
}
```
选择分组,设置分组属性如下:
如果我们仅将场景作为分组,其中静态摆放的物件不单独设置为Addressable也会一并打包到场景所在bundle。那么,这是会产生一个问题:两个场景都使用同样资源是否产生冗余?答案是肯定的!!
那么,如何消除冗余呢?当我们Adressable面板的Tools-->Analyze进行分析时,可看到以下内容:
此时,我们应将这些冗余的内容单独进行设置为Addressable。而更为简单的做法是:选中“Check Duplicate Bundle Dependencies”,点击“Fixed Selected Rules”,工具会自动将冗余项逐个设置为Addressable。
### 3.2.2 物件动态加载
除了静态场景外,我们还会经常动态实例化(Instantiate)或在内存中创建资源对象。比如:
```
public class LoadAssetScript : MonoBehaviour
{
public GameObject somePrefab;
private void Start()
{
Instantiate(somePrefab);
}
}
```
然而,这样做有个非常严重的问题:Prefab本身以及依赖的所有资源需要在场景加载前完成。在小游戏环境,**这意味着即使还没调用Instantiate,这部分资源就必须准备好,这会极大影响场景初始化速度**。
那么,如果我们希望把somePrefab以及它的依赖资源打包在Addressable分组进行按需下载应该如何做呢?答案是AssetReference。上述代码改写成:
```
public class LoadAssetScript : MonoBehaviour
{
public AssetReference somePrefab;
private void Start()
{
somePrefab.InstantiateAsync().Completed += (obj) =>
{
// 加载完成回调
};
}
}
```
或协程写法:
```
public class LoadAssetScript : MonoBehaviour
{
public AssetReference somePrefab;
private IEnumerator spawnSomathing()
{
var hanle = somePrefab.InstantiateAsync();
while(!handle.IsDone) {yield return null;}
// 加载完成回调
var gameObject = hanle.Result;
}
}
```
同时,对应的Prefab在editor中需设置为Addressable,并重新为somePrefab赋值。
### 3.2.3 Resources.Load改造
使用这种方式加载资源,通常需要再Asset或其子目录下创建Resources的文件夹,然后使用类似这种方式加载:
```
TextAsset text = Resources.Load("MyConfig");
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(text.text);
```
然而,Resources目录的内容都会被打包进首包资源,对于小游戏来说是**不推荐使用的方式**。
开发者在Addressable的default中能看到所有这些资源,我们需要将这些资源设置为“Addressable”,Unity将自动移动到“Resources_Moved”目录。
加载代码改写成:
```
var handle = UnityEngine.AddressableAssets.Addressables.LoadAssetAsync("MyConfig");
handle.Completed += (obj) =>
{
var text = obj.Result;
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(text.text);
...
}
```
### 3.2.4 Addressable编译与部署
默认情况下,当编译Addressable资源时会输出到Library/com.unity.addressables/,项目发布为WebGL或转换为小游戏时Unity会自动拷贝Bundle文件到最终的生成目录下。我们只需要将对应的StreammingAssets上传到对应的CDN服务器即可。
### 3.2.5 资源预加载
我们可以根据资源的加载时序以及重要程度,修改game.js文件填写预加载资源列表。小游戏加载框架将利用网络空闲期进行资源预加载。
## 四、总结
Unity WebGL转换的小游戏普遍存在首包资源较大的情况,而新Address提供了非常好的资源管理流程。我们建议开发者:
> 1. 精简首场景,首包资源中确保只包含轻量的首屏以及依赖资源
> 2. 延迟加载,避免业务逻辑需要全量资源的情况,设计上尽量按需加载
> 3. 资源拆分,利用Addressable进行更灵活和细粒度的拆解
> 4. 预加载,根据优先级设置需要预加载的分包,利用网络空闲期
### 五、参考资料
1. Addressable Asset System for Unity (Overview)
https://docs.google.com/document/d/1hPLNLdrF0qAvjEJTpKf-cuO_d4FCV0H2cqBeP1Zo6mA/edit
2. 官方Demo
https://github.com/Unity-Technologies/Addressables-Sample
3. 视频范例
https://drive.google.com/file/d/1w-Lh_jsD2VSHNvkzexJLSc6bA3gUQC8d/view
4. uwa关于Addressable的介绍
https://blog.uwa4d.com/archives/USparkle_Addressable4.html
4. Addressable与AssetBundle的对比
https://www.jianshu.com/p/8009c16fcab3