Unity Addressables资源管理系统

文章目录

  • Addressable Asset System(可寻址资产系统)
    • 1. 背景
    • 2. Addressable系统介绍
    • 3. Addressable系统优势:
    • 4. Addressable系统与AssetBundle的区别
  • Addressable系统的使用
    • 1. 安装
    • 2. Addressables管理窗口
    • 3. AddressableAssetSettings系统设置
    • 4. 资源组设置
    • 5. 标记资源
    • 6. 资源打包
    • 7. 加载资源
    • 8. 更新资源包
    • 9. 热更新代码和方案
    • 10. 资源自动分组
    • 11. 内存管理
    • 12. Addressable打包粒度

Addressable Asset System(可寻址资产系统)

1. 背景

  在开始介绍Addressable系统之前,我们先来回顾下传统的几种资源加载方式:

  • 直接引用: 使用直接引用是最简单快捷的,但不可动态加载。
  • Resources资源管理: Resources文件夹下所有文件都会打包到安装包,无论资源是否被使用,这样就会导致包体过大;Resources没办法做热更新资源;Resources加载资源时,对资源路径要求严格。
  • AssetBundle资源管理: 可热更,但管理难度大。

2. Addressable系统介绍

  可寻址资产系统是Unity推出的新的资源管理插件。Addressable系统是在 Asset Bundle之上,提供了异步加载、依赖管理以及内存管理等丰富的资源管理功能,也能够让开发者实现远程资源更新更加的便捷。

3. Addressable系统优势:

  • 快速迭代:
    使用Addressable在开发前期就进入快速开发的阶段,无论使用任何你喜欢的资源管理技术,都能快速切换到Addressable系统中。几乎不需要修改代码。
  • 依赖管理:
    系统不仅返回请求内容,还返回该内容所有依赖项,以便在返回内容之前加载所有网格、着色器、动画等。
  • 内存管理:
    Addressable不仅仅能加载资源,同时也能卸载资源。
  • 内容打包:
    Addressable系统自动管理了所有复杂的依赖连接,所以即使资源移动了或是重新命名了,系统依然能够高效地找到准确的依赖进行打包。当你需要将打包的资源从本地移到服务器上面,Addressable系统也能轻松做到。 相较于传统的资产加载方式(Resources/AssetBundle),Addressable系统拥有了完备的可视化编辑窗口以及内存管理。

4. Addressable系统与AssetBundle的区别

  • Addressable系统只需要资产的地址就可以从任意位置加载,而AssetBundle需要从指定bundle包中加载资源。
  • Addressable系统使用引用计数自动管理内存卸载,而AssetBundle需要开发者手动管理。
  • Addressable系统自动管理依赖关系,而AssetBundle需要开发者自己管理依赖关系,维护起来比较困难。
  • 可寻址资源系统默认的所有加载操作都是异步操作,可以添加事件监听,而AssetBundle则有同步和异步加载。
    Unity Addressables资源管理系统_第1张图片

Addressable系统的使用

1. 安装

    Unity 2018.2及以上版本,使用PackageManager安装Addressables。
Unity Addressables资源管理系统_第2张图片

2. Addressables管理窗口

    Window > Asset Management > Addressables > Groups进入使用界面
