Unity 热更 之 【YooAsset 热更】几分钟快速了解 YooAsset [功能面板]、以及 [基础代码] 说明

Source

Unity 热更 之 【YooAsset 热更】几分钟快速了解 YooAsset [功能面板]、以及 [基础代码] 说明

目录

Unity 热更 之 【YooAsset 热更】几分钟快速了解 YooAsset [功能面板]、以及 [基础代码] 说明

一、简单介绍

二、系统需求

三、快速引入工程中

四、功能面板 全局配置 YooAsset -> Create Setting

五、功能面板 资源配置 AssetBundle Collector

六、功能面板 资源构建 AssetBundle Builder

七、资源部署

八、功能面板 构建报告 AssetBundle Reporter

九、功能面板 调试器 AssetBundle Debugger

十、代码 YooAsset 初始化

十一、代码 YooAsset 资源更新

十二、代码 YooAsset 资源移除

十三、代码 YooAsset 资源加载

十四、代码 YooAsset 资源卸载


一、简单介绍

Unity 的资源管理系统不断发展,以适应不同项目的需求和解决特定的问题。AssetBundle 和 Addressable Asset System 是 Unity 官方提供的资源管理解决方案,而 YooAsset 是第三方提供的资源管理系统。尽管 AssetBundle 和 Addressable 已经提供了强大的功能,但开发者可能会选择 YooAsset 基于以下理由:

  1. 简化的API和易用性

    YooAsset 可能提供了更简单直观的 API,使得资源加载和管理更加容易。对于不熟悉 Addressable 或 AssetBundle 复杂配置的开发者来说,YooAsset 可能是一个更易于上手的选择。
  2. 性能优化

    YooAsset 可能在某些方面进行了特定的性能优化,比如资源加载速度、内存管理或多线程处理,这些优化可能在特定项目中提供了更好的性能表现。
  3. 定制化和灵活性

    每个项目的需求都是独特的,YooAsset 可能提供了更多的定制选项,允许开发者根据项目的具体需求进行调整和优化。

YooAsset 是一个为 Unity 设计的资源管理系统,它在资源热更新方面具有一些特定的技术优势:

  1. 简化的资源管理:YooAsset 提供了简单而强大的 API,使得资源的加载和管理更加容易,无需深入了解 Unity 的 AssetBundle 或 Addressable 的复杂配置。

  2. 高效的加载机制:YooAsset 支持异步加载资源,这意味着资源可以在后台加载,不会阻塞主线程,从而提高游戏性能。

  3. 资源缓存系统:YooAsset 提供了资源缓存系统,用于管理已加载的资源,避免重复加载,提高加载效率。

  4. 热更新支持:YooAsset 支持资源热更新,允许开发者在不重启游戏的情况下更新资源,这对于需要频繁更新内容的游戏尤为重要。

  5. 资源版本控制:YooAsset 允许获取和更新资源包的版本,确保玩家总是使用最新版本的资源。

  6. 资源包下载:YooAsset 提供了资源包下载功能,可以根据需要下载全部或部分资源,支持设置下载的最大并发数、失败重试次数和超时时间。

  7. 下载器合并:YooAsset 允许将同一个 package 下的多个下载器合并,简化下载流程。

  8. Shader 变体收集:YooAsset 提供了 Shader 变体收集功能,帮助开发者更好地管理和优化 Shader 变体,提高游戏的渲染效率和性能。

  9. 灵活的构建选项:YooAsset 支持不同的构建模式和加密选项,可以根据项目需求进行定制。

  10. 跨平台支持:YooAsset 支持所有主流的游戏引擎和平台,包括 Unity、Unreal、Cocos2x、Godot、微信小游戏等。

  11. 与热更新方案的兼容性:YooAsset 支持所有主流的热更新方案,如 HybridCLR、ILRuntime、xLua、tLua、sLua、puerts 等。

  12. 详细的文档和社区支持:YooAsset 有详细的文档和活跃的社区,为开发者提供了丰富的学习资源和技术支持。

这些优势使得 YooAsset 成为 Unity 资源热更新的一个有力工具,尤其适合需要高效资源管理和灵活更新策略的项目。开发者可以根据自己的项目需求和偏好选择合适的资源管理工具。

