Addressable
Bundle
- Bundle 的简单原理就是按照一定的规则和压缩格式,将 Unity 中的所需要的文件都集合成统一的形式。
- 每一个单独形成的 Bundle 文件,都有一个描述的 manifest 文件来表述这个 Bundle 文件中包含的文件。
- 而 Bundle 与 Bundle 之间又会有一个总的 AssetBundles.manifest 文件来描述他们之间的关系。
Bundle 的实现细节
- Build Asset Bundle 总会通过下面这样的方法来进行。
1 | public static AssetBundleManifest BuildAssetBundles(string outputPath, AssetBundleBuild[] builds, BuildAssetBundleOptions assetBundleOptions, BuildTarget targetPlatform) |
- 其中 outputPath 是 Asset Bundle 导出的路径,builds 是构建好的 AssetBundleBuild 文件的集合,assetBundleOptions 是构建时的选项,targetPlatform 是目标平台。
- 接下来是一个简单的构建 Bundle 的方法,来解释上面的方法都进行了怎么样的工作。
1 | [MenuItem("Tools/BuildAssetBundles")] |
outputPath
在上面的方法中,我的路径是 “AssetBundles”,最后得到的文件在和 Asset 同级的目录下,也就是说这个方法导出的根目录在工程下面。
AssetBundleBuild[] builds
AssetBundleBuild 是一个很关键的结构体,该结构体在 Asset Bundle 和 Addressable 中都用到了,不过现在属于 Asset Bundle,所以可以忽略最后一个属性。
1 | public struct AssetBundleBuild |
先看下面的方法,我举了两个例子来说明每个属性的意义。
1 | private static AssetBundleBuild[] GetAssetBundleBuilds() |
这样的设置会生成如下的 Bundle
assetBundleName 就是 Asset Bundle 的名字,最好就不要重复了,但是理论上来讲,决定一个 Bundle 是否重复的真正名字是 assetBundleName + assetBundleVariant。
比如下面的方法就会生成这样的 Bundle。
1 | private static AssetBundleBuild[] GetAssetBundleBuilds() |
所以 assetBundleVariant 的意义就是为了给同一个资源创建不同的变体,比如一张贴图创建一个 hd 格式和一个 ld 格式的.
assetNames 是一个 string 类型的数组,这就意味着你可以在里面放一个资源,或者一些资源,而这也就是 Asset Bundle 配置的策略意义了。
比如同一个 UI 的图集就很自然的打一个 Bundle,而比较大的模型和其包含的默认贴图就打一个 Bundle,但是该模型的其他贴图或者“皮肤”就应该根据玩法和实际使用场景进行策略配置了。
BuildAssetBundleOptions 选项
接下来就是选项了,下面这张表是官方的表,事实上这个枚举的选项是 17 个,但是从 IL 来看,其中的三个配置已经默认开启了在 Asset Bundle 5.0 系统中(“This has been made obsolete. It is always enabled in the new AssetBundle build system introduced in 5.0.”)。
Properties | 特性 |
---|---|
None | Build assetBundle without any special option 无需任何特殊选项即可构建 assetBundle。 |
UncompressedAssetBundle | Don’t compress the data when creating the AssetBundle. 创建AssetBundle时不要压缩数据。 |
DisableWriteTypeTree | Do not include type information within the AssetBundle. 不要在 AssetBundle 中包含类型信息。 |
DeterministicAssetBundle | Builds an asset bundle using a hash for the id of the object stored in the asset bundle. 使用资产包中存储的对象 ID 的哈希值构建资产包。 |
ForceRebuildAssetBundle | Force rebuild the assetBundles. 强制重建 assetBundles。 |
IgnoreTypeTreeChanges | Ignore the type tree changes when doing the incremental build check. 进行增量构建检查时忽略类型树更改。 |
AppendHashToAssetBundleName | Append the hash to the assetBundle name. 将哈希值附加到 assetBundle 名称。 |
ChunkBasedCompression | Use chunk-based LZ4 compression when creating the AssetBundle. 创建 AssetBundle 时使用基于块的 LZ4 压缩。 |
StrictMode | Do not allow the build to succeed if any errors are reporting during it. 如果在构建过程中报告任何错误,则不允许构建成功。 |
DryRunBuild | Do a dry run build. 进行试运行构建。 |
DisableLoadAssetByFileName | Disables Asset Bundle LoadAsset by file name. 按文件名禁用资源包 LoadAsset。 |
DisableLoadAssetByFileNameWithExtension | Disables Asset Bundle LoadAsset by file name with extension. 按带扩展名的文件名禁用资源包 LoadAsset。 |
AssetBundleStripUnityVersion | Removes the Unity Version number in the Archive File & Serialized File headers during the build. 在构建期间删除存档文件和序列化文件标头中的 Unity 版本号。 |
UseContentHash | Use the content of the asset bundle to calculate the hash. Enabling this flag is recommended to improve incremental build results, but it will force a rebuild of all existing AssetBundles that have been built without the flag. 使用资产包的内容来计算哈希值。建议启用此标志以改善增量构建结果,但它将强制重建所有在没有该标志的情况下构建的现有 AssetBundle。 |
不同平台的 Bundle
Asset Bundle 要根据不同的平台来构建,原因如下:
平台兼容性
AB 包平台强绑定:
Unity 为不同平台编译的 AB 包互不兼容。例如:
Android 包 无法在 iOS 或 Windows 上加载。
独立平台包(如 Windows/Mac/Linux)仅限同类平台使用。
原因:不同平台的资源编码、渲染管线(如 Metal/Vulkan)和依赖库(如 OpenGL ES)不同,导致二进制格式不兼容。
压缩格式差异
Unity 支持三种压缩方式,但不同平台需针对性优化:
压缩方式 | 适用平台 | 特性与影响 |
---|---|---|
LZMA | 全平台通用 | 压缩率最高(包体最小),但解压需完整读取整包,内存占用高,移动端慎用。 |
LZ4 (Chunk) | 移动端首选 | 支持按需解压(即用即加载),内存占用低,适合内存敏感的 iOS/Android。 |
不压缩 | 开发调试/PC 平台 | 加载最快,但包体巨大,禁止用于线上发布。 |
移动端建议:Android/iOS 优先使用 LZ4 压缩,平衡包体与内存开销
存储路径与加载方式
不同平台下 AB 包的存放路径和加载接口存在差异:
字节序问题
不同平台可能使用不同的字节序(大小端问题)。例如:
Android(ARM 架构)通常是小端序。
某些嵌入式系统可能是大端序。
Asset Bundle 文件的二进制数据会根据目标平台调整字节序,以保证运行时能够正确解析数据。
实际场景中的多平台构建 Asset Bundle
多平台构建,假设一个游戏需要在以下平台运行:
PC(Windows 和 macOS)
Android
iOS
为确保资源在不同平台正确加载,需要分别生成以下 Asset Bundle:Windows:包含 DirectX 着色器、DXTC 压缩纹理、MP3 音频。
macOS:包含 Metal 着色器、BC 压缩纹理、AAC 音频。
Android:包含 Vulkan/OpenGL ES 着色器、ETC2/ASTC 压缩纹理、OGG 音频。
iOS:包含 Metal 着色器、PVRTC/ASTC 压缩纹理、AAC 音频。
通过 BuildPipeline.BuildAssetBundles 方法为不同平台生成 Asset Bundle:
1 | using UnityEditor; |
如何管理多平台 Asset Bundle
命名规则
使用平台名称作为 Asset Bundle 的路径前缀:
- Windows/level1_assets
- Android/level1_assets
- iOS/level1_assets
运行时加载
在运行时,根据当前平台加载对应的 Asset Bundle:
1 | string platform = ""; |
Manifest 文件
每个 Asset Bundle 会生成一个对应的 Manifest 文件。Manifest 文件的主要功能是提供关于 Asset Bundle 的元数据,帮助开发者和运行时管理 Asset Bundle 的依赖关系、内容信息等。
Manifest 文件的作用
Manifest 文件是 Asset Bundle 构建过程中自动生成的,主要包含:
- 资源依赖关系:描述该 Asset Bundle 所依赖的其他 Asset Bundle。
- 资源清单:列出该 Asset Bundle 内包含的所有资源。
- 版本控制信息:包括文件校验码(CRC)和哈希值,帮助确保资源的完整性和一致性。
- 加载顺序:通过依赖关系确定加载的优先级。
Manifest 文件的命名
Manifest 文件的名字与 Asset Bundle 名字相同,但带有 .manifest 后缀。
例如,如果构建的 Asset Bundle 名为 example.bundle,则生成的 Manifest 文件名为:
1 | example.bundle.manifest |
Manifest 文件的内容
Manifest 文件是一个纯文本文件,可以用任意文本编辑器打开查看。它的内容结构如下:
示例 Manifest 文件
1 | ManifestFileVersion: 1 |
Manifest 文件的主要字段说明
以下是各字段的详细含义:
ManifestFileVersion
- 含义:Manifest 文件的版本号。
- 用途:指示文件格式的版本,确保不同 Unity 版本生成的 Manifest 文件兼容。
Hash
含义
- Asset Bundle 文件内容的唯一哈希值。
用途:
- 用于版本控制,判断 Asset Bundle 是否发生了变化。
- 如果 Asset Bundle 内容更新,哈希值也会改变。
CRC
含义
- 循环冗余校验码,用于快速检测文件是否完整。
用途
- 加载时可用来验证文件完整性,防止损坏或传输错误。
- Unity 提供 BuildPipeline.GetCRCForAssetBundle 方法可获取该值。
AssetBundleName
含义
- 该 Manifest 文件对应的 Asset Bundle 名称。
用途
- 帮助开发者识别和管理 Asset Bundle。
AssetBundleVariant
含义
- 如果该 Asset Bundle 有变体,会在这里列出。
用途
- 区分不同变体(如高分辨率和低分辨率版本的资源)。
Dependencies
含义
- 列出当前 Asset Bundle 所依赖的其他 Asset Bundle。
用途
- 确保在加载当前 Asset Bundle 前,先加载它的依赖项。
- 防止因缺少依赖导致资源无法正确加载。
- 示例:- shared_assets.bundle 表示 example.bundle 依赖于 shared_assets.bundle。
Assets
含义
- 当前 Asset Bundle 包含的所有资源路径。
用途:
- 用于记录该 Asset Bundle 内的资源清单。
- 可以验证特定资源是否正确包含在 Asset Bundle 中。
示例:
- Assets/Textures/texture1.png
- Assets/Models/model1.prefab
运行时与 Manifest 文件的关系
在运行时,Unity 不直接加载 .manifest 文件,而是通过加载 Asset Bundle 来解析它的依赖关系。开发者可以使用 .manifest 文件的内容来手动管理和优化加载流程。
依赖加载示例:
如果 example.bundle 依赖于 shared_assets.bundle:
- 先加载 shared_assets.bundle。
- 再加载 example.bundle。
- 通过 Unity 的 AssetBundle.GetAllDependencies 方法可以自动解析依赖关系。
特殊的全局 Manifest 文件
除了每个单独的 Asset Bundle 的 Manifest 文件外,Unity 还生成一个全局的 Manifest 文件,用于描述所有构建的 Asset Bundle。
全局 Manifest 文件命名
位于构建目录下,通常命名为 AssetBundles.manifest。
1 | ManifestFileVersion: 1 |
全局 Manifest 文件的用途:
- 列出所有生成的 Asset Bundle 名称。
- 记录每个 Asset Bundle 的哈希值,用于版本更新和文件校验。
- 描述 Asset Bundle 之间的依赖关系。
使用 Manifest 文件的注意事项
版本控制:Manifest 文件中包含的哈希值和依赖关系信息非常重要,建议将其纳入版本控制系统。
增量更新:通过比对 Manifest 文件的哈希值,判断哪些 Asset Bundle 需要更新。
手动加载优化:使用 Dependencies 字段提前加载依赖的 Asset Bundle,减少运行时的加载失败风险。
分析 Assets 字段优化资源的分布,避免一个 Asset Bundle 包含过多资源。
示例:动态加载依赖的 Asset Bundle
通过读取全局 Manifest 文件,动态加载某个 Asset Bundle 及其依赖:
1 | using UnityEngine; |
Manifest 文件的总结
Manifest 文件提供了关于 Asset Bundle 的元数据信息,包括依赖关系、资源清单、哈希值等。
每个 Asset Bundle有一个对应的 .manifest 文件,同时构建目录下会生成一个全局的 Manifest 文件。
Bundle 文件本身
Asset Bundle 的文件结构虽然是 Unity 特有的,但它遵循一定的格式规范来存储资源及其相关信息。主要由两部分组成,数据头和数据段。了解 Asset Bundle 的文件结构有助于更好地理解资源加载和管理的底层机制。以下是对 Asset Bundle 文件结构的详细介绍。
数据头(Header)
存储元数据(如版本、压缩信息、资源索引表)。数据段(Data Segment)
包含序列化后的资源二进制数据(如贴图、预制体、场景等)。
1 | +---------------------+ |
总结
Asset Bundle 的文件结构是高度优化的二进制格式,旨在提高资源加载的效率。它通过以下几个关键部分来组织数据:
- 文件头:包含版本信息、平台信息、压缩信息等。
- 资源列表:包含所有资源的基本信息和索引。
- 资源数据:包含资源的实际内容,通常是压缩格式。
- 资源索引:帮助 Unity 快速定位资源数据。
- 压缩块:资源数据分块存储,提高加载效率。
- 依赖项:记录资源之间的依赖关系,确保按需加载。
- 文件结尾:辅助加载和校验的信息。
这种结构使得 Asset Bundle 在存储、传输和加载资源时更加高效,能够支持大型项目中资源的动态加载和按需获取。
Bundle 压缩算法
LZMA(默认算法)
特点:高压缩率(文件体积最小),但需整体解压后才能使用资源。
内存开销:解压时需将完整包加载到内存,峰值内存占用高。
适用场景:
网络分发(CDN下载),减少流量消耗。
需配合缓存机制(如 UnityWebRequest),下载后自动转为LZ4格式存储。
LZ4(基于分块压缩)
特点:
按需解压资源块(无需解压整包),加载速度快。
压缩率较低(文件体积大于LZMA),但内存占用低。
启用方式:打包时设置 BuildAssetBundleOptions.ChunkBasedCompression36。
适用场景:
本地资源加载(如 StreamingAssets)。
移动端运行时,避免内存峰值问题。
无压缩(Uncompressed)
特点:文件体积最大,但加载速度最快(无需解压)。
适用场景:
开发调试或极小资源包。
对加载延迟极端敏感的场景(如高频加载资源)。
压缩算法对比与选择建议
算法 | 压缩率 | 加载速度 | 内存占用 | 典型使用场景 |
---|---|---|---|---|
LZMA | 5 | 2 | 4 | CDN分发、网络下载 |
LZ4 | 3 | 5 | 2 | 本地资源、移动端运行时 |
无压缩 | 1 | 5 | 1 | 开发调试、高频加载资源 |
进阶策略:LZMA转LZ4优化
CDN分发:使用LZMA格式减小下载体积。
本地转换:下载后通过 AssetBundle.RecompressAssetBundleAsync 转为LZ4格式存储。
效果:
流量节省 ≈ 5070%(LZMA优势)。50%(LZ4优势)。
运行时内存峰值降低 30
注意事项
平台兼容性:
WebGL 平台不支持 LZMA,需强制使用 LZ4。
缓存机制:
启用 Caching.compressionEnabled = true 可自动压缩缓存数据(默认开启)。
加载 API 匹配:
LZ4 包优先用 LoadFromFile(零内存开销)。
LZMA 包需用 UnityWebRequest 避免内存泄漏。
AssetBundle 框架
Asset Bundle 的打包策略无非就是两种,根据文件夹打包和根据文件打包,所以写一个编辑器用来进行打包数据收集是很有必要的。
增量更新
增量更新的主要目的是减少下载量和更新所需的时间。随着资源的迭代和版本的更新,通常需要以下几种处理:
- 检测哪些资源发生了变化:避免每次更新都下载全部资源。
- 优化客户端下载:只下载增量的部分,而不是整个 Asset Bundle。
增量更新的工作原理
增量更新的核心思想是通过比较新旧版本的差异,客户端只下载那些发生变化的 Asset Bundle 文件。具体流程可以分为以下几个步骤:
生成 Asset Bundle 的版本信息
每次构建 Asset Bundle 时,可以为每个资源设置版本号或者使用哈希值来标识资源。通常有两种常见的做法:
通过哈希值对比:对 Asset Bundle 内部的资源进行哈希计算,生成一个唯一的标识。每次资源发生变化时,哈希值会不同。
使用版本号管理:给每个资源或每个 Asset Bundle 包分配版本号,基于版本号进行增量更新。
常见的方式是维护一个 版本清单文件,该文件会列出所有的资源路径、版本号或哈希值。
资源变动检测
客户端需要通过向服务器请求版本信息或哈希信息,来检测哪些 Asset Bundle 发生了变化。
CDN 端在每个版本的资源文件中都放入最新的所有的 Asset Bundle 的 CRC 表。
客户端根据版本规则请求下一个版本的资源进行比对更新。
增量下载
客户端根据比较结果,发现哪些 Asset Bundle 需要更新,哪些是未变动的。只有需要更新的资源包才会被下载。
Unity 支持使用 Asset Bundle Manifest 文件,它记录了 Asset Bundle 之间的依赖关系。客户端可以根据 Manifest 文件动态加载和卸载资源,确保资源的高效管理。
Unity 的 UnityWebRequest 或者 AssetBundle.LoadFromFileAsync 等 API 可以用于异步加载更新的 AB 文件。
常见的增量更新策略
资源打包与拆分
如果资源包体积较大,可以将其拆分成多个小的 Asset Bundle,这样只需要更新其中变化的部分,而不是整个大包。
按场景、功能拆分:根据场景、关卡或模块来拆分 AB 文件。例如,一个游戏可能有多个关卡,每个关卡打包成独立的 Asset Bundle,这样只需更新某一关的资源。
依赖管理
Unity 中的 Asset Bundle 有依赖关系管理,确保一个 Asset Bundle 中的资源与其他资源的引用关系得到正确处理。
每次更新时,要确保 Asset Bundle 之间的依赖关系没有破坏,尤其是在拆分和增量更新时。
不要让打出的 Asset Bundle 产生循环依赖的问题,所以写一个简车 Asset Bundle 是否有循环依赖的工具是非常必要的。
增量更新的挑战
资源依赖问题:有时,修改一个资源会影响到多个 Asset Bundle,因此可能导致多次下载。需要合理设计资源依赖关系,避免不必要的重复下载。
版本兼容问题:客户端需要处理老旧版本和新版本之间的兼容性,尤其是当多个 Asset Bundle 包含相同资源的不同版本时。
同步问题:增量更新的过程中,客户端和服务器端的资源版本可能不同步,需要额外的逻辑来处理版本不一致的情况。
文件的哈希值
一般来说,检查一个 Bundle 是否发生了变化,可以通过比对这个 Bundle 的 CRC值或者哈希值。
生成 Bundle 时,每个 Bundle 也会生成一个 Manifest 文件,这个文件中也会有一个 CRC 值,但是这个算法的碰撞概率比较大,MD5 优于 CRC,但是和 SHA-256 相比还是差一些。
CRC 与 哈希值 的主要区别
计算目标:
CRC:主要用于检查数据传输和存储的完整性,它检测的是文件内容是否发生了改变。
哈希值:广泛用于校验数据一致性、生成数据索引、加密等应用,注重数据的唯一性和安全性。
碰撞抵抗性:
CRC:虽然 CRC 值对于大多数应用足够有效,但它并不具备强大的碰撞抵抗性,多个不同的输入可能会产生相同的 CRC 值。
哈希值:具有较强的碰撞抵抗性,尤其是 SHA-256 等现代哈希算法,它们的设计目标就是尽可能避免不同数据产生相同的哈希值。
输出长度:
CRC:通常为 32 位(4 字节)或 16 位(2 字节),相对较短。
哈希值:通常为 128 位(16 字节)到 512 位(64 字节),例如 MD5 是 128 位,SHA-256 是 256 位。
应用场景:
CRC:多用于文件校验、传输协议中(如检查文件传输是否出错)。
哈希值:广泛应用于数据安全、数字签名、密码存储等领域。
碰撞抵抗
用 CRC 难免会遇到碰撞问题,所以如果项目体量比较大可以进行碰撞抵抗处理。
一个简答的办法就是用双重检测,即先进行 CRC 比对,然后再进行长度或者名字的比对,不过这样双重是比较费时间的。也可以用分资源用不同值来检测的办法。
资源类型 | 校验策略 | 理由 |
---|---|---|
代码/配置/敏感资产 | SHA-256 | 高风险需强保护 |
大型纹理/模型 | CRC+文件尺寸比对 | 平衡安全性与性能 |
音频/视频流 | CRC(附加分块校验) | 实时性要求高,碰撞概率极低 |
全平台下载、缓存、删除
Bundle 下载到本地之后要找个路径缓存一下,然后和本地校验,删除本地的无关缓存。
安卓
IOS
YooAssets
flowchart TD
A[开始资源更新] --> B[获取远端资源版本号]
B -- 成功 --> C[更新补丁清单<br>PackageManifest]
B -- 失败 --> D[尝试使用本地清单]
C --> E[创建增量下载器<br>CreatePatchDownloader]
E --> F[分析差异<br>TotalDownloadCount/Bytes]
F --> G[需要下载?]
G -- 是 --> H[注册回调监听进度]
H --> I[开始下载<br>BeginDownload]
G -- 否 --> K[跳过下载]
I --> J{下载结果}
J -- 成功 --> L[更新完成]
J -- 失败 --> M[下载失败<br>可根据策略重试]
D --> N[验证本地清单完整性]
N -- 完整 --> L
N -- 不完整 --> O[提示需要网络连接]