当前位置: 首页 > news >正文

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 输出 BuildMap,Builder 消费 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 List<BuildBundleInfo> Bundles = new List<BuildBundleInfo>(); // 资源到 Bundle 的映射 public Dictionary<string, string> AssetToBundle = new Dictionary<string, string>(); // 资源依赖关系 public Dictionary<string, List<string>> AssetDependencies = new Dictionary<string, List<string>>(); } public class BuildBundleInfo { public string BundleName; public string FileName; public List<string> Assets = new List<string>(); public List<string> Dependencies = new List<string>(); 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 Dictionary<string, List<string>> AnalyzeDependencies(List<string> assets) { Dictionary<string, List<string>> dependencies = new Dictionary<string, List<string>>(); foreach (var asset in assets) { // 使用 Unity API 获取资源依赖 string[] deps = AssetDatabase.GetDependencies(asset, true); dependencies[asset] = new List<string>(deps); } return dependencies; } }

AssetDatabase.GetDependencies 详解

AssetDatabase.GetDependencies 是 Unity 提供的 API,用于获取资源的所有依赖(包括直接依赖和间接依赖)。

递归依赖详解

GetDependencies(asset, true) 会递归获取资源的所有依赖,包括间接依赖。

3.3 循环依赖检测

循环依赖检测的源码实现:

public class CycleDetector { public List<List<string>> DetectCycles(Dictionary<string, List<string>> graph) { List<List<string>> cycles = new List<List<string>>(); HashSet<string> visited = new HashSet<string>(); HashSet<string> inStack = new HashSet<string>(); Stack<string> path = new Stack<string>(); foreach (var node in graph.Keys) { DFS(node, graph, visited, inStack, path, cycles); } return cycles; } private void DFS(string node, Dictionary<string, List<string>> graph, HashSet<string> visited, HashSet<string> inStack, Stack<string> path, List<List<string>> 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 List<string> FindSharedAssets(BuildMap buildMap) { Dictionary<string, int> assetRefCount = new Dictionary<string, int>(); // 统计每个资源的引用次数 foreach (var bundle in buildMap.Bundles) { foreach (var asset in bundle.Assets) { if (!assetRefCount.ContainsKey(asset)) assetRefCount[asset] = 0; assetRefCount[asset]++; } } // 找出被多个 Bundle 引用的资源 List<string> sharedAssets = new List<string>(); 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 List<BuildBundleInfo> AllocateBundles(List<CollectAssetInfo> assets, AssetBundlePackage package) { List<BuildBundleInfo> bundles = new List<BuildBundleInfo>(); 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 名称唯一性 HashSet<string> bundleNames = new HashSet<string>(); 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.FromJson<BuildMap>(json); } public BuildMap Diff(BuildMap oldMap, BuildMap newMap) { // 对比两个 BuildMap,找出变化的 Bundle var changedBundles = new List<BuildBundleInfo>(); 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)
下一篇:构建管线分析

http://www.jsqmd.com/news/1177075/

相关文章:

  • Java 面向对象基础:构造方法、toString重写与封装实战详解
  • 【Midjourney批量生成终极指南】:20年AI图像工程师亲授17种高精度可控批量工作流(含V6.1最新API+Discord Bot实战)
  • 《机械工厂6S白皮书05|第4章 执行核心:6S执行力三问诀——跳出“员工不听话“误区,把人员管理难题转化为系统优化问题,打通全员长效自主落地通道》
  • AI推荐结果怎么优化:适合深圳少儿素质培训机构的GEO服务商哪家好?全程零代码SAAS操作
  • 2026年不错的AI漫剧制作平台 核心优势与适配场景
  • 可白嫖源码---课程设计--毕业设计--springboot3中医养生药膳推荐系统[编号:project11931](案例分析)-附源码
  • 用Ai开发微信小程序,没想到那么简单(三)视频提取
  • RAG 实战教学完全指南
  • HLW8112芯片软件实现代码(8)
  • closeEvalDialog is not defined、pages.json 语法、硬编码 ID ——记几个低级错误
  • 去烟味喷雾效果好的有哪些?核心特质解析与全场景选购使用指南 - 行业洞察分析师
  • Socket网络编程入门:UDP+TCP双案例手把手教你写服务器
  • 免费开源2D动画制作软件:OpenToonz完整指南
  • Glorytun安全配置详解:保护你的UDP隧道通信的完整策略
  • 快速上手PUBG地图黑客:5分钟搭建实战指南
  • 开心消消乐分数系统详解:如何设计刺激玩家的积分机制
  • 如何用PrivateGPT快速搭建企业级私有化AI应用:完整指南
  • morss部署教程:从本地到云端的完整步骤指南
  • 【Matlab】不确定干扰下无人机鲁棒控制实现
  • 我的第一个工具调用Agent:从零构建“知识库+计算器+时间+文件”四合一智能体
  • 手把手教你学pcie--TLP 长什么样?(不看协议也能懂)
  • 如何利用开源AI框架实现精准时间序列预测:5个关键技术解析
  • Rufus终极指南:三步搞定Windows 11安装,轻松绕过TPM 2.0限制
  • 暑期学习怎么做才有效?别再让孩子“假努力”,这套学习方法正在被家长用起来
  • vagas平台的未来路线图:即将到来的功能与改进
  • MetaTube SDK Go实战案例:构建企业级元数据管理平台的完整方案
  • 【API文档自动化革命】:ChatGPT生成高质量OpenAPI文档的5大实战范式(2024企业级落地指南)
  • Inventory Kamera实战教程:如何一键扫描并导出原神完整库存数据
  • 智能拼版构建柔性与刚性线路板智造
  • 如何快速部署本地大语言模型:TextGen完整使用指南