转载//10/27/AssetBundle_usage_pattern_2/#more
给 AssetBundle 打补丁
给 AssetBundle 打补丁就如简单地下载一个新的 AssetBundle 然后替换已存在的。如果 WWW.LoadFromCacheOrDownload or UnityWebRequest 被用来管理应用已缓存的 AssetBundle,这个过程就是给所选的 API 传递不同的版本号一样简单。(可以参考上面的脚本参考链接来查看更多详情。)
更困难的问题是解决补丁系统探测哪个 AssetBundle 需要被替换。一个补丁系统需要两个信息列表:
当前已下载的 AssetBundle 和它们的版本信息列表远程服务器上的 AssetBundle 和它们的版本信息列表
z
修补程序需要下载服务器端的 AssetBundle 列表和然后比较这个列表。AssetBundle 丢失或者 AssetBundle 的版本信息改变了,都需要重新下载。
Unity 5 的 AssetBundle 系统会在编译完成后创建一个额外的 AssetBundle。这个额外的 AssetBundle 包括一个 AssetBundleManifest 对象。这个清单对象包含 AssetBundle 和它们的哈希值,可以用来给客户端传递有效的 AssetBundle 下载列表和版本信息。关于 AssetBundle manifest bundle,请参照 Unity 手册。
我们也可以写一个定制的系统来探测 AssetBundle 的改变。大部分写开发者写的系统都选择使用行业标准的数据结构作为他们的 AssetBundle 文件列表,比如 JSON,还有用来计算 checksum 的标准 C# 类,比如 MD5。
增量更新
在 Unity 5 中,Unity 可以将数据按确定的顺序编译出 AssetBundle。这就允许定制下载器实现增量更新。要让 AssetBunle 按确定的布局编译,需要将 BuildAssetBundleOptions.DeterministicAssetBundle 标签传递给 BuildAssetBundles 函数。(更多详情请参照脚本参考的链接。)
Unity 没有提供内置的增量更新的机制,WWW.LoadFromCacheOrDownload 和 UnityWebRequest 使用内置的缓存系统也没有实现增量更新。如果一个系统中,增量更新是必须的,那么必须要写一个定制的下载器。
iOS 中的按需加载资源
按需加载资源是 Apple 为 iOS 和 TVOS 设备提供内容的 API。它在 App Store 发布 iOS app 不是必须的需求,但是在 TVOS app 中是必须的。
关于 Apple 的按需加载资源系统的资料可以从 Apple Developer site. 找到。
Unity 5.2.1 中对 Apple 的应用分割和按需资源支持都是 Apple 的另外一个系统上构建,这个系统是 Asset Catalogs。在 Unity 编辑器中注册回调函数之后,iOS 的编译管道会给出被自动放到 Asset Catalogs 中并分配了按需资源标签的文件集合。
新的 API UnityEngine.iOS.PnDemandResources 提供了运行时获取和缓存按需资源文件的支持。一旦资源通过按需资源系统加载,它就可以通过标准的 API AssetBundle.LoadFromFile 加载进 Unity。
示例和更多的细节可以参照这篇帖子。
常见陷阱
这小节讲述了使用 AssetBundle 的项目常出现的几个问题。
资产重复
Unity 5 的 AssetBundle 系统会找出打包进 AssetBundle 的对象的所有依赖。这是 AssetDatabase 来完成的。依赖信息决定了打包进 AssetBundle 的对象集合。
对象被显式的分配到某个 AssetBundle 后它们只会被打包到那个 AssetBundle 中。对象被 “显式分配” 是对象的 AssetImpoter 的 assetBundleName 属性被设置为了非空字符串。这个行为可以在对象 Inspector 中选择 AssetBundle 或者通过编辑器脚本完成。
没有被显式分配到 AssetBundle 的对象会被打包到拥有一个或者多个没有标签的对象的 AssetBundle 中。
如果两个不同的对象被分配到不同的 AssetBundle ,而它们都引用了共同依赖对象,然后这个共同的对象会被拷贝到每个 AssetBundle 中。重复的依赖对象会被实例化,这意味着这些依赖对象的拷贝会被认为是拥有不同标识不同对象。这会增加应用的 AssetBundle 的总大小。这也让加载这两个不同对象所在的 AssetBundle 时,它们会被加载进内存中。
有几种方式来应付这种问题:
确保被打包进 AssetBundle 中的不同对象不会有同样的依赖。任何跟其他对象没有共同依赖的对象都会打包到 AssetBundle 中,而不同重复拷贝依赖。 这种方法对有很多共享依赖的项目不太合适。它会产生的巨大的 AssetBunle,而且这个 AssetBunle 必须频繁地重建和下载,不方便而又低效。AssetBundle 分片,这样就不会同时有两个有共同依赖的 AssetBundle 会被加载 这个方法可能只对某些项目管用,比如基于关卡的游戏。但是它会给项目增加不必要的 AssetBundle 大小和增加编译与加载时间。把所有的依赖都打包到依赖他们的 AssetBundle 中。这完全地排除了冗余资产的风险,但是它也引入了复杂性。应用程序必须 AssetBundle 间的依赖,来确保在调用 AssetBundle.LoadAsset API 前加载了正确的 AssetBundle。
Unity 5 中,对象的依赖是通过 AssetDatabase API 来跟踪的,这些 API 位于 UnityEditor 命名空间。就行命名空间名字所表明意思一样,这些 API 只有在 Unity 编辑器中可用,在运行时不可用
AssetDatabase.GetDependencies 可用来得到特定对象或者资产当前的依赖。注意得到的依赖可能还有它们自己的依赖。另外的,AssetImporter API 可用来查询 AssetBundle 被指派到了那些指定的对象上。
通过 AssetDatabase 和 AssetImporter API 的组合使用,让编程用来确保一个 AssetBundle 的所有直接或者间接的依赖都指派到了同一个 AssetBundle 上,或者不存在没有指派到 AssetBundle 的依赖被两个 AssetBundle 共享着。出于对重复资源内存消耗考虑,建议所有的项目都有这种的编辑器脚本。
图集的冗余
下面小结介绍了 Unity 5 的资产依赖计算代码中,用于自动生成的图集结合的一种缺陷。Unity 5.2.4p4 和 Unity 5.3 已经对这个行为打了补丁。
Unity 5.2.2p4,5.3 和更新版本
所有自动生成的精灵图集所有对象都会指派到包含它们的 AssetBundle 中。如果这些对象被指派到多个 AssetBunle, 精灵对象会被复制多份,而不是只指派到一个 AssetBundle 中,并且精灵图集也不会只指派到一个 AssetBundle 中。
为了确保精灵图集不会冗余,确保所有标在同一个图集中的精灵都指派到了同一个 AssetBundle 中。
Unity5.2.2p3 以及更老的版本
在这些版本中,自动生成的精灵图集永远不会指派到 AssetBundle 中。因为这样,包含有组成这个图集的精灵对象的 AssetBundle 和引用了组成这个图集的精灵对象的 AssetBundle 都会包含这个图集。
因为这个问题,建议所有使用 Unity 的 Sprite Packer 的版本都生升级到 Uinty 5.2.2p4,5.4 或者更新的 Unity 版本。
对于不能升级的项目,有两种临时解决方案:
简单的方案:避免使用 Unity 的内置 Sprite Packer. 外部的打包工具生成的精灵图集是常的资产,可以正确的指派到 AssetBundle 中。难的方案:将所有使用了自动打图集的精灵的对象都指派到和精灵同一个 AssetBundle 中 这可以确保生成的精灵图集对于非直接的依赖的 AssetBundle 不可见,也不会有冗余这个方案解决了使用 Unity Sprite Packer 的简单流程,但是它让开发者不能讲分配资产到不同的 AssetBundle 中,并且引用了这个图集的组件上的任何数据变动都要强制重新下载整个精灵图集,即使图集自己没有任何变动。
安卓的纹理
由于安卓生态中设备的碎片化特别严重,常常我们需要将纹理压缩成好几种格式。所有的安卓设备都支持 ETC1, 而 ETC1 不支持 alpha 通道。在不是必须要求使用 OpenGL ES 2 支持的设备上,最彻底简便的解决这个问题方法就是使用 ETC2 格式,这个格式被所有使用 OpenGL ES 3 的安卓设备支持。
如果很多应用程序需要在不支持 ETC2 的老设备支持。一个解决这个问题的方法就是使用 Unity 5 的 AssetBundle 变体。(更多设置详情请查看 Unity 的安卓优化指导。)
为了使用 AssetBundle 变体,所有不能使用 ETC1 的纹理多要被隔离到只包含纹理的 AssetBunld 中。然后为非 ETC2 设备创建主变种,并使用第三方纹理压缩格式,比如 DXT5, PVRTC 和 ATITC。对于每个 AssetBundle 变种,改变包含的纹理的 TextureImporter 设置来改变为变种对应的格式。
在运行时,不同纹理压缩格式可以用 SystemInfo.SupportsTextureFormat API 来探测到。这个信息应该用来选择和加载其所支持纹理格式的AssetBundle 变种。
iSO 文件句柄过度使用
下面描述的问题已经在 Unity 5.3.2p2 中修复,当前版本的 Unity 并不受其影响
在 Unity 5.3.2p2 之前,Unity 会在整个周期内保持对已加载过的 AssetBundle 的文件句柄的占用。这个对大多数平台不算大问题。但是 iOS 限制了并行的文件句柄数量最大为 255。 如果超过这个数量时加载 AssetBundle, 加载的调用会失败,并抛出 “打开文件句柄太多” 错误。
这对那些把内容拆分到上百甚至上千个 AssetBundle 的项目算是比较常见的问题。
对于没法升级 Unty 补丁版本的 Unity, 有两个临时解决方案可以用:
合并相关的 AssetBundle 来减少 AssetBundle 的数量使用 AssetBundle.Unload(false) 关闭文件句柄,并手动的管理加载的对象的生命周期
AssetBunle 变体
Unity 5 AssetBundle 系统关键的一个功能就是引入了 AssetBundle 变体。变体的目的是允许应用可以调整他们的内容来更适用于运行环境。它可以让不同 AssetBundle 中的不同 UnityEngine.Objects 对象在加载和引用处理的时候表现的像 “同一个” 对象一样。从概念上来讲,它允许两个不同的 UnityEngine.Objects 对象表现为共享了相同的文件 GUID 和 本地 ID,然后通过变体 ID 来断定实际要加载的 UnityEngine.Object 对象。
这个系统有两个主要的用途:
变体简化了平台对应 AssetBundle 的加载。 例子:编译系统可能创建了一个适用于 DirectX11 Windows Standalone 程序 的AssetBundle, 它包含高分辨率纹理和复杂的 Shader,然后另外一个给 Android 平台的低质量的 AssetBundle。在运行期,项目资源加载代码可以根据运行的平台加载对应的 AssetBundle 变体,而不用改变传递给 AssetBundle.Load API 的名字。变体可以让应用在相同平台上为不同的硬件加载不同的内容。 这是支持大范围移动设备的关机。在真实世界中应用 iPhone4 显示 iPhone6 质量的内容会比较吃力在 Android 上,AssetBundle 变体可以用来处理不同设备间屏幕分辨率和 DPI 碎片化严重的问题
AssetBundle 变体限制
AssetBunble 变体系统的主要限制是要求变体需要从不同的资产中编译出来。即使只有导入设置的改变也受这个限制影响。如果一个纹理需要打包进变体 A 和变体 B 中,两个变体中这个纹理的唯一差别是导入设置中的压缩算法不同,这种情况下,变体 A 和变体 B 必须要求是完全不同的资产,意味着必须是磁盘上独立分开的文件。
这个限制会导致在版本管理中同一个资产可能会有多份拷贝,增加了大项目的管理复杂度。而且如果开发者要更新资产的内容时,所有这些拷贝也要更新。
针对这个问题,现在还没有内置的临时解决方案。
大部分团队会有他们自己的 AssetBundle 变种方式。通常靠将定义好的后缀加入到编译的 AssetBundle 文件名中,用来区分不同的变体。然后用代码在打包这些 AssetBundle 时修改资产的导入设置。也有些开发会扩展他们定制的系统,让其能修改预设上的组件的参数。
压缩还是不压缩
是否要压缩 AssetBundle 需要仔细的考虑,重点问题有几个:
加载时间是不是这个 AssetBundle 的主要因数?从磁盘或缓存中加载没有压缩的 AssetBundle 会比压缩的 AssetBundle 快很多。但是通常从服务器上下载一个压缩的文件会比一个未压缩的 AssetBundle 快。编译时间是不是这个 AssetBundle 的主要因数?LZMA 和 LZ4 在压缩式很慢,并且 Unity 编辑器会序列化 AssetBundle。有很多 AssetBundle 的项目会在压缩上花费很长的时间。程序大小是不是主要因数?如果 AssetBundle 是跟程序一起发布,压缩他们会减少包体的大小。另外 AssetBundle 可以在程序安装后安装。内存使用是不是主要因数?在 Unity 5.3 之前,所有 Unity 的解压机制都要求解压前将整个 AssetBundle 都加载到内存中。如果内存使用率比较重要,请使用 LZ4 压缩 AssetBundles 或者不压缩 AssetBundle。下载时间是不是主要因数?压缩仅在 AssetBundle 比较大或者用户在带宽有限的环境中才需要,比如移动端通过 3G 下载或者在低速连接中。如果只有几十 M 的数据需要传输到 PC 或者在高速连接中,可以把压缩去掉。
AssetBundle 和 WebGL
Unity 强烈推荐开发者在 WebGL 项目上不压缩 AssetBundle。
Unity 5.3 中所有的 AssetBundle 解压和加载在 WebGL 项目中都发生在主线程之上。这是因为 Unity 5.3 的 WebGL 导出选项不支持工作线程。(AssetBundle 的下载通过 Javascript 的 XMLHttpRequest API 代理给了浏览器,不是在主线程中。)这意味着在 WebGL 中加载压缩的 AssetBundle 开销比较昂贵。
知道了这些,你能会想到使用解压很高效的 LZ4 压缩来避免 LZMA 压缩格式。如果你需要传输比 LZ4 更小的压缩大小,你可以通过配置 Web 服务器在 Http 协议层中使用 gzip 压缩文件(在 LZ4 压缩之上使用)。
如果觉得《AssetBundle 使用模式 (2)》对你有帮助,请点赞、收藏,并留下你的观点哦!