Unity热更新之Addressables——补充与总结(叁)

目录

目录

五、其他知识补充

(一)根据资源定位信息加载资源

1、之前学过的加载资源方式

  • 资源标识类AssetReference
  • 动态加载单个资源(资源名/标签名)
  • 动态加载多个资源(资源名/标签名/两者组合)

2、加载资源时Addressables做的事情

  • 查找资源位置
  • 收集依赖项列表
  • 下载所需的所有远程AB包(或本地)
  • 加AB包加载到内存
  • 设置Result资源对象的指
  • 更新Status状态变量参数,调用完成时间Completed

加载成功时,可以从Result获得内容,如果加载失败,若启用了AddressableAssetSettings中Diagnostics(诊断)的Log Runtime Exceptions参数,则会打印相关信息

3、资源定位信息

之前动态加载资源时,是有重载方法的,可以传入IResourceLocation(资源定位信息)来进行资源的加载。

IResourceLocation存放了资源数据、定位、依赖等信息,我们可以获取到这个资源定位信息,再通过这个资源定位信息加载资源(相当于分两步,获取资源定位信息为Addressable做的事情的前两步,通过资源定位信息加载资源相当于后四步)

4、根据名字/标签获取资源定位信息并加载资源

主要方法:Addressables.LoadResourceLocationsAsync

AsyncOperationHandle<IList<IResourceLocation>> handle = Addressables.LoadResourceLocationsAsync("Cube", typeof(GameObject));

handle.Completed += (handle) =>
{
    if (handle.Status == AsyncOperationStatus.Succeeded)
    {
        foreach (IResourceLocation item in handle.Result)
        {
            Debug.Log(item.PrimaryKey); // 资源名
            // 利用定位信息加载资源
            Addressables.LoadAssetAsync<GameObject>(item).Completed += (handle) =>
            {
                Instantiate(handle.Result);
            };
        }
    }
    else
    {
        Addressables.Release(handle);
    }
};

5、根据两者组合获取资源定位信息并加载资源

使用上面那种方法的重载即可

Addressables.LoadResourceLocationsAsync(
    new List<string>() { "Cube","Sphere"}, 
    Addressables.MergeMode.Union,
    typeof(GameObject));

6、为什么使用资源定位信息

  • 可以获取资源的额外信息
    • PrimaryKey:资源主键(资源名)
    • InternalId:资源内部ID(资源路径)
    • ResourceType:资源类型(Type可获取资源类型名)
  • 比如加载多个不同类型资源时,可以通过资源类型进行判断和处理逻辑
  • 资源定位信息加载资源并不会增大性能开销,只是分成两步完成而已

(二)异步加载的几种使用方式

1、异步加载知识回顾

handle = Addressable.LoadAssetAsync<>();
handle.Completed += (handle) => { ... }

2、三种异步加载资源方式

  • 事件监听(之前学的,就上面这种)
  • 协同程序
  • 异步函数

3、使用协程进行异步加载

void Start()
{
    StartCoroutine(LoadAsset());
}

IEnumerator LoadAsset()
{
    AsyncOperationHandle<GameObject> handle = Addressables.LoadAssetAsync<GameObject>("Cube");

    if (!handle.IsDone)
        yield return handle;

    if (handle.Status == AsyncOperationStatus.Succeeded)
        Instantiate(handle.Result);
    else
        Addressables.Release(handle);
}

4、通过异步函数async和await加载

  • WebGL平台不支持异步函数语法

单任务等待:

void Start()
{
    Load();
}

async void Load()
{
    AsyncOperationHandle<GameObject> handle = Addressables.LoadAssetAsync<GameObject>("Cube");
    await handle.Task;
    Instantiate(handle.Result);
}

多任务等待:

async void Load()
{
    AsyncOperationHandle<GameObject> handle = Addressables.LoadAssetAsync<GameObject>("Cube");
    AsyncOperationHandle<GameObject> handle2 = Addressables.LoadAssetAsync<GameObject>("Sphere");

    await Task.WhenAll(handle.Task,handle2.Task);

    Instantiate(handle.Result);
    Instantiate(handle2.Result);
}

