实战-资源组织与依赖管理实战篇章10-实战篇-从零搭建状态已完成阅读时间约 25 分钟一、引言1.1 本章在系列中的定位资源组织与依赖管理是 YooAsset 工程实践中的第一性原理。资源组织决定了哪些资源放在一起依赖管理决定了资源之间如何引用。两者共同决定了一个项目的包体大小组织不合理会导致重复打包加载性能依赖混乱会导致加载慢更新成本依赖图复杂会导致每次更新量巨大可维护性资源关系清晰的项目更容易维护本章通过具体的实战案例讲解如何在 YooAsset 中组织资源、管理依赖。1.2 本章要解决的核心问题资源组织有哪些常见模式依赖关系如何识别如何优化共享资源如何抽取大型项目如何分 Package1.3 阅读前置要求完成 10-01 到 10-03了解 Unity 资源类型Prefab、Texture、Audio 等了解项目工程化基础二、资源组织模式2.1 按业务模块组织最常见的组织方式Assets/ ├── Common/ # 公共资源 │ ├── Atlas/ # 公共图集 │ ├── Font/ # 字体 │ └── Sound/ # 通用音效 ├── Login/ # 登录模块 │ ├── UI/ # 登录 UI │ ├── Script/ # 登录逻辑 │ └── Scene/ # 登录场景 ├── MainCity/ # 主城模块 │ ├── UI/ │ ├── Scene/ │ └── Script/ ├── Battle/ # 战斗模块 │ ├── Hero/ # 角色 │ ├── Skill/ # 技能 │ ├── Effect/ # 特效 │ └── Scene/ ├── Activity/ # 活动模块 └── DLC/ # 资料片优点业务边界清晰模块可独立打包团队协作明确缺点资源可能被多个模块共享公共资源归类困难2.2 按资源类型组织Assets/ ├── Prefabs/ # 所有 Prefab ├── Textures/ # 所有贴图 ├── Audio/ # 所有音频 ├── Materials/ # 所有材质 ├── Animations/ # 所有动画 └── Scenes/ # 所有场景优点同类资源集中管理缺点不利于按业务打包模块化困难2.3 按加载时机组织推荐Assets/ ├── Startup/ # 启动必需首包 │ ├── Logo/ │ ├── Loading/ │ └── Config/ ├── Main/ # 主要资源热更 │ ├── UI/ │ ├── Audio/ │ └── Config/ ├── LazyLoad/ # 按需加载热更 │ ├── Battle/ │ ├── Activity/ │ └── DLC/ └── Discard/ # 不打包 └── Editor/优点与 YooAsset 的首包/热更设计完美匹配性能优化直观适合 YooAsset 的按需加载模型2.4 按包组织每个 YooAsset Package 对应一个目录Assets/ ├── Package_Default/ # 默认包 ├── Package_Activity/ # 活动包 ├── Package_DLC1/ # DLC1 包 └── Package_DLC2/ # DLC2 包适用场景项目有明确的包概念如多个游戏模式、多个资料片。三、依赖分析3.1 依赖的产生YooAsset 中的依赖来源于依赖类型例子Prefab 引用Hero.prefab引用了SkillConfig.json材质引用Material.mat引用了Texture.png动画引用Animation.anim引用了Avatar.asset脚本引用HeroController.cs引用了SkillConfig场景引用Battle.unity引用了多个 Prefab、Light、Audio3.2 依赖分析工具3.2.1 构建报告YooAsset 构建后会生成详细的依赖报告BuildReport.html ├── Bundle 列表 │ ├── bundle_01 │ ├── bundle_02 │ └── ... ├── 资源依赖图 └── 重复资源检测3.2.2 Editor 端依赖分析public class DependencyAnalyzer : EditorWindow { [MenuItem(YooAsset/Analyze Dependencies)] public static void Analyze() { var settings AssetBundleCollectorSetting.GetDefaultCollector(); var buildMap AssetBundleCollector.BuildMap(); // 1. 列出所有 Bundle 的依赖 foreach (var bundle in buildMap.BundleList) { Debug.Log($Bundle: {bundle.BundleName}); foreach (var dep in bundle.Dependencies) { Debug.Log($ 依赖: {dep}); } } // 2. 检测循环依赖 var hasCycle DetectCycle(buildMap); if (hasCycle) Debug.LogError(检测到循环依赖); } static bool DetectCycle(BuildMap buildMap) { var visited new HashSetstring(); var recursionStack new HashSetstring(); foreach (var bundle in buildMap.BundleList) { if (HasCycleDFS(bundle, visited, recursionStack, buildMap)) return true; } return false; } }3.3 依赖可视化YooAsset 不直接提供依赖图的可视化但可以通过其他工具工具功能Unity Asset Bundle Browser官方工具查看 AB 依赖AssetStudio第三方解析 AB 内容Graphviz通用图可视化自己实现通过BuildMap输出 DOT 文件public class DependencyVisualizer { public static void ExportToDOT(BuildMap buildMap) { var sb new StringBuilder(); sb.AppendLine(digraph Dependencies {); foreach (var bundle in buildMap.BundleList) { foreach (var dep in bundle.Dependencies) { sb.AppendLine($ \{bundle.BundleName}\ - \{dep}\;); } } sb.AppendLine(}); File.WriteAllText(dependencies.dot, sb.ToString()); } }四、共享资源抽取4.1 共享资源的概念共享资源Shared Asset是指被多个 Bundle 引用的资源。共享资源如果不抽离出来会导致Bundle_A 包含 SharedTexture.png Bundle_B 包含 SharedTexture.png Bundle_C 包含 SharedTexture.png 总大小 3 * SharedTexture.png 浪费 2/3 空间4.2 共享资源抽取策略4.2.1 自动抽取推荐YooAsset 在构建时会自动识别共享资源并将其抽取为独立的 BundleBundle_A Bundle_B ↓ ↓ Shared_Bundle自动生成 SharedTexture.png4.2.2 手动标记共享在 Editor 中显式标记共享资源public class SharedAssetRule : IPackRule { public bool Match(AssetInfo info) { // 共享图集规则 return info.AssetPath.StartsWith(Assets/Common/Atlas/); } public string GetBundleName(AssetInfo info) { return shared_atlas; // 强制打到一个 Bundle } }4.2.3 共享包的最佳实践资源类型是否共享理由公共 UI 图集✅多个 UI 共享字体✅全局共享通用配置✅多个系统读取主城模型❌仅主城使用战斗角色❌仅战斗使用活动资源❌仅活动使用4.3 避免过度共享反例shared_bundle包含 1000 个资源 └── Bundle_A、B、C、D... 都依赖它问题shared_bundle 任何资源修改都会导致整个共享包重打所有依赖它的 Bundle 都需重新下载解决按使用频率分类共享shared_hot高频共享 ├── UI 公共图集 └── 字体 shared_warm中频共享 ├── 角色基础模型 └── 通用特效 shared_cold低频共享 ├── 历史活动图标 └── 远期功能 UI五、依赖优化5.1 依赖深度依赖深度是指从最顶层资源到最底层资源的依赖层数。Hero.prefab (深度 0) ↓ 引用 SkillConfig.json (深度 1) ↓ 引用 CharacterConfig.json (深度 2) ↓ 引用 GlobalConfig.json (深度 3)依赖深度越深加载时需要加载的中间层越多。最佳实践核心资源深度 ≤ 3一般资源深度 ≤ 5边缘资源深度 ≤ 85.2 减少依赖的方法5.2.1 拆分大 Prefab❌ 错误一个大 Prefab 包含所有 Player_AllInOne.prefab ├── 模型 ├── 动画 ├── 技能 ├── 装备 └── 特效 ✅ 正确拆分为多个小 Prefab Player_Model.prefab Player_Animation.prefab Player_Skills.prefab Player_Equipment.prefab Player_Effects.prefab5.2.2 使用 ScriptableObject将配置数据抽离为 ScriptableObject// 数据类 [CreateAssetMenu(fileName HeroConfig, menuName Config/Hero)] public class HeroConfig : ScriptableObject { public int HeroId; public string Name; public int HP; public int Attack; } // Prefab 引用 ScriptableObject不引用具体数值 public class HeroController : MonoBehaviour { public HeroConfig Config; // 引用配置 }5.2.3 异步加载链避免加载一个大 Prefab 必须先加载所有依赖改为按需加载public class OnDemandLoader { public async TaskT LoadHeroT(string heroAddress) where T : MonoBehaviour { // 1. 先加载核心 Prefab var coreOp YooAssets.LoadAssetAsyncGameObject($Hero/{heroAddress}_Core); await coreOp.Task; var hero Instantiate(coreOp.Result); // 2. 异步加载技能 var skillOp YooAssets.LoadAssetAsyncSkillConfig($Hero/{heroAddress}_Skill); // 不等 skill 加载完就让玩家看到 Hero return hero.GetComponentT(); } }5.3 依赖检测脚本public class DependencyOptimizer { [MenuItem(YooAsset/Optimize Dependencies)] public static void Optimize() { var buildMap AssetBundleCollector.BuildMap(); // 1. 检测深度过深的依赖 foreach (var bundle in buildMap.BundleList) { var depth CalculateDepth(bundle, buildMap); if (depth 5) { Debug.LogWarning($Bundle {bundle.BundleName} 依赖深度 {depth} 过深); } } // 2. 检测被多次引用的资源 var refCount new Dictionarystring, int(); foreach (var bundle in buildMap.BundleList) { foreach (var asset in bundle.Assets) { if (!refCount.ContainsKey(asset.AssetPath)) refCount[asset.AssetPath] 0; refCount[asset.AssetPath]; } } foreach (var kvp in refCount) { if (kvp.Value 3) { Debug.LogWarning($资源 {kvp.Key} 被 {kvp.Value} 个 Bundle 引用建议抽取为共享包); } } } }六、大型项目的分 Package 策略6.1 单 Package 的局限当项目资源总量超过 10GB 时单 Package 会有构建时间长30 分钟清单文件大10MB加载速度慢解析清单慢团队协作冲突所有人修改同一个清单6.2 分 Package 模式6.2.1 按业务分 PackagePackage_Default # 主包必备资源 Package_Battle # 战斗模块 Package_Activity # 活动模块 Package_DLC1 # DLC1 Package_Language # 多语言6.2.2 按平台分 PackagePackage_PC # PC 平台专属 Package_Mobile # 移动平台专属 Package_WebGL # WebGL 专属6.2.3 按加载时机分 PackagePackage_Startup # 启动时首包 Package_Login # 登录启动后立即 Package_MainCity # 主城登录后 Package_Battle # 战斗按需6.3 多 Package 的依赖管理Package_Default ↓ 依赖 Package_Common共享 UI、配置 Package_Battle ↓ 依赖 Package_Common Package_Activity ↓ 依赖 Package_Common依赖加载顺序先加载 Package_Default再加载 Package_Common最后加载业务 Packagepublic class MultiPackageLoader { public async Task InitializeAll() { // 1. 默认包 await YooAssets.InitializeAsync(); // 2. 公共包 var commonOp YooAssets.LoadPackageAsync(Common); await commonOp.Task; // 3. 业务包并行 var tasks new[] { YooAssets.LoadPackageAsync(Battle).Task, YooAssets.LoadPackageAsync(Activity).Task, YooAssets.LoadPackageAsync(DLC1).Task }; await Task.WhenAll(tasks); } }七、资源命名规范7.1 命名规则模块前缀 资源类型 资源名 例 ui_button_normal ← UI 模块的普通按钮 ui_icon_home ← UI 模块的 Home 图标 hero_assassin ← 英雄刺客 skill_fireball ← 技能火球 audio_bgm_main ← 背景音乐主城 config_character ← 角色配置7.2 命名一致性统一前缀按模块统一前缀ui_、hero_、skill_等小写 下划线避免大小写问题避免中文不同操作系统对中文路径处理不同避免特殊字符不要有空格、括号等7.3 地址命名 vs 文件路径// YooAsset 推荐使用 Location 而非路径 // Location 类似于逻辑名与文件路径解耦 // ❌ 不推荐用文件路径 var op YooAssets.LoadAssetAsyncSprite(Assets/Art/UI/Button_Normal.png); // ✅ 推荐用 Location var op YooAssets.LoadAssetAsyncSprite(ui_button_normal);通过 AssetBundleCollector 配置 Addresspublic class AddressableConfig { public string AssetPath Assets/Art/UI/Button_Normal.png; public string Address ui_button_normal; // 显式指定 }八、实战案例8.1 案例MMO 项目项目: MMO Game 资源总量: 15GB Package 数: 5 个 Package 划分: ├── Package_Default (2GB) │ ├── 启动资源 │ ├── 登录资源 │ ├── 主城基础资源 │ └── 主线 1-3 章 │ ├── Package_Common (1GB) │ ├── 公共 UI │ ├── 公共特效 │ ├── 字体 │ └── 通用配置 │ ├── Package_DLC1 (5GB) │ ├── DLC1 地图 │ ├── DLC1 角色 │ └── DLC1 任务 │ ├── Package_Activity (2GB) │ ├── 节日活动 │ ├── 限时副本 │ └── 商城资源 │ └── Package_Battle (5GB) ├── PVE 副本 ├── PVP 战场 └── Boss 战8.2 案例休闲小游戏项目: Casual Game 资源总量: 500MB Package 数: 3 个 Package 划分: ├── Package_Startup (10MB) │ ├── Logo │ ├── Loading │ └── 配置 │ ├── Package_Main (200MB) │ ├── 主玩法 │ ├── 主关卡 1-100 │ └── 主 UI │ └── Package_DLC (290MB) ├── DLC 关卡 101-200 ├── DLC 角色 └── DLC 皮肤九、总结9.1 本章要点回顾资源组织模式按业务、按类型、按加载时机、按 Package依赖管理识别依赖、抽取共享、减少深度共享资源自动 手动抽取避免过度共享多 Package大型项目的必然选择9.2 与前后章节的关联前章10-03 介绍了 Addressable本章聚焦资源组织和依赖管理后章10-05 将介绍资源配置与打包9.3 实践建议资源组织从一开始就规划好后期重构成本高共享资源适度抽取过度共享会反向影响依赖深度控制在 5 层以内避免加载慢地址命名规范化用 Location 而非文件路径大型项目必须分 Package单 Package 性能不可接受上一篇Unity Addressable Assets 实战下一篇资源配置与打包