设为首页收藏本站

微软Hololens全息现实网

 找回密码
 立即注册

QQ登录

只需一步,快速开始

扫一扫,访问微社区

一键登录:

查看: 1937|回复: 1

Unity之AssetBundles低级API探究(一)难度: 高级

[复制链接]
发表于 2017-3-5 17:34:43 | 显示全部楼层 |阅读模式

本帖最后由 HoloLens全息现实网 于 2017-3-5 17:49 编辑 综述 AssetBundle系统提供了一种用于以Unity可以索引的存档格式存储一个或多个文件的方法。 该系统的目的是提供与Unity的序列化系统兼容的数据传送方法。 AssetBundles是Unity在安装后交付和更新非代码内容的主要工具。 这允许开发...

本帖最后由 HoloLens全息现实网 于 2017-3-5 17:49 编辑


综述



AssetBundle系统提供了一种用于以Unity可以索引的存档格式存储一个或多个文件的方法。 该系统的目的是提供与Unity的序列化系统兼容的数据传送方法。 AssetBundles是Unity在安装后交付和更新非代码内容的主要工具。 这允许开发人员减少运输的资产大小,最小化运行时内存压力,并选择性地加载为最终用户的设备优化的内容。



了解AssetBundles的工作方式对于为移动设备构建成功的Unity项目至关重要。


何谓AssetBundle?

AssetBundle由两部分组成:头部和数据段。


当构建AssetBundle时,头由Unity生成。它包含有关AssetBundle的信息,例如AssetBundle的标识符,AssetBundle是压缩还是未压缩的,以及清单。


清单是由对象的名称锁定的查找表。每个条目提供一个字节索引,指示在AssetBundle的数据段中可以找到给定对象的位置。在大多数平台上,此查找表实现为STL std :: multimap。虽然由STL的任何给定平台的实现使用的特定算法变化,但大多数是各种平衡搜索树。 Windows和OSX派生平台(包括iOS)采用红黑树。因此,构建清单所需的时间将随着AssetBundle中的资产数量的增加而线性增加。


数据段包含通过序列化AssetBundle中的资产生成的原始数据。如果数据段被压缩,则LZMA算法已被应用于串行化字节的集合序列 - 也就是说,所有资产被串行化,然后完整的字节阵列被压缩。


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


Unity 5.3添加了一个LZ4压缩选项。使用LZ4压缩选项构建的AssetBundles将压缩AssetBundle中的单个对象,允许Unity将压缩的AssetBundle存储在磁盘上。这也允许Unity解压缩各个对象,而不需要解压缩整个AssetBundle。






Loading AssetBundles


在Unity 5中,AssetBundles可以通过四个不同的API加载。 这四个API的行为是不同的,取决于两个标准:

无论AssetBundle是LZMA压缩,LZ4压缩还是未压缩
正在加载AssetBundle的平台
有四个API是:


AssetBundle.LoadFromMemoryAsync


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

Unity 5.3.3更新:此API在Unity 5.3.3中重命名。在Unity 5.3.2(或更早版本)中,此API称为AssetBundle.CreateFromMemory。其功能没有改变。


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


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


AssetBundle.LoadFromFile

Unity 5.3更新:此API在Unity 5.3中重命名。在Unity 5.2(或更早版本)中,此API称为AssetBundle.CreateFromFile。其功能尚未更改。


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


移动设备:API将仅加载AssetBundle的头,并将剩余的数据保留在磁盘上。 AssetBundle的对象将被按需加载,因为加载方法(例如AssetBundle.Load)被调用或者它们的InstanceID被取消引用。在这种情况下不会消耗过多的内存。


Unity编辑器:API将加载整个AssetBundle到内存,就像字节读取磁盘和AssetBundle.LoadFromMemoryAsync使用。如果项目在Unity Editor中进行配置,此API可能会导致在AssetBundle加载期间出现内存尖峰。这不应该影响设备上的性能,并且在采取补救措施之前应当在设备上重新测试这些尖峰。


注意:在使用Unity 5.3或更早版本的Android设备上,当尝试从流媒体资产路径(Streaming Assets)加载AssetBundles时,此API会失败。这是因为该路径的内容将驻留在压缩的.jar文件中。有关更多详细信息,请参阅AssetBundle usage patterns章节的 Distribution - shipped with project 部分。此问题在Unity 5.4中解决。使用Unity 5.4或更高版本构建的游戏现在可以使用此API从流媒体资源加载资源包。


注意:对AssetBundle.LoadFromFile的调用对LZMA压缩的AssetBundles总是失败。



WWW.LoadFromCacheOrDownload

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