(三)关于Async Operation Handle

1、获取加载进度

获取DownloadStatus结构体,如果已经下载过则结果为0

IEnumerator LoadAsset()
{
    AsyncOperationHandle<GameObject> handle = Addressables.LoadAssetAsync<GameObject>("Cube");

    while (!handle.IsDone)
    {
        DownloadStatus status = handle.GetDownloadStatus();
        Debug.Log(status.Percent); // 进度
        Debug.Log($"{status.DownloadedBytes}/{status.TotalBytes}"); // 下载字节数
        yield return null;
    }

    ...
}

2、无类型句柄转换

可以使用无类型的handle接收,并且可以根据需要转换成对应泛型,主要用于管理器进行统一管理handle的时候

// 无类型
AsyncOperationHandle handle = Addressables.LoadAssetAsync<GameObject>("Cube");
// 转换为泛型
AsyncOperationHandle<GameObject> handle2 = handle.Convert<GameObject>();

3、强制同步资源加载

了解即可,不建议使用

AsyncOperationHandle handle = Addressables.LoadAssetAsync<GameObject>("Cube");
handle.WaitForCompletion(); // 强行等待资源加载结束,会卡线程

(四)自定义更新目录和下载AB包

1、目录文件的作用

目录文件本质时Json文件和一个Hash文件,内容如下:

  • Json文件
    • 加载AB包、图集、资源、场景、实例化对象所用的脚本(会通过反射加载)
    • AB包所有资源类型对应的类(通过反射加载)
    • AB包对应路径
    • 资源的Path名
    • 等等
  • Hash文件
    • 存储目录文件对应的Hash码,用于判断文件是否变化
    • 更新时本地文件hash码和远端目录hash码进行对比,不同则更新目录文件

2、手动更新目录

手动更新目录时,建议在设置中关闭自动更新(勾选AddressableAssetSettings的Only update catalogs manually)

  • 调用如下方法自动检查所有目录是否有更新,并且自动更新目录:
Addressables.UpdateCatalogs().Completed += (handle) =>
{
	Addressables.Release(handle); // 更新完毕记得释放资源
};
  • 也可以获取变化的目录,再更新目录:
Addressables.CheckForCatalogUpdates().Completed += (handle) =>
{
    if (handle.Result.Count > 0)
    {
        Addressables.UpdateCatalogs(handle.Result).Completed += (obj) =>
        {
            // 更新完毕记得释放资源
            Addressables.Release(handle);
            Addressables.Release(obj);
        };
    }
};

上面这些方法都有第二个bool参数,代表是否自动释放句柄,这样就可以不用自己手动释放

3、预加载包

可以提前对资源进行加载,避免游戏过程中资源来不及加载。

  • 先通过Addressables.GetDownloadSizeAsync获取下载的包大小,只有大于0才说明没加载过,需要加载
  • 再通过Addressables.DownloadDependenciesAsync加载AB包所有需要的依赖
  • 加载时可以获取进度条信息
IEnumerator LoadAsset()
{
    // 1、获取下载包大小(可以传资源名/标签名或两者结合)
    AsyncOperationHandle<long> handleSize = Addressables.GetDownloadSizeAsync("Cube");
    yield return handleSize;
    // 2、预加载
    if (handleSize.Result > 0) // 大于0说明没有下载过
    {
        // 加载所有依赖的AB包(可以传资源名/标签名或两者结合)
        AsyncOperationHandle handle = Addressables.DownloadDependenciesAsync("Cube");
        // 3、进度条,可以打印进度条信息
        while (!handle.IsDone)
        {
            DownloadStatus status = handle.GetDownloadStatus();
            Debug.Log(status.Percent);
            yield return null;
        }
		// 释放句柄
        Addressables.Release(handle);
    }
}

4、总结

游戏开始前,可以显示加载界面,对一些资源进行预加载,也可以手动对目录进行更新

(五)引用计数规则

1、概念