YooAsset 官网:https://www.yooasset.com/docs/Introduce

Yoo Asset 版本:

  • Yoo Asset 2.2.4-preview

二、系统需求

支持版本: Unity2019.4 & Unity2020.3 & Unity2021.3 & Unity2022.3

支持平台: Windows、OSX、Android、iOS、WebGL

开发环境: .NET4.x

三、快速引入工程中

1、通过PackageManager安装

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

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

 

2、打开管理界面 Edit/Windows/Package Manager

(注意:其中 Pacakges 选择 My Registers)

  

3、通过Packages清单安装

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

{
  "dependencies": {
    "com.tuyoogame.yooasset": "2.1.0",
    ......
  },
  "scopedRegistries": [
    {
      "name": "package.openupm.com",
      "url": "https://package.openupm.com",
      "scopes": [
        "com.tuyoogame.yooasset"
      ]
    }
  ]
}

4、通过Github下载安装

在发布的Release版本中,选择最新版本下载Source Code压缩包。

5、Yoo Asset Package 目录结构

  

四、功能面板 全局配置 YooAsset -> Create Setting

1、在工程中创建一个 Resources 脚本,通过右键创建配置文件(Project窗体内右键 -> Create -> YooAsset -> Create Setting)

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

2、配置说明:

  • Manifest File Name : 清单文件名称
  • Default Yoo Folder Name : 沙盒目录和内置目录的文件夹名称

五、功能面板 资源配置 AssetBundle Collector

1、打开 YooAsset - AssetBundle Collector

  

2、AssetBundle Collector 界面如下

(该界面是配置过的界面显示)

  

3、界面介绍

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

导入按钮:可以导入保存的XML文件。

导出按钮:可以将配置数据导出为XML文件。

修复按钮:在配置里的文件夹挪动位置之后,可以通过该按钮按钮来修正。

注意:该工具仅支持Unity2019.4+

4、全局设置

  • Show Package

    是否展示资源包列表视图。

  • Show Editor Alias

    是否显示为中文模式。

  • Unique Bundle Name

    资源包名追加PackageName作为前缀。

5、包裹设置

  • Enable Addressable

    启用可寻址资源定位系统。

    开启可寻址的同时也支持全路径加载!

  • Location To Lower

    资源定位地址大小写不敏感。

  • Include Asset GUID

    资源清单里包含资源GUID信息。

  • Auto Collect Shaders

    将所有着色器构建到独立的资源包内。

  • File Ignore Rule

    文件全局忽略规则。该规则可以扩展!

    原生文件配置选择RawFileIgnoreRule

6、资源分组

  • Active Rule

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

    • EnableGroup 启用分组。

    • DisableGroup 禁用分组。

    //自定义扩展范例
    public class DisableGroup : IActiveRule
    {
        public bool IsActiveGroup()
        {
            return false;
        }
    }
    
  • Grouper Name

    分组名称

  • Grouper Desc

    分组备注信息

  • Asset Tags

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

    注意:多个标签用分号隔开,例如 level1;level2;level3

7、搜集器

  • Collect Path

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

  • Collector Type

    收集器类型:

    • MainAssetCollector 收集参与打包的主资源对象,并写入到资源清单的资源列表里(可以通过代码加载)。
    • StaticAssetCollector 收集参与打包的主资源对象,但不写入到资源清单的资源列表里(无法通过代码加载)。
    • DependAssetCollector 收集参与打包的依赖资源对象,但不写入到资源清单的资源列表里(无法通过代码加载)(当依赖资源没有被任何主资源引用的时候,则会在打包的时候自动剔除)。

    StaticAssetCollector收集器和DependAssetCollector收集器适合对资源进行定制化打包策略。

    示例1:一个游戏的粒子特效的纹理会非常多,通常特效制作师会把这些纹理放到一个文件夹内管理。如果我们把这些纹理打进一个AssetBundle文件内,当下次更新的时候,如果新增或改动了一个纹理,那么就要上传整个纹理的AssetBundle文件。我们可以把特效纹理通过DependAssetCollector收集器进行收集并自定义打包规则,通过文件名称的首字母进行小粒度打包,这样一个AssetBundle文件会被拆分为26个AssetBundle文件。

    示例2:当我们需要严格控制某个文件夹内的依赖资源打进同一个AssetBundle文件内,那么StaticAssetCollector收集器是最佳选择,该收集器收集的资源,无论是否被其它资源引用或被多个资源引用,这些资源都会按照设定的打包规则打包,且这些资源不会被处理为share资源包。

  • AddressRule

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

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

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

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

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

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

    用户自定义数据,可以帮助定制化AddressRule和PackRule。

  • AssetTags

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

