YooAsset介绍

YooAsset是一套用于Unity3D的资源管理系统,用于帮助研发团队快速部署和交付游戏,它可以满足商业化游戏的各类需求,并且经历多款百万DAU游戏产品的验证。

  • 安全高效的分包方案

    基于资源标签的分包方案,自动对依赖资源包进行分类,避免人工维护成本。可以非常方便的实现零资源安装包,或者全量资源安装包。

  • 强大灵活的打包系统

    可以自定义打包策略,自动分析依赖实现资源零冗余,基于资源对象的资源包依赖管理方案,天然的避免了资源包之间循环依赖的问题。

  • 基于引用计数方案

    基于引用计数的管理方案,可以帮助我们实现安全的资源卸载策略,更好的对内存管理,避免资源对象冗余。还有强大的分析器可帮助发现潜在的资源泄漏问题。

  • 多种模式自由切换

    编辑器模拟模式,单机运行模式,联机运行模式。在编辑器模拟模式下,可以不构建资源包来模拟真实环境,在不修改任何代码的情况下,可以自由切换到其它模式。

  • 强大安全的加载系统

    • 异步加载 支持协程,Task,委托等多种异步加载方式。
    • 同步加载 支持同步加载和异步加载混合使用。
    • 边玩边下载 在加载资源对象的时候,如果资源对象依赖的资源包在本地不存在,会自动从服务器下载到本地,然后再加载资源对象。
    • 多线程下载 支持断点续传,自动验证下载文件,自动修复损坏文件。
    • 多功能下载器 可以按照资源分类标签创建下载器,也可以按照资源对象创建下载器。可以设置同时下载文件数的限制,设置下载失败重试次数,设置下载超时判定时间。多个下载器同时下载不用担心文件重复下载问题,下载器还提供了下载进度以及下载失败等常用接口。
  • 原生格式文件管理

    无缝衔接资源打包系统,可以很方便的实现原生文件的版本管理和下载。

  • 可寻址资源定位

    默认支持相对路径的资源定位,也支持可寻址资源定位,不需要繁琐的过程即可高效的配置寻址路径。

  • 灵活多变的版本管理

    支持线上版本快速回退,支持区分审核版本,测试版本,线上版本,支持灰度更新及测试。

YooAsset快速安装

  1. 通过PackageManager安装

    打开管理界面 Edit/Project Settings/Package Manager

    // 输入以下内容
    Name: package.openupm.cn
    URL: https://package.openupm.cn
    Scope(s): com.tuyoogame.yooasset
    

    打开管理界面 Edit/Windows/Package Manager安装即可

  1. 通过Packages清单安装

    直接修改Packages文件夹下的清单文件manifest.json

    {
      "dependencies": {
        "com.tuyoogame.yooasset": "0.0.1-preview",
        ......
      },
      "scopedRegistries": [
        {
          "name": "package.openupm.cn",
          "url": "https://package.openupm.cn",
          "scopes": [
            "com.tuyoogame.yooasset"
          ]
        }
      ]
    }
  2. 通过Github下载安装

    在发布的Release版本中,选择最新版本下载源代码压缩包。

系统需求

  • 支持版本: Unity2019.4+
  • 支持平台: Windows、OSX、Android、iOS
  • 开发环境: .NET4.x

目录结构

Assets
└─ YooAsset
   ├─ Editor 编辑器源码目录  
   ├─ Runtime 运行时源码目录 
   ├─ LICENSE 版权文档
   └─ README 说明文档 

全局配置

  • 通过右键创建配置文件(Project窗体内右键 -> Create -> YooAsset -> Create Setting)

    注意:请将配置文件放在Resources文件夹下

    配置说明:

    • Asset Bundle File Variant : AssetBundle资源包后缀名
    • Raw File Variant : 原生资源包后缀名
    • Patch Manifest File Name : 补丁清单文件名称
    • Unity Manifest File Name : Unity构建的清单名称

资源收集

左侧为分组列表,右侧为该分组的配置界面。

导出按钮可以将配置数据导出为XML文件,导入按钮可以导入保存的XML文件。

注意:该工具仅支持Unity2019+

公共设置

  • Enable Addressable:启用可寻址资源定位系统。
  • Auto Collect Shaders:自动收集所有依赖的材质球使用的着色器,并将这些着色器打进一个资源包里。
  • Shader Bundle Name:收集的着色器资源包名称。

