10-03-YooAsset实战-Unity-Addressable-Assets实战

📅 2026/7/26 23:47:42 👁️ 阅读次数 📝 编程学习
10-03-YooAsset实战-Unity-Addressable-Assets实战

实战-Unity Addressable Assets实战

篇章:10-实战篇-从零搭建
状态:已完成
阅读时间:约 25 分钟


一、引言

1.1 本章在系列中的定位

Addressable Assets(简称 AA)是 Unity 官方推出的资源管理解决方案,目的是取代传统 AssetBundle。它的核心理念是"资源位置 + 资源标签"的解耦,让开发者只关心"我想要什么资源"而不必关心"资源在哪个包里"。

作为 Unity 官方的资源管理方案,AA 与 YooAsset 在设计上有许多相似之处。本章将系统对比 AA 与 YooAsset,帮助你深入理解 YooAsset 的设计取舍。

1.2 本章要解决的核心问题

  • Addressable Assets 的核心概念是什么?
  • AA 与 YooAsset 在架构上有何异同?
  • AA 的最佳实践是什么?
  • 什么场景下选 AA?什么场景下选 YooAsset?

1.3 阅读前置要求

  • 完成 10-01 YooAsset 项目搭建
  • 完成 10-02 Unity 原生 AB 实战
  • 了解 Unity 编辑器扩展基础

二、Addressable Assets 核心概念

2.1 安装

通过 Package Manager 安装:

Window → Package Manager → 搜索 "Addressables" → Install

安装后,菜单栏会出现Window → Asset Management → Addressables

2.2 核心概念

2.2.1 Address(地址)

Address是资源的唯一标识符,类似 YooAsset 的Location

// YooAsset var op = YooAssets.LoadAssetAsync<Sprite>("Assets/UI/Button.png"); // Addressable var op = Addressables.LoadAssetAsync<Sprite>("Button_Normal");

AA 的 Address 可以是:

  • 资源路径
  • 自定义标签
  • 资源 GUID
2.2.2 Group(组)

Group是资源的容器,类似 YooAsset 的 Group:

AA ├── Group_Default ← 默认组 │ ├── UI_Button_Normal.png │ ├── UI_Icon_Home.png │ └── ... ├── Group_Battle │ ├── Hero_Assassin.prefab │ ├── Skill_Fireball.prefab │ └── ... └── Group_Shared └── Atlas_Common.png
2.2.3 Label(标签)

Label是资源的"软分组",可以在运行时按标签批量加载:

Addressables.LoadAssetsAsync<Sprite>("ui_icons", null);
2.2.4 Catalog(目录)

AA 的 Catalog 类似 YooAsset 的 Manifest,记录所有资源与 Group 的映射。

2.3 Addressable 的工作流

[Editor 端] 1. 标记资源为 Addressable 2. 配置 Group 3. 构建(Build → New Build) [Runtime 端] 1. Addressables.InitializeAsync() 2. LoadAssetAsync(address) 3. Release(handle)

三、Addressable 实战

3.1 标记资源

步骤 1:开启 Addressable

Window → Asset Management → Addressables → Groups

步骤 2:标记资源

选中资源 → Inspector 中勾选Addressable

Sprite Icon_Home ├── Asset Name: Home ├── Address: icons/home └── Labels: [ui, icons]

3.2 配置 Group

// Group 配置示例 public class MyGroupConfig { public string GroupName = "Default Local Group"; public BundleNamingStyle Naming = BundleNamingStyle.NoHash; // 或 FileNameHash public bool IncludeInBuild = true; public bool UseAssetBundleCache = true; public bool UseAssetBundleCrc = true; }

3.3 加载资源

