Unity 集成 YooAsset 资源管理系统:从零到热更新的完整实战指南

一、YooAsset 是什么?

YooAsset 是由途游游戏开源的 Unity 资源管理系统,它可以满足以下典型需求:

  • 发布零资源安装包,玩家边玩边下载
  • 发布保证前期体验的安装包,后续内容按需下载
  • 发布 300MB 以内的安装包,进游戏前下载剩余内容
  • 偏单机游戏,联网时更新,断网时玩老版本
  • MOD 游戏,玩家上传和下载自制内容
  • 超大体量项目,分工程构建上百 GB 资源

核心特性

特性 说明
构建管线无缝切换 支持内置构建管线(BBP)和可编程构建管线(SBP)
分布式构建 支持分工程、分内容构建,方便 MOD 支持
可寻址资源定位 支持完整路径和可寻址地址两种定位方式
基于标签的分包 自动对依赖资源包分类,避免人工维护
引用计数管理 安全的资源卸载策略,附带泄漏分析器
多种运行模式 编辑器模拟、单机、联机、WebGL、自定义模式自由切换
边玩边下载 加载时自动下载缺失资源包,支持断点续传
原生文件管理 无缝衔接打包系统,支持原生文件版本管理和下载

二、安装 YooAsset

YooAsset 提供三种安装方式,推荐使用 OpenUPM 安装,方便后续版本升级。

方式一:通过 Package Manager 安装(推荐)

  1. 打开 Edit → Project Settings → Package Manager
  2. 添加 Scoped Registry:
字段
Name package.openupm.com
URL package.openupm.com
Scope(s) com.tuyoogame.yooasset
  1. 打开 Edit → Windows → Package Manager,选择 My Registries,找到 YooAsset 点击 Install

注意:按照自己需求安装特定版本,不要安装最新版,避免 API 变更导致代码报错。

方式二:通过 manifest.json 安装

直接修改 Packages/manifest.json 文件:

json 复制代码
{
  "dependencies": {
    "com.tuyoogame.yooasset": "2.3.18",
    ...
  },
  "scopedRegistries": [
    {
      "name": "package.openupm.com",
      "url": "https://package.openupm.com",
      "scopes": [
        "com.tuyoogame.yooasset"
      ]
    }
  ]
}

方式三:通过 GitHub 下载

YooAsset Releases 页面下载最新版本的 Source Code 压缩包,解压到项目的 Assets/YooAsset 目录下。

系统要求

  • Unity 版本:2019.4 / 2020.3 / 2021.3 / 2022.3 / 6.0
  • 支持平台:Windows、macOS、Android、iOS、WebGL
  • 开发环境:.NET 4.x

安装完成后的目录结构:

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

三、全局配置

安装完成后,第一步是创建全局配置文件。

  1. 在 Project 窗口中右键 → Create → YooAsset → Create Setting
  2. 重要 :将生成的配置文件放到 Resources 文件夹下

配置项说明:

配置项 说明
Default Yoo Folder Name 沙盒目录和内置目录的根目录名称

这个名称决定了运行时资源在设备上的存储路径,一般保持默认即可。


四、资源配置

资源配置是 YooAsset 工作流的核心环节,它决定了哪些资源参与打包、如何分组、如何寻址。

打开资源配置窗口:Window → YooAsset → AssetBundle Collector

4.1 界面概览

  • 左侧:分组列表
  • 右侧:当前分组的详细配置

注:第一次进入的时候有可能左右结构,需要把 Global Setting下方的 Show Packages选中,Package Settings里面的内容按需勾选,参考4.2

工具栏按钮:

  • 导入:导入保存的 XML 配置文件
  • 导出:将配置导出为 XML 文件(方便版本管理和团队协作)
  • 修复:当配置中的文件夹被移动后,点击修正路径

4.2 包(Package)设置

每个 Package 是一个独立的资源包单元,支持以下配置:

