资源打包流程详解
篇章 :02-原理篇 · 基础 阅读时间 :约 40 分钟 前置知识:了解 Unity 基本资源加载方式
一、引言
资源打包是 Unity 资源管理流程的核心环节,它决定了资源如何被组织、压缩和分发。一个高效的打包流程可以显著减少构建时间、优化资源体积、提高资源复用率。本章将深入解析资源打包的完整流程,包括资源准备、依赖分析、打包规则选择和构建优化,结合 YooAsset 的实际实现进行详细说明。
资源打包不仅仅是将资源文件压缩成一个包那么简单。它涉及到资源的分类、依赖分析、共享资源处理、压缩策略选择等多个方面。理解这些原理,可以帮助开发者设计出更合理的资源管理方案。
二、资源打包概述
2.1 打包目标
资源打包的主要目标包括:
- 减小安装包体积:通过压缩和去重,减小安装包体积
- 实现按需加载:将资源分成多个小包,根据需要动态加载
- 支持热更新:将频繁更新的资源单独打包,支持热更新
- 优化加载性能:通过合理的分包策略,优化加载性能
- 提高资源复用率:通过共享资源处理,减少资源冗余
2.2 打包流程
资源打包的完整流程如下:
markdown
资源打包流程
├── 1. 资源选择
│ ├── 选择需要打包的资源
│ └── 设置资源的 AssetBundle 标签
├── 2. 依赖分析
│ ├── 分析资源之间的依赖关系
│ └── 构建依赖图
├── 3. 资源分组
│ ├── 根据依赖关系分组
│ └── 处理共享资源
├── 4. 数据序列化
│ ├── 将资源数据序列化为二进制格式
│ └── 处理引用关系
├── 5. 压缩处理
│ ├── 根据配置的压缩算法压缩数据
│ └── 生成压缩后的数据块
├── 6. 文件组装
│ ├── 将压缩后的数据组装成 AssetBundle 文件
│ └── 生成资源清单和依赖信息
└── 7. 清单生成
├── 生成全局资源清单
└── 生成依赖信息文件
2.3 打包系统架构
YooAsset 的打包系统由以下几个核心组件组成:
markdown
YooAsset 打包系统架构
├── AssetBundleCollector:资源收集器
│ ├── MainAssetCollector:主资源收集器
│ ├── StaticAssetCollector:静态资源收集器
│ └── DependAssetCollector:依赖资源收集器
├── BundlePackRule:打包规则
│ ├── PackSeparately:单独打包
│ ├── PackDirectory:目录打包
│ ├── PackTopDirectory:顶级目录打包
│ └── PackGroup:分组打包
├── BuildPipeline:构建管线
│ ├── EditorSimulateBuildPipeline:编辑器模拟构建
│ ├── BuiltinBuildPipeline:内置构建管线
│ ├── ScriptableBuildPipeline:可编程构建管线
│ └── RawFileBuildPipeline:原生文件构建管线
└── BuildOutput:构建输出
├── AssetBundle 文件
├── Manifest 文件
└── 依赖信息文件
三、资源选择
3.1 资源选择策略
资源选择是打包的第一步,需要决定哪些资源参与打包。常见的资源选择策略包括:
- 按路径选择:选择指定路径下的所有资源
- 按类型选择:选择指定类型的资源
- 按标签选择:选择具有特定标签的资源
- 手动选择:手动选择需要打包的资源
3.2 资源收集器
YooAsset 提供了三种资源收集器:
| 收集器类型 | 描述 | 适用场景 |
|---|---|---|
| MainAssetCollector | 主资源收集器,写入资源清单 | 需要代码加载的资源 |
| StaticAssetCollector | 静态资源收集器,参与打包但不写入清单 | 定制化打包策略 |
| DependAssetCollector | 依赖资源收集器,不写入清单 | 未被引用时自动剔除 |
MainAssetCollector 详解:
MainAssetCollector 是最常用的资源收集器。它会将收集的资源写入资源清单,开发者可以通过代码加载这些资源。MainAssetCollector 适用于需要动态加载的资源,如预制体、场景、纹理等。
StaticAssetCollector 详解:
StaticAssetCollector 会将资源打包到 AssetBundle 中,但不会写入资源清单。这种收集器适用于需要定制化打包策略的资源,如 Shader、音频等。
DependAssetCollector 详解:
DependAssetCollector 会将资源打包到 AssetBundle 中,但不会写入资源清单。当资源没有被其他资源引用时,它会被自动剔除。这种收集器适用于可选资源,如 DLC 内容。
3.3 资源标签
资源标签是用于标识资源所属 Bundle 的标记。在 YooAsset 中,可以通过设置资源的 AssetBundle 属性来指定其所属的 Bundle。
csharp
// 设置资源的 AssetBundle 标签
AssetImporter importer = AssetImporter.GetAtPath(assetPath);
importer.assetBundleName = "mybundle";
importer.assetBundleVariant = "bundle";
四、依赖分析
4.1 依赖分析原理
依赖分析是打包过程中最关键的一步。Unity 在打包时会递归地分析资源之间的依赖关系,确保所有依赖的资源都被正确打包。
依赖分析流程:
- 资源扫描:扫描每个分组中的所有资源
- 依赖提取:提取每个资源的直接依赖
- 依赖图构建:将资源之间的依赖关系构建为有向图
- 循环依赖检测:检测依赖图中是否存在循环依赖
- 共享资源提取:识别被多个分组共享的资源,提取为独立 Bundle
依赖分析的关键点:
- 递归分析:依赖分析是递归进行的,确保所有层级的依赖都被找到
- 去重处理:使用 HashSet 避免重复分析同一个资源
- 循环依赖检测:Unity 会检测循环依赖并抛出错误
- 外部依赖处理:对于不在当前 Bundle 中的依赖,需要单独处理
4.2 依赖图
依赖图是描述资源之间依赖关系的有向图。在依赖图中,每个节点代表一个资源,每条边代表一个依赖关系。
rust
依赖图示例:
Prefab1 -> Texture1
Prefab1 -> Material1
Material1 -> Texture1
Prefab2 -> Texture1
Prefab2 -> Material1
依赖图:
Prefab1 --> Texture1
Prefab1 --> Material1 --> Texture1
Prefab2 --> Texture1
Prefab2 --> Material1
4.3 共享资源处理
当多个 Bundle 共享同一个资源时,需要将共享资源提取为独立的 Bundle,避免资源重复。
共享资源处理流程:
- 识别共享资源:扫描所有 Bundle,识别被多个 Bundle 共享的资源
- 提取共享资源:将共享资源提取为独立的 Bundle
- 更新依赖关系:更新所有引用共享资源的 Bundle 的依赖关系
共享资源处理策略:
| 策略 | 描述 | 优点 | 缺点 |
|---|---|---|---|
| 提取为独立 Bundle | 将共享资源提取为独立 Bundle | 避免资源重复 | 增加依赖关系 |
| 复制到每个 Bundle | 将共享资源复制到每个 Bundle | 简化依赖关系 | 资源重复 |
| 合并到第一个 Bundle | 将共享资源合并到第一个 Bundle | 简化依赖关系 | 第一个 Bundle 变大 |
五、数据序列化
5.1 序列化原理
序列化是将对象的状态转换为可以存储或传输的格式的过程。Unity 使用自定义的序列化格式来存储资源数据。
序列化流程:
- 读取资源文件:读取原始资源文件
- 解析资源数据:解析资源文件中的数据
- 序列化数据:将资源数据序列化为二进制格式
- 写入 Bundle:将序列化后的数据写入 AssetBundle 文件
5.2 引用处理
在序列化过程中,需要处理资源之间的引用关系。Unity 使用引用索引来管理资源之间的引用关系。
引用处理流程:
- 收集所有引用:收集资源中所有引用的其他资源
- 分配引用索引:为每个被引用的资源分配唯一的索引
- 写入引用列表:将引用列表写入序列化数据中
六、压缩处理
6.1 压缩算法
AssetBundle 支持多种压缩算法:
| 压缩算法 | 压缩率 | 解压速度 | 适用场景 |
|---|---|---|---|
| LZMA | 60-80% | 慢 | 网络传输 |
| LZ4 | 40-60% | 极快 | 运行时加载 |
| LZ4HC | 50-70% | 快 | 平衡场景 |
| 无压缩 | 0% | 最快 | 首包资源 |
6.2 压缩流程
压缩流程如下:
- 读取序列化数据:读取序列化后的数据
- 选择压缩算法:根据配置的压缩算法选择压缩方式
- 压缩数据:使用选择的压缩算法压缩数据
- 写入 Bundle:将压缩后的数据写入 AssetBundle 文件
七、文件组装
7.1 文件组装流程
文件组装是将压缩后的数据和元数据组装成 AssetBundle 文件的过程。
文件组装流程:
- 写入文件头:写入 AssetBundle 文件头
- 写入资源数据:写入压缩后的资源数据
- 写入依赖信息:写入依赖信息
- 写入资源清单:写入资源清单
- 写入类型树:写入类型树
7.2 资源清单
资源清单是 AssetBundle 的索引部分,记录了 Bundle 中所有资源的名称、偏移量、大小和类型信息。
资源清单格式:
erlang
资源清单
├── 资源数量
├── 资源 1
│ ├── 资源名称
│ ├── 资源偏移量
│ ├── 资源大小
│ └── 资源类型
├── 资源 2
│ ├── 资源名称
│ ├── 资源偏移量
│ ├── 资源大小
│ └── 资源类型
└── ...
八、清单生成
8.1 全局资源清单
全局资源清单记录了所有 Bundle 的信息,包括 Bundle 名称、依赖关系、资源列表等。
全局资源清单格式:
erlang
全局资源清单
├── Bundle 1
│ ├── Bundle 名称
│ ├── Bundle 大小
│ ├── Bundle 依赖
│ └── 资源列表
├── Bundle 2
│ ├── Bundle 名称
│ ├── Bundle 大小
│ ├── Bundle 依赖
│ └── 资源列表
└── ...
8.2 依赖信息文件
依赖信息文件记录了所有 Bundle 之间的依赖关系,用于在加载时验证依赖关系。
九、构建优化
9.1 增量构建
增量构建可以显著减少构建时间:
- 变化检测:检测哪些资源发生了变化
- 增量打包:只重新打包发生变化的资源
- 缓存复用:未变化的资源直接从缓存复用
9.2 并行构建
并行构建可以充分利用多核 CPU:
- 模块并行:不同模块的打包可以并行执行
- 资源并行:同一模块内的资源打包可以并行执行
- 依赖管理:确保并行执行时依赖关系正确
9.3 压缩优化
压缩优化可以在体积和加载速度之间取得平衡:
- LZMA 压缩:适合网络传输,压缩率高
- LZ4 压缩:适合运行时加载,解压速度快
- 混合压缩:不同资源使用不同的压缩方式
十、总结
资源打包是 Unity 资源管理的核心环节,理解打包流程对于优化资源管理和加载性能至关重要。在实际项目中,建议:
- 合理选择资源:根据项目需求合理选择需要打包的资源
- 优化依赖关系:避免循环依赖,减少依赖层级
- 选择合适的压缩算法:根据场景选择合适的压缩算法
- 监控打包结果:使用 Profiler 等工具监控打包结果
通过深入理解资源打包的原理,我们可以更好地利用 Unity 的资源管理功能,开发出性能更优、体验更好的游戏。
下一篇 :资源组织与依赖分析