Assets, Resources and AssetBundles(四):AssetBundle fundamentals

版本检查: 2017.3-难度: 高级

本章讨论AssetBundles。它介绍了构建AssetBundles的基本系统,以及用于与AssetBundles交互的核心API。特别是,它既讨论了AssetBundles本身的加载和卸载,也讨论了从AssetBundles加载和卸载特定资产和对象的问题。

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

3.1. Overview

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

了解AssetBundles的工作方式对于建立一个成功的移动设备Unity项目至关重要。有关AssetBundle内容的总体说明,请参阅AssetBundle documentation.

3.2. AssetBundle layout

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

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

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

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

3.3. Loading AssetBundles

AssetBundles可以通过四个不同的API加载。根据两个标准,这四个API的行为是不同的:

  • Assetbundle是否为LZMA压缩,LZ4压缩或未压缩
  • 装载Assetbundle的平台

These APIs are:

  1. AssetBundle.LoadFromMemory(Async optional)

  2. AssetBundle.LoadFromFile(Async optional)

  3. UnityWebRequest's DownloadHandlerAssetBundle

  4. WWW.LoadFromCacheOrDownload (on Unity 5.6 or older)

3.3.1 AssetBundle.LoadFromMemory(Async)

建议不要使用这个API。

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

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

在Unity5.3.3之前,这个API被称为AssetBundle.CreateFromMemory。其功能没有改变。

3.3.2. AssetBundle.LoadFromFile(Async)

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

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

注意:在具有Unity5.3或更旧版本的Android设备上,当试图从Streaming Assets path加载AssetBundles时,此API将失败。这个问题已在团结5.4中得到解决。有关详细信息,请参阅AssetBundle使用模式一章的项目部分附带的分发部分。

在Unity5.3之前,这个API被称为AssetBundle.CreateFromFile。其功能没有改变。

3.3.3. AssetBundleDownloadHandler

UnityWebRequest API 允许开发人员准确地指定Unity应如何处理下载的数据,并允许开发人员消除不必要的内存使用。使用UnityWebRequest下载AssetBundle的最简单方法是调用UnityWebRequest.GetAssetBundle.

就本指南而言,感兴趣的类是DownloadHandlerAssetBundle。使用工作线程,它将下载的数据流到一个固定大小的缓冲区中,然后根据下载处理程序的配置方式将缓冲数据放到临时存储或AssetBundle缓存中。所有这些操作都发生在本机代码中,消除了扩展托管堆的风险。此外,此下载处理程序不保留所有下载字节的本机代码副本,从而进一步减少了下载AssetBundle的内存开销。

LZMA-压缩AssetBundles将在下载和缓存期间使用LZ4压缩。可以通过设置Caching.CompressionEnable来更改此行为。

下载完成后,下载处理程序的assetbundle属性提供对下载的assetbundle的访问,就像AssetBundle.LoadFromFile已在下载的AssetBundle上调用一样。

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

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

3.3.4. WWW.LoadFromCacheOrDownload

注:从Unity2017.1开始,WWW.LoadFromCacheOrDownload简单地包装了UnityWebRequest。因此,使用Unity2017.1或更高版本的开发人员应该迁移到UnityWebRequest。在未来的版本中,WWW.LoadFromCacheOrDownload将弃用。

以下信息适用于Unity5.6或更旧版本。

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

如果AssetBundle尚未缓存,则WWW.LoadFromCacheOrDownload将从其源读取AssetBundle。如果AssetBundle被压缩,它将使用工作线程进行解压缩并写入缓存中。否则,它将通过工作线程直接写入缓存。在缓存资产绑定之后,WWW.LoadFromCacheOrDownload将从缓存的、解压缩的AssetBundle加载头信息。然后,API将与加载了AssetBundle.LoadFromFile的AssetBundle行为相同。此缓存在WWW.LoadFromCacheOrDownload和UnityWebRequest之间共享。与一个API一起下载的任何AssetBundle也可以通过另一个API获得。

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

由于在WWW对象中缓存AssetBundle的字节的内存开销,Assetbundle应保持较小(最多)几兆字节。有关AssetBundle大小调整的更多讨论,请参阅AssetBundle usage patterns 一章中的“Asset assignment strategies”部分。

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

3.3.5. Recommendations

一般来说,只要有可能,就应该使用AssetBundle.LoadFromFile。这个API在速度、磁盘使用和运行时内存使用方面是最有效的。