Unity Addressables资源管理系统_第3张图片
Addressable设置:
    资源默认分为Built In Data和LocalGroup(Default)两组,前者包含一些内置资源,不能改动,后者可以进行添加或删除资源。

  • Create: 创建新的资源分组,也可以在Addressables Group窗口中右键 Create New Group > Packed Assets创建新的资源组。
  • Profile: Profile的作用主要是用于指定项目中需要用到的几个地址,我们可以在这个窗口中点击Create->Profile创建Profile,每个Profile中都包含4个默认的变量,我们也可以点击Create->Variable扩展变量。
  • Tools:
    • Profiles: 用于给整个Addressable设置范围(配置四种路径的具体值)之后可以给每个组的Build,Load选择路径种类,则根据Profile的选取确定。
    • Labels: 添加删除标签Label。
    • Analyze: Analyze是一个工具,收集有关您的项目的可寻址布局的信息。在某些情况下,Analyze可能会采取适当的措施来清理项目的状态。在其他情况下,Analyze纯粹是一种信息工具,使您可以对可寻址布局做出更明智的决策。Unity Addressables资源管理系统_第4张图片
    • Hosting Services: Addressables系统自带的一个资源服务,可以指定一个目录存放远程资源,然后通过连接这个服务器,来更新资源。
    • Event Viewer: 可以用于监测,调试。前提是在Inspect System Settings窗口勾选发送消息(由于消耗性能所以默认关闭)。
          使用Addressables事件查看器可监视所有Addressables系统操作的资源的引用计数。在Unity中选择Window > Asset Management > Event Viewer。
          请注意,事件查看器只关心引用计数,而不关心内存消耗。在"Asset"栏下列出每一帧中,可以看到每一个资源的如下信息:
          FPS: 每秒帧数。
          MonoHeap: 内存使用的总量。
          Event Counts: 每帧事件总数。
      Unity Addressables资源管理系统_第5张图片
          可以单击左箭头和右箭头逐帧观察,或者单击Current跳转到最新的帧。按+按钮展开一行以获得更多详细信息。
          事件查看器中显示的信息与在Play Mode Script中选择的游戏模式有关。 使用事件查看器时,应避免使用 Use Asset Database模式,因为它不考虑资产之间的任何依赖关系。使用Simulate Groups 或Use Existing Build模式,但是后者更适合于事件查看器,因为它可以更准确地监视资源的引用计数。
    • Check for Content Updata Restrictions: 更新静态资源组。
  • Play Mode Script:
    • Use Asset Database(fastest): 允许你在游戏流程中快速运行游戏。它直接通过Asset Database加载Asset ,这会在不需要分析器或assetBundle创建的情况下进行快速迭代。
    • Simulate Groups(advanced): 在不创建assetBundles的情况下分析布局和依赖项的内容。asset 通过ResourceManager从assetDataBase加载,就假装它们是通过包加载的一样。若要查看游戏期间bundles加载或卸载的时间,请在Addressables事件查看器窗口Tools > Event Viewer
    • Use Exiting Build(requires built groups): 最接近于已部署的应用程序生成,但它要求你将数据作为单独的步骤进行构建。如果不修改Asset,则此模式是最快的,因为它在进入Play模式时不处理任何数据。必须通过选择Build>New Build>Default Build Script,或者在游戏脚本中使用AddressableAssetSettings.BuildPlayerContent()方法,在Addressables组窗口(Window>Asset Management>Addressable>group>group)中构建此模式的内容。
  • Build:
    • New Build: 当所有资源都已准备就绪,点击此项打包。
    • Update a Previous Build: 资源更新时,点击此项更新资源包。
    • Clear Build: 清除已经Build的资源,再次运行游戏需要重新Build。

3. AddressableAssetSettings系统设置

Unity Addressables资源管理系统_第6张图片
Addressable系统的基础配置:

  • Disable Catalog Update On Startup: 默认是没有勾选的,没有勾选,那么每次AA系统初始化的时候,会自动更新catalog文件,勾选上,将不会自动更新catalog文件,也就意味着不会自动更新资源.AA系统的初始化会在任意接口第一次调用时初始化,也可以主动调用Addressables.InitializeAsync()初始化.
  • Build Remote Catalog: 默认没有勾选,只有勾选上才会创建catalog在指定目录,以后客户端才可以下载这个catalog来进行对比更新.
  • Build Path: 资源打包后存放的地址。
  • Load Path: 资源加载地址。
  • Send Profiler Events: 调试用,允许加载资源的时候发送事件给EventViewer,可以通过这个工具查看资源的使用情况
  • Log Runtime Exception: 输出加载资源时的异常,开启时如果资源加载发生异常,会直接抛出.如果关闭,我们也可以通过加载资源时返回的句柄,来获取到异常信息.同时我们也可以给项目添加宏ADDRESSABLES_LOG_ALL,来查看更多的日志信息

4. 资源组设置