资源分组

  • Active Rule:激活规则,规则可以自定义扩展。下面是内置规则:

    • EnableGroup:启用分组。
    • DisableGroup:禁用分组。
    //自定义扩展范例
    public class DisableGroup : IActiveRule
    {
      public bool IsActiveGroup()
      {
        return false;
      }
    }
  • Grouper Name:分组名称

  • Grouper Desc:分组备注信息

  • Asset Tags:资源分类标签列表,该分组下收集的资源会全部被打上该标签。(注意:多个标签用分号隔开,例如 level1;level2;level3)

资源搜集器

  • Collect Path:收集路径,可以指定文件夹或单个资源文件。

  • Collector Type:收集器类型:

    • MainAssetCollector:收集参与打包的主资源对象,并写入到资源清单的资源列表里(可以通过代码加载)。
    • StaticAssetCollector:收集参与打包的主资源对象,但不写入到资源清单的资源列表里(无法通过代码加载)。
    • DependAssetCollector:收集参与打包的依赖资源对象,但不写入到资源清单的资源列表里(无法通过代码加载)。
  • AddressRule

    可寻址规则,规则可以自定义扩展。下面是内置规则:

    • AddressByFileName 以文件名为定位地址。
    • AddressByGrouperAndFileName 以分组名称+文件名为定位地址。
    • AddressByCollectorAndFileName 以收集器名+文件名为定位地址。
    //自定义扩展范例
    public class AddressByFileName : IAddressRule
    {
      string IAddressRule.GetAssetAddress(AddressRuleData data)
      {
        return Path.GetFileNameWithoutExtension(data.AssetPath);
      }
    }
  • PackRule

    打包规则,规则可以自定义扩展。下面是内置规则:

    • PackSeparately 以文件路径作为资源包名,每个资源文件单独打包。
    • PackDirectory 以父类文件夹路径作为资源包名,文件夹下所有文件打进一个资源包。
    • PackTopDirectory 以收集器路径下顶级文件夹为资源包名,文件夹下所有文件打进一个资源包。
    • PackCollector 以收集器路径作为资源包名,收集的所有文件打进一个资源包。
    • PackGrouper 以分组名称作为资源包名,收集的所有文件打进一个资源包。
    • PackRawFile 目录下的资源文件会被处理为原生资源包。
    //自定义扩展范例
    public class PackDirectory : IPackRule
    {
      string IPackRule.GetBundleName(PackRuleData data)
      {
        return Path.GetDirectoryName(data.AssetPath); //"Assets/Config/test.txt" --> "Assets/Config"
      }
    }
  • FilterRule

    过滤规则,规则可以自定义扩展。下面是内置规则:

    • CollectAll 收集目录下的所有资源文件
    • CollectScene 只收集目录下的场景文件
    • CollectPrefab 只收集目录下的预制体文件
    • CollectSprite 只收集目录下的精灵类型的文件
    //自定义扩展范例
    public class CollectScene : IFilterRule
    {
      public bool IsCollectAsset(FilterRuleData data)
      {
        return Path.GetExtension(data.AssetPath) == ".unity";
      }
    }
  • AssetTags

    资源分类标签列表,该收集器下收集的资源会全部被打上该标签。

资源构建

界面介绍

  • Build Output:构建输出的目录,会根据Unity编辑器当前切换的平台自动划分构建结果。

  • Build Version:构建版本号,也是资源版本号,版本号必须大于零。

  • Build Mode:构建模式

    (1) 强制构建模式:会删除指定构建平台下的所有构建记录,重新构建所有资源包。

    (2) 增量构建模式:以上一次构建结果为基础,对于发生变化的资源进行增量构建。

    (3) 演练构建模式:在不生成AssetBundle文件的前提下,进行演练构建并快速生成构建报告和补丁清单。

    (4) 模拟构建模式:在编辑器下配合EditorSimulateMode运行模式,来模拟真实运行的环境。

  • Encryption

    加密类列表。

  • Compression

    资源包的压缩方式。

  • Append Extension

    构建的资源包文件名是否包含后缀格式。

  • Buildin Tags

    标记为安装包里的资源标签列表。构建成功后,会将相关标记的资源包拷贝到StreamingAssets文件夹下。

  • 构建

    点击构建按钮会开始构建流程,构建流程分为多个节点顺序执行,如果某个节点发生错误,会导致构建失败。错误信息可以在控制台查看。

资源包加密

编写继承IEncryptionServices接口的加密类。注意:加密类文件需要放置在Editor文件夹里。

