02-03-原理篇-资源打包流程详解 资源打包流程详解篇章02-原理篇 · 基础阅读时间约 40 分钟前置知识了解 Unity 基本资源加载方式一、引言资源打包是 Unity 资源管理流程的核心环节它决定了资源如何被组织、压缩和分发。一个高效的打包流程可以显著减少构建时间、优化资源体积、提高资源复用率。本章将深入解析资源打包的完整流程包括资源准备、依赖分析、打包规则选择和构建优化结合 YooAsset 的实际实现进行详细说明。资源打包不仅仅是将资源文件压缩成一个包那么简单。它涉及到资源的分类、依赖分析、共享资源处理、压缩策略选择等多个方面。理解这些原理可以帮助开发者设计出更合理的资源管理方案。二、资源打包概述2.1 打包目标资源打包的主要目标包括减小安装包体积通过压缩和去重减小安装包体积实现按需加载将资源分成多个小包根据需要动态加载支持热更新将频繁更新的资源单独打包支持热更新优化加载性能通过合理的分包策略优化加载性能提高资源复用率通过共享资源处理减少资源冗余2.2 打包流程资源打包的完整流程如下资源打包流程 ├── 1. 资源选择 │ ├── 选择需要打包的资源 │ └── 设置资源的 AssetBundle 标签 ├── 2. 依赖分析 │ ├── 分析资源之间的依赖关系 │ └── 构建依赖图 ├── 3. 资源分组 │ ├── 根据依赖关系分组 │ └── 处理共享资源 ├── 4. 数据序列化 │ ├── 将资源数据序列化为二进制格式 │ └── 处理引用关系 ├── 5. 压缩处理 │ ├── 根据配置的压缩算法压缩数据 │ └── 生成压缩后的数据块 ├── 6. 文件组装 │ ├── 将压缩后的数据组装成 AssetBundle 文件 │ └── 生成资源清单和依赖信息 └── 7. 清单生成 ├── 生成全局资源清单 └── 生成依赖信息文件2.3 打包系统架构YooAsset 的打包系统由以下几个核心组件组成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。// 设置资源的 AssetBundle 标签 AssetImporter importer AssetImporter.GetAtPath(assetPath); importer.assetBundleName mybundle; importer.assetBundleVariant bundle;四、依赖分析4.1 依赖分析原理依赖分析是打包过程中最关键的一步。Unity 在打包时会递归地分析资源之间的依赖关系确保所有依赖的资源都被正确打包。依赖分析流程资源扫描扫描每个分组中的所有资源依赖提取提取每个资源的直接依赖依赖图构建将资源之间的依赖关系构建为有向图循环依赖检测检测依赖图中是否存在循环依赖共享资源提取识别被多个分组共享的资源提取为独立 Bundle依赖分析的关键点递归分析依赖分析是递归进行的确保所有层级的依赖都被找到去重处理使用 HashSet 避免重复分析同一个资源循环依赖检测Unity 会检测循环依赖并抛出错误外部依赖处理对于不在当前 Bundle 中的依赖需要单独处理4.2 依赖图依赖图是描述资源之间依赖关系的有向图。在依赖图中每个节点代表一个资源每条边代表一个依赖关系。依赖图示例 Prefab1 - Texture1 Prefab1 - Material1 Material1 - Texture1 Prefab2 - Texture1 Prefab2 - Material1 依赖图 Prefab1 -- Texture1 Prefab1 -- Material1 -- Texture1 Prefab2 -- Texture1 Prefab2 -- Material14.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 支持多种压缩算法压缩算法压缩率解压速度适用场景LZMA60-80%慢网络传输LZ440-60%极快运行时加载LZ4HC50-70%快平衡场景无压缩0%最快首包资源6.2 压缩流程压缩流程如下读取序列化数据读取序列化后的数据选择压缩算法根据配置的压缩算法选择压缩方式压缩数据使用选择的压缩算法压缩数据写入 Bundle将压缩后的数据写入 AssetBundle 文件七、文件组装7.1 文件组装流程文件组装是将压缩后的数据和元数据组装成 AssetBundle 文件的过程。文件组装流程写入文件头写入 AssetBundle 文件头写入资源数据写入压缩后的资源数据写入依赖信息写入依赖信息写入资源清单写入资源清单写入类型树写入类型树7.2 资源清单资源清单是 AssetBundle 的索引部分记录了 Bundle 中所有资源的名称、偏移量、大小和类型信息。资源清单格式资源清单 ├── 资源数量 ├── 资源 1 │ ├── 资源名称 │ ├── 资源偏移量 │ ├── 资源大小 │ └── 资源类型 ├── 资源 2 │ ├── 资源名称 │ ├── 资源偏移量 │ ├── 资源大小 │ └── 资源类型 └── ...八、清单生成8.1 全局资源清单全局资源清单记录了所有 Bundle 的信息包括 Bundle 名称、依赖关系、资源列表等。全局资源清单格式全局资源清单 ├── 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 的资源管理功能开发出性能更优、体验更好的游戏。下一篇资源组织与依赖分析九、原理篇补充知识资源依赖管理的深层原理资源依赖管理是资源管理系统中最复杂的部分之一。当游戏加载一个资源时这个资源可能依赖于其他资源而其他资源又可能依赖于更多的资源。这种依赖关系构成了一张有向无环图。资源管理系统需要能够解析这张图确定正确的加载顺序并且在依赖资源还未加载完成时正确处理加载请求。依赖管理的基本原理是引用追踪。每个资源在构建时会被分析其引用关系这些关系被序列化为依赖数据。在运行时通过依赖数据确定资源的加载顺序。当加载一个资源时先加载它的所有依赖所有的依赖都加载完成后再加载它自己。这种递归的依赖加载机制确保了资源在加载完成时处于完整可用的状态。资源对象的生命周期管理资源对象从创建到销毁经历了完整的生命周期。生命周期管理的关键在于确定何时加载资源和何时卸载资源。过早加载会浪费内存过晚加载会导致卡顿。过早卸载会导致资源被频繁地加载和卸载过晚卸载会导致内存浪费。引用计数是解决生命周期问题的基础机制。每个资源对象维护一个引用计数器。当资源被加载时引用计数设置初始值当其他系统获取资源引用时计数加一当其他系统释放资源引用时计数减一。当引用计数降为零时资源可以被安全地卸载。AssetBundle 的底层文件格式Unity AssetBundle 使用了一种特定的文件格式来存储资源数据。这个格式包含了文件头和数据块两部分。文件头包含了文件的基本信息包括文件大小、压缩方式和资源列表。数据块包含了实际的资源数据可以是压缩的也可以是未压缩的。AssetBundle 文件头的结构包括一个魔数标识文件。格式版本号指示使用的序列化版本。文件大小记录了整个文件的大小。压缩方式指示了数据块使用的压缩算法。资源列表包含了包内所有资源的路径和偏移量。异步加载的实现机制Unity 的资源加载接口设计为异步方式。异步加载的实现依赖于 Unity 的 PlayerLoop 系统。资源加载请求被提交后立即返回 AsyncOperation 对象主循环在后续帧中检查加载进度。当加载完成时触发完成回调通知调用者。十、原理篇补充材料资源加载的核心路径理解资源加载的核心路径有助于在遇到问题时进行排查。资源加载路径包括资源定位、依赖解析、数据读取、对象实例化和资源激活等步骤。每一步都可能成为性能瓶颈。资源定位是资源加载的第一步。资源系统根据资源的地址和类型信息找到对应的资源文件。资源定位的效率直接影响首次加载的速度。YooAsset 通过资源索引表加速资源定位将资源地址到文件路径的映射预计算并缓存。依赖解析是资源加载中最复杂的步骤。资源系统分析资源的依赖关系确定需要加载的所有文件和加载的顺序。YooAsset 的依赖图在构建时生成并在运行时按需解析。数据读取后需要经过对象实例化和资源激活才能被游戏所使用。对象实例化是从资源数据创建游戏对象的过程。资源激活是调用资源上的初始化方法的步骤。内存管理的核心机制资源管理系统通过多种机制协同工作来管理内存。引用计数是最基本的机制跟踪资源的使用情况。缓存管理机制决定资源在内存中保留的时间。生命周期管理机制控制资源的创建和销毁。垃圾回收是 Unity 引擎提供的自动内存管理机制。它在回收不再使用的对象时会引起 GC 停顿。减少 GC 分配是性能优化的重要方向。资源更新的技术细节热更新是网络游戏的核心需求之一。热更新的实现依赖版本管理、差异对比和安全校验等技术。版本管理确保客户端和服务器使用一致的资源版本。差异对比减少了不必要的数据传输。安全校验确保下载的资源是完整的和未被篡改的。增量更新只下载变化的部分是最常用的更新方式。断点续传在网络不稳定的环境中提高更新的成功率。并发下载利用带宽资源缩短更新等待的时间。十一、关键技术原理补充AssetBundle 格式的深入理解AssetBundle 的文件格式可以分为几个关键部分。文件头包含了包的元数据信息。数据段包含了实际的资源数据。可选的信息段包含了额外的元数据。理解这些结构对于排查加载问题和优化性能非常有帮助。文件头的结构包括魔数字段用于标识文件类型。文件版本字段用于兼容性检查。数据偏移字段指示数据段的起始位置。资源列表字段包含了包内所有资源的路径和索引。数据段的组织方式取决于使用的压缩方式。未压缩的数据段直接包含了资源文件的序列化数据。LZ4 压缩的数据段按块压缩解压时按需解压特定的块。LZMA 压缩的数据段按流压缩解压速度较慢但压缩率更高。资源哈希与版本管理资源哈希是确定资源唯一性的重要手段。哈希值是根据文件内容计算得出的固定长度字符串。相同内容的文件会产生相同的哈希值。通过比较哈希值可以判断文件是否有变化。在版本管理中使用哈希值进行增量更新。服务器端计算每个资源包的哈希值。客户端下载版本文件后与本地缓存对比。哈希值不同的资源包就是需要更新的包。这种方法可以精确地确定需要更新的资源。异步编程模型在资源管理中的应用Unity 的资源加载大量使用了异步编程模型。异步操作的核心是不阻塞主线程。资源数据在后台线程中读取读取完成后通知主线程进行对象创建。协程是 Unity 中实现异步操作的传统方式。通过 yield return 语句将操作分发到多个帧执行。Task 是 .NET 提供的异步编程模型。在 Unity 中通过 async await 关键字使用。资源缓存的设计要点资源缓存的设计需要在命中率和内存占用之间取得平衡。缓存空间有限需要决定哪些资源应该保留哪些资源应该淘汰。访问频率是决定淘汰策略的重要因素。最近最少使用的资源优先被淘汰。缓存大小的设置直接影响缓存的效率。缓存空间过大会占用过多内存。缓存空间过小会导致频繁的缓存缺失增加加载次数。建议根据设备的内存大小和游戏的需求动态调整缓存大小。十二章 补充知识点资源加载的详细流程资源加载的详细流程涉及多个步骤的协作。当游戏代码调用资源加载接口时资源系统接收请求。第一步解析资源地址确定资源所属的资源包。第二步检查资源缓存看资源是否已经加载。第三步如果需要加载则创建加载任务并提交到队列。第四步加载任务执行从存储介质读取数据。第五步数据读取完成后进行解密和解压。第六步将原始数据转换为 Unity 可识别的资源对象。第七步检查资源的依赖是否已经加载完成。第八步触发加载完成回调通知调用者。每一步都可能出现异常需要相应的错误处理。地址解析失败可能返回无效地址错误。资源包不存在可能返回文件未找到错误。数据读取失败可能返回 IO 错误。解密失败可能返回密钥错误。资源对象创建失败可能返回内存不足错误。资源加载的性能分析资源加载的性能受到多个因素的影响。资源的数量影响总加载时间。资源包的数量影响加载请求的次数。资源包的大小影响单次加载的时间。资源的依赖关系影响加载的复杂度。使用 Unity Profiler 可以分析资源加载中的性能瓶颈。查看每个加载操作的时间消耗。查看加载过程中的 GC 分配。查看资源的加载顺序和依赖关系。根据 Profiler 的数据进行有针对性优化。十三章 补充知识点AssetBundle 的兼容性AssetBundle 在不同版本的 Unity 之间存在兼容性问题。高版本 Unity 构建的 AssetBundle 可能无法在低版本 Unity 中加载。低版本 Unity 构建的 AssetBundle 可以在高版本 Unity 中加载。在项目中应该统一使用相同版本的 Unity 构建资源。AssetBundle 在不同平台之间也不兼容。为 iOS 平台构建的 AssetBundle 不能在 Android 平台使用。在发布时应该为目标平台分别构建资源包。构建工具的版本也要保持一致。资源管理的自动化实践自动化是提高资源管理效率的重要手段。自动构建可以在每次代码提交后自动执行资源构建。自动测试可以在构建通过后自动运行测试用例。自动部署可以将构建产物自动部署到测试环境。自动化的实现依赖于脚本和工具链。使用命令行工具可以集成到 CI 流程中。使用构建脚本可以保证构建过程的重复性。使用测试脚本可以自动验证构建结果的正确性。补充说明资源管理系统的稳定性和效率直接影响到游戏的整体表现。一个设计良好的资源管理系统需要兼顾加载速度和内存效率。在开发过程中持续关注和管理资源的使用状况是保证游戏品质的重要手段。开发团队应该建立资源管理的规范和流程定期检查和优化资源的使用情况。通过这些措施可以有效提升游戏的性能和用户体验。