配置项 说明
Enable Addressable 启用可寻址资源定位。开启后同时支持全路径加载
Location To Lower 资源定位地址大小写不敏感
Include Asset GUID 资源清单中包含资源 GUID 信息
Auto Collect Shaders 将所有着色器构建到独立的资源包内
File Ignore Rule 文件全局忽略规则,可自定义扩展

提示 :当项目有多个 Package 时,建议开启 Unique Bundle Name,为资源包名追加 PackageName 前缀,避免冲突。

4.3 资源分组(Group)

分组是对资源的逻辑划分,比如可以按功能模块分为 UISceneAudioConfig 等。

分组配置:

配置项 说明
Active Rule 激活规则,支持自定义。内置:EnableGroup(启用)、DisableGroup(禁用)
Grouper Name 分组名称
Grouper Desc 分组备注
Asset Tags 资源分类标签,用分号分隔,如 level1;level2;level3

4.4 收集器(Collector)

收集器定义了如何收集某个路径下的资源,是资源配置的最细粒度。

配置项 说明
Collect Path 收集路径,可以是文件夹或单个文件
Collector Type 收集器类型(见下文)
AddressRule 可寻址规则
PackRule 打包规则
FilterRule 过滤规则
User Data 用户自定义数据
Asset Tags 该收集器下资源的标签

收集器类型

类型 说明
MainAssetCollector 主资源,写入资源清单,可通过代码加载
StaticAssetCollector 参与打包但不写入清单,适合定制化打包策略
DependAssetCollector 依赖资源,不写入清单,未被引用时自动剔除

可寻址规则(AddressRule):

规则 说明
AddressByFileName 以文件名为地址
AddressByFilePath 以文件路径为地址
AddressByGroupAndFileName 分组名 + 文件名
AddressByFolderAndFileName 文件夹名 + 文件名

打包规则(PackRule):

规则 说明
PackSeparately 每个文件单独打包
PackDirectory 同一文件夹的文件打成一个包
PackTopDirectory 收集器下顶级文件夹打成一个包
PackGroup 整个分组打成一个包
PackRawFile 处理为原生资源包

过滤规则(FilterRule):

规则 说明
CollectAll 收集所有资源文件
CollectScene 只收集场景文件
CollectPrefab 只收集预制体文件
CollectSprite 只收集精灵类型文件

4.5 实战:配置一个示例项目

假设我们的项目结构如下:

swift 复制代码
Assets/
└── GameRes/
    ├── Scenes/        场景资源
    ├── Prefabs/       预制体
    ├── UI/
    │   ├── Prefabs/   UI预制体
    │   └── Atlas/     UI图集
    ├── Audio/         音频
    └── Config/        配置表

推荐配置方案:

分组名 Collect Path Collector Type PackRule FilterRule AddressRule
Scene Assets/GameRes/Scenes MainAssetCollector PackSeparately CollectScene AddressByFileName
Prefab Assets/GameRes/Prefabs MainAssetCollector PackDirectory CollectAll AddressByFileName
UIPrefab Assets/GameRes/UI/Prefabs MainAssetCollector PackDirectory CollectPrefab AddressByFileName
UIAtlas Assets/GameRes/UI/Atlas MainAssetCollector PackDirectory CollectSprite AddressByFileName
Audio Assets/GameRes/Audio MainAssetCollector PackDirectory CollectAll AddressByFileName
Config Assets/GameRes/Config MainAssetCollector PackGroup CollectAll AddressByFileName

场景资源建议使用 PackSeparately,因为场景通常较大,单独打包有利于增量更新。


五、资源构建

配置完成后,就可以进行资源构建了。

打开构建窗口:Window → YooAsset → AssetBundle Builder

5.1 构建参数详解

参数 说明
Build Package 选择要构建的资源包裹
Build Pipeline 构建管线(见下文)
Build Output 构建输出目录,按平台自动划分
Build Version 资源包版本号
Clear Build Cache 清理构建缓存(不勾选则增量打包,速度更快)
Use Asset Depend DB 使用资源依赖数据库,大幅提升构建速度
Encryption Services 资源包加密类
Compression 压缩方式
File Name Style 输出文件名样式
Copy Buildin File Option 首包资源拷贝方式