using System;
using YooAsset.Editor;

public class GameEncryption : IEncryptionServices
{
    /// <summary>
    /// 检测资源包是否需要加密
    /// </summary>
    bool IEncryptionServices.Check(string bundleName)
    {
        // 对配置表相关的资源包进行加密
        return bundleName.Contains("assets/config/");
    }

    /// <summary>
    /// 对数据进行加密,并返回加密后的数据
    /// </summary>
    byte[] IEncryptionServices.Encrypt(byte[] fileData)
    {
        int offset = 32;
        var temper = new byte[fileData.Length + offset];
        Buffer.BlockCopy(fileData, 0, temper, offset, fileData.Length);
        return temper;
    }
}

补丁包

构建成功后会在输出目录下找到补丁包文件夹,该文件夹名称为本次构建时指定的资源版本号。

补丁包文件夹里包含补丁清单文件,资源包文件,构建报告文件等。

资源包文件都是以文件的哈希值命名。

补丁清单

补丁清单是一个Json格式的文本文件,里面包含了所有资源包的信息,例如:名称,大小,CRC等。

Jenkins支持

如果需要自动化构建,可以参考如下代码范例:

private static void BuildInternal(BuildTarget buildTarget)
{
    Debug.Log($"开始构建 : {buildTarget}");

    // 命令行参数
    int buildVersion = GetBuildVersion();

    // 构建参数
    string defaultOutputRoot = AssetBundleBuilderHelper.GetDefaultOutputRoot();
    AssetBundleBuilder.BuildParameters buildParameters = new AssetBundleBuilder.BuildParameters();
    buildParameters.OutputRoot = defaultOutputRoot;
    buildParameters.BuildTarget = buildTarget;
    buildParameters.BuildMode = EBuildMode.ForceRebuild;
    buildParameters.BuildVersion = buildVersion;
    buildParameters.BuildinTags = "buildin";
    buildParameters.VerifyBuildingResult = true;
    buildParameters.EnableAddressable = false;
    buildParameters.AppendFileExtension = false;
    buildParameters.CopyBuildinTagFiles = true;
    buildParameters.EncryptionServices = new GameEncryption();
    buildParameters.CompressOption = ECompressOption.LZ4;

    // 执行构建
    AssetBundleBuilder builder = new AssetBundleBuilder();
    builder.Run(buildParameters);
}

// 从构建命令里获取参数
private static int GetBuildVersion()
{
    foreach (string arg in System.Environment.GetCommandLineArgs())
    {
        if (arg.StartsWith("buildVersion"))
            return int.Parse(arg.Split("="[0])[1]);
    }
    return -1;
}

资源部署

在资源补丁包构建成功之后,需要将补丁包传输到CDN服务器上。

如果是本地测试,可以在本地创建一个WEB服务器,然后将补丁包拷贝到WEB服务器下。

按照游戏版本目录部署

在业务开发过程中,每个游戏版本实际都会创建一个SVN分支,该分支工程内每次构建的补丁包上传到对应的CDN目录下即可。

CDN
└─android
    ├─v1.0
    ├─v1.1
    └─v2.0
└─iphone
    ├─v1.0
    ├─v1.1
    └─v2.0   

构建报告

报告工具,可以查看概览信息(Summary),资源对象列表信息(AssetView),资源包列表信息(BundleView)。

注意:该工具仅支持Unity2019+

  • 概览视图:打包时的参数信息。

  • 资源对象列表视图:可以查看资源对象列表以及每个资源对象所依赖的资源包。

  • 资源包列表视图:可以查看资源包列表以及每个资源包所包含的资源对象。

调试器

调试器是在游戏运行时,帮助我们查看资源包加载信息的工具,通过该工具可以发现潜在的资源泄漏。

可以查看资源对象列表信息(AssetView),资源包列表信息(BundleView)。

真机远程调试注意事项

在构建安装包的时候,需要勾选上Development Build和Autoconnect Profiler

初始化

资源系统的运行模式支持三种:编辑器模拟模式,单机运行模式,联机运行模式。

// 资源系统初始化方法,根据不同的模式,我们传递不同的创建参数类
YooAssets.InitializeAsync(InitializeParameters parameters);

编辑器模拟模式

在编辑器下,不需要构建资源包,来模拟运行游戏。

注意:该模式只在编辑器下起效