Unity Addressables资源管理系统_第7张图片

  • Build Path: 资源打包后存放的地址。
  • Load Path: 资源加载地址。
  • Advanced Options(高级设置):
    • Asset Bundle Compression: 当前.bundle文件的压缩方式,支持LZ4和LZMA压缩。
    • Include in Build: 是否被打包,默认是勾选的,若不勾选当前组不打包。
    • Requset Timeout: 设置UnityWebRequest在超时的秒数超过后尝试中止。(仅适用于远程资源包)
    • Bundle Mode: 控制捆绑包的打包方式。PackTogether将一个分组打包成一个资源包;Pack Separately每一个资源打成一个包,Pack Together By Label将Lable相同资源打成一个资源包。
  • Content Update Restriction:
    • Update Restriciton:
      • Cannot Change Post Release: 静态Group,在发布后,无法被修改,只能通过Check for Content Updata Restrictions做增量更新,创建一个新的资源分组.
      • Can Change Post Release: 非静态Group,在发布后,允许修改,更新时做覆盖更新。

5. 标记资源

Unity Addressables资源管理系统_第8张图片
    这里有两种方式将资源标记成可寻址的,在安装好可寻址资源包后,你可以在属性面板进行标记或者将其拖拽到管理窗口指定分组上。在资源的属性窗口上,点击Address复选框并为资源设置唯一标识符。

注:如果我们标记的资产在Resources文件夹下时,Addressable系统会提示你讲资产移出Resources文件夹。
Unity Addressables资源管理系统_第9张图片

6. 资源打包

    配置好资源分组,根据需要设置Play Mode Script,再通过Build > New Build > Default Build Script打包测试。
    本地资源打包路径: Library/com.unity.addressables/StreamingAssetsCopy/aa/Android/
    远程资源打包路径: ServerData/Android/
Unity Addressables资源管理系统_第10张图片
    同时远程目录下会生成有.hash和.json文件,.hash文件内只包含一个catalog文件的Hash值,用于客户单检测catalog更新时,通过对比这个hash值,判断是否有catalog更新,json文件内包含每个Ab包的hash值和地址。
    使用Build > Update a previous Build 更新资源包时,需要选择一个bin文件(android环境为例,Anroid/.bin),这个bin文件记录了所有Ab包之间的依赖关系和分组信息,Addressable系统通过这个bin文件管理依赖。

7. 加载资源

    使用AssetReference加载资源:

[SerializeField] private AssetReference m_AssetReference; 
    private  void Start() { 
        m_AssetReference.LoadAssetAsync<GameObject>(); 
    }

    使用Addressables加载单个资源:

 private void OnResLoadAsset(string key) 
    { 
        Addressables.LoadAssetAsync<GameObject>(key).Completed += OnCompleteLoad;
    } 
    private void OnCompleteLoad(AsyncOperationHandle<GameObject> asyncOperationHandle) 
    { 
        GameObject go = GameObject.Instantiate(asyncOperationHandle.Result); 
    }
    private void OnResInstantiate(string key) 
    { 
        Addressables.InstantiateAsync(key); 
    }

     加载多个资源:

private void OnResLoadAsset(string key,string lable) 
    { 
        Addressables.LoadAssetsAsync<Texture2D>(new List<object> { key, lable }, null, 
            Addressables.MergeMode.Intersection).Completed += OnCompleteLoadAssets; 
    } 
    private void OnCompleteLoadAssets(AsyncOperationHandle<IList<Texture2D>> asyncOperationHandle) 
    { 
        //DebugTools.Log(asyncOperationHandle.Result.Count); 
    }

注:第三个参数,MergeMode查找资源的合并模式,以传入的参数是new List{key,label}为例

  • Node或UseFirst时,会取第一个key查询到的资源
  • Union时,取并集
  • Intersection时,取交集