8、原生文件配置

原生文件配置和AssetBundle配置需要分开,各自创建自己的Package。

在原生文件的包裹设置界面里将File Ignore Rule下拉选择RawFileIgnoreRule,这样构建管线才会识别Unity引擎无法识别的原生文件。

资源包构建阶段选择RawFileBuildPipeline构建管线!

开发者可以通过访问收集器的实例类实现自定义需求。

例如:下面示例是通过代码关闭某个Group

using YooAsset.Editor;

private void SetGroupDsiable(string packageName, string groupName)
{
    foreach (var package in AssetBundleCollectorSettingData.Setting.Packages)
    {
        if (package.PackageName == packageName)
        {
            foreach (var group in package.Groups)
            {
                if (group.GroupName == groupName)
                {
                    group.ActiveRuleName = nameof(DisableGroup);
                    break;
                }
            }
        }
    }
}

六、功能面板 资源构建 AssetBundle Builder

1、打开 YooAsset - AssetBundle Builder

  

2、AssetBundle Builder 界面如下

  

2、界面介绍

  • Build Package

    资源包裹的列表,下拉选择要构建的资源包裹。

  • Build Pipeline

    构建管线的列表,下拉选择要使用的构建管线。

    (1) BuiltinBuildPipeline: 传统的内置构建管线

    (2) ScriptableBuildPipeline: 可编程构建管线

    ​ Unity后续推出的可编程构建管线,和可编程渲染管线一样,让开发者有能力控制打包流程。

    注意:从Unity2021.3版本开始,推荐使用该构建管线!

    (3) RawFileBuildPipeline: 原生文件构建管线

    ​ 用于构建Unity引擎无法识别的资源类型,例如FMOD的音频文件(bank后缀格式)

  • Build Output

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

  • Build Version

    构建的资源包版本。

  • Build Mode

    构建模式

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

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

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

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

  • Encryption

    加密类列表。

  • Compression

    资源包的压缩方式。

  • File Name Style

    输出的资源包文件名称样式

    HashName:哈希值

    BundleName: 资源包名

    BundleName_HashName:资源包名+哈希值

  • Copy Buildin File Option

    首包资源文件的拷贝方式

    (1) None:不拷贝任何文件

    (2) ClearAndCopyAll:先清空已有文件,然后拷贝所有文件

    (3) ClearAndCopyByTags:先清空已有文件,然后按照资源标签拷贝文件

    (4) OnlyCopyAll:不清空已有文件,直接拷贝所有文件

    (5) OnlyCopyByTags:不清空已有文件,直接按照资源标签拷贝文件

  • 构建

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

3、加密方法

在Editor目录下实现一个继承IEncryptionServices接口的类。

加密支持三种方式:

  • LoadFromFileOffset 通过文件偏移来解密加载。
  • LoadFromMemory 通过文件内存来解密加载。
  • LoadFromStream 通过文件流来解密加载。
/// <summary>
/// 文件偏移加密方式
/// </summary>
public class FileOffsetEncryption : IEncryptionServices
{
    public EncryptResult Encrypt(EncryptFileInfo fileInfo)
    {
        // 注意:只对音频资源包加密
        if (fileInfo.BundleName.Contains("_gameres_audio"))
        {
            int offset = 32;
            byte[] fileData = File.ReadAllBytes(fileInfo.FilePath);
            var encryptedData = new byte[fileData.Length + offset];
            Buffer.BlockCopy(fileData, 0, encryptedData, offset, fileData.Length);

            EncryptResult result = new EncryptResult();
            result.Encrypted = true;
            result.EncryptedData = encryptedData;
            return result;
        }
        else
        {
            EncryptResult result = new EncryptResult();
            result.Encrypted = false;
            return result;
        }
    }
}

4、补丁包

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

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

  