构建管线选择

管线 说明 推荐场景
EditorSimulateBuildPipeline 编辑器模拟构建,不生成 Bundle 编辑器开发调试
BuiltinBuildPipeline 内置构建管线 Unity 2020 及以下
ScriptableBuildPipeline 可编程构建管线 Unity 2021.3+ 推荐
RawFileBuildPipeline 原生文件构建管线 非Unity识别的资源(如 FMOD bank 文件)

文件名样式

样式 说明
HashName 哈希值命名(最安全,推荐)
BundleName 资源包名
BundleName_HashName 资源包名 + 哈希值

首包资源拷贝方式

方式 说明
None 不拷贝任何文件
ClearAndCopyAll 清空后拷贝全部
ClearAndCopyByTags 清空后按标签拷贝
OnlyCopyAll 不清空,直接拷贝全部
OnlyCopyByTags 不清空,按标签拷贝

5.2 执行构建

  1. 选择 Build Package(如 DefaultPackage)
  2. 选择 Build Pipeline(推荐 ScriptableBuildPipeline)
  3. 填写 Build Version(如 1.0.0)
  4. 勾选 Use Asset Depend DB
  5. 选择 Compression(推荐 LZ4)
  6. 选择 File Name Style(推荐 HashName)
  7. 选择 Copy Buildin File Option(如 ClearAndCopyByTags,配合标签实现首包分包)
  8. 点击 构建 按钮

构建成功后,输出目录结构:

css 复制代码
BuildOutput/
└── [平台名]/
    └── [版本号]/
        ├── PackageManifest_DefaultPackage.version    版本文件
        ├── PackageManifest_DefaultPackage_xxx.hash   清单哈希
        ├── PackageManifest_DefaultPackage_xxx.json   清单(JSON格式,可读)
        ├── PackageManifest_DefaultPackage_xxx.bytes  清单(二进制格式,运行时使用)
        ├── xxx.bundle                                资源包文件
        └── PackageManifest_DefaultPackage_xxx.report 构建报告

5.3 资源加密(可选)

如果需要对资源包进行加密。以下是一个简单的 XOR 加密示例:

csharp 复制代码
/// <summary>
/// XOR 解密文件流,读取时自动对每个字节执行异或解密
/// </summary>
public class TestBundleStream : FileStream
{
    public const byte KEY = 64;

    public TestBundleStream(string path, FileMode mode, FileAccess access, FileShare share) : base(path, mode, access, share)
    {
    }
    public TestBundleStream(string path, FileMode mode) : base(path, mode)
    {
    }
    public override int Read(byte[] array, int offset, int count)
    {
        var index = base.Read(array, offset, count);
        for (int i = 0; i < array.Length; i++)
        {
            array[i] ^= KEY;
        }
        return index;
    }
}
/// <summary>
/// 流模式 XOR 加密器,对 TestRes3 目录的 Bundle 进行逐字节异或加密
/// </summary>
public class TestFileStreamEncryption : IBundleEncryptor
{
    public BundleEncryptResult Encrypt(BundleEncryptArgs fileInfo)
    {
        // 说明:对TestRes3资源目录进行加密
        if (fileInfo.BundleName.Contains("_testres3_"))
        {
            var fileData = File.ReadAllBytes(fileInfo.FilePath);
            for (int i = 0; i < fileData.Length; i++)
            {
                fileData[i] ^= TestBundleStream.KEY;
            }

            return new BundleEncryptResult(true, fileData);
        }
        else
        {
            return new BundleEncryptResult(false, null);
        }
    }
}

加密支持三种加载方式:

  • LoadFromFileOffset:通过文件偏移解密加载(性能最好)
  • LoadFromMemory:通过内存解密加载
  • LoadFromStream:通过文件流解密加载

六、资源部署

构建完成后,需要将补丁包部署到 CDN 服务器。

6.1 部署目录结构

