Unity文档——AssetBundle基本原理

AssetBundle基本原理

版本检查:2017.3

-

难度:高级

 

这是关于Unity 5中资产,资源和资源管理系列文章的第四章。

本章讨论AssetBundles。它介绍了构建AssetBundle的基本系统,以及用于与AssetBundles交互的核心API。特别是,它讨论了AssetBundles本身的加载和卸载,以及AssetBundles中特定Asset和Objects的加载和卸载。

有关AssetBundles使用的更多模式和最佳实践,请参阅本系列的下一章。

3.1。概观

AssetBundle系统提供了一种以Unity可以索引和序列化的存档格式存储一个或多个文件的方法。AssetBundles是Unity在安装后交付和更新非代码内容的主要工具。这允许开发人员提交较小的应用程序包,最小化运行时内存压力,并有选择地加载针对最终用户设备优化的内容。

了解AssetBundles的工作方式对于为移动设备构建成功的Unity项目至关重要。有关AssetBundle内容的整体说明,请查看AssetBundle文档

3.2。AssetBundle布局

总而言之,AssetBundle由两部分组成:标题和数据段。

标头包含有关AssetBundle的信息,例如其标识符,压缩类型和清单。清单是一个由Object名称键入的查找表。每个条目都提供一个字节索引,指示在AssetBundle的数据段中可以找到给定Object的位置。在大多数平台上,此查找表实现为平衡搜索树。具体来说,Windows和OSX派生的平台(包括iOS)使用红黑树。因此,随着AssetBundle内资产数量的增加,构建清单所需的时间将增加超过线性

数据段包含通过序列化AssetBundle中的资产生成的原始数据。如果将LZMA指定为压缩方案,则会压缩所有序列化资产的完整字节数组。如果指定了LZ4,则单独压缩单独Assets的字节。如果不使用压缩,则数据段将保留为原始字节流。

在Unity 5.3之前,无法在AssetBundle中单独压缩对象。因此,如果指示5.3之前的Unity版本从压缩的AssetBundle读取一个或多个对象,则Unity必须解压缩整个AssetBundle。通常,Unity会缓存AssetBundle的解压缩副本,以提高同一AssetBundle上后续加载请求的加载性能。

3.3。加载AssetBundles

AssetBundles可以通过四个不同的API加载。这四种API的行为根据两个标准而有所不同:

  1. AssetBundle是否为LZMA压缩,LZ4是压缩还是未压缩
  2. 正在加载AssetBundle的平台

这些API是:

  • AssetBundle.LoadFromMemory(异步可选)

  • AssetBundle.LoadFromFile(异步可选)

  • UnityWebRequest的DownloadHandlerAssetBundle

  • WWW.LoadFromCacheOrDownload(在Unity 5.6或更早版本上)

3.3.1 AssetBundle.LoadFromMemory(Async)

Unity建议不要使用此API。

