AssetBundle04

首先附上原文链接:https://unity3d.com/cn/learn/tutorials/topics/best-practices/assetbundle-fundamentals?playlist=30089

                            AssetBundle基本概念

        这是Unity5中Asset,Resources和资源管理系列文章的第四章。

        本章讨论AssetBundles。它介绍了构建AssetBundles的基本系统,以及用于与AssetBundles进行交互的核心API。具体来说,它讨论了AssetBundles本身的加载和卸载以及AssetBundles中特定Asset和Object的加载和卸载。

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


3.1 概述

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

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


3.2 AssetBundle布局

        概括来讲,一个AssetBundle由两部分组成:数据头和数据段。数据头包含有关AssetBundle的信息,例如其标识符,压缩类型,还有一个manifest。manifest是一个由Object名字作为键的查找表。每个条目都提供一个字节索引,用于标明指定的Object在AssetBundle的数据段中哪里可以找到。在大多数平台上,这个查找表被实现为一个平衡搜索树。具体来说,Windows和OSX派生的平台(包括iOS)采用红黑树。因此,构建manifest所需的时间将随着AssetBundle内Asset数量的增长而呈比线性更多的增加。

        数据段包含通过序列化AssetBundle中的Asset生成的原始数据。如果将LZMA指定为压缩方案,那么序列化Asset的所有完成的字节数组都将被压缩。如果指定LZ4,单独Asset的字节将被单独压缩。如果不使用压缩,数据段将保持为原始字节流。

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


3.3 加载AssetBundle

        AssetBundles可以通过五个不同的API加载。这五个API基于两个标准而不同:

        1.AssetBundle是否是LZMA压缩,LZ4压缩或未压缩

        2.AssetBundle正在加载的平台

        这些API是:

        AssetBundle.LoadFromMemory(异步可选)

        AssetBundle.LoadFromFile(异步可选)

        AssetBundle.LoadFromStream(异步可选)

        UnityWebRequest的DownloadHandlerAssetBundle

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

        这些API中的AssetBundle引用可以自由混合。也就是说,使用UnityWebRequest加载的AssetBundles与通过AssetBundle.LoadFromFile或AssetBundle.LoadFromMemoryAsync加载的AssetBundles 兼容。


