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.png2.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_japublic 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 架构对比
| 维度 | Addressable | YooAsset |
|---|---|---|
| 厂商 | Unity 官方 | tuyoogame 团队 |
| 内核 | 资源管理框架 | 资源管理框架 |
| 上层抽象 | ResourceManager | YooAssets |
| 异步系统 | AsyncOperationHandle | AsyncOperationBase |
| 引用计数 | 自动(基于 handle) | 显式(基于 Release) |
| 缓存 | Resource Locator | IFileSystem |
| 加密 | 无内置 | 支持 |
| 断点续传 | 内置 | 内置 |
| 跨平台 | 完整 | 完整(含小游戏) |
| VFS 抽象 | 无 | 完整支持 |
| 性能 | 高 | 高 |
| 易用性 | ★★★★ | ★★★★ |
| 文档 | 丰富 | 中等 |
| 社区 | 庞大 | 活跃 |
| 中文支持 | 一般 | 完善 |
5.2 API 对比
| 功能 | Addressable | YooAsset |
|---|---|---|
| 加载资源 | LoadAssetAsync<T>(address) | LoadAssetAsync<T>(location) |
| 按标签加载 | LoadAssetsAsync<T>(label, callback) | 不直接支持(需遍历) |
| 加载场景 | LoadSceneAsync(address, mode) | LoadSceneAsync(name, mode) |
| 释放 | Release(handle) | op.Release() |
| 检查更新 | CheckForCatalogUpdates | RequestCheckVersionAsync |
| 更新目录 | UpdateCatalogs | RequestPackageManifestAsync |
| 加载进度 | handle.PercentComplete | op.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 | 中文文档完善 |
| 小游戏项目 | YooAsset | YooAsset 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 实践建议
- 新项目选型:根据团队熟悉度、是否需要小游戏、是否多语言决定
- 不轻易切换:AA ↔ YooAsset 切换成本高,选定后就坚持用
- 混合使用:理论上可行,但不推荐(增加复杂度)
- 关注性能:两者都需要关注内存、并发、构建时长
上一篇:Unity 原生 AssetBundle 实战
下一篇:资源组织与依赖管理实战
