Addressable详解|Unity资源管理系统|AssetBundles加载方案
前言
Addressable资源管理系统是Unity为简化资源管理、提高加载性能以及减少开发工作量而推出的一套高效、灵活的资源管理解决方案。
Unity原有的资源管理系统(如Resources文件夹和AssetBundles)虽然提供了基本的资源加载和管理功能,但使用起来相对繁琐。开发人员需要手动管理资源的依赖关系、打包和分发等过程,这增加了开发的复杂性和工作量。
于是乎,Unity 在2018版本推出了全新的Addressable的资源管理方案,解决原有资源管理系统的局限性和复杂性,以及提供更加便捷、高效和灵活的资源加载和管理功能。简化了开发者对资源管理底层的关注。
本文将深入解析Addressable资源管理系统的使用,详细介绍其各项功能与应用。如果在阅读后觉得这篇文章内容对您有所启发和帮助,欢迎来评论区讨论。
AssetReference
资产的引用,可以在MonoBehaviour或者ScriptableObject中引用一个addressable资产,较传统的prefab而言,这种做法是通过AB包加载的,当引用的资产不存在内存里,那么就会去远程加载对应的AB包下来,因为这个AssetReference包含这个addressable资产所设置的信息,包括name和label等,所以它可以为我们自动完成AB包的远程拉取和加载。
如果将一个非Addressable的资源拖拽到AssetReference上,则会自动将这个资源标记为Addressable并加到默认分组中。
需要注意的是:如果要将一个组内的资产附加给AssetReference引用,需要开启 Include GUID in catalog 的选项(默认是开启的)。
using UnityEngine;
using UnityEngine.AddressableAssets;
internal class InstantiateReference : MonoBehaviour
{
[SerializeField]
private AssetReferenceGameObject reference;
void Start() {
if (reference != null)
reference.InstantiateAsync(this.transform);
}
private void OnDestroy() {
if (reference != null && reference.IsValid())
reference.ReleaseAsset();
}
}
限制AssetReference为特定的Label类型
[AssetReferenceUILabelRestriction("animals", "characters")] public AssetReference labelRestrictedReference;
Note:这里只能限制在编辑器下用拖拽的方式去引用,限制不了通过代码去赋值引用。另外添加了限制之后,就不能拖拽非Addressable到这个引用上。
打包方式
build方式有三种选项:
- FastMode(AssetDataBase):editor开发中使用,不会有实际的打包发生,而是直接从asset文件夹直接读取。即使没有打ab包。
- Simulate Groups: editor开发中使用,更贴近实机的效果。分析内容的布局和依赖关系,而无需创建资产包。也是直接从asset文件夹里读取,引入时间延迟远程资产包的下载速度和本地捆绑包的文件加载速度。
- Use Existing Build:真实的打包和下载过程,时间过长,一般用在测试阶段和实机发布阶段。从构建创建的assetbundle中加载资源。在使用之前Build Script 运行完整的打包。远程内容必须要在build配置文件里去配置 RemoteLoadPath 。
配置打包路径
Profiles | Addressables | 1.19.19 (unity3d.com)
Editor Host
EditorHost用于资源托管,用于模拟远程资源的加载。(用于编辑器模式)
Hosting service有两个类型选项,localHosting为本地主机服务,用于主机。
如果更新了RemoteLoadPath,请点击buildScript重新生成打包代码。否则path还是之前设置路径,因为打包的脚本没有更新。实际上buildScript是Addressable替我们做了关于AB包管理和打包的逻辑。因此,我们一般情况下不需要对AB包直接处理。
Profile配置逻辑
要注意编辑器读取文件,流程跟客户端是一致的,第一次从服务器下载所需ab包到本地保存,之后如果没有新版本的ab包的话,是不会从远程下载的,使用本地的ab包。
我们指定特定平台下的AB包是不同的,我们想要每个平台下都能从对应的地址去下载AB包,那么怎么办呢?其实可以制作多个profile指定不同的路径,对应的平台选择对应的profile。还有更方便的,addressable提供一个buildTarget插槽,它会获取编辑器当前所选的平台的名称,直接以这个名称作为后缀,这样就快了。
记住开发模式和正式版本的AB包环境要分离,一定要通过Profile配置来区分开来,否则用户端也会收到开发阶段用于测试的AB包版本。
一般做增量更新会用到Check for Content Update Restrictions 工具,用于内容更新重定向检测的。
这个工具的检查和处理跟每个Group所设置的Update Restriction有关。
在做 New Build 的时候,会对所有资源的静态资源做一个缓存,存到ContentState文件,用于做Update Buiild时,提取更新内容作为对比。
例如,将一个组设置为Cannot Change Post Release(静态资源组),然后执行资源打包,打包后默认将会在AddressableAssetsData/Windows/(win平台下)生成一个content_state.bin的文件(该文件需要在最终发版的时候,保存好)。
Update Restriction资源重定向
- Cannot Change Post Release:一个已经发布的包发生了更新,通过将发生更新的资源单独放到新分组上,原AB包不发生变化。可以认为已发布的为静态资源,一经发布后面的更新就不会影响它在磁盘上的分布了。
- Can Change Post Release:一个已经发布的包发生了更新,整个bundle都会被重新打包给用户下载,可称为动态资源
静态资源组
思考:那难道等到下次大版本的时候,我还要把这个资源从update分组中移动到原先的分组?
动态资源组
发生更新后整个bundle都会被重新打包给用户下载。只不过,里面的Asset没有变更的情况下,打出来的bundle的hash不会变,这样玩家有缓存的情况下也不需要重新下载了。
每次发生变更的时候这个hash文件里面的值就会变化,估计就是用来判断变更的。
对比分析
版本发布时的资源情况如下
这时候我们分别对A,L,X资源都做了修改,然后执行Check for Content Update Restrictions,则项目资源分组如下
然而,在不重新打包让用户重新安装应用的前提下,对于这时候已安装应用的用户来说,
本地资源是包含A(旧的),B,C 的
本地静态资源,用户将通过Content Update组,更新到新的A,而旧的A将存在磁盘里永远废弃掉。
-
用户还未下载缓存远程静态资源: 这种情况下,用户将下载包含L(废弃的),M,N,以及从Content Update分组中最新的L资源。
-
用户已经缓存过静态资源:这种情况是一个比较理想的情况,用户只需要通过Content Update更新最新的L资源。
当然,以上两种情况,都会在本地存在一个永久不会被用到的废弃L资源在磁盘里。
做完内容重定向之后,最后一步就是Update Build。点击Update a Previous build(更新先前的构建)去更新增量包。在增量更新之前需要开启Build Remote Catalog。
Build Remote Catalog会构建当前版本下的包信息。生成hash和json文件
hash文件用于客户端判断是否有包存在更新,通过hash客户端还不知道更新了哪个AB包。只有当远程的hash文件里的hash值和客户端本地的hash值不同时,客户端会比对本地和远程的catalog.json文件内容。
tip:内置的管线shader那些unity自带的必要资源也会打成AB包并记录在这份json文件里,只是它是随我们的程序包体打包。以及Resouces文件也会记录到这份json文件中。有一点不明白的是,为什么要把它们合并到remote catalog.json里,那不是占用字节吗。
Unique Bundle IDs
对于一些需要支持在玩的过程中进行更新的资源,则需要开启Unique Bundle IDs。
开启这个选项,将支持资源在内存中更新。
另外,开启后,对于引用了该资源的其他资源,也得被重新构建,这个很好理解,因为资源一旦加载到了内存中,同样也需要被更新。因此,会更新更多的内容而造成花费更多的时间来构建和加载。
Addressable打包后的物体,在对应设备平台显示正常,但是在编辑器下材质显示紫色
描述 : 通过AssetBundle或者Addressable打包了安卓或者IOS远程资源,然后在设备上下载运行一切正常,但是在电脑Editor环境下运行却显示为紫色,查看MeshRenderer发现材质并未丢失,而且手动重置一下是能正常显示
导致问题的原因 :
平台图形API不兼容
解决方案:
点击Project Settings-> windows平台->Other Settings 下 Graphics APIs,添加图形API库,设置与所打出的AB包的平台兼容或一致图形API即可。
addressable资源热更流程
注意如果想要手动从服务器更新本地的catalog配置,请勾选disable catalog update来禁用catalog的自动更新。否则CheckForCatalogUpdates检测不到。
如果下载依赖的时候报错,需要设置key请求结果的合并方式。
key合并方式
-
Union:并,取这些key对应的所有结果。
-
Intersection:交,取同时匹配这些key的结果。
-
UseFirst:取第一个key映射的结果。
提供的一个keys列表中的每个key都映射了对应的资源列表,最终所需要做的事情就是将它们以某种合并方式合并成一个列表,作为结果返回。
一个key可以是aa-namekey,也可以是aa-labelkey。我们通过key可以获取指定的asset,而key会出现重复的情况。
一个asset即可以有name也可以有labelkey,产生更多组合的key。
比如 一个ab包里存在资源的namekey重复的,而label不同的情况,我们就可以同时提供name,label这两个key来请求获取资源。
name对应的资源列表为【a,b,c】
label对应的资源列表为[b,d,f]。
那么我们使用intersection合并方式就可以获取到同时匹配两个key的资源。
如果只提供单个key,那么合并方式一定不要指定。因为不需要合并。
var loadLuaInfoHandle = Addressables.LoadAssetsAsync<TextAsset>("LuaLabel",null);
这个设计就有点不合理了,这个明显可以在内部就去判断key的个数、去避免报错。
var downloadHandle = Addressables.DownloadDependenciesAsync(elem.Keys,Addressables.MergeMode.Union);
Addressable加载热更脚本思路
1.先加载lua资源,根据资源label key从addressable批量加载lua文件,将所有lua文件内容加载进内存。
2.luadLoader根据lua文件名称从字典里取出lua脚本内容。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.AddressableAssets;
using XLua;
public class LuaLoader:ScriptSingleTon<LuaLoader>
{
private readonly string LuaFileInfoContainer="LuaFileInfoContainer";
public LuaEnv luaEnv;
public Dictionary<string, TextAsset> luaScriptDic = new Dictionary<string, TextAsset>();
[HideInInspector]public bool LuaScriptIsLoaded = false;
[HideInInspector]public bool LuaEnvIsConfigurationFinish = false;
public void InitializeLua()
{
//思路:
//1.先加载lua资源
//2.再去配置lua
Debug.Log("初始化Lua环境");
luaEnv = new LuaEnv();
LuaScriptIsLoaded = false;
LoadLuaFromAddressable();
}
void LoadLuaFromAddressable()
{
Debug.Log("准备载入Lua资源");
LoadLuaByLabel();
}
async void LoadLuaByKeyListInLua()
{
var loadLuaInfoHandle = Addressables.LoadAssetAsync<TextAsset>(LuaFileInfoContainer);
await loadLuaInfoHandle.Task;
if (loadLuaInfoHandle.Status == UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationStatus.Succeeded)
{
if (loadLuaInfoHandle.Result.text.Length > 0)
{
Debug.Log("载入lua列表");
luaEnv.DoString(loadLuaInfoHandle.Result.text);
GetAllLuaByKeyListInLua();
}
}
}
List<string> GetAllLuaByKeyListInLua()
{
//从lua vm 获取存放key的table。
return null;
}
async void LoadLuaByLabel()
{
var loadLuaInfoHandle = Addressables.LoadAssetsAsync<TextAsset>("LuaLabel",null);
await loadLuaInfoHandle.Task;
if (loadLuaInfoHandle.Status==UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationStatus.Succeeded)
{
IEnumerable<TextAsset> textAssets = loadLuaInfoHandle.Result;
foreach (var luaAsset in textAssets)
{
luaScriptDic.Add(luaAsset.name,luaAsset);
}
LuaScriptIsLoaded = true;
Debug.Log("Lua脚本载入完成");
luaEnv.AddLoader(CustomLoader);
LuaEnvIsConfigurationFinish = true;
}
else
{
Debug.Log("无可载入的Lua");
}
}
byte[] CustomLoader(ref string key)
{
key = key + ".lua";
if (!luaScriptDic.ContainsKey(key))
{
Debug.LogWarning($"key为{key}的Lua脚本不存在于构建的AB包中,正尝试默认的传统加载");
return null;
}
return luaScriptDic[key].bytes;
}
}
文章分享
Addressable使用心得 - Dr.Persona - 博客园 (cnblogs.com)
Enum Addressables.MergeMode | Addressables | 1.19.19 (unity3d.com)
合并方式 Loading multiple assets - 知乎 (zhihu.com)
来源:麦瑞克博客
链接:https://www.playcreator.cn/archives/unity/3948/
本博客所有文章除特别声明外,均采用CC BY-NC-SA 4.0许可协议,转载请注明!