如果AssetBundle尚未被缓存,那么WWW.LoadFromCacheOrDownload将从其源中读取AssetBundle。如果AssetBundle被压缩,它将使用工作线程解压缩并写入缓存。否则,它将通过工作线程直接写入高速缓存。


一旦AssetBundle被缓存,WWW.LoadFromCacheOrDownload将从缓存的,解压缩的AssetBundle加载头信息。然后,API将与使用AssetBundle.LoadFromFile加载的AssetBundle具有相同的行为。


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


由于在WWW对象中缓存AssetBundle的字节的内存开销,建议所有使用WWW.LoadFromCacheOrDownload的开发人员确保他们的AssetBundles保持最小 - 最多几兆字节。还建议操作在有限内存平台(例如移动设备)上的开发人员确保其代码一次只能下载一个AssetBundle,以避免内存尖峰。有关AssetBundle大小调整的更多讨论,请参阅AssetBundle使用模式一章中的资产分配策略部分。


注意:每次调用此API都会产生一个新的工作线程。在多次调用此API时,请注意创建过多的线程。如果需要下载5-10个以上的AssetBundle,建议您编写代码,以确保只有几个AssetBundle下载同时运行。



AssetBundleDownloadHandler
在Unity 5.3的移动平台上引入,UnityWebRequest API提供了一个更灵活的替代Unity的 WWW API。 UnityWebRequest允许开发人员明确指定Unity如何处理下载的数据,并允许开发人员消除不必要的内存使用。通过UnityWebRequest下载AssetBundle的最简单的方法是 UnityWebRequest.GetAssetBundle API。

出于本指南的目的,感兴趣的类是DownloadHandlerAssetBundle。使用时,此下载处理程序的行为类似于WWW.LoadFromCacheOrDownload。使用工作线程,它将下载的数据流传输到固定大小的缓冲区,然后将缓冲的数据暂存到临时存储或AssetBundle缓存,具体取决于下载处理程序的配置方式。 LZMA压缩的AssetBundles将在下载并缓存未压缩期间解压缩。

所有这些操作都在本地代码中进行,从而消除了扩展托管堆的风险。此外,此下载处理程序不保留所有下载字节的本机代码副本,进一步减少下载AssetBundle的内存开销。

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

UnityWebRequest API还以与WWW.LoadFromCacheOrDownload相同的方式支持缓存。如果缓存信息提供给UnityWebRequest对象,并且所请求的AssetBundle已经存在于Unity的缓存中,则AssetBundle将立即可用,并且此API将与AssetBundle.LoadFromFile相同地操作。

注意:Unity AssetBundle缓存在WWW.LoadFromCacheOrDownload和UnityWebRequest之间共享。使用一个API下载的任何AssetBundle也将通过其他API可用。

注意:与WWW不同,UnityWebRequest系统具有内部工作线程池和内部作业系统,以确保开发人员无法启动大量的同时下载。线程池的大小当前不可配置。


Recommendations

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

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

当使用WWW.LoadFromCacheOrDownload时,强烈建议确保项目的AssetBundles保持小于项目最大内存预算的2-3%,以防止应用程序因内存使用峰值而终止。对于大多数项目,AssetBundles的文件大小不应超过5MB,并且不能同时下载超过1-2个AssetBundles。

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

对于具有大量工程团队和唯一缓存或下载要求的项目,可能需要自定义下载程序。编写自定义下载程序是一项非平凡的工程任务,任何自定义下载程序都应与AssetBundle.LoadFromFile兼容。有关更多详细信息,请参阅下一章的 Distribution分发部分。



Loading Assets From AssetBundles

UnityEngine.Objects可以从AssetBundles使用三个不同的API加载,这些API都附加到AssetBundle对象:LoadAsset,LoadAllAssets和LoadAssetWithSubAssets。所有这些API还具有异步变体,后缀为-Async:LoadAssetAsync,LoadAllAssetsAsync和LoadAssetWithSubAssetsAsync。


同步API将始终比异步API至少快一帧。这在Unity 5.1或更早版本中尤其如此。在Unity 5.2之前,所有异步API每帧将最多加载一个对象。这意味着LoadAllAssetsAsync和LoadAssetWithSubAssetAsync将显著慢于相应的同步API。此行为已在Unity 5.2中更正。异步加载现在将加载每帧多个对象,直到它们的时间片限制。有关此行为的基本技术原因以及有关时间分割的更多详细信息,请参阅下面低级加载详细信息部分。


LoadAllAssets应在加载多个独立的UnityEngine.Objects时使用。它只应在需要加载AssetBundle中的大多数(或所有)对象时使用。与其他两个API相比,LoadAllAssets比对LoadAssets的多个单独调用稍微快一些。因此,如果要加载的资产数量很大,但需要在单个时间加载的数量小于AssetBundle总内容的2/3,请考虑将AssetBundle拆分为多个较小的捆绑包,并使用LoadAllAssets。


