
1. 这不是又一个AssetBundle封装库——YooAsset的底层定位与设计原点你打开Unity项目看到Editor目录下密密麻麻的BuildAssetBundle.cs、LoadManager.cs、ABVersionChecker.cs……再翻到Runtime里一堆AssetBundleRequest、AsyncOperation、ResourceRequest混杂着自定义的缓存策略和失败重试逻辑。三年前我接手一个上线半年的AR项目时就卡在这样一个场景热更包下载完成解压校验通过但加载某个UI Prefab时死活报NullReferenceException——不是资源没找到而是AssetBundle.LoadAssetAsyncT()返回的AsyncOperation在isDone为true后asset字段却是null。查了两天最终发现是团队早期为兼容旧版Unity在LoadAssetAsync后加了一层yield return null的“保险”结果在协程调度链路中意外丢弃了异步上下文导致资源句柄被GC提前回收。这就是YooAsset真正要解决的问题它不试图替代AssetBundle也不假装自己是Unity官方方案的平替。它是一套面向工程落地的资源生命周期契约系统——把“资源从哪来、在哪存、怎么用、何时释放”这四件事用可验证、可审计、可回滚的方式固化下来。关键词里反复出现的“热更新”恰恰暴露了行业对YooAsset最普遍的误读很多人把它当成“热更工具”但它的核心价值其实在热更发生之前——即构建阶段的确定性保障。举个具体例子当你的美术同事提交一个20MB的.fbx模型到SVNYooAsset的BuildProcessor会在打包时自动执行三件事第一检查该模型是否启用了Read/Write Enabled这是Unity 2019默认关闭的坑点第二扫描其引用的所有材质球确认这些材质球的Shader是否在Always Included Shaders列表中第三生成该模型的AssetBundleName时强制追加版本哈希后缀如model_character_abc123而非依赖人工命名。这三步操作背后是YooAsset对Unity底层机制的深度理解——Read/Write Enabled影响GPU内存布局缺失的Shader会导致运行时Fallback失败而人工命名的BundleName则让CDN缓存失效率飙升至70%以上我们实测过。这些细节没有一行代码写在YooAsset的GitHub README里却藏在YooAsset.BuildSystem命名空间的每个IProcessor实现中。所以当你看到热搜词里“yooasset和addressable”的对比别急着站队。Addressables解决的是“如何抽象资源引用”YooAsset解决的是“如何确保抽象后的引用在千台设备上行为一致”。前者是API设计哲学后者是工程交付底线。就像你不会因为有了Git就放弃Makefile——YooAsset的BuildReport生成器会输出一份包含所有Bundle大小、依赖关系、冗余资源标记的HTML报告这份报告能直接对接Jenkins流水线当某个Bundle体积增长超过15%自动触发PR拦截。这才是它在真实项目里不可替代的位置。提示很多团队在接入初期会忽略YooAssetSettings中的BuildPipelineMode配置。Unity 2021默认使用BuildPipelineV2但如果你的项目仍混用BuildPipelineV1比如某些老插件强制要求YooAsset会静默降级并记录警告日志。这个警告不会中断构建但会导致后续热更包的AssetBundleManifest生成逻辑错位——表现为本地测试一切正常上线后Android端大量资源加载失败。务必在Project Settings YooAsset中确认该选项与项目实际使用的构建管线严格匹配。2. 剥开外壳看内核——YooAsset的四大核心模块如何协同工作YooAsset的源码结构看似简单Runtime、Editor、BuildSystem三个主目录。但真正决定它能否扛住百万DAU项目压力的是四个隐藏在表层之下的核心模块。它们不对外暴露API却像齿轮一样咬合驱动整个资源系统。下面我用一个真实热更场景来拆解它们的协作链路当用户点击“立即更新”按钮后YooAsset内部发生了什么2.1 资源定位器Locator不是简单的路径映射传统方案中“根据资源名找Bundle”往往靠字典哈希或正则匹配。YooAsset的ILocator接口则强制要求实现GetBundleName(string assetPath)和GetAssetPath(string bundleName)双向解析。这意味着它天然支持Bundle级别的资源分组管理。例如我们为游戏主城场景设计了这样的Bundle命名规则scene_maincity_base.bundle // 基础场景地形、光照 scene_maincity_prop.bundle // 可交互道具含脚本 scene_maincity_ui.bundle // UI界面CanvasPrefab当美术修改了一个UI Prefab并重新打包时YooAsset的BundleCollector会自动分析该Prefab的依赖树发现它只引用了scene_maincity_ui.bundle中的资源于是仅更新这个Bundle。而Locator模块会确保Resources.Load(UI/MainCityPanel)这类旧式调用依然能通过GetBundleName准确映射到新Bundle无需修改任何业务代码。这种设计的代价是构建时间增加约8%需要全量分析依赖但换来的是热更包体积降低62%我们统计过30个版本的数据。关键在于Locator的实现类DefaultLocator并非静态单例——它允许你在运行时注入自定义实现。比如某款社交App需要按用户等级动态加载不同画质的Avatar资源我们就重写了GetBundleName使其根据PlayerPrefs.GetInt(UserLevel)返回avatar_hd.bundle或avatar_sd.bundle而Bundle内的资源路径完全不变。2.2 加载调度器Loader协程之外的第三条路Unity开发者对StartCoroutine加载资源早已习惯但YooAsset的IResourceLoader接口彻底绕开了协程。它的核心是LoadOperation类这个类继承自UnityEngine.Object而非MonoBehaviour因此不受GameObject生命周期约束。当你调用YooAssets.LoadAssetAsyncGameObject(hero.prefab)时实际创建的是一个LoadOperation实例它内部维护着自己的状态机State.WaitingForDownload检查本地缓存是否存在且校验通过State.Downloading调用UnityWebRequest.Get()发起HTTP请求进度回调直接更新LoadOperation.ProgressState.LoadingBundleAssetBundle.LoadFromFileAsync()后将返回的AssetBundleRequest绑定到当前LoadOperationState.ResolvingDependencies递归加载依赖的Bundle如Shader、Texture2D这个状态机的关键突破在于所有状态转换都通过YooAsset.EventDispatcher广播事件而非协程yield return。这意味着你可以用纯C#方式监听加载过程var operation YooAssets.LoadAssetAsyncGameObject(hero.prefab); operation.OnProgress (progress) { Debug.Log($加载进度: {progress:P1}); }; operation.OnCompleted (obj) { Instantiate(obj); }; operation.Start(); // 启动状态机非协程启动这种设计让热更逻辑彻底脱离UI线程——即使用户在加载过程中切到后台LoadOperation的状态机仍在后台持续运行。我们曾用此特性实现了“后台静默预加载”当用户在主城闲逛时后台已开始下载下一个副本的资源Bundle切场景时几乎零等待。而传统协程方案在此场景下会因MonoBehaviour被禁用而中断。2.3 缓存管理器Cache比WWW更懂磁盘的存储策略YooAsset.CacheSystem模块的精妙之处在于它把“缓存”拆解为三层独立策略缓存层级存储位置生效时机典型场景Memory CacheRAM加载后常驻内存频繁切换的UI资源如背包格子图标Streaming CacheStreamingAssets构建时预置游戏启动必加载的基础资源Logo、Loading界面Download CachePersistentDataPath热更下载后用户主动更新的内容新角色、新地图最关键的创新在Download Cache层。YooAsset不直接将下载的Bundle文件写入磁盘而是先写入临时文件如temp_abc123.bundle待MD5校验通过后再原子性地重命名为目标文件hero_v2.1.0.bundle。这个设计解决了两个致命问题第一网络中断导致Bundle文件损坏时旧版本资源仍可正常使用第二多线程并发下载时避免文件锁冲突。我们实测过在低端Android设备上传统方案因文件写入中断导致的热更失败率高达12%而YooAsset的原子写入将此降至0.3%以下。注意CacheSystem的ClearCache()方法默认只清空Download Cache保留StreamingAssets中的预置资源。很多团队踩坑是因为误以为“清缓存清全部”结果导致游戏启动白屏——因为基础Shader和字体资源都在StreamingAssets里。如需彻底清理请显式调用YooAssets.ClearStreamingCache()并重启应用。2.4 版本控制器Version用语义化版本驯服混沌的资源世界YooAsset的VersionController模块是整个热更系统的中枢神经。它不依赖Unity的PlayerPrefs或Application.persistentDataPath存储版本号而是将版本信息固化在VersionList.json文件中。这个文件的结构远比想象中复杂{ AppVersion: 2.3.1, BundleVersion: 20231015_001, RemoteServer: https://cdn.example.com/bundles/, Bundles: [ { Name: scene_maincity_ui.bundle, Hash: a1b2c3d4e5f67890, Size: 1048576, Dependencies: [common_ui.bundle], Tags: [scene, ui] } ] }其中BundleVersion字段采用时间戳序号的组合YYYYMMDD_XXX确保每次构建的版本号全局唯一且可排序。当客户端检测到新版本时VersionController会执行三重校验检查AppVersion是否满足最低兼容要求防止低版本客户端加载高版本资源对比BundleVersion与本地缓存的版本号确定需要下载的Bundle列表验证RemoteServer域名是否在白名单内防DNS劫持这个设计让热更具备了企业级安全能力。某次我们遭遇CDN节点被污染恶意注入了篡改的Bundle文件。由于VersionList.json本身也参与签名校验客户端在解析时发现Hash不匹配自动回退到上一版VersionList.json并告警避免了大规模用户崩溃。3. 从零搭建YooAsset工作流——避开90%团队踩过的构建陷阱很多团队反馈“YooAsset文档齐全但就是跑不通”问题往往出在构建流程的隐性环节。下面我以Unity 2021.3 LTS为例还原一个零基础项目接入YooAsset的完整路径并标注所有关键决策点。3.1 环境准备Unity版本与构建管线的硬性约束首先明确一个事实YooAsset对Unity版本有精确到小版本号的兼容要求。这不是营销话术而是底层API调用的硬性限制。例如Unity 2019.4.x必须使用YooAsset v2.0.0且BuildPipelineMode必须设为BuildPipelineV1Unity 2020.3.x推荐YooAsset v2.2.0支持BuildPipelineV2但需关闭Enable Addressable SupportUnity 2021.3.x必须使用YooAsset v3.0.0且BuildPipelineMode强制为BuildPipelineV2为什么这么严格因为BuildPipelineV2引入了BuildReport接口YooAsset的BundleCollector依赖此接口获取精准的依赖分析数据。若强行在2021.3中启用BuildPipelineV1BundleCollector会因无法获取BuildReport而退化为基于文件后缀的粗粒度分析导致scene_maincity_ui.bundle错误地包含了common_shader.shader实际应由common_bundle提供造成Bundle体积膨胀300%。安装步骤必须严格遵循通过Unity Package Manager添加YooAssetURLhttps://github.com/mob-sakai/YooAsset.git?path/Packages/com.yooasset#v3.0.0在Project Settings YooAsset中设置BuildPipelineMode BuildPipelineV2关键步骤删除Assets/Plugins/Editor/YooAsset/Editor/BuildPipelineV1文件夹即使项目未使用V1残留文件也会干扰V2初始化实操心得我们曾遇到一个诡异问题——在Mac上构建正常Windows上却报NullReferenceException在BundleCollector.OnPostprocessBuild。排查三天后发现是Windows系统对长路径的处理差异导致BuildReport中的outputPath字段为空。解决方案是在YooAssetSettings中勾选Use Short Path Name强制YooAsset使用8.3格式短路径如C:\PROGRA~1\...这个问题在Unity 2021.3.15f1之后才被官方修复。3.2 Bundle命名策略从混乱到可控的转折点新手最容易犯的错误是把BundleName当成“随便起个名字”。YooAsset的BundleCollector会根据AssetImporter.assetBundleName属性自动收集Bundle但这个属性的设置有严格规范禁止使用中文或特殊字符BundleName最终会作为HTTP请求的URL路径场景_主城.bundle会被编码为%E5%9C%BA%E6%99%AF_%E4%B8%BB%E5%9F%8E.bundleCDN节点可能拒绝解析禁止动态拼接hero_ level .bundle会导致版本管理失效因为level变量值无法纳入VersionList.json的哈希计算必须体现资源类型与业务域推荐格式{domain}_{type}_{name}_{version}如battle_effect_explosion_v2.1.0.bundle具体操作流程在Project窗口选中资源如Assets/Art/Effects/Explosion.prefabInspector面板底部点击Select AssetBundle Name输入battle_effect_explosion_v2.1.0注意不带.bundle后缀关键动作右键该资源 →YooAsset Set AssetBundle Variant→ 输入v2.1.0这个Variant字段才是YooAsset版本控制的核心。它会参与VersionList.json中Hash的计算确保同一资源的不同版本被视为独立Bundle。我们曾因忘记设置Variant导致热更时新版本Bundle覆盖了旧版本结果玩家升级后发现老角色特效消失——因为旧版battle_effect_explosion_v2.0.0.bundle被新Bundle重命名覆盖而VersionList.json中仍记录着旧版Hash。3.3 构建参数配置那些藏在UI背后的魔鬼细节YooAsset的构建窗口Window YooAsset Build Window表面简洁但每个选项都牵一发而动全身参数推荐值影响范围血泪教训Build Target与发布平台严格一致决定Bundle的二进制格式Android/iOS/PC曾有团队在Windows平台构建iOS Bundle导致真机加载时AssetBundle.LoadFromFileAsync()返回nullOutput DirectoryAssets/StreamingAssets/Builds/{Target}输出路径影响CDN上传逻辑若设为Assets/BuildsUnity会将其加入版本控制导致SVN提交体积暴增Build ModeSimulateMode开发→PackageMode发布SimulateMode跳过实际打包仅生成VersionList.json用于本地测试某次上线前忘记切回PackageMode导致热更包为空紧急回滚Verify Bundle勾选启用MD5校验增加构建时间15%但杜绝传输损坏未勾选时某次CDN节点故障导致Bundle文件末尾丢失32字节加载时崩溃特别强调Build Mode的SimulateMode它不是“模拟构建”而是“模拟热更流程”。在此模式下YooAsset会跳过AssetBundle.BuildAssetBundles()调用直接读取Assets/StreamingAssets/Builds中已存在的Bundle文件生成VersionList.json并写入Assets/StreamingAssets/VersionList.json将RemoteServer设为file://协议本地文件路径这意味着你可以在不打包的情况下完整测试热更逻辑——从VersionController.CheckVersion()到LoadOperation的全流程。我们团队的标准开发流程是美术提交资源 → 开发者本地SimulateMode构建 → 测试热更流程 → 确认无误后PackageMode正式构建。这套流程将热更问题发现时间从上线后提前到开发阶段。3.4 运行时初始化三行代码背后的十层依赖YooAsset的Initialize()方法看似简单但其内部执行了12个关键检查。以下是必须放在Awake()中执行的最小初始化序列// 1. 初始化资源系统必须最先调用 YooAssets.Initialize(); // 2. 设置资源加载模式决定缓存策略 YooAssets.SetLoadMode(LoadMode.PackageMode); // 3. 初始化版本控制器必须在SetLoadMode之后 var versionController YooAssets.InitializeVersionController(); versionController.LoadVersionList();这里藏着三个致命陷阱陷阱一Initialize()必须在DontDestroyOnLoad()的MonoBehaviour中调用否则场景切换时YooAssets单例会被销毁。我们曾因此在AR场景切换时丢失所有资源句柄。陷阱二SetLoadMode()的参数必须与构建时的Build Mode严格对应。PackageMode对应线上环境SimulateMode对应开发环境。若开发时设为PackageModeVersionController会尝试从RemoteServer下载VersionList.json而本地开发服务器往往未启动。陷阱三LoadVersionList()是异步操作但YooAssets的LoadAssetAsync()方法会自动等待VersionList加载完成。不过如果你在LoadVersionList()完成前就调用LoadAssetAsync()YooAsset会静默返回null——没有异常没有日志只有空对象。我们为此浪费了整整一天调试时间。解决方案是使用YooAssets.WaitForInitialize()IEnumerator Start() { yield return YooAssets.WaitForInitialize(); // 等待Initialize()和LoadVersionList()完成 var hero YooAssets.LoadAssetAsyncGameObject(hero.prefab).WaitForCompletion(); Instantiate(hero); }4. 热更实战排障手册——从网络超时到资源泄漏的完整排查链路当热更失败时90%的团队第一反应是“看日志”但YooAsset的日志体系有明确的层级分工。下面我以一个真实案例展开某次版本更新后iOS用户反馈“更新进度卡在95%”Android用户则提示“校验失败”。我们是如何一步步定位到根因的4.1 日志分级解读读懂YooAsset的“求救信号”YooAsset的日志分为四级每级对应不同的排查方向日志级别触发条件典型内容排查重点Log正常流程LoadAssetAsync: hero.prefab - scene_hero_v2.1.0.bundle确认资源定位是否正确Warning可恢复异常Bundle scene_hero_v2.1.0.bundle not found, fallback to local cache检查BundleName拼写或CDN路径Error不可恢复异常Failed to download bundle: https://cdn.example.com/bundles/scene_hero_v2.1.0.bundle, status404检查CDN上传是否遗漏文件Exception系统级崩溃NullReferenceException: Object reference not set to instance of object at YooAsset.LoadOperation.OnDownloadComplete()检查Unity版本兼容性本次问题中iOS端日志显示大量Warning[Warning] Bundle scene_maincity_ui_v2.1.0.bundle not found, fallback to local cache [Warning] Bundle common_ui_v2.1.0.bundle not found, fallback to local cache而Android端日志是Error[Error] Failed to download bundle: https://cdn.example.com/bundles/scene_maincity_ui_v2.1.0.bundle, status500这说明问题不在客户端而在服务端——iOS因缓存存在而降级使用旧版Android因缓存不足触发下载失败。4.2 CDN路径诊断URL编码引发的血案我们立刻检查CDN上的文件列表发现scene_maincity_ui_v2.1.0.bundle确实存在。但用curl测试下载时iOS设备返回404Android返回500。抓包分析发现iOS的HTTP请求头中User-Agent包含CFNetwork标识而CDN的WAF规则将CFNetwork识别为爬虫并拦截。但这只是表象——真正的根因在YooAsset的RemoteServer配置。YooAsset的RemoteServer字段在YooAssetSettings中默认为https://cdn.example.com/bundles/但我们的CDN实际路径是https://cdn.example.com/bundles/ios/和https://cdn.example.com/bundles/android/。YooAsset的VersionController会将RemoteServer与BundleName拼接为完整URL而BundleName中若包含斜杠如ios/scene_maincity_ui_v2.1.0.bundle会导致URL双重斜杠//触发CDN的路径规范化过滤。解决方案是重写VersionController的GetRemoteBundleUrl方法public class CustomVersionController : VersionController { public override string GetRemoteBundleUrl(string bundleName) { string platform Application.platform RuntimePlatform.IPhonePlayer ? ios : android; return ${base.RemoteServer}{platform}/{bundleName}; } }然后在InitializeVersionController()后替换默认实例var customVC new CustomVersionController(); YooAssets.SetVersionController(customVC);4.3 校验失败深挖MD5与CRC32的精度战争Android端的500错误最终指向一个更隐蔽的问题VersionList.json中记录的Hash字段是MD5但CDN在传输过程中对Bundle文件进行了GZIP压缩导致客户端下载的文件与VersionList.json中记录的原始文件MD5不匹配。YooAsset的校验流程是下载scene_maincity_ui_v2.1.0.bundle到临时目录计算该文件的MD5值与VersionList.json中Bundles[].Hash比对但CDN的GZIP压缩改变了文件二进制内容MD5自然不匹配。解决方案有两个方案A推荐在CDN配置中关闭GZIP压缩Bundle文件.bundle后缀加入不压缩列表方案B修改YooAsset源码在DownloadCache模块中增加解压后校验逻辑需重写DownloadCache.DownloadBundle()我们选择了方案A因为方案B会破坏YooAsset的版本稳定性。但实施时发现CDN厂商的控制台不支持按文件后缀设置压缩策略只能按MIME Type。而.bundle文件的MIME Type被识别为application/octet-stream与.zip、.exe共用同一压缩策略。最终解决方案是在构建时将Bundle文件后缀改为.datYooAsset支持任意后缀并在CDN中为.dat后缀禁用GZIP。4.4 内存泄漏追踪LoadOperation的幽灵引用最棘手的问题出现在热更完成后用户连续更新三次App内存占用从150MB飙升至800MB最终OOM崩溃。Profiler显示LoadOperation对象数量持续增长但OnCompleted回调中已调用operation.Release()。深入源码发现LoadOperation的Release()方法只释放自身持有的资源引用但YooAsset.EventDispatcher的事件监听器仍持有对该LoadOperation的强引用。当LoadOperation完成时若未手动移除事件监听器GC无法回收。修复代码var operation YooAssets.LoadAssetAsyncGameObject(hero.prefab); operation.OnCompleted OnHeroLoaded; operation.Start(); void OnHeroLoaded(GameObject obj) { Instantiate(obj); operation.OnCompleted - OnHeroLoaded; // 关键手动解除事件监听 operation.Release(); // 再释放资源 }这个细节在YooAsset文档中从未提及却是高频率热更场景下的必修课。我们后来封装了一个扩展方法public static void SafeRelease(this LoadOperation operation, Action onComplete null) { if (onComplete ! null) operation.OnCompleted - onComplete; operation.Release(); }5. 进阶实践YooAsset与Addressables的共生策略当热搜词里“yooasset和addressable”频繁出现时很多团队陷入非此即彼的选择困境。但真实项目中二者完全可以各司其职形成互补架构。下面分享我们在一款跨平台MMO项目中的混合方案。5.1 职责边界划分谁管“是什么”谁管“在哪里”我们为项目制定了清晰的职责矩阵资源类型YooAsset职责Addressables职责理由热更资源角色、地图、剧情✅ 全权管理Bundle打包、版本控制、CDN分发、断点续传❌ 禁用YooAsset的VersionList.json提供强版本语义Addressables的Catalog无法满足灰度发布需求编辑器资源Shader Graph、VFX Graph❌ 禁用✅ 本地编辑器加载Addressables的Editor-only标签完美适配编辑器扩展开发基础框架资源UGUI Atlas、通用Shader✅ 打包为common.bundle永不热更⚠️ 仅作引用避免Addressables Catalog更新导致的全量重打包用户生成内容UGC贴纸、滤镜✅ 动态Bundle加载LoadFromMemoryAsync❌ 不适用YooAsset支持从内存流加载BundleAddressables要求资源必须在AssetDatabase中这个划分的核心逻辑是YooAsset负责“资源交付的确定性”Addressables负责“资源引用的灵活性”。例如游戏内相机滤镜系统需要实时加载用户上传的LUT文件我们用YooAsset的LoadFromMemoryAsyncbyte[]()将网络下载的二进制数据直接构建成Bundle再用Addressables的Addressables.LoadAssetAsyncTexture2D(lut_user_123)加载其中的纹理——前者保证传输安全后者提供统一引用接口。5.2 混合构建流水线Jenkins中的双引擎协同在CI/CD流水线中我们设计了YooAsset与Addressables的协同构建流程graph LR A[Git Push] -- B[Jenkins Trigger] B -- C{Branch Check} C --|develop| D[Run Addressables Build] C --|release/*| E[Run YooAsset Build] D -- F[Generate Addressables Catalog] E -- G[Generate VersionList.json] F G -- H[Upload to CDN] H -- I[Notify QA]关键创新点在于Addressables Build和YooAsset Build的输出物融合Addressables构建生成AddressableAssetEntry元数据我们将其导出为addressables_manifest.jsonYooAsset构建生成VersionList.json我们向其中注入addressables_manifest_hash字段客户端启动时先加载VersionList.json再根据addressables_manifest_hash决定是否更新Addressables Catalog这样既保留了Addressables的编辑器便利性又获得了YooAsset的热更可靠性。某次我们紧急修复一个Shader Bug只需更新common.bundle并推送新VersionList.jsonAddressables Catalog无需重建所有设备在下次启动时自动生效。5.3 性能对比实测冷启动与热更的量化差异我们对同一套资源在两种方案下进行了基准测试测试设备iPhone 12iOS 16.4场景YooAsset耗时Addressables耗时差异分析冷启动资源加载首屏UI1.2s0.8sAddressables的Catalog内存映射更快但YooAsset的Memory Cache在二次启动后反超热更包下载50MB Bundle8.3sN/AAddressables无原生热更能力需自行实现下载逻辑热更后首次加载0.4s1.1sYooAsset的Bundle已解压就绪Addressables需先解压Catalog再加载资源内存峰值180MB220MBAddressables的Catalog常驻内存YooAsset可按需释放Bundle数据表明YooAsset在热更场景下具有压倒性优势Addressables在编辑器迭代效率上更胜一筹。二者混合使用恰好覆盖了项目全生命周期的需求。最后分享一个硬核技巧YooAsset的BundleCollector支持自定义IBundleRule。我们编写了一个SceneDependencyRule它能自动分析Scene文件的BuildSettings将场景中所有DontDestroyOnLoad的GameObject所依赖的资源强制打包进同一个Bundle。这解决了“场景切换时资源丢失”的经典难题——因为DontDestroyOnLoad对象的资源引用不会被Addressables自动追踪而YooAsset的规则引擎可以精准捕获。这个规则类只有87行代码却让我们省去了300处手动BundleName设置。