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)

remote 远程服务默认是Editor Host,Editor Host用于编辑器模式,它的路径取决于你怎么样设置Hosting service。
所以一般选择custom即可

Editor Host

EditorHost用于资源托管,用于模拟远程资源的加载。(用于编辑器模式)

Hosting service有两个类型选项,localHosting为本地主机服务,用于主机。

CustomService:可以自定义Service的来实现自己的逻辑,需要实现 IHostingService 接口。

如果更新了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都会被重新打包给用户下载,可称为动态资源

静态资源组

发生更新后通过比对ContentState.bin文件将变更的资源提取出来划分新的ContentUpdate分组,原AB包不会发生变化,就是说原AB包的结构不变,变更前的资源仍存在原AB包上,只是被废弃了。这份变更的资源的索引就记录在了两份AB中,那么这时候Addressable总是会从最新下载下来的AB包里加载这份资源,可以理解为被指向了新的分组的AB包上。

思考:那难道等到下次大版本的时候,我还要把这个资源从update分组中移动到原先的分组?

动态资源组

发生更新后整个bundle都会被重新打包给用户下载。只不过,里面的Asset没有变更的情况下,打出来的bundle的hash不会变,这样玩家有缓存的情况下也不需要重新下载了。

每次发生变更的时候这个hash文件里面的值就会变化,估计就是用来判断变更的。

再一次强调,除非要进行完整构建,否则不要去改变Content Update Restriction,因为改变了这个设置,导致旧的content_state与实际的分组配置不一致。就无法进行争取的Update Build。

对比分析

版本发布时的资源情况如下

这时候我们分别对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 SettingsGraphics APIs,添加图形API库,设置与所打出的AB包的平台兼容或一致图形API即可。

addressable资源热更流程

function Addressables.InitializeAsync 初始化addressable System
function CheckForCatalogUpdates检测服务端是否有新的catalog配置。
function updateCatalog 更新catalog配置文件

注意如果想要手动从服务器更新本地的catalog配置,请勾选disable catalog update来禁用catalog的自动更新。否则CheckForCatalogUpdates检测不到。

 

如果下载依赖的时候报错,需要设置key请求结果的合并方式。

No MergeMode is set to merge the multiple keys requested.

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)

 

作者:Miracle
来源:麦瑞克博客
链接:https://www.playcreator.cn/archives/unity/3948/
本博客所有文章除特别声明外,均采用CC BY-NC-SA 4.0许可协议,转载请注明!
THE END
分享
打赏
海报
Addressable详解|Unity资源管理系统|AssetBundles加载方案
前言 Addressable资源管理系统是Unity为简化资源管理、提高加载性能以及减少开发工作量而推出的一套高效、灵活的资源管理解决方案。 Unity原有的资源管理系统……
<<上一篇
下一篇>>
文章目录
关闭
目 录