python 复制代码
CDN/
├── android/
│   ├── v1.0/     APP版本(不是资源版本)
│   │   ├── PackageManifest_DefaultPackage.version
│   │   ├── PackageManifest_DefaultPackage_xxx.hash
│   │   ├── PackageManifest_DefaultPackage_xxx.bytes
│   │   └── xxx.bundle
│   └── v2.0/
└── iphone/
    ├── v1.0/
    └── v2.0/

关键理解v1.0 是 APP 版本而非资源版本。在不更换安装包的情况下,每次生成的补丁文件覆盖到同一 APP 版本目录下即可。第二次上传会覆盖第一次的版本记录文件。

6.2 本地测试

开发阶段可以在本地搭建 Web 服务器进行测试:

bash 复制代码
# 使用 Python 快速搭建本地服务器
python -m http.server 8080

将构建产物拷贝到服务器目录下,即可通过 http://127.0.0.1:8080/CDN/Android/v1.0 访问资源。


七、运行时初始化

这是最关键的环节,YooAsset 提供了多种运行模式,适配不同的业务场景。

7.1 初始化资源系统

csharp 复制代码
using YooAsset;

// 初始化资源系统(全局只需调用一次)
YooAssets.Initialize();

// 创建资源包对象
var package = YooAssets.CreatePackage("DefaultPackage");

// 设置为默认资源包(设置后可使用 YooAssets 的快捷加载接口)
YooAssets.SetDefaultPackage(package);

7.2 编辑器模拟模式(EditorSimulateMode)

适用场景:编辑器开发调试,不需要构建资源包即可模拟真实环境。

csharp 复制代码
private IEnumerator InitPackage()
{
    var buildResult = EditorSimulateModeHelper.SimulateBuild("DefaultPackage");
    var packageRoot = buildResult.PackageRootDirectory;
    // 创建资源包裹类
    var package = YooAssets.TryGetPackage("DefaultPackage");
    if (package == null)
    {
        package = YooAssets.CreatePackage("DefaultPackage");
    }
    InitializationOperation initializationOperation = null;
    var createParameters = new EditorSimulateModeParameters();
    initializationOperation = package.InitializeAsync(createParameters);
    yield return initializationOperation;
}

注意:该模式只在编辑器下生效,每次启动都会执行一次模拟构建,资源量大时会有卡顿。

7.3 单机运行模式(OfflinePlayMode)

适用场景:不需要热更新的单机游戏。

csharp 复制代码
private IEnumerator InitPackage()
{
    // 创建资源包裹类
    var package = YooAssets.TryGetPackage("DefaultPackage");
    if (package == null)
    {
        package = YooAssets.CreatePackage("DefaultPackage");
    }
    var fileSystemParams = FileSystemParameters.CreateDefaultBuildinFileSystemParameters();

    var createParameters = new OfflinePlayModeParameters();
    createParameters.BuildinFileSystemParameters = fileSystemParams;

    var initOperation = package.InitializeAsync(createParameters);
    yield return initOperation;

    if (initOperation.Status == EOperationStatus.Succeed)
        Debug.Log("资源包初始化成功!");
    else
        Debug.LogError($"资源包初始化失败:{initOperation.Error}");
}

7.4 联机运行模式(HostPlayMode)

适用场景:需要热更新的网络游戏,这是最常用的模式。

csharp 复制代码
private IEnumerator InitPackage()
{
    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 cacheFileSystemParams = FileSystemParameters.CreateDefaultCacheFileSystemParameters(remoteServices);
    var buildinFileSystemParams = FileSystemParameters.CreateDefaultBuildinFileSystemParameters();

    var createParameters = new HostPlayModeParameters();
    createParameters.BuildinFileSystemParameters = buildinFileSystemParams;
    createParameters.CacheFileSystemParameters = cacheFileSystemParams;

    var initOperation = package.InitializeAsync(createParameters);
    yield return initOperation;

    if (initOperation.Status == EOperationStatus.Succeed)
        Debug.Log("资源包初始化成功!");
    else
        Debug.LogError($"资源包初始化失败:{initOperation.Error}");
}