using UnityEngine.AddressableAssets; using UnityEngine.ResourceManagement.AsyncOperations; public class AddressableLoader { /// <summary> /// 同步获取句柄 /// </summary> public AsyncOperationHandle<Sprite> LoadSprite(string address) { return Addressables.LoadAssetAsync<Sprite>(address); } /// <summary> /// 异步加载 /// </summary> public async Task<Sprite> LoadSpriteAsync(string address) { var handle = Addressables.LoadAssetAsync<Sprite>(address); await handle.Task; return handle.Result; } /// <summary> /// 按标签批量加载 /// </summary> public async Task<List<Sprite>> LoadSpritesByLabel(string label) { var handle = Addressables.LoadAssetsAsync<Sprite>(label, null); await handle.Task; return new List<Sprite>(handle.Result); } /// <summary> /// 释放 /// </summary> public void Release<T>(AsyncOperationHandle<T> handle) { Addressables.Release(handle); } }

3.4 加载场景

public async Task LoadSceneAsync(string address) { var handle = Addressables.LoadSceneAsync(address, LoadSceneMode.Single); await handle.Task; } public void UnloadScene(AsyncOperationHandle<SceneInstance> handle) { Addressables.UnloadSceneAsync(handle); }

四、AA 高级特性

4.1 远端分发

public class RemoteDistribution { /// <summary> /// 配置远端 CDN /// </summary> public void SetupRemote() { // Profile 配置 var profile = AddressableAssetSettingsDefaultObject.Settings.profileSettings.GetProfile("Remote"); var remoteLoadPath = profile.GetValueByName("RemoteLoadPath"); var remoteBuildPath = profile.GetValueByName("RemoteBuildPath"); } /// <summary> /// 检查更新 /// </summary> public async Task CheckForCatalogUpdates() { var checkHandle = Addressables.CheckForCatalogUpdates(false); await checkHandle.Task; if (checkHandle.Result.Count > 0) { // 有更新,更新 Catalog var updateHandle = Addressables.UpdateCatalogs(checkHandle.Result, false); await updateHandle.Task; } Addressables.Release(checkHandle); } }

4.2 资源变体(Variants)

AA 支持按变体打包(如中/英/日语言包):

Group_UI ├── ui_textures_zh ├── ui_textures_en └── ui_textures_ja
public async Task<Sprite> LoadWithVariant(string baseAddress, string variant) { var address = $"{baseAddress}[{variant}]"; return await Addressables.LoadAssetAsync<Sprite>(address).Task; }

4.3 资源预加载

public class ResourcePreloader { public async Task PreloadByLabel(string label, int priority = 0) { var handle = Addressables.LoadAssetsAsync<Object>( label, null, Addressables.MergeMode.Union, releaseDependenciesOnFailure: false, priority: priority ); await handle.Task; } }

4.4 内存管理

public class AAMemoryManager { public void ReleaseAll() { // 释放所有引用 Resources.UnloadUnusedAssets(); } public long EstimateMemory() { // AA 不直接提供内存查询 // 需结合 Profiler return Profiler.GetTotalAllocatedMemoryLong(); } }

五、AA vs YooAsset 对比

5.1 架构对比

维度AddressableYooAsset
厂商Unity 官方tuyoogame 团队
内核资源管理框架资源管理框架
上层抽象ResourceManagerYooAssets
异步系统AsyncOperationHandleAsyncOperationBase
引用计数自动(基于 handle)显式(基于 Release)
缓存Resource LocatorIFileSystem
加密无内置支持
断点续传内置内置
跨平台完整完整(含小游戏)
VFS 抽象完整支持
性能
易用性★★★★★★★★
文档丰富中等
社区庞大活跃
中文支持一般完善

5.2 API 对比

功能AddressableYooAsset
加载资源LoadAssetAsync<T>(address)LoadAssetAsync<T>(location)
按标签加载LoadAssetsAsync<T>(label, callback)不直接支持(需遍历)
加载场景LoadSceneAsync(address, mode)LoadSceneAsync(name, mode)
释放Release(handle)op.Release()
检查更新CheckForCatalogUpdatesRequestCheckVersionAsync
更新目录UpdateCatalogsRequestPackageManifestAsync
加载进度handle.PercentCompleteop.Progress
取消加载Release(handle)op.Cancel()
实例化InstantiateAsync(address)自己实现

5.3 详细对比

5.3.1 异步操作
// Addressable var handle = Addressables.LoadAssetAsync<Sprite>("icon"); await handle.Task; var sprite = handle.Result; // YooAsset var op = YooAssets.LoadAssetAsync<Sprite>("icon"); await op.Task; var sprite = op.Result;

差异:AA 的 handle 需要手动 Release,YooAsset 的 op 同样需要 Release。两者 API 高度相似。

5.3.2 批量加载
// Addressable:按 Label 加载 var handles = Addressables.LoadAssetsAsync<Sprite>( "ui_icons", sprite => Debug.Log($"Loaded: {sprite.name}") ); // YooAsset:按 Tag 或遍历加载 // YooAsset 没有直接的批量加载 API // 需自己遍历资源

差异:AA 的 Label 系统更强大,YooAsset 需要自己遍历。

5.3.3 多 Package
// Addressable:通过不同的 Group 实现 // 一个 Group = 一个 Package // 运行时通过 Address 自动路由 // YooAsset:内置多 Package var package = await YooAssets.LoadPackageAsync("ActivityPackage"); var op = package.LoadAssetAsync<Sprite>("activity_icon");

差异:YooAsset 的多 Package 更显式,适合复杂业务场景。

5.4 选型建议

场景推荐理由
全新 Unity 项目AA 或 YooAsset 均可两者都能满足
中文项目YooAsset中文文档完善
小游戏项目YooAssetYooAsset VFS 专为小游戏设计
海外项目AA 或 YooAsset都支持
已有 AA 项目继续用 AA迁移成本高
已有 YooAsset 项目继续用 YooAsset迁移成本高
多 Package 复杂业务YooAsset内置多 Package
单机游戏AA简单易用
联机游戏两者均可看团队偏好

六、AA 性能优化

6.1 内存优化

// 1. 及时释放 public void OnDestroy() { if (_handle.IsValid()) Addressables.Release(_handle); } // 2. 预加载控制 public async Task PreloadCritical(string label) { var handle = Addressables.LoadAssetsAsync<Object>(label, null); await handle.Task; _criticalHandles.Add(handle); } // 3. 异步释放 public async Task UnloadCritical() { foreach (var handle in _criticalHandles) { Addressables.Release(handle); } _criticalHandles.Clear(); Resources.UnloadUnusedAssets(); }

6.2 加载优化

// 1. 并发控制 public async Task BatchLoad(List<string> addresses, int maxConcurrent = 10) { var semaphore = new SemaphoreSlim(maxConcurrent); var tasks = addresses.Select(async address => { await semaphore.WaitAsync(); try { return await Addressables.LoadAssetAsync<Sprite>(address).Task; } finally { semaphore.Release(); } }); return await Task.WhenAll(tasks); }

6.3 构建优化

// 1. 压缩配置 public class BuildConfig { public void Optimize() { // 在 Group 配置中 BundleMode = BundlePackMode.PackTogetherByLabel; IncludeAddressInCatalog = false; IncludeGUIDInCatalog = false; IncludeLabelsInCatalog = true; } }

七、AA 踩坑记录

7.1 常见问题

问题原因解决方案
资源找不到Address 拼写错误启用Addressable Asset Settings → Send Diagnostic Event
内存泄漏忘记 Release配合 weak reference
加载失败构建时未包含检查 Group 的 Include In Build
跨域错误CDN 配置错误检查 Profile 中的 RemoteLoadPath
场景加载慢资源未预加载PreloadDependenciesAsync
Hash 变化资源被修改启用Use Asset Bundle Cache

7.2 调试技巧

// 开启诊断事件 [InitializeOnLoadMethod] public static void EnableDiagnostics() { Addressables.ResourceManager.ResourceProviders .OfType<IResourceProvider>() .ToList() .ForEach(p => { if (p is ResourceProvider rp) rp.DiagnosticEvent += (op, type, ctx) => Debug.Log($"[AA] {type}: {ctx}"); }); }

7.3 监控面板

public class AAMonitor { public void ShowStatus() { Debug.Log($"正在下载: {Addressables.WebRequestOverride}"); Debug.Log($"已加载 Bundle: {GetBundleCount()}"); Debug.Log($"总内存: {Profiler.GetTotalAllocatedMemoryLong() / 1024 / 1024} MB"); } }

八、迁移到 YooAsset

8.1 迁移评估

评估项内容
AA 资源数统计需要迁移的资源
API 使用面列出所有 LoadAssetAsync 调用
跨域资源是否有 CDN 资源
自定义 Provider是否有自定义 ResourceProvider

8.2 迁移工具

public class AAToYooAssetMigrator { public void MigrateAddressables() { // 1. 读取 AA 配置 var aaSettings = AddressableAssetSettingsDefaultObject.Settings; // 2. 创建 YooAsset Group foreach (var group in aaSettings.groups) { var yooGroup = new PackRule { Name = group.Name, Filter = "Assets/...", // 根据 Group 配置生成 }; } // 3. 转换 Address foreach (var entry in aaSettings.GetAllAssets()) { // entry.address → YooAsset Location } } }

九、总结

9.1 本章要点回顾

  • AA 核心:Address + Group + Label + Catalog
  • AA 工作流:Editor 标记 → Build → Runtime Load
  • AA 优势:Unity 官方、Label 系统、丰富的文档
  • AA 痛点:没有原生加密、引用计数相对隐式
  • YooAsset 优势:VFS(小游戏)、中文支持、多 Package

9.2 与前后章节的关联

  • 前章:10-02 介绍了 Unity 原生 AssetBundle
  • 本章:对比 Addressable,介绍 AA 与 YooAsset 的异同
  • 后章:10-04 将介绍资源组织与依赖管理

9.3 实践建议

  1. 新项目选型:根据团队熟悉度、是否需要小游戏、是否多语言决定
  2. 不轻易切换:AA ↔ YooAsset 切换成本高,选定后就坚持用
  3. 混合使用:理论上可行,但不推荐(增加复杂度)
  4. 关注性能:两者都需要关注内存、并发、构建时长

上一篇:Unity 原生 AssetBundle 实战
下一篇:资源组织与依赖管理实战