5、补丁清单

补丁清单文件是上图中以PackageManifest开头命名的文件。

  • PackageManifest_DefaultPackage_xxx.hash

    记录了补丁清单文件的哈希值。

  • PackageManifest_DefaultPackage_xxx.json

    该文件为Json文本格式,主要用于开发者预览信息。

  • PackageManifest_DefaultPackage_xxx.bytes

    该文件为二进制格式,主要用于程序内读取加载。

6、构建报告

BuildReport_DefaultPackage_xxx.json文件为构建报告文件。可以通过构建报告窗口查看本次构建的详细信息。

7、Jenkins支持

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

下面是使用内置构建管线来构建资源包的代码。

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

    var buildoutputRoot = AssetBundleBuilderHelper.GetDefaultBuildOutputRoot();
    var streamingAssetsRoot = AssetBundleBuilderHelper.GetStreamingAssetsRoot();
    
    // 构建参数
    BuiltinBuildParameters buildParameters = new BuiltinBuildParameters();
    buildParameters.BuildOutputRoot = buildoutputRoot;
    buildParameters.BuildinFileRoot = streamingAssetsRoot;
    buildParameters.BuildPipeline = EBuildPipeline.BuiltinBuildPipeline.ToString();
    buildParameters.BuildTarget = BuildTarget;
    buildParameters.BuildMode = EBuildMode.ForceRebuild;
    buildParameters.PackageName = "DefaultPackage";
    buildParameters.PackageVersion = "1.0";
    buildParameters.VerifyBuildingResult = true;
    buildParameters.EnableSharePackRule = true; //启用共享资源构建模式,兼容1.5x版本
    buildParameters.FileNameStyle = EFileNameStyle.HashName;
    buildParameters.BuildinFileCopyOption = EBuildinFileCopyOption.None;
    buildParameters.BuildinFileCopyParams = string.Empty;
    buildParameters.EncryptionServices = CreateEncryptionInstance();
    buildParameters.CompressOption = ECompressOption.LZ4;
    
    // 执行构建
    BuiltinBuildPipeline pipeline = new BuiltinBuildPipeline();
    var buildResult = pipeline.Run(buildParameters, true);
    if (buildResult.Success)
    {
         Debug.Log($"构建成功 : {buildResult.OutputPackageDirectory}");
    }
    else
    {
        Debug.LogError($"构建失败 : {buildResult.ErrorInfo}");
    }
}

// 从构建命令里获取参数示例
private static string GetBuildPackageName()
{
    foreach (string arg in System.Environment.GetCommandLineArgs())
    {
        if (arg.StartsWith("buildPackage"))
            return arg.Split("="[0])[1];
    }
    return string.Empty;
}

8、重要概念

  • 增量构建

    增量构建是在Unity的帮助下实现的一种快速打包机制。主要是利用资源构建相关的缓存文件来避免二次构建,以此来提高打包效率。

  • 强制构建

    强制构建是每次构建之前,都会清空之前构建的所有缓存文件,以此来重新构建资源包。

  • 首包资源

    在构建应用程序的时候,我们希望将某些资源打进首包里,首包资源拷贝至StreamingAssets/yoo/目录下。首包资源如果发生变化,也可以通过热更新来更新资源。

  • 补丁包

    无论是通过增量构建还是强制构建,在构建完成后都会生成一个以包裹版本(PackageVersion)命名的文件夹,我们把这个文件夹统称为补丁包。补丁包里包含了游戏运行需要的所有资源,我们可以无脑的将补丁包内容覆盖到CDN目录下,也可以通过编写差异分析工具,来筛选出和线上最新版本之间的差异文件,然后将差异文件上传到CDN目录里。

七、资源部署

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

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

1、部署目录

在业务开发过程中,发版本之前都会创建一个SVN或GIT分支,以分支工程为基础去构建APP。

CDN
└─android
    ├─v1.0(APP版本)
    ├─v1.1(APP版本)
    └─v2.0(APP版本)
└─iphone
    ├─v1.0(APP版本)
    ├─v1.1(APP版本)
    └─v2.0(APP版本)

2、APP版本说明

v1.0 代表的是APP版本,不是资源版本。在没有更换安装包的情况下,不需要新增加APP版本目录。

