
这个系列写到这里前几篇我们把Asset Group划分、Addressable Assets条目的设置思路都过了一遍也是时候碰一碰最让人心里打鼓的环节了——构建Build。说实话在我接触过的Unity团队里能把加载和释放写顺的人不算少但能把Addressables的构建讲明白的人真不多。很多人点完Build按钮之后就盯着Console看日志刷屏构建成功就继续干活一旦遇到构建卡住、报错、产物不对、远程资源404这类问题就完全没了排查思路。更常见的情况是开发期一切正常打正式包才发现包体莫名膨胀或者玩家手机上资源加载不出来。这篇内容就是专门针对构建这个环节做的完整梳理适合已经跑通Addressables基础Demo、准备把它用到真实项目里的Unity开发者。我会从构建的本质、构建脚本选择、完整实操步骤、产物结构、CI集成、内容更新策略这几个角度展开把构建前前后后的逻辑摊开讲清楚。1. 构建的本质把分组配置翻译成运行时加载地图1.1 一次构建到底做了什么很多人对Addressables有一个误解觉得它是Unity新推出来替代AssetBundle的独立加载系统。实际上Addressables并不是绕开了AssetBundle它是在AssetBundle之上做了一层自动化管理把传统手工打包、记录依赖、写加载路径这些繁琐工作收敛到了可视化的Group配置里。构建这个步骤本质上就是一次编译把你配置的Asset Group规则、条目列表、资源依赖关系连同Shader、Prefab、Texture这些资源本身一起打包成若干个AssetBundle文件再生成一张记录哪个Key对应哪个Bundle、哪个Bundle依赖哪个Bundle的Catalog索引。运行时的Addressables初始化第一步就是读取这个Catalog后续的加载请求才能在几十个bundle里快速定位资源。具体拆开看一次完整的Default Build大致包含这么几个环节收集所有被标记为Addressable的资源条目解析它们的依赖图谱。根据每个Group的打包策略Bundle Mode、压缩格式、分组大小决定bundle如何切分。计算每个bundle的CRC、Hash生成Addressables的Catalog数据。将需要本地加载的bundle拷贝到StreamingAssets目录把远程加载的bundle输出到ServerData目录。生成构建报告Build Report供开发者排查资源归属和包体大小。我遇到过一个团队把整个项目几百个Prefab全部丢进一个Group里然后用默认的打包策略构建。结果跑一次构建要二十多分钟包体里的重复贴图多到离谱。原因就是单个bundle把所有公共依赖比如UI框架的图集全部内联了一遍每个Group都被撑得巨大。这就是没有理解分组配置会被构建翻译成具体的bundle产物导致的。1.2 为什么AssetBundle老手也容易翻车如果你之前直接用过BuildPipeline.BuildAssetBundles接口对bundle的切分逻辑会比较敏感但用Addressables时反而容易栽跟头。原因在于传统手写打包是自己控制每一个bundle的边界而Addressables的自动依赖分析会把资源按依赖链重新归档。你以为某个Prefab只依赖一张贴图它却因为引用了公共Shader、公共材质、公共图集把一整套资源都拉进了同一个bundle。这在编辑器里用Asset Database模式跑的时候完全看不出来因为编辑器加载的是源Asset文件不走bundle逻辑。一旦切到真机或者用Existing Build模式跑才发现加载一个简单界面要连带加载几十MB的图集依赖。打个比方分组配置就好比是点菜时跟后厨说我要这几道菜而构建是后厨实际配菜装盘的过程。后厨会考虑哪些配菜是几道菜共用的把它们单独切好也可能为了省事把共用配菜直接塞到其中一道菜的盘子里。你要是不清楚后厨的配菜逻辑光看菜单分组配置是猜不到最终装盘结果的。所以构建环节一定要主动去理解而不是把它当成一个黑盒按钮。2. 构建脚本三兄弟选错一个效率差三倍2.1 Play Mode Scripts的三种模式到底怎么选Addressables在编辑器里跑预览时有Play Mode Scripts设置位于Addressables的Preferences窗口里。三个选项分别是Use Asset Database、Simulate Groups、Use Existing Build很多新手不知道它们之间的差别随手选了一个就开始开发结果踩了一堆坑。Use Asset Database这种模式下编辑器不会打任何bundle加载资源时直接走AssetDatabase。它的最大优势是刷新快改代码、改Prefab、改Shader不用重新构建适合纯逻辑开发阶段。缺点是它完全屏蔽了bundle依赖和加载路径的模拟你在这儿测试通过的代码到了真机上很可能因为加载路径配置错误而直接抛异常。Simulate Groups它会用上一次构建生成的Catalog数据来模拟分组行为但不会真的加载bundle文件。这个模式最大的价值在于验证分组划分和加载策略比如你可以通过它确认某个资源到底归属于哪个group、加载时会触发哪些依赖但它也无法覆盖bundle序列化和读取路径的问题。Use Existing Build直接使用上一次构建出来的bundle产物加载流程和真机几乎一致。这个模式最适合验证构建结果本身或者在真机上复现问题之前先在编辑器里排查一遍。代价是每次修改了代码或资源都需要重新构建迭代速度明显变慢。我给团队的建议是日常编码用Use Asset Database涉及分组调整、路径配置验证时切换到Simulate Groups到了出包前的联调阶段再切到Use Existing Build。别指望一种模式通吃整个开发周期那是给自己埋雷。2.2 Build Script默认脚本和自定义脚本的边界在Groups窗口点开Build按钮下拉菜单里会有New Build和Update Previous Build。New Build里的Default Build Script就是默认的完整构建流程日常出包用这个就够了。很多人一看Addressables支持自定义Build Task就急着造轮子想把构建报告推送、上传CDN、版本号生成全部塞进去。我的看法是项目规模没到那个程度之前默认脚本是最稳的选择它经过了官方和社区大量项目验证踩坑成本最低。那什么时候才值得动自定义构建脚本我总结了几类典型需求需要在构建完成后把ServerData目录里的远程bundle自动上传到CDN或对象存储而不是靠人工手动拷贝。需要在构建产物里注入版本信息、渠道标识、加密盐值等自定义数据。需要对接公司内部的CI系统在构建前后执行额外的资源校验、命名检查等步骤。Addressables提供了IBuildTask接口理论上可以重新编排整个构建任务流但说实话这个门槛不算低。以我自己的经验大部分团队其实只需要在现有构建流程的末尾挂一个回调把产物搬运和上传做好就够了不必从头重写构建管线。你可以在编辑器脚本里监听构建结束事件或者干脆在一个静态方法里先调用AddressableAssetSettings.BuildPlayerContent()再执行自定义上传逻辑。3. 完整构建实操从分组检查到产物验证3.1 构建前必查的分组清单我见过不少人在Groups窗口里一顿操作猛如虎结果构建完才发现这个资源没勾上Addressable、那个资源被引用了却不在任何组里。构建之前最好按下面几项做个快速自检能省去后面一大半排查时间。第一确认所有运行时需要加载的资源都被标记成了Addressable。你可以在Groups窗口的任意列表里检查Addressable列是否已勾选或者直接在Project窗口选中某个资源看Inspector面板。一个容易被忽略的情况是一些纯代码动态加载的Prefab没有做成Addressable条目导致运行时报KeyNotFoundException。第二排查掉不需要参与打包的资源。这里说的不参与构建有两类一类是根本没有被任何Addressable条目引用、也不在依赖链上的资源这类资源构建时不会被打包但也可能因为被某一个条目间接引用而意外进入bundle需要通过构建报告仔细查另一类是资源虽然存在于工程里但被显式排除或归属于非Addressable的文件夹这类资源要确保不会在运行时被Addressables加载否则真机上是找不到的。第三检查每个Group的打包设置。尤其是Bundle Mode和压缩格式Local分组一般用LZ4压缩优点是加载时解压开销低Remote分组可以考虑LZMA包体会更小但下载后首次解压耗时较长。如果你的远程资源包形态比较大又想兼顾下载体积和加载速度LZ4往往比LZMA更合适因为LZMA的解压会阻塞调用线程。3.2 Profile配置本地路径和远程路径必须分开看Profile是Addressables里一组命名变量用来描述构建路径和加载路径。默认情况下Local组和Remote组各有一套路径配置。构建产物最终落到哪里运行时从哪个路径加载资源都取决于这些变量。很多人会犯一个低级错误把LocalLoadPath改成了自己想的某个本地路径结果构建完跑到真机上发现资源加载不出来。原因很简单Local的内容和StreamingAssets目录强相关。编辑器里构建本地bundle时最终会拷贝到Assets/StreamingAssets/aa这个目录下真机启动时也是从这个目录读取。你如果把加载路径指到了别的地方编辑器里因为路径宽松可能没问题真机上就直接404了。Remote的情况更要注意。项目的RemoteLoadPath默认是http://localhost/[BuildTarget]这种占位地址这只是一个示意不是让你真的用。真实项目里必须改成你实际的CDN地址并且要匹配RemoteBuildPath的设置。构建是把资源放到ServerData/平台目录下upload这一步通常由你自己的流水线脚本搬运但什么时候传、传到哪个子目录需要和RemoteLoadPath严格对应起来。否则客户端拿着远程catalog去下载bundle地址对不上就必然失败。还有一个细节是多个平台构建时路径变量里的[BuildTarget]会自动替换成对应的平台名比如StandaloneWindows64。建议保持这个约定不要手写死平台名否则切换平台构建时很容易覆盖掉之前的产物。3.3 执行一次Default Build并验证关键节点操作路径其实很简单Addressables Groups窗口右上角点Build按钮选择Build New Build然后用Default Build Script跑一次。但我建议大家不要只看最后弹窗说Build Complete就完事要验证几个关键节点。构建完成后打开Project窗口看Assets/StreamingAssets/aa目录下是否出现了平台子目录里面是否有bundle文件。再看项目根目录下的ServerData文件夹确认Remote产物是否生成catalog的json和hash文件是否存在。这两个节点都能对上说明构建主流程走通了。接着写一小段验证代码随便加载一个你预期的资源Key确保能正确实例化。这里我建议用Addressables的Debug窗口来观察加载过程打开Window Asset Management Addressables Event Viewer能看到每次加载请求命中了哪个bundle、加载耗时多少、引用计数变化情况。这一步能帮你确认运行时用的确实是刚构建出来的产物而不是编辑器缓存里的旧数据。3.4 构建Log里那些必须认识的警告第一次构建时Console会刷出大量日志很多人直接被淹没。其实Addressables的构建日志结构是有规律的以--- Addressables Build ---开头后面跟着构建步骤、bundle大小、依赖分析结果、警告和错误。重点关注的警告包括资源重复被打进多个bundle、某个Addressable条目找不到对应Asset、引用链中断导致资源丢失等。这些警告不会让构建失败但往往预示着后续运行时问题。比如常见的Asset is included in multiple bundles警告就说明同一个资源出现在两个bundle里既浪费包体空间又可能在运行时出现重复加载。这种警告出现后你应该去检查对应的Group划分把公共资源单独拆到一组。4. 构建产物怎么看Catalog、Bundle与不参与构建的资源4.1 ServerData目录里到底放了些什么构建完成后如果你用的是默认ProfileRemote产物会落在项目根目录的ServerData/平台名/下。打开这个目录你会看到一堆文件我用一个示例结构来说明ServerData/ └── StandaloneWindows64/ ├── catalog_2024.05.18.10.30.00.json ├── catalog_2024.05.18.10.30.00.hash ├── settings.json ├── assets_0.bundle ├── assets_1.bundle └── shaders.bundle其中catalog开头的json文件就是前面说的加载地图。它记录了所有Addressable Key与bundle文件的对应关系还包含每个Asset的GUID、InternalId、依赖项等关键信息。以.json结尾的hash文件是这份catalog的校验值用于内容更新时判断是否需要下载新的catalog。settings.json记录了一些构建相关的元数据一般不需要手动去改运行时也不会直接读。bundle文件就是真正包含资源的AssetBundle。它们的命名规则受Group设置里Bundle Naming Mode影响默认可能是哈希命名比如assets_0.bundle这种形式。打开bundle文件没用那是二进制格式编辑器或真机运行时才会解析。这里必须反复强调一句不要手动修改catalog和bundle里的任何内容。Addressables加载时会校验catalog的hash一旦发现不匹配轻则警告重则直接拒载。很多人在遇到资源更新问题时第一反应是手动改一下bundle里的配置这绝对是一条不归路。4.2 资源没更新、Key找不到的时候先查这几处构建产物相关的运行时报错最常见的两个是KeyNotFoundException和加载路径找不到对应bundle。排查顺序建议从产物本身开始。先看catalog里到底有没有这个Key。用文本编辑器打开最新的catalog json文件搜一下你的资源Key如果搜不到说明这个资源没有被正确标记为Addressable或者它所在的分组被排除出构建了。如果搜到了再看它对应的bundle文件是否存在于正确目录。有一种很典型的坑你在编辑器里用Simulate Groups模式测试catalog是旧的构建产物是新的两边不一致运行时就会拿到错误的映射关系。另一个坑是分组里标记了某个资源但资源文件已经从工程里被删了导致构建时无法解析对应的Assetcatalog里会残留一个悬空引用。这种问题通常会在构建日志里以警告形式出现。排查时需要找到是哪个Group里的哪个条目失效了把无效条目移除再重新构建。至于不参与构建的资源大家要有一个认知Addressables构建时并不是工程里所有资源都会被打进bundle。只有从Addressable条目出发、通过依赖分析能触达的资源才会进入产物。一个资源如果既不是Addressable条目也不被任何Addressable资源引用那它再大也不会出现在bundle里。反过来说如果你发现某个资源莫名其妙被打包了那一定是某个Addressable资源引用了它这时候就要顺着依赖链找到源头考虑是否需要调整分组策略来避免非预期依赖被打进去。4.3 构建报告是排查包体膨胀的唯一正道Addressables的Build菜单下面有一个Build Report每次构建后都会生成一份构建报告。这份报告记录了每个bundle的资源列表、依赖关系、大小是排查为什么包体这么大的最好工具。我自己的排查经验是打开报告后先按bundle大小排序挑出最大的几个bundle逐个展开看里面包含了哪些资源。很多时候你会发现某个bundle里混进了一批和它本身毫无逻辑关系的公共图集或者音效文件这就是依赖分析把它们绑到一起的结果。然后你需要回到Groups窗口确认这些公共依赖是否应该单独一组、并被多个Group以引用的方式共享而不是被自动内联到某个Group里。构建报告的另一个用途是查看资源重复情况。同一张图被十个Prefab引用如果分配不当它可能被复制到十个bundle里每份都是一份独立拷贝。这种情况在传统AssetBundle手动打包时代也常见Addressables只是把问题从手动控制转移到了分组策略不学习构建报告的话这个问题一样会找上门。5. CI与多平台构建命令行跑构建的四条经验5.1 命令行构建的最小示例把Addressables构建接入CI/CD核心其实就是一条命令以batch模式启动Unity执行一个Editor脚本方法在里面调用AddressableAssetSettings.BuildPlayerContent()。我写了个最小示例using UnityEditor; using UnityEditor.AddressableAssets; using UnityEditor.AddressableAssets.Settings; public static class BuildExecutor { public static void BuildAddressables() { AddressableAssetSettings settings AddressableAssetSettingsDefaultObject.Settings; if (settings null) { UnityEngine.Debug.LogError(Addressables settings not found.); EditorApplication.Exit(1); return; } AddressableAssetSettings.BuildPlayerContent(); EditorApplication.Exit(0); } }对应的命令行调用是Unity.exe -batchmode -quit -projectPath C:\YourProject \ -executeMethod BuildExecutor.BuildAddressables \ -logFile build.log \ -buildTarget StandaloneWindows64这里要强调一个点-buildTarget参数不是Addressables独有的它会同时影响Unity编辑器的构建目标。构建Addressables之前最好先确认当前项目的目标平台已经切换成了实际要发布的平台。否则编辑器处于默认的Standalone平台时构建出来的产物平台目录就不对接在后面的Player构建会重新生成一份平台对应的产物同时白白多跑一次构建。我在实际团队里见过一种浪费CI流水线每天跑两次Addressables构建一次是单独构建Addressables资源并上传CDN另一次是构建Player包时又把Addressables自动构建了一遍。结果一天生成两套内容CDN上的catalog和客户端里的不一致排查了整整一下午。后来统一为只在构建Player时执行Addressables构建单独的资源构建只用于内容更新的发布场景。5.2 构建机上绕不开的三个环境问题命令行构建和本地编辑器构建最大的区别就是构建机上没有一个完整可视化的Unity编辑器环境因此在配置上有些坑要注意。第一个坑是杀毒软件和系统安全扫描。Windows服务器上如果开了Windows Defender实时扫描Unity构建时大量读写Library目录会被反复拦截构建时间能慢上一倍不止。我遇到过一台构建机同样的项目本地构建12分钟服务器上跑了35分钟最后排查下来发现就是Defender在扫描临时文件。把项目目录和Unity缓存目录加入白名单之后构建时间立刻恢复正常。第二个坑是Unity许可证激活。batchmode下Unity不会自动弹激活窗口如果你用的是个人激活证书在新机器上第一次跑构建会直接卡死在进程启动阶段日志里只有一行Licensing错误。这个需要在构建机上先手动打开一次Unity编辑器完成激活或者配置好证书相关的环境变量。很多团队在迁移CI服务器时都会踩这个日志看了半天才发现是激活问题。第三个坑是中文路径和特殊字符。项目路径、Output路径、甚至是构建服务器的用户名里都不要出现中文否则bundle序列化阶段可能出现文件写入异常。这个在老版本Unity里尤其严重新版本虽然做了兼容但没必要在CI环境里赌这一把。5.3 增量构建的真相别指望像代码编译一样的严格增量关于Addressables增量构建很多人的理解是跑第二次构建时只打包变动过的资源所以应该很快。事实并非如此Addressables构建的每一次都会重新分析依赖关系、重新生成catalog这个过程本身就是耗时大头。如果资源没有变化Unity会复用一部分缓存结果构建时间确实会明显变短但别把它当成和代码增量编译一个级别的东西。我观察到的规律是纯代码改动时Addressables构建时间基本稳定改动一个Shader或一个公共材质时构建时间会突然飙升因为Shader的变体收集和依赖分析是构建流程里最重的环节之一。如果项目里Shader变体特别多构建时间会成倍增长。这也是很多项目在优化构建体验时容易忽略的点。那怎么优化我的建议是合理拆分分组把大型Shader、公共图集放进独立的Group这样依赖分析时不会因为它们的变化而重算整个资源图谱。同时构建机上保持干净的Library缓存也很重要不要频繁删掉Library目录否则下次构建会从零开始分析耗时直接回到地狱模式。6. 构建策略的进阶Content Update与自定义构建脚本的价值边界6.1 Content Update能解决什么问题不能解决什么问题当你把Remote分组用于线上热更时Content Update是必须掌握的策略。它的思路是线上客户端里已经有一部分旧catalog和旧bundle了你发布一个新版本时不一定需要玩家重新下载整个包而是只下发变化的内容。操作上是这样的首次发版用Normal Build完整构建把Remote产物全部上传到CDN。后续小版本更新时用Content Update Build Script构建生成新的远程catalog和变化过的bundle。客户端启动时拉取最新远程catalog和本地已有的版本做比对只下载差异部分。这里有一个容易误用的大坑Content Update是构建层面的事但它能不能生效其实完全取决于你的分组划分。更新只对Remote分组里的内容有效Local分组的东西已经打进客户端包体了Content Update根本碰不到它们。很多项目上线前才发现自己把所有业务资源都塞进了Local分组热更计划直接泡汤。所以我常跟团队强调如果你有热更需求从第一天开始就把可能变动的资源隔离到Remote分组里并且保持这个分组划分永远不变。Content Update不是什么万能药它要求你在一开始就做好内容分层的规划。6.2 自定义构建脚本到底值不值得写这个话题我前面提过一嘴这里展开说。Addressables的构建流程允许你在某个环节插入自定义Task但我的建议是先想清楚你要解决的是构建流程本身的问题还是构建产物发布的问题。前者比如默认构建脚本不满足你们内部的加密、混淆、版本号规则。这种情况我建议先用简单方案构建完跑一个后处理脚本对ServerData目录做扫描和处理即可不必动构建Task。后者比如构建产物要自动上传到CDN这个同样可以用后处理脚本实现接一个FTP或OSS上传逻辑就完了。真正需要动IBuildTask的时候是你要改变构建流程本身比如修改catalog的生成规则、插入额外的依赖分析、定制bundle命名格式等。这种改动的影响面很大门控逻辑很复杂建议至少让团队里最熟悉Addressables的人来评估并且在分支工程里验证充分后再合并。不要在一个周五下午临时决定顺手改一下构建脚本然后周一早上所有同事的构建全炸了。6.3 微信小游戏这类平台带来的构建差异如果你做的是微信小游戏这类平台Addressables构建还需要额外确认几件事。首先小游戏包体和原生平台不同StreamingAssets目录的处理方式有差异本地资源的加载路径要对应上平台的本地文件系统。其次远程资源的下载在小游戏环境里走的是平台自己的下载能力Addressables默认的WebGL加载方式不一定适配建议提前做平台适配测试。这些平台通常还会限制单个bundle文件的大小可能要求你调整Group的Bundle Size设置把大Group拆细。构建前先看平台文档比构建完再从头排查要省太多时间。说白了Addressables的构建不是一键出包那么简单的操作它和你的发布平台、网络环境、包体策略是强绑定的。最后再分享一个小技巧不管用不用CI每次构建完都花两分钟扫一眼Build Report里的bundle列表确认没有出现意外的大bundle或者资源重复。构建这个环节再怎么自动化最终包体的构成是否合理还是得靠人来看一眼把好关。个人经验是构建报告里能看到的变化趋势往往比几百行构建日志更早暴露出分组策略的问题。