3.3.1 AssetBundle.LoadFromMemory(异步)

        Unity的建议是不使用这个API。

        AssetBundle.LoadFromMemory异步从托管代码字节数组(在C#中的字节数组)中加载一个AssetBundle。它将始终把来自托管代码字节数组的源数据复制到新分配的连续本机内存块中。如果AssetBundle是LZMA格式压缩的,它将在复制时解压缩AssetBundle。未压缩的和LZ4格式压缩的AssetBundles将被逐字节复制。

        此API消耗的最大内存量至少为AssetBundle的两倍:一份是由这个API创建的本地内存中的一个副本,另一份是传递给API的托管字节数组中的一个副本。因此,通过此API创建的AssetBundle加载的Asset将在内存中复制三次:一次是托管代码中的字节数组,一次是本地内存中的AssetBundle拷贝,第三次是GPU或系统内存中的Asset本身。

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


3.3.2 AssetBundle.LoadFromFile(异步)

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

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

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

        在Unity 5.3之前,这个API被称为AssetBundle.CreateFromFile。其功能尚未更改。


3.3.3 AssetBundleDownloadHandler

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

        就本指南的目的而言,最吸引人的类是DownloadHandlerAssetBundle。使用工作线程,它将下载好的数据流式传输到固定大小的缓冲区,然后根据DownloadHandler的配置方式将缓冲的数据缓存到临时存储或AssetBundle缓存。所有这些操作都以本机代码形式进行,消除了扩展托管堆的风险。此外,该DownloadHandler并没有把所有下载的字节的副本保存在本机代码中,进一步降低了下载的AssetBundle的内存开销。

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

        当下载完成后,通过DownloadHandler的assetBundle属性可以访问下载好的AssetBundle,如同使用AssetBundle.LoadFromFile调用已经下载好的AssetBundle一样。

        如果将缓存信息提供给UnityWebRequest对象,并且所请求的AssetBundle已经存在于Unity的缓存中,则此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,允许从远程服务器和本地存储加载Object。文件可以通过file:// URL从本地存储中加载。如果AssetBundle存在于Unity缓存中,则此API的行为与AssetBundle.LoadFromFile完全相同。

        如果AssetBundle尚未缓存,则WWW.LoadFromCacheOrDownload将从其源头读取其AssetBundle。如果AssetBundle被压缩,它将使用工作线程解压缩并写入缓存。如果没有被压缩,它将通过工作线程直接写入缓存。一旦AssetBundle被缓存,WWW.LoadFromCacheOrDownload将从缓存的解压缩的AssetBundle中加载标题头信息。然后,此API将与使用AssetBundle.LoadFromFile加载AssetBundle的行为相同。该缓存在WWW.LoadFromCacheOrDownload和UnityWebRequest之间共享,任何通过其中一个API下载的AssetBundle也将为其他API提供。

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

        这个对AssetBundle的额外副本保留是为了支持WWW.bytes属性。

        由于在WWW的Object中缓存AssetBundle字节的内存开销,AssetBundles应该保持很小,最多几兆字节。有关AssetBundle大小的更多讨论,请参阅AssetBundle使用模式章节中的Asset分配策略部分。

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


3.3.5 建议

        一般情况下,应尽可能使用AssetBundle.LoadFromFile。就速度,磁盘使用情况和运行时内存使用情况而言,此API是最高效的。

        对于必须下载或打补丁AssetBundles的项目,强烈建议对于使用Unity 5.3或更新版本的项目使用UnityWebRequest,对于使用Unity 5.2或更早版本的项目,则强烈建议使用WWW.LoadFromCacheOrDownload。正如下一章的分配部分详细描述的那样,可以使用包含在项目安装程序中的Bundle来缓存基本的AssetBundle。

        使用UnityWebRequest *或* WWW.LoadFromCacheOrDownload时,请确保下载器代码在加载AssetBundle后正确调用Dispose。或者,C#的using语句是确保WWW或UnityWebRequest安全的Dispose的最便捷方式。

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


3.4 从AssetBundles加载Asset

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

        ·LoadAsset(LoadAssetAsync)

        ·LoadAllAssets(LoadAllAssetsAsync)

        ·LoadAssetWithSubAssets(LoadAssetWithSubAssetsAsync)

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

        异步加载将每帧加载多个Object,数量最多到它们的时间片段中的限制。有关此行为的根本技术原因,请参阅低级加载详细信息部分。

        LoadAllAssets应该在加载多个独立UnityEngine.Object时使用。只有在需要加载AssetBundle中的大部分或全部对象时才能使用它。与其他两个API相比,LoadAllAssets比多个单独调用LoadAssets稍快。因此,如果要加载的Asset数量很大,且一次需要加载的AssetBundle的比例不超过66%,请考虑将AssetBundle拆分为多个较小的包并使用LoadAllAssets。

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

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


3.4.1 低级加载细节

        UnityEngine.Object加载是在主线程之外执行的:从工作线程的存储中读取Object的数据。任何不接触Unity系统的线程敏感部分(脚本,图形)的部分都将在工作线程上进行转换。例如,VBO将从mesh创建,texture将被解压缩等。从Unity 5.3开始,对象加载已经并行化。多个对象在工作线程上被反序列化,处理和集成。当一个Object完成加载时,它的Awake回调将被调用,并且该Object将在下一帧时在Unity Engine的其余运行部分启用。

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

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

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

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

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

        从Unity 5.2开始,多个Object被加载,直到达到Object加载的帧时间限制。假设所有其他因素相同,由于发出异步调用和Object变为对于引擎可用的最小一帧的延迟,异步变体Asset加载API的完成时间总是比可比较的同步版本更长。


3.4.2 AssetBundle依赖关系

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

        当一个或多个父AssetBundle的UnityEngine.Object引用到一个或多个其他AssetBundle的UnityEngine.Object时,那么这个AssetBundle依赖于另一个AssetBundle。有关Object间引用的更多信息,请参阅Asset,Object和序列化文章的Object间引用部分。

        如该文章的序列化和实例部分所述,AssetBundles充当了用于辨认在AssetBundle中包含的每个Object的FileGUID和 LocalID的源数据的源。

        由于一个Object是当它的Instance ID被间接引用时被加载,并且因为一个Object在加载其AssetBundle时被分配了有效的实例ID,所以AssetBundles加载的顺序并不重要。相反,加载Object本身之前加载包含Object的依赖关系的所有AssetBundles是非常重要的。加载父级AssetBundle时,Unity不会尝试自动加载任何子AssetBundle。

        例:

        假设material A引用到texture B。material A打包到AssetBundle1中,texture B打包到AssetBundle2中。(图片见原网页)

        在这种使用情况下,AssetBundle 2必须在从AssetBundle 1中加载material A之前加载。这并不意味着必须在AssetBundle 1之前加载AssetBundle 2,或者必须从AssetBundle 2明确加载Texture B。在将Material A从AssetBundle 1中加载之前加载AssetBundle 2就足够了。

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

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


3.4.3 AssetBundle清单

        当使用BuildPipeline.BuildAssetBundles API执行AssetBundle构建流水线时,Unity序列化一个Object包含了每个AssetBundle的依赖信息。此数据存储在单独的AssetBundle中,包含AssetBundleManifest类型的单个Object。

        这个Asset将被存储在与AssetBundle被构建的同名的父级目录中的一个AssetBundle中。如果项目将其AssetBundles构建到(projectroot)/ build / Client /文件夹,则包含该清单的AssetBundle将被保存为(projectroot)/build/Client/Client.manifest。

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

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

        ·AssetBundleManifest.GetAllDependencies返回AssetBundle的所有层级依赖项,其中包括该AssetBundle依赖关系的直接子项,子项的子项等等。

        ·AssetBundleManifest.GetDirectDependencies只返回一个AssetBundle的直接子项。

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


3.4.4 建议

        在许多情况下,在玩家进入应用程序的性能关键区域(例如游戏主关卡或世界)之前,最好加载尽可能多的所需对象。这在移动平台上尤其重要,因为访问本地存储的速度很慢,而且在运行时加载和卸载Object的内存流动可能会触发GC。

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

你可能感兴趣的:(AssetBundle04)