ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Unity Addressables构建为何生成两个catalog?Build面板配置全解析

Unity Addressables构建为何生成两个catalog?Build面板配置全解析 最近又有同事跑过来问我我就点了一次Build怎么工程里同时冒出来两个catalog是不是Addressables哪里配错了重复构建了一份这个问题我早几年刚接触Addressables时也撞到过当时还反复Clean Build了好几次结果重建完照样是两个一度怀疑人生。后来把构建流程顺着源码捋了一遍才算彻底想明白不是重复了这是Addressables设计上就需要两份catalog一份给编辑器自己用一份给运行时真正加载用。这篇文章就把这个问题的来龙去脉和AA设置Build面板里的关键选项一次讲清楚正在被catalog困扰的Unity开发者可以少走点弯路。先给结论如果你用的是默认配置一次Build Player Content之后至少会在两个目录下看到catalog文件——一个在Library或Assets下的编辑器数据区另一个在StreamingAssets/aa/{平台名}/下。再多配一个远程目录还会出现第三份。每份的职责不同缺一不可。下面我按catalog是什么 - 为什么有两个 - Build面板怎么配 - 实操复现 - 常见问题的顺序展开。1. 先把catalog到底是什么说清楚1.1 Catalog是一张资源地图很多新手容易把catalog和AssetBundle搞混。AssetBundle是资源本体一个Prefab、一堆贴图、一个场景经过打包后变成的二进制文件而catalog是描述这些bundle的元数据索引记录的是哪个Addressable地址对应哪个bundle、这个bundle里包含哪些资源、依赖哪些兄弟bundle、该用什么Provider去加载。你可以把它想象成图书馆的检索卡片书AssetBundle整整齐齐码在书架上你要找某本书通过Addressable地址加载资源时不可能一本本翻而是先查检索卡catalog拿到第几排第几层的定位信息再过去取书。运行时加载Addressable资源的完整链路就是初始化时加载catalog。根据传入的Address比如Assets/Prefabs/Enemy.prefab或一个自定义短名在catalog中查找到对应的ResourceLocation。根据location里的信息bundle名、依赖、Provider类型加载对应AssetBundle。从bundle中实例化出真正的资源返回给你。所以catalog一旦缺失或损坏哪怕bundle文件全都在运行时也找不到任何资源这就是为什么很多报错会直接提示Failed to load catalog。1.2 一次Build到底产出了什么Addressables的构建不是把资源打成一个包这么简单而是走了一条完整的流水线。以官方默认的Default Build Script为例执行一次Build后输出目录里会出现这几类东西若干.bundle文件按分组Group和构建模式生成的AssetBundle这是资源真实载体。一个catalog_{hash}.json上述的索引文件。一个catalog_{hash}.hashcatalog内容的短哈希值用于版本对比。link.xml、addressables_link.png等辅助文件帮助IL2CPP裁剪时保留必要类型以及供编辑器识别构建信息。构建脚本的逻辑大致是先打包bundle再根据打包结果生成Location列表最后把Location列表序列化成catalog。也就是说catalog是构建流程的最后一步它事后地汇总了整个构建产物。任何资源的增删改、分组变更、Profile路径变化、Unity版本或Addressables版本升级都可能影响catalog的hash值让它看起来每次构建都不一样。2. 一次构建为何生成两个catalog核心原因拆解2.1 两份catalog各自的身份我们需要先明确一个概念catalog文件不是某个单一实例而是同一份索引数据在不同生命周期阶段写入不同位置。以默认配置为例在你执行一次完整的Build Player Content后常见产物路径如下表文件路径身份定位谁在使用Assets/AddressableAssetsData/Generated/ContentCatalogData.asset编辑器侧catalog以ScriptableObject形式存在编辑器内Use Existing Build模式、Groups窗口信息展示Library/com.unity.addressableassets/aa/{平台}/catalog_{hash}.json构建缓存目录中的catalog编辑器内验证构建结果也是后续复制到StreamingAssets的来源Assets/StreamingAssets/aa/{平台}/catalog_{hash}.json运行时catalog真机/打包后运行时加载RemoteBuildPath/catalog_{hash}.json勾选Build Remote Catalog时远程catalog远程更新场景下客户端从服务器拉取大部分人说一次构建生成两个catalog看到的主要是第二项和第三项或者在Assets/AddressableAssetsData/Generated下的.asset文件和StreamingAssets下的.json文件。它们内容同源但服务对象不同编辑器模式需要一份能被AssetDatabase管理的资产型catalog运行时则需要一份能被文件系统读取的序列化catalog。2.2 为什么不能只保留一份可能有人会问既然内容一样为什么不只生成一份运行时直接从项目数据里读原因很直接第一编辑器环境有AssetDatabase可以方便地创建、查找、修改.asset文件但打包后的Player运行环境完全没有AssetDatabase这个概念它只能通过Application.streamingAssetsPath这类文件路径来读取数据。第二编辑器里跑Use Existing Build模式时Addressables需要按真实bundle加载的方式模拟运行此时必须有一个catalog告诉它bundle在哪、依赖是什么这个catalog如果放在Library缓存目录编辑器倒也能读但放在Assets下以.asset形式存在用起来更稳妥还能在Groups面板里直接展示当前构建的catalog信息。第三StreamingAssets下的catalog必须跟随Player一起打包。你在编辑器中构建的资源如果不同步到StreamingAssets打出的安装包里就什么都没有。所以Build Player Content这步会把构建产物主动复制/写入StreamingAssets/aa/{平台}/。一句话总结不是构建逻辑重复了是编辑器和运行时两个消费场景各自需要一份catalog。2.3 你看到的另一个catalog也有可能是旧hash残留除了上面说的两份跨界catalog还有一个特别常见的现象目录里躺着好几个catalog_{不同hash}.json。原因是catalog文件名带hash只要内容有任何变化哪怕只是某个Addressable分组里一个资源的导入设置变了新构建生成的catalog hash就会变而旧文件默认不会立刻被删掉。这看起来就更像生成了一堆catalog了。其实旧的属于历史残留确实可以手动清理或者用Clean Build清一次构建缓存。判断哪个才是当前真正生效的catalog可以看同目录下catalog_{hash}.hash文件里记录的hash值或者直接看构建日志末尾输出的catalog路径。提示如果你改了Addressable设置怀疑构建产物是旧的不要手动去删文件用Build Clean Build All配合Build Player Content重来一次比手工清理靠谱得多。3. AA设置Build面板逐项拆解3.1 Build面板入口和整体布局打开Window Asset Management Addressables Groups在Groups窗口左上角有一排下拉菜单其中标着Build的就是Build面板入口选中Assets/AddressableAssetsData/AddressableAssetSettings.asset在Inspector里也能看到Build相关的配置区。不同Addressables版本UI位置略有差异但核心选项基本一致。Build相关设置主要分成三块Build and Play Mode Scripts、Build Player Content/Clean Build、Catalog设置区以及跟路径相关的Profiles配置。下面逐个过。3.2 Build and Play Mode Scripts怎么选这个区域通常是个列表显示当前工程可用的构建脚本和播放模式脚本。Default Build Script默认构建脚本也就是上文中提到的打包AssetBundle、生成catalog的完整流程。一般不需要换除非你写了自己的IBuildScript。Play Mode Scripts有三个选项Use Asset Database (fastest)编辑器下直接引用源资源不加载bundle。迭代最好用改完Prefab马上能看到效果但它绕过了真正的bundle构建链路不能用于验证打包结果。Use Existing Build (requires built groups)按上一次构建出的bundle和catalog来加载。你在编辑器里模拟真机加载体验时选这个。Simulate Groups (advanced)用Addressables内置的模拟系统分析依赖主要用于调试资源重复、依赖加载顺序等问题不太常用。实际开发中我的习惯是日常写逻辑用Use Asset Database要排查打包后表现不对就切到Use Existing Build复现。很多人测试正常但真机出错就是因为在Use Asset Database模式下自嗨了半天压根没验证过真实bundle链路。3.3 Build Player Content与Clean Build的区别Build Player Content一键完成构建Addressables内容 构建Unity Player。它会先走一遍构建脚本生成bundle和catalog再调用BuildPipeline.BuildPlayer打安装包。如果你只是想在编辑器里验证资源加载不需要每次都点这个用New Build Default Build Script就够了。New Build Default Build Script只构建Addressables内容不打包Player。日常调试时用这个更快。Update a Previous Build做内容更新热更时用的差量构建。它依赖上一次构建的ContentState.bin只构建有变化的部分适合已有正式包之后只发新资源的场景。Clean Build All / Content Update / Bundles清理构建缓存。Clean Build会把相关中间产物bundle、cache等清掉下次构建强制全量重来。这里建议所有遇到构建结果莫名其妙的问题先执行一次Clean Build All再重新构建。很多Addressables的诡异表现都源于旧缓存残留尤其在你改了Group、改了Profile路径、升级了Unity版本之后。3.4 Catalog设置区逐项说明在AddressableAssetSettings的Inspector里有一个Catalog折叠区里面几个选项直接影响catalog生成行为设置项作用实操建议Player Version Override手动指定catalog版本号。留空时用构建时间等自动生成填了之后catalog文件名会固定带上这个版本信息便于远程更新对比有多人协作或CI打包时建议设成明确版本号如1.3.2否则不同机器构建的hash差异会让你很难排查Compress Catalog压缩catalog的json内容减小磁盘占用和首包体积代价是运行时加载catalog需要解压有一点点性能消耗包体敏感的项目建议开本机调试可以关日志方便看Optimize Catalog Size用字符串表压缩重复字段进一步减小catalog体积建议开启对运行时透明能显著降低远程catalog下载量Build Remote Catalog把catalog同时生成到远程构建目录支持远程更新catalog要做热更就开启后面配合Remote Load Path使用Player Version Override这个选项特别容易被忽略。默认catalog文件名里的hash是由构建内容计算得出的内容一变hash就变。如果你在CI里每天构建产物hash每天都不同甚至同一个仓库同一份代码不同开发者电脑构建出来的hash也可能不一样排查远程更新问题时会非常痛苦。手动指定版本号后文件名会稳定很多至少你一眼能看出哪个catalog属于哪个版本。3.5 Profiles与路径配置catalog生成在哪、运行时从哪里读最终都由Profiles里的路径变量决定。打开Groups窗口顶部的Profile下拉菜单点Manage Profiles能看到默认配置。关键变量就四个变量名默认值含义Local Build Path{UnityEngine.AddressableAssets.Addressables.BuildPath}实际指向Library/com.unity.addressableassets/aa/{平台}/本地bundle和本地catalog的构建输出目录Local Load Path{UnityEngine.AddressableAssets.Addressables.RuntimePath}实际指向StreamingAssets/aa/{平台}/运行时从本地读取bundle/catalog的目录Remote Build Path默认留空或自定义如ServerData/aa/{平台}/远程bundle和远程catalog的构建输出目录构建完要手动上传服务器Remote Load Path默认留空一般填URL如https://yourcdn.example.com/aa/{平台}/运行时从远程下载bundle/catalog的URL前缀这里有几个踩过无数次的坑。第一个把Local Build Path改成Assets下某个目录比如Assets/BuildBundles/。这样确实方便你在工程里直接翻bundle文件但每次构建产物都会被Unity当作工程资源导入一遍轻则让工程体积膨胀重则触发资源导入循环甚至把bundle文件误打进包体。默认的Library目录不受AssetDatabase管理这就是它被设计成默认构建路径的原因。第二个把Local Load Path改成绝对路径或非StreamingAssets路径。编辑器下测着没问题因为编辑器有完整文件系统权限但真机上你根本写不到那个路径。移动端打包后唯一稳定可读的本地目录就是StreamingAssets别瞎改。第三个Remote相关路径没做平台分区。多个平台共用同一个远程目录会导致catalog互相覆盖格式务必带上{PlatformName}占位符。注意Profiles里的变量支持自定义但别乱删默认变量某些代码会引用{UnityEngine.AddressableAssets.Addressables.BuildPath}这个内置变量删了之后构建脚本直接报错。4. 实操亲手复现两个catalog的完整过程4.1 准备一个最小复现工程为了做实验新建一个空Unity工程然后通过Window Package Manager安装Addressables。在场景里创建一个Cube转成Prefab保存到Assets/Prefabs/Cube.prefab。选中Cube.prefab在Inspector顶部勾选Addressable给个Address名字Cube让它进入默认分组。打开Window Asset Management Addressables Groups确认Default Local Group里能看到这个条目。这个工程足够简单构建速度快产物路径清晰。4.2 执行构建并观察产物在Groups窗口点Build New Build Default Build Script只构建内容不打包Player。构建完成后打开以下两个目录对比打开Library/com.unity.addressableassets/aa/里面会多出平台目录如StandaloneWindows或StandaloneOSX目录内有catalog_{hash}.json和对应的.hash文件以及若干.bundle文件。打开Assets/StreamingAssets/aa/如果之前没生成过这里通常是空的——因为New Build只会把产物写到构建路径不会同步到StreamingAssets。接着执行Build Build Player Content。这步会把构建产物同步一份到StreamingAssets/aa/{平台}/你会看到catalog_{hash}.json和.hash出现在这里。再回到Assets/AddressableAssetsData/Generated/目录能看到ContentCatalogData.asset这个编辑器侧catalog文件。到这一步两个catalog的现象就完整复现了.asset一个StreamingAssets下一个本质上都是同一次构建生成的索引数据。如果刚才执行New Build时选的是Use Existing Build播放模式编辑器运行时加载的catalog来自Library或Assets侧真机运行时加载的则是StreamingAssets侧。两边路径对不上加载行为就会有差异。4.3 用代码验证当前catalog是谁在加载写个简单的调试脚本挂到一个空GameObject上using UnityEngine; using UnityEngine.AddressableAssets; public class CatalogDebugger : MonoBehaviour { private void Start() { foreach (var locator in Addressables.ResourceLocators) { Debug.Log($LocatorId: {locator.LocatorId}); } Addressables.LoadAssetAsyncGameObject(Cube).Completed handle { if (handle.Status UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationStatus.Succeeded) { Instantiate(handle.Result); Debug.Log(Addressable resource loaded successfully.); } else { Debug.LogError(Failed to load Addressable resource.); } }; } }Addressables.ResourceLocators里会列出当前初始化完成的catalog定位器。Play Mode切到Use Existing Build时LocatorId通常会指向编辑器catalog对应的路径打出的Player运行时则指向StreamingAssets/aa/{平台}/catalog_{hash}.json。从日志里就能看到运行时实际加载的是哪一份。4.4 打开Remote Catalog之后会变成几份在AddressableAssetSettings勾选Build Remote Catalog并给Remote Build Path设一个目录比如ServerData/aa/{PlatformName}/。再执行一次完整构建你会发现远程构建目录下多出catalog_{hash}.json和catalog_{hash}.hash这是准备上传CDN的远程catalog。本地构建路径和StreamingAssets下的catalog仍然存在。也就是说只需一次构建catalog就会出现在三个位置编辑器侧.asset、本地运行时目录、远程发布目录。这就是为什么远程更新项目里catalog相关的问题会更多——你得时刻分清这份catalog是给谁用的。5. 常见问题与排查技巧实录5.1 为什么catalog的hash每次构建都变这个是正常现象catalog的hash由构建内容决定。只要存在一处细微差异例如某个Prefab的GUID变了、某个资源的导入配置变了、Profile路径变了甚至Unity版本升级导致bundle构建参数变化hash就会变。如果你需要让hash保持稳定唯一可控的办法是设置Player Version Override但要注意这是版本标识层面的稳定不代表内容没变远程更新恰恰依赖hash变化来判断有新版catalog发布。5.2 为什么编辑器测试正常打包到真机却加载失败先确认当前Play Mode Script是Use Asset Database还是Use Existing Build。前者只走源资源路径不加载任何bundle所以它测不出bundle或catalog缺失类问题。排查这类问题第一件事切到Use Existing Build把编辑器当成准真机跑一遍。如果仍然失败继续查Local Load Path是否指向了StreamingAssets以及在Build Player Content之后StreamingAssets/aa/{平台}/下到底有没有生成文件。5.3StreamingAssets下根本没有catalog文件夹常见原因是只执行了New Build Default Build Script没有执行Build Player Content。前者只写入构建路径后者才会把产物同步到StreamingAssets。另外如果之前用Clean Build把缓存清了但没重新构建StreamingAssets里也不会有东西。记住这个顺序New Build生成产物 -Build Player Content同步并打Player。5.4 远程catalog一直没生成先看Build Remote Catalog有没有勾选再看Remote Build Path是否留空或指向了一个无效目录。有时候你以为设置了但Profiles里改的是默认Profile而当前使用的不是这个Profile。所有路径配置都要以当前选中的Profile为准。5.5 能不能手动删除Assets/AddressableAssetsData/Generated下的catalog能删但删完编辑器内Use Existing Build模式下PlayMode读取catalog会失效Groups窗口的一些展示也会异常。它属于构建产物你大可在确定不影响需求的情况下删但下次构建会重新生成。与其手动删不如用Clean Build管好整个构建链路。5.6 打出的Player包体里既有bundle又有catalog但单独拷出这些文件放到别的机器不行因为StreamingAssets/aa/{平台}/里的catalog文件名和内部hash是对应当前构建的你手动拷贝catalog和bundle到另一台设备如果平台不同或安装包内其他内容不一致运行时还是加载不了。Addressables资源分发应当通过正规的内容更新流程而不是手工搬运文件。6. 过来人的几个实操建议以我个人的项目经验来说关于catalog和Build这套东西有几个习惯值得坚持。第一CI或多人协作时Player Version Override一定不要留空。手工指定版本号之后catalog文件名可控出问题能用文件名直接定位到构建版本。我见过因为不设版本号同一个功能在A机器构建正常、B机器构建后远程加载失败查了两天最后发现是hash不同导致新旧catalog混用的。第二每次要发布版本至少执行一次Clean Build All再Build Player Content。Addressables的增量构建省时间但也会把历史遗留问题带进新产物。干净构建多花几分钟能省下后面几小时的排查时间。第三不要折腾默认的Local Build Path。它就老老实实放Library里别为了看得见产物挪到Assets下这个坑我栽过代价是整个工程导入了一堆bundle资源Unity卡到怀疑人生。第四真要多平台加远程更新强烈建议先读一遍BuildScriptPackedMode.cs源码不需要全懂只要看它怎么调GenerateCatalog、怎么复制到StreamingAssets、怎么决定BuildPath和LoadPath你对两个catalog的困惑就会彻底消失。源码比任何文档都诚实。Addressables这套系统本身不算复杂但它的间接层很容易让新手在路径、目录、副本之间绕晕。搞清楚catalog的生成与消费机制后面配远程更新、做资源分包、优化首包大小都会顺畅很多。
返回列表