例如:我们游戏的当前APP版本是v1.0,那么每次生成的补丁文件全部覆盖到v1.0的目录下即可。

下面的示例里一共上传过2次补丁包。第二次上传的补丁包会把第一次的版本记录文件(PackageManifest_DefaultPackage.version)覆盖掉。当我们想回退资源版本的时候,把第一次生成的版本记录文件覆盖到同目录下即可。

v1.0(APP版本)
├─PackageManifest_DefaultPackage.version
├─PackageManifest_DefaultPackage_2023-02-01-654.hash
├─PackageManifest_DefaultPackage_2023-02-01-654.bytes
├─PackageManifest_DefaultPackage_2023-02-12-789.hash
├─PackageManifest_DefaultPackage_2023-02-12-789.bytes
├─2bb5a28d37dabf27df8bc6a4706b8f80.bundle
├─2dbea9c3056c8839bc03d80a2aebd105.bundle
├─6e8c3003a64ead36a0bd2d5cdebfbcf4.bundle
...

八、功能面板 构建报告 AssetBundle Reporter

报告工具,可以查看概览信息(Summary),资源对象视图,资源包视图。

注意:该工具仅支持Unity2019.4+

1、打开 YooAsset -  AssetBundle Reporter

  

2、AssetBundle Reporter 界面如下,Summary 概览视图

  

3、AssetView 资源对象列表视图

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

  

4、BundleView资源包列表视图

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

  

九、功能面板 调试器 AssetBundle Debugger

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

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

注意:该工具仅支持Unity2019.4+

安卓平台:Unity2020, Unity2021版本直接支持,Unity2019版本需要借助ADB命令

adb forward tcp:34999 localabstract:Unity- 包名

(真机远程调试注意事项:在构建安装包的时候,需要勾选上Development Build和Autoconnect Profiler)

1、打开 YooAsset - AssetBundle Debugger

  

2、BundleView 资源包列表视图  

3、BundleView 资源预制体列表视图

    

十、代码 YooAsset 初始化

1、创建资源包对象

// 初始化资源系统
YooAssets.Initialize();

// 创建默认的资源包
var package = YooAssets.CreatePackage("DefaultPackage");

// 获取指定的资源包,如果没有找到会报错
var package = YooAssets.GetPackage("DefaultPackage");

// 获取指定的资源包,如果没有找到不会报错
var package = YooAssets.TryGetPackage("DefaultPackage");

// 设置该资源包为默认的资源包,可以使用YooAssets相关加载接口加载该资源包内容。
YooAssets.SetDefaultPackage(package);

2、销毁资源包对象

private IEnumerator DestroyPackage()
{
    // 先销毁资源包
    var package = YooAssets.GetPackage("DefaultPackage");
    DestroyOperation operation = package.DestroyAsync();
    yield return DestroyOperation;
    
    // 然后移除资源包
    if (YooAssets.RemovePackage(package))
    {
        Debug.Log("移除成功!");
    }
}

3、资源系统的运行模式

  • 编辑器模拟模式 (EditorSimulateMode)
  • 单机运行模式 (OfflinePlayMode)
  • 联机运行模式 (HostPlayMode)
  • Web运行模式 (WebPlayMode)

4、编辑器模拟模式 (EditorSimulateMode)

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

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

private IEnumerator InitPackage()
{
    //注意:如果是原生文件系统选择EDefaultBuildPipeline.RawFileBuildPipeline
    var buildPipeline = EDefaultBuildPipeline.BuiltinBuildPipeline; 
    var simulateBuildResult = EditorSimulateModeHelper.SimulateBuild(buildPipeline, "DefaultPackage");
    var editorFileSystem = FileSystemParameters.CreateDefaultEditorFileSystemParameters(simulateBuildResult);
    var initParameters = new EditorSimulateModeParameters();
    initParameters.EditorFileSystemParameters = editorFileSystem;
    yield return package.InitializeAsync(initParameters);
    
    if(initOperation.Status == EOperationStatus.Succeed)
        Debug.Log("资源包初始化成功!");
    else 
        Debug.LogError($"资源包初始化失败:{initOperation.Error}");
}

5、单机运行模式 (OfflinePlayMode)

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

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

