6.总结
6.2 核心要点速览
AssetBundle 是什么
- 可以把它理解成按目标平台打出来的、带压缩的「资源包」:里面装的是美术与数据向的内容,不是「再塞一份工程源文件」那种概念。
- 常见会打进包里的东西:模型、贴图、Prefab、音效、材质球等;托管的 C# 程序集逻辑不当作 AssetBundle 里的「资源条目」来理解,原文把「C# 代码」划在 AB 资产讲解范围之外。
相对 Resources,为什么要用 AB
- 资源组织:比起把什么都丢进
Resources那条管线,AB 更符合「按需拆分、按包发布」的思路,版本和资源边界更好管。 - 包体与首包:能打压缩包、拆分内容,有助于控制初次安装体积;Resources 往往倾向整进包、全搜得到,体积与裁剪策略都不如 AB 灵活。
- 热更新链条:热更语境下常见组合是:运行时拉取 AB 包(以及业务里常见的 Lua 等脚本载体)做资源或逻辑的增补替换;只动内置
Resources很难支撑「上线后持续换资源」的节奏。
Resources 加载与 AB 加载的常见对比
| 对比点 | Resources | AssetBundle |
|---|---|---|
| API | 走 Resources.Load 一类接口 |
走 AssetBundle 装载、再 LoadAsset 等流程 |
| 放哪 | 资源必须在工程的 Resources 目录规则下 |
理论上路径很自由;原文举例常用 Application.streamingAssetsPath 或 Application.persistentDataPath |
| 内容形态 | 更偏「工程里就位的一堆单资源」 | 更像是打好的资源集合 / 包,一次构建对应一批资源的二进制结果 |
编辑器里怎么打 AB:Browser 与资源归包
- 工具:早年可自研编辑器扩展打包,现在多用 Asset Bundle Browser;低版本常见从 Package Manager 装,高版本往往列表里不再有,可走 Addressables 路线,或从 GitHub 导入 AssetBundles-Browser 工程(导入报错时删掉包内 Samples 示例 往往就好),也可以在
Packages/manifest.json里手写com.unity.assetbundlebrowser版本让编辑器去拉包。 - 资源怎么进包:在 Inspector 底部给资源指定 包名 / 变体,示例包名如
model、icon;可多选一批图塞进同一个包。列表没刷新时点 Browser 上的 Refresh。 - 和脚本的边界:不能把 C# 当 AB「内容」塞进去(编译型托管代码);Prefab 本质是序列化数据,会记下挂了哪些脚本,打进包的是 Prefab 数据,脚本程序集仍按平常方式进包,别理解成「把
.cs打进 AB」。
Build 页常用开关
| 项 | 作用 | 原文口径 |
|---|---|---|
| Build Target | 打哪一个平台 | Windows / iOS / Android 等;换平台要重打 |
| Output Path | AB 输出目录 | 与清空、拷贝等选项联动 |
| Clear Folders | 构建前是否清空输出目录 | 和 Force Rebuild 不同:后者重打已存在的包,不会顺带删掉「已经不打的悬空包」 |
| Copy To StreamingAssets | 是否把结果复制到 StreamingAssets |
与运行时 LoadFromFile(Application.streamingAssetsPath + …) 的路径习惯一致 |
| Compression | 不压缩 / LZMA / LZ4 | LZMA 体最小但要取包内单项时往往得整包解压;LZ4 体积略大但按需解压,内存友好,原文倾向 LZ4 |
| Exclude Type Information | 包里不写类型信息 | 不建议勾,否则类型元数据缺失易埋雷 |
| Ignore Type Tree Changes | 增量构建时忽略类型树变化 | 不建议勾 |
| Append Hash | 包名后附哈希 | 便于版本区分、CDN 缓存策略 |
| Strict Mode | 任一步报错则整体失败 | 建议开,早暴露问题 |
| Dry Run | 干跑 / 预演式构建 | 先跑校验、少踩坑;是否完全不落盘以工具版本为准,按团队流程选用 |
构建若勾了清空目录 + 拷贝 StreamingAssets,Unity 可能弹窗问是否删除目标目录与 StreamingAssets 里旧文件,按流程点确认即可。打完后若在 Project 窗口里没看到 StreamingAssets 更新,右键 Refresh 一下。
Configure / Inspect
- Configure:看工程里每个 AB 塞了哪些资源。
- Inspect:把已打好的 AB 文件或目录丢进去,读内部信息(验包、对依赖很有用)。
磁盘上多出来的文件分别是什么
- 输出目录名若叫
PC,会多出一个与目录同名、看起来像「没亲手建过的包」的 PC:原文把它当主包 / 总控——放了关键索引、依赖关系等;真正业务向的model、icon才是你按包名打出去的内容。 - 无扩展名:一般是 AB 数据本体;**
.manifest:记录资源信息、依赖、版本等元数据的旁路清单**,和同名数据文件成对出现。
运行时加载与卸载要点
- 路径:示例用
Application.streamingAssetsPath + "/" + bundleName,前提是构建时拷进过StreamingAssets,或你自己把包部署到别的可读路径再给绝对/相对路径。 - 同一路径同一 AB 不要
LoadFromFile两次:会直接抛错(原文引了英文报错);工程上常用 字典缓存已打开的AssetBundle引用,并配套 卸载策略,练习题也是这个方向。 - 从包里掏资源:
LoadAsset只写字符串容易撞上同名不同类型;泛型重载 C# 里好用,但 Lua 绑定往往吃不到泛型;示例里对 Cube 用LoadAsset("Cube", typeof(GameObject)) as GameObject兼顾可读与脚本侧。 - 异步:
LoadFromFileAsync→yield return到AssetBundleCreateRequest,再用assetBundle.LoadAssetAsync(name, type)→yield return到AssetBundleRequest,最后从asset强转成Sprite等。协程里 Type 别传错,否则后面as全废。 - 卸载:
Unload(false)/UnloadAllAssetBundles(false)在示例里是「卸包壳、先留着场景里已经实例化出来的对象」;Update里演示UnloadAllAssetBundles(true)是另一类语义对照,记住 bool 管不管已经Instantiate出来的那份,别背反。
拆分 AB 之后为什么会丢材质
- 根因:Prefab 在 model 里,材质却进了 icon 时,运行期只
LoadFromFile("model")再Instantiate,GPU 能画 mesh,但材质那条 Asset 还没进内存,表现就是洋红 / 丢材质。 - 手工修法:再对依赖包执行
LoadFromFile("icon"),不用改LoadAsset名字,只要依赖 AB 也在内存里,引用就能对上。 - 分包里怎么记依赖:材质没在手选包名里也会进别的包时,Browser 里依赖边会自己画出来;把材质硬塞进 icon 且与 model 无自动依赖边时,就不会帮你「连带」进 model 包——依赖长什么样纯看打包划分。
主包、Manifest 与自动跟依赖
- 主包仍是那只与输出文件夹同名的包(如 PC)。先
LoadFromFile(… + 主包名),再用固定资源名AssetBundleManifest取出清单对象:main.LoadAsset<AssetBundleManifest>("AssetBundleManifest")。 GetAllDependencies("model"):返回 model 依赖的其它包名(示例里会打出 icon)。返回值里不含 “model” 自己,所以循环里只加载数组中的包还不够,**务必再对目标包LoadFromFile("model")**,才能LoadAsset("Cube", …)。- 加载顺序:主包 → 读清单 → 按数组把依赖包逐个打开 → 打开目标业务包 → 再取资源。把这段封装成函数后,依赖顺序由清单驱动,不必在业务代码里写死包名先后。
AssetBundleManager 封装思路
| 块 | 干什么 |
|---|---|
主包 + AssetBundleManifest |
单独字段缓存;首次用依赖时再 LoadFromFile,Manifest 从主包取固定名资源。 |
Dictionary<string, AssetBundle> |
防重复 LoadFromFile;键是打包时的 AB 名字符串,和 GetAllDependencies 返回值同一套。 |
LoadAssetBundleDependencies |
先确保主包与清单,再 GetAllDependencies,字典里没有才 Load 并加入。 |
LoadAssetBundleTarget |
打开请求的那只业务包,同样走字典去重。 |
LoadAssetBundleTargetAndDependencies |
先依赖、后目标,再从字典里拿目标包做 LoadAsset / LoadAssetAsync。 |
LoadAssetBundleResource 系列 |
同步三条重载 + 异步协程 + UnityAction;**GameObject 自动 Instantiate**,其它 Object 子类直接返回加载结果。 |
UnloadAssetBundle / ClearAssetBundle |
单包 Unload(false) 并 Remove;全清时 UnloadAllAssetBundles(false),字典清空,主包与 Manifest 引用也置空,下次重新拉主包。 |
主包进不进字典:原文实现里主包单独挂在字段上,依赖与业务包走字典;卸载全清时三类状态一起归零,避免 Manifest 悬在旧主包里。
小结记忆
- AB = 平台相关、可压缩的资源包;记清「资源侧能打什么」和「托管代码走别的热更链路」,别把所有热更都理解成「往 AB 里塞 C#」。
- 选型口诀:要强管理、可拆包、可热更、可控首包——往 AB 思路靠;只放永不更新的小内置资源才偶尔考虑 Resources。
- 打包侧:
Clear Folders与Force Rebuild语义别混;压缩默认往 LZ4 想;输出目录里同名主包多承担索引与依赖,无扩展名文件与.manifest成对。 - 加载侧:
LoadFromFile缓存防双开;LoadAsset注意 Lua 不吃泛型时的typeof+as;Unload(bool)的 true/false 直接决定实例化物体要不要跟包一起带走,对照示例里false与空格true两段。 - 依赖链:材质进别的包就只加载 Prefab 会照样丢材质;Manifest 给出依赖名数组后,依赖包 + 目标包都要进内存;
GetAllDependencies不包含目标包自己。 - 管理器:字典键与 AB 名一致;
TargetAndDependencies= 先扫依赖再打开目标;主包与清单单独字段,和字典里的业务包分仓。
6.3 面试题精选
基础题
1. AssetBundle 一般指什么样的东西?里面通常装什么、不装什么?
题目
用一两句话说明 AssetBundle 在 Unity 里是什么形态的内容,并说明常见包含与不包含哪些东西。
深入解析
- 原文把它定义成特定目标平台的、经过压缩的资源归档,类比「压缩包」是为了形状上别和「普通文件夹资源」搞混。
- 会装进去的:模型、贴图、Prefab、音频、材质等数据与资源对象。
- 刻意排除在「AB 里装的资产」叙述之外的:C# 托管代码——练习题里把「资源、Lua 脚本打包成 AB 做热更」和「减少包体」并列为作用,回答时别说成「把
.cs当普通资源塞进 AB 就代表脚本热更完成了」,概念上要分清资源包和脚本方案的分工。
答题示例
AssetBundle 是打出来的、跟平台相关的压缩资源包,用来装模型贴图 Prefab 音频材质这类内容。
它面向的是资源侧资产,不把 C# 程序集当成往里塞的那类「资源条目」;逻辑更新要另说一套方案。
参考文章
- 1.AB包的基本概念和作用
2. Build 时 LZMA 和 LZ4 差别在哪,为什么更倾向 LZ4?
题目
结合打包选项,说明两种压缩在「取包内单个资源」时的典型行为差异。
深入解析
- LZMA:压缩率最好,但原文强调要摸到包里某一个资源,往往得先把整个 AB 解压开来,时间和内存尖峰都难看。
- LZ4:体积比 LZMA 稍大一点,却是 「用哪个资源就解压哪块」 的风格,内存压力小,和热更新、边玩边下场景更合拍,所以原文把它标成建议默认优先考虑那一档。
- 不压缩:加载最快、包最大,线上一般不会当首选。
答题示例
LZMA 最省磁盘,但取单个资源经常意味着整包先解压,代价大。
LZ4 牺牲一点体积换按需解压,内存和行为更可控,所以原文更推荐日常使用。
参考文章
- 2.生成AB包资源文件
3. 为什么只加载了 Prefab 所在 AB,实例化后会出现材质丢失?
题目
结合分包规则,说明丢材质的典型原因和最小修复手段。
深入解析
- Prefab 与材质不在同一 AB:引用关系仍在序列化数据里,但材质所在 AB 没加载时,运行期找不到那张
Material,shader / 贴图链断掉,Unity 常用洋红提示。 - 修复:对承载材质的依赖 AB 再
LoadFromFile,或改用下一条 Manifest 自动批量加载依赖;不是改Instantiate参数能解决的。
答题示例
Prefab 在 model,材质在 icon,只开 model 时材质资源没进内存,Instantiate 出来就丢材质。
把 icon 也 Load 进来,或按 Manifest 把依赖包先扫一遍,再实例化。
参考文章
- 4.AB包的依赖包
进阶题
1. 和 Resources 相比,用 AssetBundle 管资源主要赢在哪几条?
题目
从目录约束、API、内容组织、包体与更新里挑重点,对比 Resources 与 AssetBundle。
深入解析
- 目录:Resources 必须把资源放进
Resources;AB 生成的包路径约束弱,部署到 StreamingAssets、持久化目录或 CDN 都可,便于模拟真实发版。 - API:两套加载入口完全不同,别背混。
- 组织:练习题写得很直白——AB 是打好的资源集合,Resources 更像工程内就位的零散资源;拆包、增量、依赖关系都建立在「包」这一层。
- 包体与热更:AB 可压缩、可拆分,利于减小首包;热更时换包或增量拉资源,比动内置 Resources 现实得多。
答题示例
Resources 只能从 Resources 目录 Load,API 固定;AB 是打出来的包,路径灵活,常用 StreamingAssets 或 persistentDataPath。
组织上 AB 是一坨打好的资源集合,Resources 多是工程里的单资源;包体上可以打压缩、拆分,线上资源迭代和热更也是 AB 那套更顺。
参考文章
- 1.AB包的基本概念和作用
2. 同一个 AssetBundle 连续 LoadFromFile 两次会发生什么,工程上怎么防?
题目
说明报错现象,并给出与练习题一致的管理思路。
深入解析
- Unity 侧会把同文件的 AB 视为已在内存里占坑,再
LoadFromFile一次会直接报错,原文贴了英文提示大意:同名 AB 已加载。 - 防法没有魔法:维护「包名 → 已加载 AssetBundle」映射,二次请求直接返回缓存引用;一旦引入缓存,就必须有对称的
Unload/ 版本切换清理,否则会泄漏或拉到脏数据。
答题示例
第二次 LoadFromFile 会直接抛异常,提示同名 AB 已加载。
用字典记已经打开的句柄,二次加载走缓存;同时要配卸载钩子,热更换包时才知道什么时候真正 Unload。
参考文章
- 3.使用AB包资源文件
3. 主包里的 AssetBundleManifest 一般怎么用,GetAllDependencies 之后还要做什么?
题目
按原文步骤说明:加载主包、取清单、拉依赖、再取业务资源。
深入解析
- 主包名与输出目录名一致;
LoadAsset<AssetBundleManifest>("AssetBundleManifest")资源名固定。 GetAllDependencies(业务包名)得到依赖包名字符串数组,循环LoadFromFile只解决「别人」;业务包自身要再LoadFromFile(业务包名)才能对该包LoadAsset。- 这样写可以戒掉手写依赖顺序,和 Browser 里看到的依赖边一致。
答题示例
先 Load 主包,取出 Manifest,对目标包名调 GetAllDependencies,把返回里的每个依赖包 Load 一遍。
数组里不含目标包自己,所以要再 Load model,然后才能从 model 里 LoadAsset 出 Cube。
参考文章
- 4.AB包的依赖包
深度题
1. 「减小包体」和「热更新」用 AssetBundle 时,各自在工程上大致怎么用?
题目
结合项目经验或上文结论,说明 AB 如何帮助控制安装体积,以及如何接入资源热更(脚本侧点到即可)。
深入解析
- 包体:打 AB 时选择压缩、把非首包内容拆出去,首装包只带最小集合;Resources 往往大量内置,裁剪空间小。
- 热更:运行时从指定路径或网络拿到新的或增量的 AB,加载后与本地版本协同;原文练习题把脚本热更和 Lua 等脚本打包成 AB 放在同一句话里,答题时承认「逻辑与资源两条线」即可,除非面试官追问到具体框架。
答题示例
包体侧通过打 AB、开压缩、把可后载内容拆包,首装只带必须的;资源热更是上线后拉 AB 替换或增补。
脚本热更在工程里常与 Lua 等载体配套;别把所有热更都说成「往 AB 里塞 C#」。
参考文章
- 1.AB包的基本概念和作用
2. AssetBundle.Unload 和 UnloadAllAssetBundles 的布尔参数到底控制什么?
题目
解释 false 与 true 对包里资源和场景实例的影响,并说明示例里为何同时演示两种写法。
深入解析
- 参数语义在原文里被口述成:是否连「场景里已经加载/实例化出来的那份对象」也一起卸掉。
false路线:更多是在释放 AB 压缩块与内部表,已Instantiate的物体还活着,适合短暂换手还没想好要不要删实例的阶段;代价是托管对象与 AB 的 bookkeeping 容易不同步,后续若还要从同包再加载要记得引用别悬空。true路线:从该 AB 出来的实例也干掉,更狠、更干净,适合玩家切大关、整包资源作废一类场景,但要防止 UI、池化对象还在用旧引用。- 文末示例在
Start里Unload(false)、Update里空格键触发UnloadAllAssetBundles(true),就是用对照实验帮助你记 bool 两端的差别。
答题示例
bool 决定卸包时是否把从该 AB 加载出来并在场景里的实例也一起干掉。
false 更像「先关压缩档案但物体暂留」,true 是连根拔起;示例里 Start 用 false、空格用 true 就是在对比这两种力度。
参考文章
- 3.使用AB包资源文件
3. AssetBundleManager 里为什么要「先 LoadDependencies 再 LoadTarget」,字典和主包各管什么?
题目
对照原文封装,说明加载顺序、字典键含义、主包与清单为何单独存放。
深入解析
- 顺序:依赖 AB 若晚于业务包加载,实例化时照样缺资源;所以 API 写成 先
GetAllDependencies扫一遍并LoadFromFile,再打开目标包,和资源实际引用方向一致。 - 字典:缓存 **已打开的
AssetBundle**,键用 打包时的包名字符串;重复请求同包不会二次LoadFromFile,与「同一 AB 文件不要重复LoadFromFile」的约束配套。 - 主包 + Manifest:单独字段保存;清单对象不从字典取,Clear 时和字典一并清空,避免主包句柄还留着、Manifest 却指向过期构建。
答题示例
先加载依赖再打开目标包,否则 Prefab 引用到的材质包可能还没进内存。
字典按包名缓存 AssetBundle;主包挂字段里专门喂 Manifest,全清时字典和主包指针一起扔掉,免得混版本。
参考文章
- 4.AB包的依赖包
- 5.AB包管理器
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com