小结:
(1)加载资源时,若加载资源指定的类型与资源类型不一致,Addressable系统找不到该资源,则抛出异常,无法加载资源,前提:系统设置勾选了Send Profiler Events。
(2)使用标签管理,同一个资源的地址和标签可以相同,当有多个资源标签相同,Addressable系统会返回第一个满足条件的资源。
(3)若资源的地址名称与下一个资源的标签相同,返回还是第一个资源,Addressable系统会对比资源的地址和标签,若都不相同,才会继续向下查找

8. 更新资源包

    热更新资源包修改后,需要对资源重新打包
    Check for Content Update Restrictions: 针对是静态资源组,既是Update Restriciton属性为Cannot Change Post Release值。点击后弹出选择之前打包资源组生成的bin文件,点击“Apply Changes”应用更改,增加或修改的资源会被移动到新建Content Update分组。Unity Addressables资源管理系统_第11张图片
    Update a Previous Build: 动态资源组更新时,执行该操作,同样需要选择bin文件,系统会自动生成一个新的AB包。
Unity Addressables资源管理系统_第12张图片

9. 热更新代码和方案

    核心代码

 public IEnumerator CheckForContentUpdate(List<object> keys)
 {
     for (int index = 0; index < keys.Count; index++)
     {
         AsyncOperationHandle<long> DownloadSize = Addressables.GetDownloadSizeAsync(keys[index]);
         yield return DownloadSize;
         if (DownloadSize.Result <= 0)
         {
             Debug.Log("[Addressable]:不需要更新的资源标签:" + keys[index]);
             keys.Remove(keys[index]);
         }
         else
         {
             m_TotalSize += DownloadSize.Result / Mathf.Pow(1024, 2);
         }
     }
     m_DownloadDependencies = Addressables.DownloadDependenciesAsync(keys, Addressables.MergeMode.Union, false);
     yield return m_DownloadDependencies;
}
  • 方案一:
        从服务器获取更新资源标签。

注:动态资源更新,旧资源会被覆盖,动态资源组中有一个资源需要更新,热更时会将整个资源组都下载下来
,因此合理划分资源分组十分重要,减少重复下载以及打包粒度(多个资源需要相同的材质、贴图等资源)。

  • 方案二:
        使用官方提供的Addressables.CheckForCatalogUpdates()方法检查目录,获取需要更新的目录。
  • 方案三:
        暴力获取所有Key,使用官方的Addressables.GetDownloadSizeAsync(Key)检查资源是否需要更新,从而获取需要更新的所有Key值。
        资源更新具体实现:
            使用官方提供的Addressables.GetDownloadSizeAsync(Key)方法获取所有需要更新的资源地址或标签集合,再通过Addressables.DownloadDependenciesAsync(Key, Addressables.MergeMode.Union, false)下载更新的资源。

10. 资源自动分组

    项目中需要管理的资源过多时,勾选Addressable或拖拽的方式明显不在合适,因此需要实现一个方法,将某个文件夹下的所有资源标记为可寻址资源。
    编辑状态创建一个新的菜单,并创建一个asset文件,配置需要标记的资源文件夹,可同时标记多个资源,配置如下:
Unity Addressables资源管理系统_第13张图片

public static void AutoSetGroup(string groupName, string lableName, string assetPath, bool isSimplied = false) 
{ 
    var set = AddressableAssetSettingsDefaultObject.Settings; 
    AddressableAssetGroup Group = set.FindGroup(groupName); 
    if (Group == null) 
    { 
        Group = set.CreateGroup(groupName, false, false, false, new List<AddressableAssetGroupSchema> 
            { set.DefaultGroup.Schemas[0], set.DefaultGroup.Schemas[1]}, typeof(SchemaType)); 
    } 
    string Guid = AssetDatabase.AssetPathToGUID(assetPath);  //获取指定路径下资源的 GUID(全局唯一标识符)
    AddressableAssetEntry asset = set.CreateOrMoveEntry(Guid, Group); 
    if (isSimplied) 
    { 
         asset.address = Path.GetFileNameWithoutExtension(assetPath); 
    } 
    else 
    { 
         asset.address = assetPath; 
    } 
    asset.SetLabel(lableName, true, true); 
}

    具体思路:
        根据文件夹路径,获取该文件夹下的所有资源的路径信息,调用添加分组接口,检查是否存在当前分组,若无,则创建AddressableAssetGroup类型分组对象,设置默认状态,使用AssetDatabase.AssetPathToGUID获取当前路径资源的GUID,通过AddressableAssetSettingsDefaultObject.Settings.CreateOrMoveEntry(),创建AddressableAssetEntry对象,既勾选了addressable,再去简化资源地址和设置标签。