private IEnumerator InitializeYooAsset()
{
    var initParameters = new YooAssets.EditorSimulateModeParameters();
    initParameters.LocationServices = new DefaultLocationServices("Assets/GameRes");
    yield return YooAssets.InitializeAsync(initParameters);
}

单机运行模式

对于不需要热更新资源的游戏,可以使用单机运行模式。

注意:该模式需要构建资源包

private IEnumerator InitializeYooAsset()
{
    var initParameters = new YooAssets.OfflinePlayModeParameters();
    initParameters.LocationServices = new DefaultLocationServices("Assets/GameRes");
    yield return YooAssets.InitializeAsync(initParameters);
}

联机运行模式

对于需要热更新资源的游戏,可以使用联机运行模式,该模式下初始化参数会很多。

注意:该模式需要构建资源包

  • LocationServices : 资源定位的实例类。

    (1) 默认的资源定位服务类(DefaultLocationServices)

    (2) 可寻址的资源定位服务类(AddressLocationServices)

    (3) 开发者自定义的资源定位服务类,需要提供实现ILocationServices接口的实例类。

  • DecryptionServices : 如果资源包在构建的时候有加密,需要提供实现IDecryptionServices接口的实例类。

  • ClearCacheWhenDirty : 安装包在覆盖安装的时候,是否清空沙盒缓存文件夹。

  • DefaultHostServer : 默认的资源服务器IP地址。

  • FallbackHostServer : 备用的资源服务器IP地址。

  • VerifyLevel : 下载文件校验等级

private IEnumerator InitializeYooAsset()
{
    var initParameters = new YooAssets.HostPlayModeParameters();
    initParameters.LocationServices = new DefaultLocationServices("Assets/GameRes");
    initParameters.DecryptionServices = null;
    initParameters.ClearCacheWhenDirty = false;
    initParameters.DefaultHostServer = "http://127.0.0.1/CDN1/Android";
    initParameters.FallbackHostServer = "http://127.0.0.1/CDN2/Android";
    initParameters.VerifyLevel = EVerifyLevel.High;
    yield return YooAssets.InitializeAsync(initParameters);
}

资源文件解密

public class BundleDecryption : IDecryptionServices
{
    public ulong GetFileOffset()
    {
        return 32;
    }
}

资源更新

获取资源版本

对于联机运行模式,在更新补丁清单之前,需要获取一个资源版本号。

该资源版本号,可以通过YooAssets提供的接口来更新,也可以通过HTTP访问游戏服务器来获取。

private IEnumerator UpdateStaticVersion()
{
    UpdateStaticVersionOperation operation = YooAssets.UpdateStaticVersionAsync();
    yield return operation;

    if (operation.Status == EOperationStatus.Succeed)
    {
        //更新成功
        int resourceVersion = operation.ResourceVersion;
        Debug.Log($"Update resource Version : {resourceVersion}");
    }
    else
    {
        //更新失败
        Debug.LogError(operation.Error);
    }
}

更新补丁清单

对于联机运行模式,在获取到资源版本号之后,就可以更新资源清单了。

private IEnumerator UpdatePatchManifest()
{
    UpdateManifestOperation operation = YooAssets.UpdateManifestAsync(resourceVersion);
    yield return operation;

    if (operation.Status == EOperationStatus.Succeed)
    {
        //更新成功
    }
    else
    {
        //更新失败
        Debug.LogError(operation.Error);
    }
}

补丁包下载

在补丁清单更新完毕后,就可以更新资源文件了。

根据产品需求,可以选择更新全部资源,或者只更新部分资源。

补丁包下载接口:

  • YooAssets.CreatePatchDownloader(int downloadingMaxNumber, int failedTryAgain)

    用于下载更新当前资源版本所有的资源包文件。

  • YooAssets.CreatePatchDownloader(string[] tags, int downloadingMaxNumber, int failedTryAgain)

    用于下载更新资源标签指定的资源包文件。

  • YooAssets.CreateBundleDownloader(string[] locations, int downloadingMaxNumber, int failedTryAgain)

    用于下载更新指定的资源列表依赖的资源包文件。