对于必须下载或修补AssetBundles的项目,强烈建议对使用Unity5.3或更高版本的项目使用UnityWebRequest,对于使用Unity5.2或更旧版本的项目使用WWW.LoadFromCacheOrDownload。正如下一章的分发部分所详细介绍的那样,可以在项目的安装程序中包含Bundles,从而实现AssetBundles缓存。

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

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

3.4. Loading Assets From AssetBundles

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

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

异步加载将加载多个对象的每帧,直到他们的时间切片限制。有关此行为的基本技术原因,请参阅“Low-level loading details”部分。

加载多个独立单元时,应使用LoadAllAssets。只有当需要加载AssetBundle中的大多数或所有对象时,才可以使用它。与其他两个API相比,LoadAllAssets比对LoadAssets的多个单独调用稍微快一些。因此,如果要加载的资产数量很大,但在单个时间需要加载少于66%的Assetbundle,请考虑将AssetBundle分割为多个较小的捆绑包,并使用LoadAllAssets。

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

对于任何其他情况,请使用LoadAsset或LoadAssetAsync。

3.4.1. Low-level loading details

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

从Unity5.3开始,对象加载就被并行化了。在工作线程上反序列化、处理和集成多个对象。当一个对象完成加载时,它的唤醒回调将被调用,该对象将在下一个帧中对UnityEngine的其余部分可用。

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

  • ThreadPriority.High: Maximum 50 milliseconds per frame

  • ThreadPriority.Normal: Maximum 10 milliseconds per frame

  • ThreadPriority.BelowNormal: Maximum 4 milliseconds per frame

  • ThreadPriority.Low: Maximum 2 milliseconds per frame.

从Unity5.2开始,将加载多个对象,直到达到对象加载的帧-时间限制为止。假设所有其他因素相等,资产加载API的异步变体将总是比可比的同步版本花费更长的时间,因为发出异步调用和对象之间有最小的一帧延迟。

3.4.2. AssetBundle dependencies

根据运行时环境的不同,使用两个不同的API自动跟踪AssetBundles之间的依赖关系。在UnityEditor中,可以通过AssetDatabase API查询AssetBundle依赖项。资产绑定分配和依赖项可以通过AssetImport API访问和更改。在运行时,Unity提供了一个可选的API,通过基于ScriptableObject的AssetBundleManifest API加载在AssetBundle构建过程中生成的依赖信息。

当一个或多个父AssetBundle 的UnityEngine.Objects中的一个或多个时,AssetBundle依赖于另一个AssetBundle。Objects指的是另一个或多个AssetBundle 的UnityEngine.Objects。有关对象间引用的更多信息。请参阅资产、对象和序列化文章的对象间引用部分。

正如本文的Serialization and instances部分所述,AssetBundles是由AssetBundle中包含的每个对象的FileGUID和LocalID标识的源数据的来源。

因为一个对象是在其Instance ID第一次被取消引用时加载的,而且由于一个对象在加载其AssetBundle时被分配了一个有效的Instance ID,所以加载AssetBundles的顺序并不重要。相反,在加载对象本身之前,重要的是加载包含对象依赖关系的所有AssetBundles。Unity不会尝试在加载父AssetBundles时自动加载任何子AssetBundles。

Example:

假设材料A是指纹理B。材料A被打包到资产束1中,纹理B被打包到资产束2中。

在此使用情况下,在将材料A从AssetBundle1中取出之前,必须加载AssetBundle2。

这并不意味着AssetBundle 2必须在AssetBundle 1之前加载,或者纹理B必须从AssetBundle 2中显式加载。在将材料A从AssetBundle 1加载之前加载AssetBundle 2就足够了。

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

有关AssetBundle依赖项的详细信息,请参阅 manual page.

3.4.3. AssetBundle manifests

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

此资产将存储在与构建AssetBundles的父目录同名的AssetBundle中。如果一个项目将其AssetBundles构建到位于(Projectroot)/Build/Client/的文件夹中,那么包含清单的AssetBundle将被保存为(Projectroot)/build/client/Client.manifest

AssetBundle manifest可以像任何其他AssetBundle一样加载、缓存和卸载。

AssetBundleManifest对象本身提供GetAllAssetBundles API来列出与清单同时构建的所有AssetBundles,以及查询特定AssetBundle的依赖项的两个方法:

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

3.4.4. Recommendations(建议)

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

对于在应用程序交互时必须加载和卸载对象的项目,请参阅AssetBundle usage patterns文章中的“Managing loaded assets”部分,以获取有关卸载对象和AssetBundle的详细信息。

官方译文(未完待续)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值