11. 内存管理

资源加载
    Addressables.LoadAssetAsync();单个资源
    Addressables.LoadAssetsAsync();多个资源
    Addressables.LoadSceneAsync();场景的加载
    GamoeObject实例化加载
    Addressables.InstantiateAsync();实例化加载
    GameObject.Instantiate();Unity提供实例化方法
资源卸载
    Addressables.UnloadSceneAsync();场景的卸载
    Addressables.Release();释放资源,参数是资源或    AsyncOperationHandle句柄
    Addressables.ReleaseInstance();销毁Addressable系统创建的实例

注:Addressables.InstantiateAsync()和其他加载调用的另一个区别就是有一个可选的trackHandle参数,当设置为false时,就必须通过AsyncOperationHandle句柄来释放资源,而不能再通过AsyncOperationHandle.Result加载资源释放了。

引用计数问题: 资源卸载,可手动和自动。
    手动卸载,Addressable系统加载和卸载资源都是成对存在的,使用Addrsssables.Release()或Addressables.ReleaseInstance()方法卸载资源,减少引用计数。当资源的引用计数为0时,该资源就准备好卸载了,并减少了所有依赖项的引用计数。
    自动卸载,包含它的场景关闭时允许自动清理。
卸载问题
    若使用Addressables.ReleaseInstance()传入的实例并不是Addressables系统API创建的,或者是通过句柄创建实例,系统会检测到并返回false,以指示该方法无法释放指定的实例。在这种情况下,实例不会被销毁。
Addressables.LoadAsset()和Addressables.InstantiateAsync()讨论
    Addressables.InstantiateAsync()有一些相关的开销,所以如果需要在每一帧中实例化数百次相同的对象,可以考虑通过Addressables.LoadAsset()方法加载,然后通过GameObject.Instantiate()实例化。缺点是Addressables系统不知道您创建了多少实例,如果管理不当,可能会导致内存问题。例如,一个Prefab引用了一个加载不正确或者已经卸载的纹理,会导致渲染问题(或更糟)。这类问题很难跟踪,因为您可能不会立即触发内存卸载 。​
清除内存
    不再被引用的资源并不一定意味着资源产已被卸载。一个常见的应用场景涉及到一个资源包中包含多个资源。例如:
     您有三个资源(“树”,“坦克”,“牛”)在同一个资源包(“东西”)。
     当“树”加载时,“树”的ref-count +1,“东西”的ref-count +1。
     稍后,当“坦克”加载时,“树”和“坦克”的ref-count均为1,并且“东西”包的ref-count为2。
     如果你释放“树”,它的ref-count就会变成0。
    在这个例子中,“树”资源实际上并没有被卸载。您可以加载资源包或其部分内容,但不能部分卸载资源包。在包本身完全卸载之前,所有资产都不会卸载。这个规则的例外是Resources.UnloadUnusedAssets,在上述场景中执行此方法将导致树卸载。因为Addressables系统不能识别这些事件, 只反映Addressables的ref-counts (不完全反映内存中存在的内容)。注意,如果您选择使用Resources.UnloadUnusedAssets,这是一个非常慢的操作,应该只在一个不会显示任何游戏内容的屏幕调用(比如加载屏幕)。

12. Addressable打包粒度

    我们在使用Addressable系统时,需要考虑的是:需要多少个Group?这个Group里面放什么资源?打包方式是Pack Together 或者 Pack Together By Label 或者 Pack Separately? 很显然,这个跟使用Assetbundle是一样的,需要开发人员自己来规划。这不是因为Addressable不够强大,而是这是跟具体项目有关,每个项目的情况各不相同。

你可能感兴趣的:(Unity,C#,unity)