private IEnumerator InitPackage()
{
    var buildinFileSystem = FileSystemParameters.CreateDefaultBuildinFileSystemParameters();
    var initParameters = new OfflinePlayModeParameters();
    initParameters.BuildinFileSystemParameters = buildinFileSystem;
    yield return package.InitializeAsync(initParameters);
    
    if(initOperation.Status == EOperationStatus.Succeed)
        Debug.Log("资源包初始化成功!");
    else 
        Debug.LogError($"资源包初始化失败:{initOperation.Error}");
}

6、联机运行模式 (HostPlayMode)

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

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

private IEnumerator InitPackage()
{
    // 注意:GameQueryServices.cs 太空战机的脚本类,详细见StreamingAssetsHelper.cs
    string defaultHostServer = "http://127.0.0.1/CDN/Android/v1.0";
    string fallbackHostServer = "http://127.0.0.1/CDN/Android/v1.0";
    IRemoteServices remoteServices = new RemoteServices(defaultHostServer, fallbackHostServer);
    var cacheFileSystem = FileSystemParameters.CreateDefaultCacheFileSystemParameters(remoteServices);
    var buildinFileSystem = FileSystemParameters.CreateDefaultBuildinFileSystemParameters();   
    var initParameters = new HostPlayModeParameters();
    initParameters.BuildinFileSystemParameters = buildinFileSystem; 
    initParameters.CacheFileSystemParameters = cacheFileSystem;
    var initOperation = package.InitializeAsync(initParameters);
    yield return initOperation;
    
    if(initOperation.Status == EOperationStatus.Succeed)
        Debug.Log("资源包初始化成功!");
    else 
        Debug.LogError($"资源包初始化失败:{initOperation.Error}");
}

7、Web运行模式 (WebPlayMode)

针对WebGL平台的专属模式,包括微信小游戏,抖音小游戏都需要选择该模式。

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

private IEnumerator InitPackage()
{
    var webFileSystem = FileSystemParameters.CreateDefaultWebFileSystemParameters();
    var initParameters = new WebPlayModeParameters();
    initParameters.WebFileSystemParameters = webFileSystem;
    var initOperation = package.InitializeAsync(initParameters);
    yield return initOperation;
    
    if(initOperation.Status == EOperationStatus.Succeed)
        Debug.Log("资源包初始化成功!");
    else 
        Debug.LogError($"资源包初始化失败:{initOperation.Error}");
}

注意:原生文件的资源包构建模式必须是RawFileBuildPipeline

注意:在WebPlayMode模式下,不支持原生文件系统 !

// 原生文件资源包的初始化方式,同样适用于上面介绍的四种资源系统的运行模式。
{
    // 原生文件的缓存文件系统
    FileSystemParameters.CreateDefaultCacheRawFileSystemParameters(remoteServices);
    
    // 原生文件的内置文件系统
    FileSystemParameters.CreateDefaultBuildinRawFileSystemParameters(); 
}

8、解密方法

实现一个继承IDecryptionServices接口的运行时的类。

/// <summary>
/// 资源文件偏移加载解密类
/// </summary>
private class FileOffsetDecryption : IDecryptionServices
{
    // AssetBundle解密方法
    AssetBundle IDecryptionServices.LoadAssetBundle(DecryptFileInfo fileInfo, out Stream managedStream)
    {
        managedStream = null;
        return AssetBundle.LoadFromFile(fileInfo.FileLoadPath, fileInfo.ConentCRC, GetFileOffset());
    }
    
    // AssetBundle解密方法
    AssetBundleCreateRequest IDecryptionServices.LoadAssetBundleAsync(DecryptFileInfo fileInfo, out Stream managedStream)
    {
        managedStream = null;
        return AssetBundle.LoadFromFileAsync(fileInfo.FileLoadPath, fileInfo.ConentCRC, GetFileOffset());
    }
    
    // 原生文件解密方法
    byte[] IDecryptionServices.ReadFileData(DecryptFileInfo fileInfo)
    {
        throw new System.NotImplementedException();
    }
    
    // 原生文件解密方法
    string IDecryptionServices.ReadFileText(DecryptFileInfo fileInfo)
    {
        throw new System.NotImplementedException();
    }
    