/// <summary>
/// 远端资源地址查询服务类
/// </summary>
private class RemoteServices : IRemoteServices
{
    private readonly string _defaultHostServer;
    private readonly string _fallbackHostServer;

    public RemoteServices(string defaultHostServer, string fallbackHostServer)
    {
        _defaultHostServer = defaultHostServer;
        _fallbackHostServer = fallbackHostServer;
    }

    string IRemoteServices.GetRemoteMainURL(string fileName)
    {
        return $"{_defaultHostServer}/{fileName}";
    }

    string IRemoteServices.GetRemoteFallbackURL(string fileName)
    {
        return $"{_fallbackHostServer}/{fileName}";
    }
}

7.5 获取资源版本(必须步骤)

初始化成功后,必须获取资源版本号:

csharp 复制代码
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($"资源版本:{packageVersion}");
    }
    else
    {
        Debug.LogError($"获取资源版本失败:{operation.Error}");
    }
}

7.7 更新资源清单(必须步骤)

获取版本号后,更新资源清单:

csharp 复制代码
private IEnumerator UpdatePackageManifest(string packageVersion)
{
    var package = YooAssets.GetPackage("DefaultPackage");
    var operation = package.UpdatePackageManifestAsync(packageVersion);
    yield return operation;

    if (operation.Status == EOperationStatus.Succeed)
    {
        Debug.Log("资源清单更新成功!");
    }
    else
    {
        Debug.LogError($"资源清单更新失败:{operation.Error}");
    }
}

7.8 完整初始化流程封装

将上述步骤封装为一个完整的初始化管理器:

csharp 复制代码
using System.Collections;
using UnityEngine;
using YooAsset;

public class YooAssetInitManager : MonoBehaviour
{
    [Header("CDN 配置")]
    [SerializeField] private string defaultHostServer = "http://127.0.0.1/CDN/Android/v1.0";
    [SerializeField] private string fallbackHostServer = "http://127.0.0.1/CDN/Android/v1.0";

    [Header("下载配置")]
    [SerializeField] private int downloadingMaxNumber = 10;
    [SerializeField] private int failedTryAgain = 3;

    private ResourcePackage _package;

    private void Start()
    {
        StartCoroutine(InitYooAsset());
    }

    private IEnumerator InitYooAsset()
    {
        // 1. 初始化资源系统
        YooAssets.Initialize();
        _package = YooAssets.CreatePackage("DefaultPackage");
        YooAssets.SetDefaultPackage(_package);

        // 2. 根据运行环境选择初始化模式
        InitializationOperation initOperation;

#if UNITY_EDITOR
        // 编辑器模拟模式
        var buildResult = EditorSimulateModeHelper.SimulateBuild("DefaultPackage");
        var editorParams = new EditorSimulateModeParameters();
        editorParams.EditorFileSystemParameters =
            FileSystemParameters.CreateDefaultEditorFileSystemParameters(buildResult.PackageRootDirectory);
        initOperation = _package.InitializeAsync(editorParams);
#else
        // 联机运行模式
        var remoteServices = new RemoteServices(defaultHostServer, fallbackHostServer);
        var hostParams = new HostPlayModeParameters();
        hostParams.BuildinFileSystemParameters =
            FileSystemParameters.CreateDefaultBuildinFileSystemParameters();
        hostParams.CacheFileSystemParameters =
            FileSystemParameters.CreateDefaultCacheFileSystemParameters(remoteServices);
        initOperation = _package.InitializeAsync(hostParams);
#endif

        yield return initOperation;
        if (initOperation.Status != EOperationStatus.Succeed)
        {
            Debug.LogError($"初始化失败:{initOperation.Error}");
            yield break;
        }

        // 3. 获取资源版本
        var versionOp = _package.RequestPackageVersionAsync();
        yield return versionOp;
        if (versionOp.Status != EOperationStatus.Succeed)
        {
            Debug.LogError($"获取版本失败:{versionOp.Error}");
            yield break;
        }

        // 4. 更新资源清单
        var manifestOp = _package.UpdatePackageManifestAsync(versionOp.PackageVersion);
        yield return manifestOp;
        if (manifestOp.Status != EOperationStatus.Succeed)
        {
            Debug.LogError($"更新清单失败:{manifestOp.Error}");
            yield break;
        }

        // 5. 下载更新资源
        yield return DownloadResources();

        Debug.Log("YooAsset 初始化完成!");
        // TODO: 进入游戏主逻辑
    }