当加载包含多个嵌入对象的复合资源(如嵌入了动画的FBX模型或嵌入了多个sprite的sprite atlas)时,应使用LoadAssetWithSubAssets。如果需要加载的对象都来自同一个资源,但是存储在具有许多其他不相关对象的AssetBundle中,则使用此API。


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


Low-level loading details低级加载详细信息

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


在Unity 5.3之前的版本中,对象加载发生在串行和某些部分对象加载只能发生在主线程。这被称为“集成”。在工作线程完成加载对象的数据后,它暂停将新加载的对象集成到主线程,并保持暂停(不加载下一个对象),直到主线程集成完成。


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


同步的AssetBundle.Load方法将暂停主线程,直到对象加载完成。在5.3之前的版本中,异步AssetBundle.LoadAsync方法将不会暂停主线程,直到他们需要将对象集成到主线程。它们还将时间片对象加载,使得对象集成不占用超过一定毫秒的帧时间。毫秒数由属性Application.backgroundLoadingPriority设置:


ThreadPriority.High:每帧最多50毫秒
ThreadPriority.Normal:每帧最多10毫秒
ThreadPriority.BelowNormal:每帧最多4毫秒
ThreadPriority.Low:每帧最多2毫秒。

在Unity 5.1及更早版本中,异步API只会每帧集成一个对象。这被认为是一个错误,并在Unity 5.2中得到修复。从Unity 5.2开始,将加载多个对象,直到达到对象加载的帧时间限制。 AssetBundle.LoadAsync将比可比较的同步API(假设所有其他因素相等)总是需要更长的时间完成,因为发出LoadAsync调用和对象变得可用于引擎之间的最小一帧延迟。


对现实世界中的对象和资产的测试表明了差异。在5.2之前,在低端设备上加载一定的大纹理将需要7ms同步或70ms异步。 5.2之后,观察到的差异接近于零。


AssetBundle dependencies

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


当一个或多个父AssetBundle的UnityEngine.Objects引用一个或多个其他AssetBundle的UnityEngine.Objects时,AssetBundle对另一个AssetBundle是“依赖”的。有关对象间引用的更多信息,请参阅Assets, Objects and Serialization文章中的对象间引用部分。


如该文章的“Serialization and instances序列化和实例)”部分所述,AssetBundles用作由AssetBundle中包含的每个Object的FileGUID&LocalID标识的源数据的源。


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


例子:
假定material A   引用texture Bmaterial A  被打包进 AssetBundle 1,texture B  被打包进 AssetBundle 2
ab1.jpg                

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


这并不意味着必须在AssetBundle 1之前加载AssetBundle 2,或者必须从AssetBundle 2中显式加载Texture Bundle 2。在从AssetBundle 1加载Material A之前加载AssetBundle 2就足够了。


当AssetBundle 1加载时,Unity不会自动加载AssetBundle 2。 这必须通过脚本手动完成。 用于加载AssetBundles 1和2的特定AssetBundle API是不相关的。 通过WWW.LoadFromCacheOrDownload加载的As ... LoadFromMemoryAsync加载的AssetBundles自由混合。



AssetBundle manifests

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


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


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


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


AssetBundleManifest.GetAllDependencies 返回所有AssetBundle的依赖项,包括AssetBundle的直接子项,子项的子项等的依赖项。

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

注意,这两个API分配字符串数组。请谨慎使用它们,最好不要在应用程序生命周期的性能敏感部分。



建议:

在用户进入应用程序的性能关键部分(例如主游戏级别或世界)之前,尽可能多地加载所需的对象被认为是最佳实践。 这在移动平台上尤其关键,其中对本地存储的访问较慢,并且在播放时装载和卸载对象的内存流失会触发垃圾收集器。


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




(欢迎加入官方QQ讨论群433190386)








上一篇:Unity之关于AssetBundles必须明白的基本原则
下一篇:Unity之AssetBundles低级API探究(二)难度: 高级
发表于 2017-5-3 22:21:48 | 显示全部楼层
精彩精彩很精彩
*滑动验证:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

注册验证邮件查收提醒:

由于各大邮箱政策原因,

如果没收到邮件提醒,

您的注册验证邮件可能

被投送到您邮箱的垃圾箱,

请注意查收验证哦


QQ|小黑屋|Hololens全息现实网 ( 京ICP备15026232号   点击这里给我发消息

GMT+8, 2018-11-21 19:57 , Processed in 0.558455 second(s), 32 queries .

Powered by Hololens全息现实网 X3.2

© 2001-2017 Hololens全息现实网

快速回复 返回顶部 返回列表