什么是 Addressables
Addressables 是 Unity 官方推出的动态资源管理系统,它允许你通过一个简单的"地址"字符串来加载资源,无论该资源是打包在应用本地,还是存放在远程服务器上。
一句话概括:Addressables = 官方在 AssetBundle 之上封装的一套"按地址加载资源"的完整方案,帮你自动处理打包、依赖、加载、卸载、远程更新等繁琐细节。
下面从"为什么需要它"讲起,逐步深入到能真正上手开发。
一、为什么需要 Addressables
在 Addressables 出现之前,Unity 加载资源主要有两种方式,它们各有明显缺陷。
1.1 Resources 文件夹
把资源放进 Assets/Resources,然后用 Resources.Load 加载。
|
|
优点:简单,不用打包配置。
缺点(也是它被官方劝退的原因):
| 问题 | 说明 |
|---|---|
| 全部打进包 | Resources 里所有资源都会被无条件打进安装包,导致包体臃肿 |
| 无法热更新 | 资源在构建后就固定了,不能在线替换 |
| 启动变慢 | 游戏启动时要加载整个 Resources 索引,文件夹越大越慢 |
| 内存僵硬 | 缺少引用计数,加载后难以精确卸载 |
| 路径字符串硬编码 | 拼错只有运行时才报错,难以维护 |
Unity 官方文档明确建议:新项目不要使用 Resources 文件夹,改用 Addressables。
1.2 AssetBundle(AB 包)
AssetBundle 是 Unity 底层的资源打包格式,功能强大但非常"原始"。
痛点:
- 要自己规划哪些资源打进哪个包
- 要自己处理依赖关系(A 包依赖 B 包,必须先加载 B)
- 要自己管理加载 / 卸载和引用计数
- 要自己实现版本号、Catalog、下载、校验
- 极易出现"资源重复打包"“内存泄漏"“依赖丢失"等问题
1.3 Addressables 的定位
Addressables 把上面这些脏活累活封装起来:
|
|
三种方式对比:
| 维度 | Resources | AssetBundle | Addressables |
|---|---|---|---|
| 上手难度 | 极低 | 高 | 中低 |
| 按需加载 | 差 | 好 | 好 |
| 依赖管理 | 自动但僵硬 | 手动 | 自动 |
| 热更新 | 不支持 | 支持(自己实现) | 支持(内置) |
| 引用计数 | 无 | 手动 | 内置 |
| 包体控制 | 差 | 好 | 好 |
| 官方推荐 | 否 | 底层可选 | 是 |
二、核心概念
理解这几个名词,就理解了 Addressables 的骨架。
| 概念 | 英文 | 说明 |
|---|---|---|
| 地址 | Address | 加载资源用的字符串 key,如 "Player"、"UI/Login" |
| 资源条目 | Asset Entry | 标记为 Addressable 的一个资源 |
| 组 | Group | 资源的集合,一个组会打成一个或多个 Bundle |
| 标签 | Label | 给资源打的标记,可按标签批量加载 |
| 配置档案 | Profile | 一组路径变量(本地/远程构建与加载路径) |
| 目录 | Catalog | 记录所有地址、依赖、Bundle 位置的清单文件 |
| 加载句柄 | AsyncOperationHandle | 异步操作的句柄,用于获取结果和释放 |
| 资源引用 | AssetReference | 可序列化的资源引用,避免直接硬引用 |
它们的关系:
|
|
关键点:地址(Address)不是资源路径。 它是一个逻辑名字,你完全可以把 Assets/Prefabs/Player.prefab 的地址设为 "Player"。地址可以任意修改,而不用改代码里的资源位置。
三、安装与初始化
3.1 安装包
- 打开
Window > Package Manager - 选择
Unity Registry - 搜索
Addressables,点击Install
或直接编辑 Packages/manifest.json:
|
|
不同 Unity 版本对应的 Addressables 版本不同,2021 LTS 常用 1.19~1.21,Unity 6 开始有 2.x。以包管理器推荐的版本为准。
3.2 初始化设置
安装后打开 Window > Asset Management > Addressables > Groups。
第一次打开会提示创建 Addressable 设置文件,它会生成:
|
|
这个文件夹必须提交到版本控制(它是配置,不是产物)。
3.3 Play Mode 脚本
Groups 窗口顶部有一个 Play Mode Script 下拉框,决定编辑器里运行时的加载方式:
| 模式 | 作用 | 使用场景 |
|---|---|---|
Use Asset Database (fastest) |
直接读 Asset 数据库,不打包 | 日常开发,迭代最快 |
Simulate Groups (advanced) |
模拟打包,但资源仍从 Asset 读 | 测试打包布局和依赖 |
Use Existing Build (requires built groups) |
用上次真实构建的 Bundle | 测试真机行为、远程下载 |
新手最常见的困惑:“我刚标记了资源,运行时却加载不到”——多半是这里选了
Use Existing Build但没重新 Build。开发期请用Use Asset Database。
四、快速上手
4.1 把一个资源标记为 Addressable
两种方式:
- 选中资源,在 Inspector 顶部勾选
Addressable - 直接把它拖进
Addressables Groups窗口
标记后,资源会出现在 Groups 窗口中,并自动生成一个地址(默认就是资源路径)。
建议:手动把地址改成语义化的短名字,例如 "Player" 而不是 "Assets/Prefabs/Player.prefab"。
4.2 加载资源
|
|
也可以不用 async/await,用回调:
|
|
4.3 直接实例化
如果只是想生成一个 Prefab 实例,用 InstantiateAsync 更省事(它内部帮你加载 + 实例化):
|
|
ReleaseInstance会销毁这个由 Addressables 实例化的对象,并递减引用计数。不要用普通的Destroy,否则引用计数不会释放,造成内存泄漏。
4.4 加载图标 / 文本等非 GameObject 资源
|
|
五、加载 API 详解
5.1 主要加载接口
| API | 说明 |
|---|---|
LoadAssetAsync<T>(key) |
加载单个资源,返回 AsyncOperationHandle<T> |
LoadAssetsAsync<T>(key, callback, mergeMode) |
按 key 或标签批量加载 |
InstantiateAsync(key, parent, ...) |
加载并实例化 Prefab |
LoadSceneAsync(key, loadMode) |
加载场景 |
DownloadDependenciesAsync(key) |
只下载依赖(远程资源) |
GetDownloadSizeAsync(key) |
查询需要下载的大小 |
5.2 AsyncOperationHandle
AsyncOperationHandle<T> 是 Addressables 异步操作的核心对象。
|
|
WaitForCompletion()会卡住主线程直到加载完成,会造成掉帧。只在初始化等确实需要同步的场景使用,普通游戏逻辑一律用异步。
5.3 批量加载
|
|
MergeMode(合并模式)用于多个 key 的结果合并方式:
| 模式 | 含义 |
|---|---|
UseFirst |
使用第一个 key 的结果 |
Union |
并集(默认),所有 key 的结果都包含 |
Intersection |
交集,只包含被所有 key 命中的资源 |
5.4 引用计数与释放
Addressables 用引用计数管理资源生命周期,这是最容易出错的地方。
规则:
- 每
Load一次,引用计数 +1 - 每
Release一次,引用计数 -1 - 引用计数归零时,资源才真正从内存卸载
|
|
实例化的释放规则不同:
|
|
口诀:
Load配对Release;InstantiateAsync配对ReleaseInstance。
六、资源组与打包策略
6.1 Group 的关键设置
在 Groups 窗口选中一个组,Inspector 里可配置:
| 设置 | 说明 |
|---|---|
Include In Build |
是否参与构建(远程组本地测试时可能临时关掉) |
Bundle Mode |
一个组打成几个 Bundle |
Compression |
压缩方式(Uncompressed / LZ4 / LZMA) |
Content Update Restriction |
内容更新限制策略 |
Asset Load Mode |
加载粒度(按需 / 整包) |
6.2 Bundle Mode(打包方式)
| 模式 | 说明 | 适用 |
|---|---|---|
Pack Together |
整个组打成一个 Bundle | 资源少、总是一起用 |
Pack Separately |
每个资源单独一个 Bundle | 大资源、需要独立更新 |
Pack Together By Label |
按标签分 Bundle | 按用途分类 |
6.3 Compression(压缩方式)
| 方式 | 特点 | 场景 |
|---|---|---|
Uncompressed |
不压缩,加载最快,包最大 | 本地频繁加载 |
LZ4 |
分块压缩,可随机读取,速度快 | 本地资源首选 |
LZMA |
压缩率最高,需整包解压,加载慢 | 远程下载首选(省流量) |
经验:本地组用
LZ4,远程组用LZMA。远程包下载到本地缓存后会解压。
6.4 打包策略建议
- 按使用场景分组:把总是一起出现的资源放同一组(如"登录界面"“新手村”)
- 控制粒度:Bundle 太多会导致 IO 次数多、Catalog 大;太少会导致加载冗余、更新不灵活
- 大资源拆开:视频、大图单独成包,避免牵连无关资源重新下载
- 避免重复:多个 Bundle 依赖同一资源时,Addressables 会自动把公共依赖提到共享 Bundle,但要检查
Check Duplicate Bundle Dependencies
七、远程资源与热更新
这是 Addressables 最有价值的部分,也是游戏上线后运营的核心能力。
7.1 Profile 与路径变量
打开 Window > Asset Management > Addressables > Profiles,可以看到路径变量。
内置变量(不同版本略有差异):
| 变量 | 含义 |
|---|---|
Local.BuildPath |
本地资源的构建输出目录 |
Local.LoadPath |
运行时本地资源的读取路径 |
Remote.BuildPath |
远程资源的构建输出目录 |
Remote.LoadPath |
运行时远程资源的下载地址(如 CDN) |
默认值类似:
|
|
7.2 把组切到远程
选中 Group,在 Content Packing & Loading > Build & Load Paths 里把:
Build Path设为RemoteLoad Path设为Remote
这样该组就会打包到远程目录,运行时从 Remote.LoadPath 下载。
7.3 构建与部署
Window > Asset Management > Addressables > Groups- 菜单
Build > New Build > Default Build Script - 产物在
ServerData/[BuildTarget]/下:
|
|
- 把
ServerData里的内容上传到 CDN / 服务器,与Remote.LoadPath对应。
7.4 运行时检查与下载
|
|
7.5 热更新的原理与限制
- 本地 Catalog 更新:游戏自带的 Catalog 版本旧了,启动时从服务器拉最新 Catalog,就能知道新资源在哪
- 资源内容更新:只需上传改动过的 Bundle + 新 Catalog,玩家按需下载
- 不能更新代码:Addressables 只能热更资源。逻辑代码的热更需要 Lua(xLua/ToLua)、ILRuntime 等方案配合
Content Update 工作流(增量更新):
- 首次发布:
Build > New Build - 内容改动后:先把现有构建结果设为基线
Build > Update a Previous Build - 选择上一次构建的
addressables_content_state.bin - 生成增量 Bundle
这个
addressables_content_state.bin文件必须妥善保存,每次增量更新都基于它,丢了就只能全量重发。
八、依赖与引用
8.1 自动依赖分析
Addressables 会自动分析资源依赖,并把公共依赖提取到共享 Bundle。
例如 Player.prefab 引用了 Weapon.mat,若把 Player.prefab 设为 Addressable,Weapon.mat 会被自动包含进依赖 Bundle。
重要副作用:如果你只想加载 Weapon.mat,它可能因为依赖关系被连带打包。此时应显式把 Weapon.mat 也标记为 Addressable,并设置好地址,避免加载 Player 才能拿到材质。
8.2 用 AssetReference 替代硬引用
问题:如果某个 MonoBehaviour 脚本里直接 public GameObject prefab; 并用 Inspector 拖拽,那么该资源会被硬引用,无法动态加载,也会被强制打进包。
解决:用 AssetReference。
|
|
AssetReference 家族:
| 类型 | 用途 |
|---|---|
AssetReference |
通用 |
AssetReferenceGameObject |
GameObject/Prefab |
AssetReferenceSprite |
Sprite |
AssetReferenceAtlasedSprite |
图集 Sprite |
AssetReferenceT<T> |
泛型自定义 |
好处:
- 不产生硬引用,资源可动态加载
- Inspector 中只能选择 Addressable 资源,避免误拖
- 可序列化,支持 ID 引用,资源改名不影响
九、标签(Label)的使用
标签用于给资源分类,便于批量加载和更新管理。
添加标签:在 Groups 窗口中选中资源条目,Inspector 里 Labels 一栏添加,如 character、ui_icon。
按标签加载:
|
|
典型用途:
- 一次性加载某类资源(所有语言文本、所有 UI 图标)
- 按标签打包(
Pack Together By Label) - 资源更新时按标签定位
标签是字符串,没有命名空间强制约束,建议团队约定命名规范,如
ui_、char_、audio_前缀。
十、场景加载
|
|
要点:
- 场景也要先标记为 Addressable 才能加载
- 单场景模式会卸载当前场景再加载新场景
- 叠加模式适合把 UI、加载页作为独立场景
- 卸载用
UnloadSceneAsync,不要用普通SceneManager.UnloadSceneAsync混用,否则引用计数对不上
十一、常见问题与陷阱
11.1 内存泄漏
症状:反复进出某界面,内存持续上涨。
原因:加载了资源却忘记 Release / ReleaseInstance。
|
|
11.2 编辑器能跑,真机报错
原因:Play Mode Script 选了 Use Asset Database,编辑器不打包;真机需要真实 Bundle。
解决:发布前务必用 Build > New Build 构建,并用 Use Existing Build 验证一次。
11.3 地址拼错
地址是字符串,拼错只有运行时才报 Failed。
缓解:
- 地址集中定义为常量或配置
- 使用
AssetReference代替字符串 - 统一命名规范
|
|
11.4 资源被重复打包 / 包体异常大
排查:Window > Asset Management > Addressables > Analyze,运行以下分析:
Check Duplicate Bundle Dependencies:检查重复依赖Check Bundle Duplicate Assets:检查 Bundle 中的重复资源
11.5 Destroy 与 ReleaseInstance 混用
InstantiateAsync 创建的对象若用 Destroy 销毁,引用计数不归零 → 泄漏。反之,若 trackHandle: false,则只能用 Destroy。要么统一用 Addressables 管理,要么统一手动管理,不要混。
11.6 远程资源加载失败
常见原因:Remote.LoadPath 地址错误、服务器未开启跨域(CORS)、HTTPS 证书问题、Catalog 未更新。先在浏览器直接访问 .bundle 地址验证。
十二、性能与最佳实践
加载性能
- 优先异步,避免
WaitForCompletion卡主线程 - 预加载:在加载界面提前
DownloadDependenciesAsync/LoadAssetAsync - 使用
DownloadDependenciesAsync提前把远程包下到本地缓存
内存
- 严格的
Load/Release配对,界面关闭时清理 - 用 Profiler 的
Addressables模块查看引用计数 - 大资源用完立即释放,不要长时间驻留
打包
- 本地 LZ4,远程 LZMA
- 按使用场景分组,避免大杂烩组
- 定期跑 Analyze 检查重复依赖
- 固定 Catalog 命名,做好版本管理
工程规范
- 地址用常量管理,不散落魔法字符串
- 优先
AssetReference而非硬引用 - 封装一个加载管理器,统一处理句柄生命周期
一个简单的加载管理器示例:
|
|
十三、完整实战流程
从零到跑通一个加载的完整步骤:
|
|
最小可运行示例:
|
|
十四、面试高频问答
14.1 Addressables 和 AssetBundle 的关系?
Addressables 是构建在 AssetBundle 之上的更高层抽象。底层仍然打包成 Bundle,但 Addressables 帮你自动完成依赖分析、引用计数、Catalog 管理、远程下载、版本更新等。你可以把它理解成"官方维护的、带引用计数的 AssetBundle 管理器”。
14.2 Addressables 和 Resources 的区别?
| Resources | Addressables | |
|---|---|---|
| 打包 | 全部无条件打进包 | 按组按需打包 |
| 热更新 | 不支持 | 支持 |
| 卸载 | 无引用计数 | 引用计数精确卸载 |
| 路径 | 固定文件夹 | 任意地址,可远程 |
| 官方推荐 | 否 | 是 |
14.3 为什么加载了资源一定要 Release?
Addressables 用引用计数管理内存。Load/Instantiate 使计数 +1,Release/ReleaseInstance 使计数 -1,只有归零时资源才卸载。不 Release 会导致计数永远大于 0,资源常驻内存,即内存泄漏。
14.4 Bundle Mode 有哪几种?
Pack Together:整组一个 BundlePack Separately:每个资源一个 BundlePack Together By Label:按标签分组打包
14.5 三种压缩方式的区别?
Uncompressed:不压缩,加载最快,体积最大LZ4:分块压缩,支持随机读取,速度与体积平衡,适合本地LZMA:压缩率最高,需整包解压,加载慢但省流量,适合远程下载
14.6 Play Mode Script 三种模式的区别?
Use Asset Database:直接从编辑器资源数据库加载,不打包,最快Simulate Groups:模拟打包,仍从数据库读,用于测试打包布局Use Existing Build:使用真实构建的 Bundle,最接近真机
14.7 如何实现资源热更新?
- 启动时
CheckForCatalogUpdates检查 Catalog 更新 UpdateCatalogs拉取新 CatalogGetDownloadSizeAsync查询下载大小DownloadDependenciesAsync下载依赖- 正常加载资源
资源改动后只需上传增量 Bundle + 新 Catalog。注意:Addressables 只能热更资源,代码热更需要 Lua / ILRuntime 等。
14.8 什么是 Catalog?
Catalog 是 Addressables 生成的清单文件(catalog.json),记录了所有地址到资源、依赖关系、Bundle 位置和哈希的映射。运行时通过它把"地址"解析为"具体要加载的 Bundle 和资源”。它是远程更新能否生效的关键。
14.9 为什么建议用 AssetReference 而不是直接引用?
直接用 public GameObject 会产生硬引用,导致资源被强制打进包且无法动态卸载。AssetReference 通过 ID 序列化引用,不产生硬引用,资源可动态加载/释放,且只能选择 Addressable 资源,更安全。
14.10 如何避免 Bundle 重复依赖?
- 用
Analyze > Check Duplicate Bundle Dependencies检测 - 把公共依赖单独成组
- 合理划分 Group,避免多个组独立引用同一大资源
- 关注
Include in Build与依赖提取设置
十五、学习路线建议
| 阶段 | 目标 | 重点内容 |
|---|---|---|
| 入门 | 能加载一个 Prefab | 安装、标记地址、InstantiateAsync、ReleaseInstance |
| 熟练 | 掌握异步与内存 | 句柄、引用计数、批量加载、标签 |
| 进阶 | 远程与分组 | Profile、Group 配置、Compression、Build |
| 高级 | 热更新与工程化 | Catalog 更新、增量 Build、加载管理器封装 |
| 实战 | 上线运营 | CDN 部署、版本管理、性能分析、内存泄漏排查 |
推荐练习:
- 把一个
Resources.Load的项目改成 Addressables - 搭建本地远程服务器(
http-server或 Pythonhttp.server)测试远程加载 - 做一个带"检查更新 → 显示下载大小 → 下载进度 → 进入游戏"的启动流程
- 用 Unity Profiler 观察 Release 前后的内存变化
本文系统梳理了 Unity Addressables 的核心概念与实际使用,从 Resources/AssetBundle 的痛点讲起,覆盖加载 API、资源组、远程热更新、依赖管理与最佳实践。示例基于 Unity 2021 LTS + Addressables 1.21 编写,不同版本 API 基本兼容。学习时建议边看边在项目中动手实验,尤其要亲手验证"引用计数与释放"这一最容易踩坑的部分。