    private IEnumerator DownloadResources()
    {
        var downloader = _package.CreateResourceDownloader(downloadingMaxNumber, failedTryAgain);

        if (downloader.TotalDownloadCount == 0)
        {
            Debug.Log("没有需要更新的资源");
            yield break;
        }

        Debug.Log($"需要下载:{downloader.TotalDownloadCount} 个文件," +
                  $"总大小:{downloader.TotalDownloadBytes / 1024f / 1024f:F2} MB");

        downloader.OnDownloadProgressCallback = OnDownloadProgress;
        downloader.OnDownloadErrorCallback = OnDownloadError;
        downloader.BeginDownload();
        yield return downloader;

        if (downloader.Status == EOperationStatus.Succeed)
            Debug.Log("资源下载完成!");
        else
            Debug.LogError("资源下载失败!");
    }

    private void OnDownloadProgress(int totalCount, int currentCount, long totalBytes, long currentBytes)
    {
        float progress = (float)currentCount / totalCount;
        Debug.Log($"下载进度:{progress:P0} ({currentCount}/{totalCount})");
    }

    private void OnDownloadError(string fileName, string error)
    {
        Debug.LogError($"下载失败:{fileName},错误:{error}");
    }
}

八、资源加载

初始化完成后,就可以加载资源了。YooAsset 提供了丰富的加载接口。

8.1 加载方法一览

