04-03-YooAsset源码-Unity构建映射与依赖分析(BuildMap)

发布时间:2026/7/12 23:37:40
04-03-YooAsset源码-Unity构建映射与依赖分析(BuildMap) 源码-构建映射与依赖分析BuildMap篇章04-源码深度-编辑器模块阅读时间约 30 分钟前置知识了解 AssetBundleCollector 模块一、引言本章将深入解析 YooAsset 构建映射与依赖分析BuildMap的源码实现。BuildMap 是 YooAsset 编辑器端的核心数据结构记录了资源到 Bundle 的映射关系以及 Bundle 之间的依赖关系。深入理解 BuildMap 的实现是掌握 YooAsset 资源打包机制的关键。BuildMap 整合了所有 Collector 的收集结果进行了依赖分析、冗余检测和打包优化是资源打包的蓝图。本章将从源码层面深入解析 BuildMap 的实现。二、BuildMap 概述2.1 BuildMap 的作用BuildMap 在 YooAsset 中起到核心作用打包依据BuildMap 是 Builder 打包的依据。Builder 根据 BuildMap 执行实际的 AssetBundle 打包操作。依赖管理BuildMap 记录了 Bundle 之间的依赖关系避免重复打包共享资源。优化分析BuildMap 可以分析资源冗余、循环依赖等问题提供优化建议。数据桥梁BuildMap 是 Collector 和 Builder 之间的数据桥梁。Collector 输出 BuildMapBuilder 消费 BuildMap。2.2 BuildMap 的生成流程BuildMap 的生成流程如下BuildMap 生成流程 1. 收集所有 Collector 输出 2. 合并所有资源信息 3. 分析资源依赖关系 4. 检测循环依赖 5. 计算共享资源 6. 分配 Bundle 7. 验证 BuildMap 8. 输出最终 BuildMap收集所有 Collector 输出详解从所有 Collector 收集资源信息。每个 Collector 输出一个 CollectAssetInfo 列表。合并所有资源信息详解合并所有 Collector 的输出去除重复资源生成统一的资源列表。分析资源依赖关系详解使用 Unity 的 AssetDatabase.GetDependencies API 分析资源之间的依赖关系。检测循环依赖详解使用图算法检测依赖图中是否存在循环依赖。如果存在循环依赖抛出错误。计算共享资源详解识别被多个 Bundle 共享的资源决定是否提取为共享 Bundle。分配 Bundle 详解根据 Pack Rule 决定每个资源属于哪个 Bundle。验证 BuildMap 详解验证 BuildMap 的完整性包括所有资源都有 Bundle 分配、所有依赖关系都正确等。2.3 BuildMap 的数据结构BuildMap 的核心数据结构public class BuildMap { // 包名 public string PackageName; // 包版本 public string PackageVersion; // Bundle 列表 public ListBuildBundleInfo Bundles new ListBuildBundleInfo(); // 资源到 Bundle 的映射 public Dictionarystring, string AssetToBundle new Dictionarystring, string(); // 资源依赖关系 public Dictionarystring, Liststring AssetDependencies new Dictionarystring, Liststring(); } public class BuildBundleInfo { public string BundleName; public string FileName; public Liststring Assets new Liststring(); public Liststring Dependencies new Liststring(); public int MainAssetCount; public long FileSize; public string Hash; public EBundleType BundleType; }BuildBundleInfo 详解BuildBundleInfo 描述了一个 Bundle 的所有信息包括 Bundle 名称、包含的资源、依赖的 Bundle、文件大小、Hash 等。AssetToBundle 详解AssetToBundle 记录了资源到 Bundle 的映射关系便于快速查找资源所属的 Bundle。AssetDependencies 详解AssetDependencies 记录了资源之间的依赖关系用于分析资源依赖图。三、依赖分析实现3.1 依赖分析的作用依赖分析是 BuildMap 的核心功能构建依赖图分析所有资源之间的依赖关系构建完整的依赖图。检测循环依赖检测依赖图中是否存在循环依赖。如果存在需要警告开发者。识别共享资源识别被多个 Bundle 共享的资源决定是否提取为共享 Bundle。优化打包根据依赖关系优化打包策略避免重复打包。3.2 依赖分析的源码实现依赖分析的核心代码public class DependencyAnalyzer { public Dictionarystring, Liststring AnalyzeDependencies(Liststring assets) { Dictionarystring, Liststring dependencies new Dictionarystring, Liststring(); foreach (var asset in assets) { // 使用 Unity API 获取资源依赖 string[] deps AssetDatabase.GetDependencies(asset, true); dependencies[asset] new Liststring(deps); } return dependencies; } }AssetDatabase.GetDependencies 详解AssetDatabase.GetDependencies 是 Unity 提供的 API用于获取资源的所有依赖包括直接依赖和间接依赖。递归依赖详解GetDependencies(asset, true) 会递归获取资源的所有依赖包括间接依赖。3.3 循环依赖检测循环依赖检测的源码实现public class CycleDetector { public ListListstring DetectCycles(Dictionarystring, Liststring graph) { ListListstring cycles new ListListstring(); HashSetstring visited new HashSetstring(); HashSetstring inStack new HashSetstring(); Stackstring path new Stackstring(); foreach (var node in graph.Keys) { DFS(node, graph, visited, inStack, path, cycles); } return cycles; } private void DFS(string node, Dictionarystring, Liststring graph, HashSetstring visited, HashSetstring inStack, Stackstring path, ListListstring cycles) { if (inStack.Contains(node)) { // 发现循环 var cyclePath path.ToList(); int index cyclePath.IndexOf(node); cycles.Add(cyclePath.GetRange(index, cyclePath.Count - index)); return; } if (visited.Contains(node)) return; visited.Add(node); inStack.Add(node); path.Push(node); if (graph.ContainsKey(node)) { foreach (var neighbor in graph[node]) { DFS(neighbor, graph, visited, inStack, path, cycles); } } path.Pop(); inStack.Remove(node); } }DFS 详解使用深度优先搜索DFS检测循环依赖。循环依赖的处理如果发现循环依赖需要警告开发者并提示修复。3.4 共享资源识别共享资源识别的源码实现public class SharedAssetExtractor { public Liststring FindSharedAssets(BuildMap buildMap) { Dictionarystring, int assetRefCount new Dictionarystring, int(); // 统计每个资源的引用次数 foreach (var bundle in buildMap.Bundles) { foreach (var asset in bundle.Assets) { if (!assetRefCount.ContainsKey(asset)) assetRefCount[asset] 0; assetRefCount[asset]; } } // 找出被多个 Bundle 引用的资源 Liststring sharedAssets new Liststring(); foreach (var kvp in assetRefCount) { if (kvp.Value 1) { sharedAssets.Add(kvp.Key); } } return sharedAssets; } }共享资源的处理共享资源通常会被提取到独立的 Bundle 中避免重复打包。四、Bundle 分配策略4.1 打包规则类型YooAsset 支持多种打包规则Collector 级别打包每个 Collector 单独打包为一个 Bundle。适用于独立模块。Group 级别打包Group 内所有 Collector 合并打包为一个 Bundle。适用于整体模块。目录级别打包按顶级目录打包每个目录一个 Bundle。适用于按目录组织的资源。自定义打包开发者可以自定义打包规则根据自己的需求打包。4.2 Bundle 分配的源码实现Bundle 分配的核心代码public class BundleAllocator { public ListBuildBundleInfo AllocateBundles(ListCollectAssetInfo assets, AssetBundlePackage package) { ListBuildBundleInfo bundles new ListBuildBundleInfo(); foreach (var group in package.Groups) { switch (group.PackRuleType) { case EGroupPackRuleType.CollectorPack: // 每个 Collector 单独打包 foreach (var collector in group.Collectors) { BuildBundleInfo bundle CreateBundleFromCollector(collector); bundles.Add(bundle); } break; case EGroupPackRuleType.GroupPack: // Group 合并打包 BuildBundleInfo groupBundle CreateBundleFromGroup(group); bundles.Add(groupBundle); break; case EGroupPackRuleType.TopDirectoryPack: // 按顶级目录打包 var topDirBundles CreateBundlesByTopDirectory(group); bundles.AddRange(topDirBundles); break; } } return bundles; } }Collector 级别打包详解每个 Collector 的所有资源打包为一个 Bundle。Bundle 名称默认为 Collector 名称。Group 级别打包详解Group 内所有 Collector 的资源合并打包为一个 Bundle。Bundle 名称默认为 Group 名称。目录级别打包详解按顶级目录打包每个顶级目录的所有资源打包为一个 Bundle。Bundle 名称默认为目录名。4.3 Bundle 命名规则Bundle 的命名规则需要考虑唯一性Bundle 名称必须唯一不能重复。可读性Bundle 名称应该易读便于调试和优化。长度限制Bundle 名称长度有限制不能过长。特殊字符Bundle 名称不能包含特殊字符。YooAsset 的命名规则YooAsset 默认使用group/collector作为 Bundle 名称便于阅读和理解。五、BuildMap 验证5.1 验证的作用BuildMap 验证用于确保 BuildMap 的正确性检查所有资源都有 Bundle 分配检查所有依赖关系都正确检查 Bundle 名称是否唯一检查 Bundle 大小是否合理检查是否存在循环依赖5.2 验证的实现public class BuildMapValidator { public ValidationResult Validate(BuildMap buildMap) { ValidationResult result new ValidationResult(); // 检查 Bundle 名称唯一性 HashSetstring bundleNames new HashSetstring(); foreach (var bundle in buildMap.Bundles) { if (!bundleNames.Add(bundle.BundleName)) { result.AddError($Duplicate bundle name: {bundle.BundleName}); } } // 检查资源完整性 foreach (var asset in buildMap.AssetToBundle.Keys) { if (!File.Exists(asset)) { result.AddError($Asset not found: {asset}); } } // 检查循环依赖 var cycles new CycleDetector().DetectCycles(buildMap.AssetDependencies); foreach (var cycle in cycles) { result.AddError($Cycle detected: {string.Join( - , cycle)}); } return result; } }Bundle 名称唯一性检查详解使用 HashSet 检查 Bundle 名称是否唯一。资源完整性检查详解使用 File.Exists 检查所有资源是否存在。循环依赖检查详解使用 CycleDetector 检查依赖图中是否存在循环依赖。5.3 错误处理BuildMap 验证发现错误时需要妥善处理严重错误循环依赖资源不存在Bundle 名称重复警告Bundle 过大资源过多依赖过深错误处理策略严重错误阻止构建要求修复警告提示开发者但允许构建继续六、BuildMap 缓存6.1 缓存的作用BuildMap 缓存用于提高构建效率避免重复计算对于未变化的部分不需要重新计算 BuildMap。支持增量构建通过对比前后两次 BuildMap识别变化的 Bundle。提高构建速度减少构建时间特别是对于大型项目。6.2 缓存的实现public class BuildMapCache { public void Save(BuildMap buildMap, string cachePath) { string json JsonUtility.ToJson(buildMap, true); File.WriteAllText(cachePath, json); } public BuildMap Load(string cachePath) { if (!File.Exists(cachePath)) return null; string json File.ReadAllText(cachePath); return JsonUtility.FromJsonBuildMap(json); } public BuildMap Diff(BuildMap oldMap, BuildMap newMap) { // 对比两个 BuildMap找出变化的 Bundle var changedBundles new ListBuildBundleInfo(); foreach (var newBundle in newMap.Bundles) { var oldBundle oldMap.Bundles.FirstOrDefault(b b.BundleName newBundle.BundleName); if (oldBundle null || !IsBundleEqual(oldBundle, newBundle)) { changedBundles.Add(newBundle); } } // 创建新的 BuildMap只包含变化的 Bundle BuildMap diffMap new BuildMap(); diffMap.Bundles changedBundles; return diffMap; } }缓存保存详解将 BuildMap 序列化为 JSON 并保存到文件。缓存加载详解从文件读取 JSON 并反序列化为 BuildMap 对象。差异对比详解对比两个 BuildMap找出变化的 Bundle。6.3 缓存的清理BuildMap 缓存需要定期清理清理过期的缓存清理无效的缓存限制缓存大小清理策略按时间清理清理超过指定时间的缓存按大小清理限制缓存总大小按版本清理清理旧版本的缓存七、总结本章深入解析了 YooAsset 构建映射与依赖分析BuildMap的源码实现包括BuildMap 概述BuildMap 的作用、生成流程、数据结构依赖分析实现依赖分析的源码、循环依赖检测、共享资源识别Bundle 分配策略打包规则类型、Bundle 分配实现、命名规则BuildMap 验证验证的作用、实现、错误处理BuildMap 缓存缓存的作用、实现、清理通过深入理解 BuildMap 的源码实现开发者可以更好地使用 YooAsset 的资源打包功能并根据自己的需求进行扩展和优化。上一篇资源收集模块AssetBundleCollector下一篇构建管线分析