加载可寻址资源时,Addressabels会在内部帮助我们进行引用计数,管理内存,避免内存泄漏。使用资源时,引用计数+1;释放资源时,引用计数-1。当引用计数为0,则可以卸载该资源。

为了避免内存泄漏,必须保证加载和卸载资源时配对使用。

AB包也有引用计数,表示该AB包被使用的次数(因为Addressables也把它们视为可寻址资源)

释放的资源不一定立即从内存中卸载。在卸载它所属AB包前,是不会释放资源所占内存的。可以使用Resources.UnloadUnusedAssets卸载AB包没被卸载但已经没被使用的资源。(简单来说就是AB包里的资源,需要等AB包没被使用才能被卸载,只要AB包里面还有一个资源在使用,其他资源都不会卸载)

2、测试

可以自己测试下引用计数,测试时,请用第三种方式加载AB包(即Play Mode Script设置为Use Existing Build),这样才能看到效果

每次调用Addressables.LoadAssetAsync,引用计数会+1

每次调用Addressables.Release(handle)释放句柄时,对应资源引用计数会-1

当引用计数为0时,资源会被卸载

3、自己的小测试

Addressables.LoadAssetAsync加载单个资源时的句柄被Release后,句柄对应的资源引用计数-1

Addressables.LoadAssetsAsync加载多个资源时的句柄被Release后,句柄中每个资源的引用计数都-1

(六)事件查看窗口

1、作用

可以监视可寻址资源的资源内存管理,查看可寻址资源对性能的影响和检查有没有释放的资源

  • 显示应用程序合适加载和卸载资源
  • 显示所有可寻址系统操作的引用计数
  • 显示应用程序帧率和分配的内存总量近似图

2、打开窗口

使用前需要打开AddressablesAssetSettings中的事件发送开关(Send Profiler Events)

  • 方式一:Window -> Asset Management -> Addressables -> Event Viewer
  • 方式二:Addressables Groups -> Window -> Event Viewer

3、使用

Event Viewer 工具现在已经被弃用,被新的 Addressables Profiler Module 取代了,这里韩式继续写旧版的Event Viewer功能

左上角:

  • Clear Event:清除所有记录的帧
  • Unhide All Hidden Events:显示所有你隐藏的事件内容

右上角:

  • Frame:当前所在帧
  • Current:选中当前所在帧

中央部分:

  • FPS:应用的帧率
  • MonoHeap:正在使用的托管堆内存量
  • Event Counts:事件计数,某一帧中发生的可寻址事件的数量
  • Instantiation Counts:实例化计数,某一帧中Addressables.InstantiateAsync的调用数量
  • 线性图标:显示统计的什么时候加载释放资源的额信息
  • Event相关:当前帧中发生的可寻址操作的事件

4、关于新版本

新版本只能使用Profiler,下面讲下怎么启用

  • Edit → Preferences → Addressables,勾选 “Debug Build Layout”,这样每次构建Addressables都会生成报告文件,构建时生成的报告文件位于 <项目>/Library/com.unity.addressables/buildReports/
  • 在Addressables的Play Mode Script中,选择 “Use Existing Build” 模式
  • 重新构建Addressables
  • 打开窗口:Window > Analysis > Profiler,运行后即可使用

(七)分析窗口

1、作用

一种收集项目可寻址布局信息的工具,它是一种信息工具,可以让我们对可寻址文件布局做出更明智的决定。

简单来说就是检测我们AB包布局是否合理

2、打开窗口

  • 方式一:Window -> Asset Management -> Addressables -> Analyze
  • 方式二:Addressables Groups -> Window -> Analyze

3、使用

image-20260206194754633

上方三个按钮:

  • Analyze Selected Rules:分析选定的规则
  • Clear Selected Rules:清除上一次选定的规则的信息
  • Fix Selected Rules:修复选定的规则