方法 说明
LoadAssetAsync<T>() 异步加载资源对象
LoadAssetSync<T>() 同步加载资源对象
LoadSubAssetsAsync<T>() 异步加载子资源对象
LoadSubAssetsSync<T>() 同步加载子资源对象
LoadAllAssetsAsync<T>() 异步加载资源包内所有对象
LoadAllAssetsSync<T>() 同步加载资源包内所有对象
LoadSceneAsync() 异步加载场景
LoadRawFileAsync() 异步获取原生文件
`LoadRawFileSync()`` 同步获取原生文件

8.2 资源定位约定

未开启可寻址模式:location 代表资源完整路径

csharp 复制代码
package.LoadAssetAsync<AudioClip>("Assets/GameRes/Audio/bgMusic");

开启可寻址模式:location 代表可寻址地址(也支持完整路径)

csharp 复制代码
// 可寻址地址(AddressRule 为 AddressByFileName 时)
package.LoadAssetAsync<AudioClip>("bgMusic");

8.3 异步加载的三种方式

委托方式

csharp 复制代码
void Start()
{
    AssetHandle handle = package.LoadAssetAsync<AudioClip>("Assets/GameRes/Audio/bgMusic");
    handle.Completed += OnLoadCompleted;
}

void OnLoadCompleted(AssetHandle handle)
{
    AudioClip audioClip = handle.AssetObject as AudioClip;
}

协程方式

csharp 复制代码
IEnumerator Start()
{
    AssetHandle handle = package.LoadAssetAsync<AudioClip>("Assets/GameRes/Audio/bgMusic");
    yield return handle;
    AudioClip audioClip = handle.AssetObject as AudioClip;
}

Task 方式

csharp 复制代码
async void Start()
{
    AssetHandle handle = package.LoadAssetAsync<AudioClip>("Assets/GameRes/Audio/bgMusic");
    await handle.Task;
    AudioClip audioClip = handle.AssetObject as AudioClip;
}

8.4 预制体加载与实例化

csharp 复制代码
IEnumerator LoadPrefab()
{
    AssetHandle handle = package.LoadAssetAsync<GameObject>("Assets/GameRes/Panel/login");
    yield return handle;

    // 直接通过句柄实例化
    GameObject instance = handle.InstantiateSync();
    Debug.Log($"实例化对象:{instance.name}");
}

8.5 场景加载

csharp 复制代码
IEnumerator LoadScene()
{
    string location = "Assets/GameRes/Scene/Login";
    var sceneMode = LoadSceneMode.Single;
    var physicsMode = LocalPhysicsMode.None;
    bool suspendLoad = false;

    SceneHandle handle = package.LoadSceneAsync(location, sceneMode, physicsMode, suspendLoad);
    yield return handle;

    Debug.Log($"场景加载完成:{handle.Scene.name}");
}

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

8.6 子对象加载(图集精灵)

csharp 复制代码
IEnumerator LoadSubAssets()
{
    SubAssetsHandle handle = package.LoadSubAssetsAsync<Sprite>("Assets/GameRes/UI/Atlas/login");
    yield return handle;

    Sprite sprite = handle.GetSubAssetObject<Sprite>("spriteName");
    Debug.Log($"精灵名称:{sprite.name}");
}

8.7 原生文件加载

原生文件必须使用 RawFileBuildPipeline 构建:

csharp 复制代码
IEnumerator LoadRawFile()
{
    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();    // 文件路径
}

8.8 热更脚本加载

适用于 Lua、ILRuntime、HybridCLR 等热更方案,将热更 DLL 的后缀改为 .bytes

csharp 复制代码
IEnumerator LoadHotfixDll()
{
    AssetHandle handle = package.LoadAssetAsync<TextAsset>("Assets/GameRes/Hotfix");
    yield return handle;

    TextAsset textAsset = handle.AssetObject as TextAsset;
    // textAsset.bytes  二进制数据
    // textAsset.text   文本数据
}

8.9 通过标签获取资源信息

csharp 复制代码
void GetAssetsByTag(string tag)
{
    AssetInfo[] assetInfos = package.GetAssetInfos(tag);
    foreach (var assetInfo in assetInfos)
    {
        Debug.Log($"资源路径:{assetInfo.AssetPath}");
    }
}

九、资源卸载

正确的资源卸载是避免内存泄漏的关键。

9.1 释放资源句柄

csharp 复制代码
IEnumerator LoadAndRelease()
{
    AssetHandle handle = package.LoadAssetAsync<AudioClip>("Assets/GameRes/Audio/bgMusic");
    yield return handle;

    // 使用资源...
    AudioClip clip = handle.AssetObject as AudioClip;

    // 不再使用时释放句柄(减少引用计数)
    handle.Release();
}

重要 :每个加载方法都会返回一个资源句柄,不再使用时必须调用 Release(),否则会造成资源泄漏。

9.2 卸载未使用的资源

csharp 复制代码
// 卸载所有引用计数为零的资源包
// 建议在切换场景后调用,或使用定时器定期执行
private IEnumerator UnloadUnusedAssets()
{
    var package = YooAssets.GetPackage("DefaultPackage");
    var operation = package.UnloadUnusedAssetsAsync();
    operation.WaitForAsyncComplete(); // 支持同步等待
    yield return operation;
}

9.3 强制卸载所有资源

csharp 复制代码
// 谨慎使用!仅在合适的时机调用
private IEnumerator ForceUnloadAllAssets()
{
    var package = YooAssets.GetPackage("DefaultPackage");
    var operation = package.UnloadAllAssetsAsync();
    yield return operation;
}

9.4 场景卸载

csharp 复制代码
IEnumerator UnloadScene(SceneHandle sceneHandle)
{
    // 卸载成功后自动释放 handle 的引用计数
    var operation = sceneHandle.UnloadAsync();
    yield return operation;
}

十、资源热更新

联机运行模式下,资源热更新是核心能力。

10.1 创建下载器

YooAsset 提供三种下载器创建方式:

csharp 复制代码
// 1. 下载当前版本所有需要更新的资源
var downloader = package.CreateResourceDownloader(downloadingMaxNumber, failedTryAgain);

// 2. 按标签下载资源
var downloader = package.CreateResourceDownloader(new string[] { "level1", "level2" }, downloadingMaxNumber, failedTryAgain);

// 3. 按指定资源列表下载
AssetInfo[] assetInfos = ...;
var downloader = package.CreateBundleDownloader(assetInfos, downloadingMaxNumber, failedTryAgain);

参数说明:

  • downloadingMaxNumber:同时下载的最大文件数
  • failedTryAgain:下载失败重试次数

10.2 完整下载流程

csharp 复制代码
IEnumerator DownloadResources()
{
    var package = YooAssets.GetPackage("DefaultPackage");
    var downloader = package.CreateResourceDownloader(10, 3);

    // 没有需要下载的资源
    if (downloader.TotalDownloadCount == 0)
    {
        Debug.Log("已是最新版本");
        yield break;
    }

    // 显示下载信息
    int totalCount = downloader.TotalDownloadCount;
    long totalBytes = downloader.TotalDownloadBytes;
    Debug.Log($"需要下载 {totalCount} 个文件,共 {totalBytes / 1024f / 1024f:F2} MB");

    // 注册回调
    downloader.OnDownloadProgressCallback = (totalCount, currentCount, totalBytes, currentBytes) =>
    {
        float progress = (float)currentCount / totalCount;
        // 更新 UI 进度条
    };
    downloader.OnDownloadErrorCallback = (fileName, error) =>
    {
        Debug.LogError($"下载失败:{fileName},{error}");
    };
    downloader.OnDownloadFinishCallback = (succeed) =>
    {
        Debug.Log(succeed ? "下载完成" : "下载失败");
    };

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

    if (downloader.Status == EOperationStatus.Succeed)
    {
        Debug.Log("资源更新完成!");
    }
}

10.3 下载器合并

可以将同一 Package 下的多个下载器合并,避免重复下载:

csharp 复制代码
var downloader1 = package.CreateResourceDownloader("level_tag", 10, 3);
var downloader2 = package.CreateBundleDownloader(assetInfos, 10, 3);

// 合并下载器
downloader1.Combine(downloader2);

// 只需开启一个下载器
downloader1.BeginDownload();

10.4 检测资源是否需要下载

csharp 复制代码
bool isNeedDownload = package.IsNeedDownloadFromRemote("Assets/GameRes/Scene/Login");

十一、销毁资源包

在游戏退出或需要重新初始化时,需要销毁资源包:

csharp 复制代码
private IEnumerator DestroyPackage()
{
    var package = YooAssets.GetPackage("DefaultPackage");

    // 先销毁资源包
    DestroyOperation operation = package.DestroyAsync();
    yield return operation;

    // 然后移除资源包
    if (YooAssets.RemovePackage(package))
    {
        Debug.Log("资源包移除成功!");
    }
}

相关推荐
SmalBox1 天前
【节点】[Arctangent节点]原理解析与实际应用
unity3d·游戏开发·图形学
烛阴2 天前
Unity资源加载进化论:从AssetBundle到Addressables,一文带你吃透手游资源管理
前端·c#·unity3d
SmalBox2 天前
【节点】[Arctangent2节点]原理解析与实际应用
unity3d·游戏开发·图形学
烛阴3 天前
TEngine 入门系列(二):三件套环境搭建 -- Unity + TEngine + AI 助手
前端·c#·unity3d
SmalBox3 天前
【节点】[Arcsine节点]原理解析与实际应用
unity3d·游戏开发·图形学
SmalBox4 天前
【节点】[Arccosine节点]原理解析与实际应用
unity3d·游戏开发·图形学
SmalBox5 天前
【节点】[Truncate节点]原理解析与实际应用
unity3d·游戏开发·图形学
SmalBox6 天前
【节点】[Step节点]原理解析与实际应用
unity3d·游戏开发·图形学
SmalBox7 天前
【节点】[Sign节点]原理解析与实际应用
unity3d·游戏开发·图形学