    private static ulong GetFileOffset()
    {
        return 32;
    }
}

十一、代码 YooAsset 资源更新

1、获取资源版本

资源包在初始化成功之后,需要获取包裹版本

private IEnumerator RequestPackageVersion()
{
    var package = YooAssets.GetPackage("DefaultPackage");
    var operation = package.RequestPackageVersionAsync();
    yield return operation;

    if (operation.Status == EOperationStatus.Succeed)
    {
        //更新成功
        string packageVersion = operation.PackageVersion;
        Debug.Log($"Request package Version : {packageVersion}");
    }
    else
    {
        //更新失败
        Debug.LogError(operation.Error);
    }
}

2、更新资源清单

在获取到资源版本号之后,就可以更新资源清单了。

private IEnumerator UpdatePackageManifest()
{
    var package = YooAssets.GetPackage("DefaultPackage");
    var operation = package.UpdatePackageManifestAsync(packageVersion);
    yield return operation;

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

3、资源包下载

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

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

补丁包下载接口:

  • YooAssets.CreateResourceDownloader(int downloadingMaxNumber, int failedTryAgain, int timeout)

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

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

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

  • YooAssets.CreateBundleDownloader(AssetInfo[] assetInfos, int downloadingMaxNumber, int failedTryAgain, int timeout)

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

IEnumerator Download()
{
    int downloadingMaxNum = 10;
    int failedTryAgain = 3;
    var package = YooAssets.GetPackage("DefaultPackage");
    var downloader = package.CreateResourceDownloader(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
    {
        //下载失败
    }
}

4、下载器合并

可以把同一个package下的多个下载器合并。

// 例如:把关卡资源下载器和某个指定资源下载器进行合并。
var downloader1 = package.CreateResourceDownloader("level_tag", downloadingMaxNum, failedTryAgain);
var downloader2 = package.CreateBundleDownloader("asset_location", downloadingMaxNum, failedTryAgain);
downloader1.Combine(downloader2);

//开启下载
downloader1.BeginDownload();

十二、代码 YooAsset 资源移除

1、清理文件系统所有的缓存资源文件

private IEnumerator ClearPackageAllCacheFiles()
{
    var package = YooAssets.GetPackage("DefaultPackage");
    var operation = package.ClearAllBundleFilesAsync();
    yield return operation;

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

2、清理文件系统未使用的缓存资源文件

以当前激活的资源清单为准,清理该资源清单内未再使用的缓存文件。

private IEnumerator ClearPackageUnusedCacheFiles()
{
    var package = YooAssets.GetPackage("DefaultPackage");
    var operation = package.ClearUnusedBundleFilesAsync();
    yield return operation;

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

十三、代码 YooAsset 资源加载

1、方法列表

  • LoadSceneAsync() 异步加载场景
  • LoadAssetSync() 同步加载资源对象
  • LoadAssetAsync() 异步加载资源对象
  • LoadSubAssetsSync() 同步加载子资源对象
  • LoadSubAssetsAsync() 异步加载子资源对象
  • LoadAllAssetsSync() 同步加载资源包内所有资源对象
  • LoadAllAssetsAsync() 异步加载资源包内所有资源对象
  • LoadRawFileSync() 同步获取原生文件
  • LoadRawFileAsync() 异步获取原生文件

2、统一约定

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

  • 在未开启可寻址模式下,location代表的是资源对象的完整路径。
// 以工程内的音频文件为例:"Assets/GameRes/Audio/bgMusic.mp3" 
package.LoadAssetAsync<AudioClip>("Assets/GameRes/Audio/bgMusic");
  • 在开启可寻址模式下,location代表的是资源对象可寻址地址。

    注意:可寻址模式下,也支持使用完整的资源路径来加载。

// 以工程内的音频文件为例:"Assets/GameRes/Audio/bgMusic.mp3" 
// 需要在资源配置界面启用可寻址功能(Enable Addressable)。
// 配置界面的可寻址规则为AddressByFileName,那么资源定位地址填写文件名称:"bgMusic"
package.LoadAssetAsync<AudioClip>("bgMusic");

3、加载路径的匹配

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

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

4、异步加载范例

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

5、预制体加载范例

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

6、子对象加载范例

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

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

7、资源包内所有对象加载范例

例如:我们将所有配置表打进了一个资源包里,我们想把所有配置文件一次性全部加载出来解析。

IEnumerator Start()
{
    // 注意:location只需要填写资源包里的任意资源地址。
    AllAssetsHandle handle = package.LoadAllAssetsAsync<UnityEngine.TextAsset>(location);
    yield return handle;
    foreach(var assetObj in handle.AllAssetObjects)
    {    
        UnityEngine.TextAsset textAsset = assetObj as UnityEngine.TextAsset;
    }
}

8、场景异步加载范例

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

IEnumerator Start()
{
    string location = "Assets/GameRes/Scene/Login";
    var sceneMode = UnityEngine.SceneManagement.LoadSceneMode.Single;
    var physicsMode = LocalPhysicsMode.None;
    bool suspendLoad = false;
    SceneHandle handle = package.LoadSceneAsync(location, sceneMode, physicsMode, suspendLoad);
    yield return handle;
    Debug.Log($"Scene name is {handle.Scene.name}");
}

9、原生文件加载范例

注意:原生文件必须使用原生构建管线来构建。

例如:wwise的初始化文件

IEnumerator Start()
{
    // 注意:该Package必须是原生文件构建管线构建的资源包裹。
    string location = "Assets/GameRes/wwise/init.bnk";
    RawFileHandle handle = package.LoadRawFileAsync(location);
    yield return handle;
    byte[] fileData = handle.GetRawFileData();
    string fileText = handle.GetRawFileText();
    string filePath = handle.GetRawFilePath();
}

10、配置文件加载范例

// 自定义的配置文件
public class MyGameConfig: ScriptableObject
{
    ...
}

IEnumerator Start()
{
    string location = "Assets/GameRes/config/gameConfig.asset";
    AssetHandle handle = package.LoadAssetFileAsync(location);
    yield return handle;
    MyGameConfig gameCOnfig = handle.AssetObject as MyGameConfig;
}

11、热更脚本加载范例

如果使用的是Lua或者ILRuntime,HybirdCLR热更方式,可以将Lua或者Hotfix.dll的文件格式修改为.bytes

IEnumerator Start()
{
    AssetHandle handle = package.LoadAssetAsync<TextAsset>("Assets/GameRes/Hotfix.bytes");
    yield return handle;
    TextAsset textAsset = handle.AssetObject as TextAsset;
    //textAsset.bytes 二进制数据
    //textAsset.text 文本数据
}

12、获取资源信息列表

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

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

11、检测资源是否需要更新下载

{
    bool isNeedDownload = package.IsNeedDownloadFromRemote(location);
}

十四、代码 YooAsset 资源卸载

1、资源句柄释放

每个资源对象加载方法都会返回一个资源句柄,开发者需要在该资源不再使用的时候释放该句柄,否则会造成资源泄露(一直存在内中当中)

IEnumerator Start()
{
    // 资源句柄释放可以减少该资源的引用计数,当资源引用计数为零的时候,可以使用资源卸载方法卸载资源包。
    AssetHandle handle = package.LoadAssetAsync<AudioClip>("Assets/GameRes/Audio/bgMusic.mp3");
    yield return handle;
    ...
    handle.Release();
}

2、资源卸载范例

// 卸载所有引用计数为零的资源包。
// 可以在切换场景之后调用资源释放方法或者写定时器间隔时间去释放。
private IEnumerator UnloadUnusedAssets()
{
    var package = YooAssets.GetAssetsPackage("DefaultPackage");
    var operation = package.UnloadUnusedAssetsAsync();
    yield return operation;
}

// 强制卸载所有资源包,该方法请在合适的时机调用。
// 注意:Package在销毁的时候也会自动调用该方法。
private void ForceUnloadAllAssets()
{
    var package = YooAssets.GetAssetsPackage("DefaultPackage");
    var operation = package.UnloadAllAssetsAsync();
    yield return operation;
}

// 尝试卸载指定的资源对象
// 注意:如果该资源还在被使用,该方法会无效。
private void TryUnloadUnusedAsset()
{
    var package = YooAssets.GetAssetsPackage("DefaultPackage");
    package.TryUnloadUnusedAsset("Assets/GameRes/Panel/login.prefab");
}