下方内容:

  • Analyze Rules:分析规则
    • Fixable Rules:可修复的规则(可分析可修复)
      • Check Duplicate Bundle Dependencies:检查重复的AB包依赖项。
        主要处理的问题:资源a和b,都用了资源c,但a、b是可寻址资源而c不是。
        建议自己处理问题,因为某些特殊情况它也会认为有问题:如FBX有多个网格信息a和b,a在包A,b在包B,但此时它也认为有重复问题。
    • Unfixable Rules:不可修复规则(只可以分析)
      • Check Resources to Addressable Duplicate Dependencies:检查可寻址重复依赖项的资源。
        主要处理的问题:同时出现在可寻址资源和应用程序构建的资源中,如资源a是可寻址资源,但它还出现在Resources、StreamingAssets等特殊文件夹中,最终会被打包出去
      • Check Scene to Addressable Duplicate Dependencies:检查场景中的可寻址重复依赖项的资源。
        主要处理的问题:同时出现在可寻址资源和某一个场景中,如资源a,它是可寻址资源但它直接出现在某一个场景中。
      • Bundle Layout Preview:AB包布局预览

选中某一个规则后,可以点击上方按钮对该规则进行处理。
当然还可以自己定义规则,需要可以去了解一下

(八)构建布局报告

1、作用

提供了有关可寻址资源的构建打包的详细信息和统计信息。包括:

  • AB包描述
  • 每个资源和AB包大小
  • 解析作为依赖项隐式包含在AB包中的不可寻址资源
  • AB包的依赖关系

2、如何查看

Edit -> Preferences -> Addressables,启用Debug Build Layout

之后构建打包可寻址资源后,可以在<项目>/Library/com.unity.addressables/buildReports/找到相关文件,可以查看信息

(九)常见问题总结

1、多包策略还是大包策略

  • 多包策略问题:每个包都有内存开销,加载过多包可能带来更多内存消耗;并且并发下载时,包小而多,则会消耗更多时间下载;目录文件会变大,因为要记录更多包信息。产生重复资源可能性增大,如多个包引用同一资源,但该资源不是可寻址资源。
  • 大包策略:包体过大下载失败时,需要重新下载,体验较差;并且大包资源数量更多,即使里面99个资源不用了,但只要还有一个资源再用,这个打包也不会卸载,会造成内存浪费

选择时只需要根据项目需求来合理安排分组打包即可,权衡两者利弊。

2、压缩方式

  • LZ4:基于块的压缩,提供了加载文件的能力,加载资源时不用全部加载AB包,只加载使用的内容,相对LZMA来说更节约内存
  • LZMA:不建议使用在本地内容中,因为虽然包体压缩最小,但是加载(解压)最慢,主要是为了节约远程下载时间和包体大小。

根据情况选择,但是LZ4相对比较优秀,默认选择就行

3、减小目录文件大小

  • 方式一,压缩本地目录:AddressableAssetSettings -> Catalog -> Compress Local Catalog
  • 方式二,禁用内置场景和资源:默认Addressables提供了从Resources等内置资源文件夹加载资源和内置场景的方法。但是如果你不想使用Addressables加载这些非寻址资源,而是用老方法加载,则可以取消。
    在Addressables Groups的Built In Data分组中,右侧Resources and Built In Scenes参数的两个选项可以取消勾选

4、AB包限制

AB包最大限制,老版本不支持大于4G的包,新版本没有这个限制,但是为了兼容性,建议控制在4G以下

5、活用Groups View的两个功能

Addressables Groups -> Tools -> Groups View中,有两个功能可以打开:

  • Show Sprite and Subobject Addresses:当该窗口内容特别多时,禁用它可以取消显示资源的子物体,提升窗口加载性能
  • Group Hierarchy with Dashes:启用后,可以在组命名时带上-来进行层级显示,内容多时可以便于查看

六、总结

1、几种常用资源加载方式

  • Resources:应用程序发布后不能动态修改,本地
  • AssetBundle:减小包体大小、热更新
  • Addressables:基于AssetBundle,帮助我们管理AssetBundle

2、选择

Resources适合小游戏、单机游戏;AssetBundle和Addressables适合商业游戏,老项目或者迭代项目用AssetBundle,新项目可以尝试Addressables,使用更方便