IEnumerator Download()
{
    int downloadingMaxNum = 10;
    int failedTryAgain = 3;
    DownloaderOperation downloader = YooAssets.CreatePatchDownloader(downloadingMaxNum, failedTryAgain);
    
    //没有需要下载的资源
    if (downloader.TotalDownloadCount == 0)
    {        
        yield break;
    }

    //需要下载的文件总数和总大小
    int totalDownloadCount = downloader.TotalDownloadCount;
    long totalDownloadBytes = downloader.TotalDownloadBytes;    

    //注册回调方法
    downloader.OnDownloadErrorCallback = OnDownloadErrorFunction;
    downloader.OnDownloadProgressCallback = OnDownloadProgressUpdateFunction;
    downloader.OnDownloadOverCallback = OnDownloadOverFunction;
    downloader.OnStartDownloadFileCallback = OnStartDownloadFileFunction;

    //开启下载
    downloader.BeginDownload();
    yield return downloader;

    //检测下载结果
    if (downloader.Status == EOperationStatus.Succeed)
    {
        //下载成功
    }
    else
    {
        //下载失败
    }
}

弱联网更新解决方案

对于偏单机但是也有资源热更需求的项目。当玩家本地网络不稳定或无网络的时候,我们又不希望玩家卡在资源更新步骤而不能正常游戏。所以当玩家本地网络有问题的时候,我们可以跳过资源更新的步骤。

private IEnumerator UpdateStaticVersion()
{
    UpdateStaticVersionOperation operation = YooAssets.UpdateStaticVersionAsync(10);
    yield return operation;
    if (operation.Status == EOperationStatus.Succeed)
    {
        // 如果获取远端资源版本成功,说明当前网络连接并无问题,可以走正常更新流程。
        ......
            
        // 注意:在成功下载所有资源之后,我们需要记录当前最新的资源版本号
        PlayerPrefs.SetInt("STATIC_VERSION", resourceVersion);
    }
    else
    {
        // 如果获取远端资源版本失败,我们走弱联网更新模式。
        // 注意:如果从来没有保存过版本信息,则需要从内部读取StaticVersion.bytes文件的版本信息。
        int staticVersion = PlayerPrefs.GetInt("STATIC_VERSION", -1);
        if (staticVersion == -1)
        {
            staticVersion = LoadStaticVersionFromStreamingAssets();
            PlayerPrefs.SetInt("STATIC_VERSION", staticVersion);
        }
        
        // 在弱联网情况下更新补丁清单
        UpdateManifestOperation operation2 = YooAssets.WeaklyUpdateManifestAsync(staticVersion);
        yield return operation2;
        if (operation2.Status == EOperationStatus.Succeed)
        {
            StartGame();
        }
        else
        {
            // 指定版本的资源内容本地并不完整,需要提示玩家更新。
            ShowMessageBox("请检查本地网络,有新的游戏内容需要更新!");
        }
    }
}

资源加载

加载方法

  • YooAssets.LoadAssetSync() 同步加载资源对象
  • YooAssets.LoadAssetAsync() 异步加载资源对象
  • YooAssets.LoadSubAssetsSync() 同步加载子资源对象
  • YooAssets.LoadSubAssetsAsync() 异步加载子资源对象
  • YooAssets.LoadSceneAsync() 异步加载场景
  • YooAssets.GetRawFileAsync() 异步获取原生文件

统一约定

Location为资源的定位地址,也是加载资源对象的唯一标识符。

  • DefaultLocationServices 默认资源定位服务,location代表的是资源对象的相对路径。
// 以工程内的音频文件为例:"Assets/GameRes/Audio/bgMusic.mp3" 
// 设定资源路径的根目录为:"Assets/GameRes",后续加载的资源定位地址填写相对路径:"Audio/bgMusic"
var createParameters = new YooAssets.EditorSimulateModeParameters();
createParameters.LocationServices = new DefaultLocationServices("Assets/GameRes");
yield return YooAssets.InitializeAsync(createParameters);
......
YooAssets.LoadAssetAsync<AudioClip>("Audio/bgMusic");
  • AddressLocationServices 可寻址资源定位服务,location代表的是资源对象可寻址地址。
// 以工程内的音频文件为例:"Assets/GameRes/Audio/bgMusic.mp3" 
// 需要在资源配置界面启用可寻址功能(Enable Addressable)。
// 配置界面的可寻址规则为AddressByFileName,那么资源定位地址填写文件名称:"bgMusic"
var createParameters = new YooAssets.EditorSimulateModeParameters();
createParameters.LocationServices = new AddressLocationServices();
yield return YooAssets.InitializeAsync(createParameters);
......
YooAssets.LoadAssetAsync<AudioClip>("bgMusic");

注意:以下范例执行环境是在DefaultLocationServices下。

加载路径的匹配方式

// 不带扩展名的模糊匹配
YooAssets.LoadAssetAsync<AudioClip>("Audio/bgMusic");