AssetBundle.LoadFromMemoryAsync从托管代码字节数组(C#中的byte [])加载AssetBundle 。它总是将源数据从托管代码字节数组复制到新分配的,连续的本机内存块中。如果AssetBundle是LZMA压缩的,它将在复制时解压缩AssetBundle。未压缩和LZ4压缩的AssetBundles将逐字复制。

此API消耗的最大内存量至少是AssetBundle大小的两倍:API创建的本机内存中的一个副本,以及传递给API的托管字节数组中的一个副本。因此,通过此API创建的AssetBundle加载的资产将在内存中重复三次:一次在托管代码字节数组中,一次在AssetBundle的本机内存副本中,第三次在GPU或系统内存中用于资产本身。

在Unity 5.3.3之前,此API称为AssetBundle.CreateFromMemory。它的功能没有改变。

3.3.2。AssetBundle.LoadFromFile(异步)

AssetBundle.LoadFromFile是一种高效API,用于从本地存储(如硬盘或SD卡)加载未压缩或LZ4压缩的AssetBundle。

在桌面独立,控制台和移动平台上,API将仅加载AssetBundle的标头,并将剩余数据保留在磁盘上。当调用加载方法(例如AssetBundle.Load)或取消引用其InstanceID时,AssetBundle的对象将按需加载。在这种情况下,不会消耗多余的内存。在Unity Editor中,API会将整个AssetBundle加载到内存中,就像从磁盘读取字节并使用AssetBundle.LoadFromMemoryAsync一样。如果在Unity Editor中对项目进行了分析,则此API可能会导致在AssetBundle加载期间出现内存峰值。这不应该影响设备上的性能,并且应该在采取补救措施之前在设备上重新测试这些尖峰。

注意:在使用Unity 5.3或更早版本的Android设备上,尝试从Streaming Assets路径加载AssetBundles时,此API将失败。Unity 5.4中已解决此问题。有关更多详细信息,请参阅AssetBundle使用模式一章中的分发 - 随项目一起提供的部分。

在Unity 5.3之前,此API称为AssetBundle.CreateFromFile。它的功能没有改变。

3.3.3。AssetBundleDownloadHandler

UnityWebRequest API允许开发人员指定统一究竟应该如何处理下载的数据,并允许开发者以消除不必要的内存使用情况。使用UnityWebRequest下载AssetBundle的最简单方法是调用UnityWebRequest.GetAssetBundle

出于本指南的目的,感兴趣的类是DownloadHandlerAssetBundle。使用工作线程,它将下载的数据流式传输到固定大小的缓冲区,然后将缓冲的数据假脱机到临时存储或AssetBundle缓存,具体取决于下载处理程序的配置方式。所有这些操作都在本机代码中进行,从而消除了扩展托管堆的风险。此外,该下载处理程序并没有把所有下载的字节的本机代码副本,进一步降低了下载的AssetBundle的内存开销。

LZMA压缩的AssetBundles将在下载期间解压缩并使用LZ4压缩进行缓存。可以通过设置Caching.CompressionEnabled来更改此行为。

当下载完成后,assetBundle下载处理程序的属性提供下载的AssetBundle,仿佛AssetBundle.LoadFromFile已经呼吁下载AssetBundle。

如果向UnityWebRequest对象提供缓存信息,并且Unity缓存中已存在所请求的AssetBundle,那么AssetBundle将立即可用,并且此API将与AssetBundle.LoadFromFile完全相同。

在Unity 5.6之前,UnityWebRequest系统使用固定的工作线程池和内部作业系统来防止过多的并发下载。线程池的大小不可配置。在Unity 5.6中,已删除这些安全措施以适应更多现代硬件,并允许更快地访问HTTP响应代码和标头。

3.3.4。WWW.LoadFromCacheOrDownload

*注意:从Unity 2017.1开始,WWW.LoadFromCacheOrDownload简单地包装UnityWebRequest。因此,使用Unity 2017.1或更高版本的开发人员应迁移到UnityWebRequest。WWW.LoadFromCacheOrDownload将在以后的版本中弃用。*

以下信息适用于Unity 5.6或更早版本。

WWW.LoadFromCacheOrDownload是一个允许从远程服务器和本地存储加载对象的API。可以通过file:// URL从本地存储加载文件。如果Unity缓存中存在AssetBundle,则此API的行为与AssetBundle.LoadFromFile完全相同。

如果尚未缓存AssetBundle,则WWW.LoadFromCacheOrDownload将从其源读取AssetBundle。如果AssetBundle被压缩,它将使用工作线程解压缩并写入缓存。否则,它将通过工作线程直接写入缓存。缓存AssetBundle后WWW.LoadFromCacheOrDownload将从缓存的解压缩AssetBundle加载标头信息。然后,API的行为与使用AssetBundle.LoadFromFile加载的AssetBundle完全相同。此缓存在WWW.LoadFromCacheOrDownloadUnityWebRequest之间共享。使用一个API下载的任何AssetBundle也可通过其他API获得。

虽然数据将通过固定大小的缓冲区解压缩并写入缓存,但WWW对象将在本机内存中保留AssetBundle字节的完整副本。保留AssetBundle的额外副本以支持WWW.bytes属性。

由于在WWW对象中缓存AssetBundle字节的内存开销,AssetBundles应保持较小 - 最多几兆字节。有关AssetBundle大小调整的更多讨论,请参阅AssetBundle使用模式一章中的资产分配策略部分。

与UnityWebRequest不同,每次调用此API都会产生一个新的工作线程。因此,在具有有限内存的平台(例如移动设备)上,应该使用此API一次只下载一个AssetBundle,以避免内存峰值。多次调用此API时,请注意创建过多的线程。如果需要下载超过5个AssetBundle,请在脚本代码中创建和管理下载队列,以确保仅同时运行少量AssetBundle下载。

3.3.5。建议

通常,应尽可能使用AssetBundle.LoadFromFile。就速度,磁盘使用和运行时内存使用而言,此API是最有效的。

对于必须下载或补丁AssetBundles项目,强烈建议使用UnityWebRequest使用Unity 5.3或更高版本,以及项目WWW.LoadFromCacheOrDownload使用Unity 5.2或以上的项目。如下一章的“ 分发”部分所述,可以使用项目安装程序中包含的Bundle来填充AssetBundle Cache。

使用UnityWebRequest *或* WWW.LoadFromCacheOrDownload时,请确保在加载AssetBundle后下载程序代码正确调用Dispose。或者,C#的using语句是确保安全地处理WWWUnityWebRequest的最方便的方法。

对于需要具有独特,特定缓存或下载要求的大量工程团队的项目,可以考虑使用自定义下载程序。编写自定义下载程序是一项非常重要的工程任务,任何自定义下载程序都应与AssetBundle.LoadFromFile兼容有关更多详细信息,请参阅下一章的“ 分发”部分。

3.4。从AssetBundles加载资产

UnityEngine.Objects可以使用三个不同的API从AssetBundles加载,这些API都附加到AssetBundle对象,它们具有同步和异步变体:

这些API的同步版本总是比其异步版本快至少一帧。

异步加载将每帧加载多个对象,直到其时间片限制。有关此行为的基本技术原因,请参阅低级加载详细信息部分。

LoadAllAssets应该加载多个独立UnityEngine.Objects时使用。它只应在需要加载AssetBundle中的大多数或所有对象时使用。与其他两个API相比,LoadAllAssets比对LoadAssets的多个单独调用稍快。因此,如果要加载的资产数量很大,但是一次只需要加载少于66%的AssetBundle,请考虑将AssetBundle拆分为多个较小的包并使用LoadAllAssets

加载包含多个嵌入对象的复合资产时,应使用LoadAssetWithSubAssets,例如具有嵌入动画的FBX模型或嵌入了多个精灵的精灵图集。如果需要加载的对象全部来自同一资产,但存储在带有许多其他无关对象的AssetBundle中,则使用此API。

对于任何其他情况,请使用LoadAssetLoadAssetAsync

3.4.1。低级加载详细信息

UnityEngine.Object加载是在主线程上执行的:从工作线程的存储中读取Object的数据。任何不接触Unity系统的线程敏感部分(脚本,图形)的东西都将在工作线程上转换。例如,VBO将从网格创建,纹理将被解压缩等。

从Unity 5.3开始,对象加载已经并行化。多个对象在工作线程上反序列化,处理和集成。当Object完成加载时,将调用其Awake回调,并且Object将在下一帧期间对Unity Engine的其余部分可用。

同步AssetBundle.Load方法将暂停主线程,直到对象加载完成。它们还会对对象加载进行时间分片,以便对象集成不占用超过一定毫秒的帧时间。毫秒数由属性Application.backgroundLoadingPriority设置:

  • ThreadPriority.High:每帧最多50毫秒

  • ThreadPriority.Normal:每帧最多10毫秒

  • ThreadPriority.BelowNormal:每帧最多4毫秒

  • ThreadPriority.Low:每帧最多2毫秒。

从Unity 5.2开始,加载多个对象,直到达到对象加载的帧时间限制。假设所有其他因素相等,资产加载API的异步变体将始终比可比较的同步版本更长,因为发出异步调用和引擎可用的对象之间的最小一帧延迟。

3.4.2。AssetBundle依赖项

AssetBundle之间的依赖关系使用两个不同的API自动跟踪,具体取决于运行时环境。在Unity Editor中,可以通过AssetDatabase API 查询AssetBundle依赖。可以通过AssetImporter API 访问和更改AssetBundle分配和依赖。在运行时,Unity提供了一个可选API,用于通过基于ScriptableObject的AssetBundleManifest API 加载在AssetBundle构建期间生成的依赖关系信息。

当一个或多个父AssetBundle的UnityEngine.Object引用一个或多个其他AssetBundle的UnityEngine.Object时,AssetBundle依赖于另一个AssetBundle。有关对象间引用的更多信息,请参阅“ 资产,对象和序列化”一文的“ 对象间引用”部分。

如该文章的序列化和实例部分所述,AssetBundles充当由AssetBundle中包含的每个Object的FileGUID和LocalID标识的源数据的源。

因为在首次取消引用其实例ID时加载了Object,并且因为在加载AssetBundle时为Object分配了有效的实例ID,所以AssetBundle的加载顺序并不重要。相反,在加载Object本身之前加载包含Object依赖关系的所有AssetBundle非常重要。加载父AssetBundle时,Unity不会尝试自动加载任何子AssetBundle。

例:

假设材料A是指组织B。材料A打包到AssetBundle 1中,纹理B打包到AssetBundle 2中。

描述

在此用例中,必须在从AssetBundle 1加载材料A 之前加载AssetBundle 2。

这并不意味着必须在AssetBundle 1之前加载AssetBundle 2,或者必须从AssetBundle 2明确加载Texture B.在将Asset A加载到AssetBundle 1之前加载AssetBundle 2就足够了。

但是,在加载AssetBundle 1时,Unity 不会自动加载AssetBundle 2。这必须在脚本代码中手动完成。

有关AssetBundle依赖关系的更多信息,请参阅手册页

3.4.3。AssetBundle表现出来

使用BuildPipeline.BuildAssetBundles API 执行AssetBundle构建管道时,Unity会序列化包含每个AssetBundle依赖项信息的Object。此数据存储在单独的AssetBundle中,该AssetBundle包含AssetBundleManifest类型的单个Object 。

此资产将存储在AssetBundle中,其名称与构建AssetBundle的父目录的名称相同。如果项目将其AssetBundle构建到(projectroot)/ build / Client /的文件夹,则包含清单的AssetBundle将保存为(projectroot)/build/Client/Client.manifest

包含清单的AssetBundle可以像任何其他AssetBundle一样加载,缓存和卸载。

AssetBundleManifest对象本身提供了GetAllAssetBundles API,用于列出与清单同时构建的所有AssetBundle,以及两种查询特定AssetBundle依赖关系的方法:

请注意,这两个API都分配字符串数组。因此,它们应该只是谨慎使用,而不是在应用程序生命周期的性能敏感部分使用。

3.4.4。建议

在许多情况下,最好在玩家进入应用程序的性能关键区域(例如主游戏级别或世界)之前加载尽可能多的所需对象。这在移动平台上尤其重要,因为移动平台对本地存储的访问速度很慢,并且在播放时加载和卸载对象的内存流失可以触发垃圾收集器。

对于必须在应用程序交互时加载和卸载对象的项目,请参阅AssetBundle使用模式文章的管理已加载资源部分,以获取有关卸载对象和AssetBundle的更多信息。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值