// 带扩展名的精准匹配
YooAssets.LoadAssetAsync<AudioClip>("Audio/bgMusic.mp3");

异步加载范例

// 委托加载方式
void Start()
{
    AssetOperationHandle handle = YooAssets.LoadAssetAsync<AudioClip>("Audio/bgMusic.mp3");
    handle.Completed += Handle_Completed;
}
void Handle_Completed(AssetOperationHandle handle)
{
    AudioClip audioClip = handle.AssetObject as AudioClip;
}
// 协程加载方式
IEnumerator Start()
{
    AssetOperationHandle handle = YooAssets.LoadAssetAsync<AudioClip>("Audio/bgMusic.mp3");
    yield return handle;   
    AudioClip audioClip = handle.AssetObject as AudioClip;
}
// Task加载方式
async void Start()
{
    AssetOperationHandle handle = YooAssets.LoadAssetAsync<AudioClip>("Audio/bgMusic.mp3");
    await handle.Task;
    AudioClip audioClip = handle.AssetObject as AudioClip;	
}

资源卸载范例

IEnumerator Start()
{
    AssetOperationHandle handle = YooAssets.LoadAssetAsync<AudioClip>("Audio/bgMusic.mp3");
    yield return handle;
    ...
    handle.Release();
}

资源释放范例

可以在切换场景之后调用资源释放方法或者写定时器间隔时间去释放。

注意:只有调用资源释放方法,资源对象才会在内存里被移除。

private void UnloadAssets()
{
    YooAssets.UnloadUnusedAssets();
}

预制体加载范例

IEnumerator Start()
{
    AssetOperationHandle handle = YooAssets.LoadAssetAsync<GameObject>("Panel/login.prefab");
    yield return handle;
    GameObject go = handle.InstantiateSync();
    Debug.Log($"Prefab name is {go.name}");
}

子对象加载范例

例如:通过TexturePacker创建的图集,如果需要访问图集的精灵对象,可以通过子对象加载接口。

IEnumerator Start()
{
    SubAssetsOperationHandle handle = YooAssets.LoadSubAssetsAsync<Sprite>(location);
    yield return handle;
    var sprite = handle.GetSubAssetObject<Sprite>("spriteName");
    Debug.Log($"Sprite name is {sprite.name}");
}

场景异步加载范例

注意:当加载新的主场景的时候,会自动释放之前加载的主场景以及附加场景。

IEnumerator Start()
{
    var sceneMode = UnityEngine.SceneManagement.LoadSceneMode.Single;
    bool activateOnLoad = true;
    SceneOperationHandle handle = YooAssets.LoadSceneAsync("Scene/Login", sceneMode, activateOnLoad);
    yield return handle;
    Debug.Log($"Scene name is {handle.Scene.name}");
}

原生文件加载范例

例如:wwise的初始化文件

IEnumerator Start()
{
    string location = "wwise/init.bnk";
    string copyPath = $"{Application.persistentDataPath}/Audio/init.bnk";
    RawFileOperation operation = YooAssets.GetRawFileAsync(location, copyPath);
    yield return operation;
    byte[] fileData = operation.GetFileData();
    string fileText = operation.GetFileText();
}

获取资源信息列表

通过资源标签来获取资源信息列表。

private GetAssetInfosByTag(string tag)
{
    AssetInfo[] assetInfos = YooAssets.GetAssetInfos(tag);
    foreach (var assetInfo in assetInfos)
    {
        Debug.Log(assetInfo.AssetPath);
    }
}

FairyGUI支持解决方案

注意:在FairyGUI的面板销毁的时候,将资源句柄列表释放,否则会造成资源泄漏。

// 资源句柄列表
private List<AssetOperationHandle> _handles = new List<AssetOperationHandle>(100);

// 加载方法
private object LoadFunc(string name, string extension, System.Type type, out DestroyMethod method)
{
    method = DestroyMethod.None; //注意:这里一定要设置为None
    string location = $"FairyRes/{name}{extension}";
    var handle = YooAssets.LoadAssetSync(location , type);
    _handles.Add(handle);
    return handle.AssetObject;
}

// 执行FairyGUI的添加包函数
UIPackage.AddPackage(name, LoadFunc);

// 释放资源句柄列表
private void ReleaseHandles()
{
    foreach(var handle in _handles)
    {
        handle.Release();
    }
    _handles.Clear();
}

UniTask支持解决方案

解决方案