Assets, Resources and AssetBundles(五):AssetBundle usage patterns

这是系列文章中的第五章,内容涉及“Unity5”中的资产、资源和资源管理。

本系列的前一章介绍了AssetBundles的基本原理,其中包括各种加载API的低级行为。本章讨论了在实践中使用AssetBundles的各个方面存在的问题和可能的解决方案。

Managing loaded Assets

在内存敏感的环境中,仔细控制加载对象的大小和数量是至关重要的。当对象从活动场景中移除时,Unity不会自动卸载对象。资产清理是在特定时间触发的,也可以手动触发。

AssetBundles本身必须小心管理。由本地存储(在Unity缓存中或通过AssetBundle.LoadFromFile加载的文件)上的文件支持的AssetBundle具有最小的内存开销,很少超过几十千字节。但是,如果存在大量的AssetBundles,这种开销仍然会成为问题。

由于大多数项目允许用户重新体验内容(例如重放一个级别),因此了解何时加载或卸载资产绑定非常重要。如果AssetBundle卸载不当,则会导致内存中的对象复制。在某些情况下,不适当地卸载AssetBundles也会导致不良行为,例如导致纹理丢失。要理解为什么会发生这种情况,请参阅Assets, Objects, and serialization 部分。

在管理资产和资产Bundles时,最重要的一点是调用AssetBundle.Unload时的行为不同,unloadAllLoadedObjects参数为true或false。

此API将卸载正在调用的AssetBundle的头信息。unloadAllLoadedObjects参数确定是否也卸载从此AssetBundle实例化的所有对象。如果设置为true,那么来自AssetBundle的所有对象也将立即卸载-即使它们目前正在活动场景中使用。

例如,假设材料M是从AssetBundle AB加载的,并且假设M当前在活动场景中。

如果调用AssetBundle.Unload(True),则M将从场景中移除,销毁并卸载。但是,如果调用AssetBundle.Unload(False),则AB的标头信息将被卸载,但M将保持在场景中,并且仍然是可用的。调用AssetBundle.Unload(False)破坏了M和AB之间的链接。如果AB稍后再次加载,则AB中包含的对象的新副本将加载到内存中。

 

如果AB稍后再次加载,则将重新加载AssetBundle的头信息的新副本。然而,M不是从这个新的AB拷贝加载的。Unity并没有在AB和M的新副本之间建立任何联系。

 

如果调用AssetBundle.LoadAsset()来重新加载M,Unity将不会将旧的M副本解释为AB中数据的实例。因此,Unity将加载一个新的副本的M和将有两个相同的副本M在现场。

对于大多数项目来说,这种行为是不可取的。大多数项目应该使用AssetBundle.Unload(True),并采用一种方法来确保对象不被复制。两种常见的方法是:

  1. 在应用程序的生存期内具有定义良好的点,在此期间卸载瞬态AssetBundle,例如在级别之间或加载屏幕期间。这是最简单和最常见的选择。
  2. 仅当单个对象的所有组成对象未使用时,才能维护单个对象的引用计数并卸载AssetBundle。这允许应用程序卸载和重新加载单个对象,而不复制内存。

如果应用程序必须使用AssetBundle.Unload(False),那么只能通过两种方式卸载各个对象:

  1. 在场景和代码中消除对不需要的对象的所有引用。做完这件事后,调用Resources.UnloadUnusedAssets.
  2. 非附加加载场景。这将销毁当前场景中的所有对象并调用Resources.UnloadUnusedAssets

如果项目有明确定义的点,用户可以等待对象加载和卸载,例如在游戏模式或级别之间,则应使用这些点根据需要卸载尽可能多的对象和加载新对象。

最简单的方法是将项目的离散块打包到场景中,然后将这些场景与所有依赖项一起构建到AssetBundles中。然后,应用程序可以输入“loading”场景,完全卸载包含旧场景的AssetBundle,然后加载包含新场景的AssetBundle。

虽然这是最简单的流程,但有些项目需要更复杂的AssetBundle管理。由于每个项目是不同的,没有通用的AssetBundle设计模式。

在决定如何将对象分组到AssetBundles时,如果必须同时加载或更新对象,则通常最好将对象捆绑到AssetBundles中。例如,考虑一个角色扮演游戏。个别的地图和裁剪场景可以按场景分组成AssetBundle,但在大多数场景中需要一些对象。AssetBundles可以用来提供肖像画,游戏中的UI,以及不同的角色模型和纹理。这些后对象和资产可以分组到第二组AssetBundles中,这些资产在启动时加载,并在应用程序的生命周期内保持加载状态。

另一个问题可能会出现,如果Unity必须在AssetBundle卸载后从其AssetBundle中重新加载一个对象。在这种情况下,重新加载将失败,该对象将以(Missing)对象的形式出现在Unity编辑器的hierarchy中。

这主要发生在Unity失去并恢复对其图形上下文的控制时,例如当移动应用程序被挂起或用户锁定他们的PC时。在这种情况下,Unity必须重新上传纹理和渲染到GPU.如果这些资产的源资产包不可用,则应用程序将场景中的对象呈现为洋红色。

4.2. Distribution

向客户端分发项目的AssetBundles有两种基本方法:与项目同时安装它们或安装后下载它们。

决定是否在安装内部或安装后交付AssetBundles,取决于项目将运行的平台的功能和限制。移动项目通常选择后安装下载,以减少初始安装大小,并保持 over-the-air 下载的大小限制。控制台和PC项目通常在初始安装时附带AssetBundles。

适当的架构允许在项目安装后修补新的或修订的内容,而不管AssetBundles最初是如何交付的。有关这方面的更多信息,请参见“Unity Manual”中的“Patching with AssetBundles”一节。

4.2.1. Shipped with project

与项目一起发送AssetBundles是分发它们的最简单方法,因为它不需要额外的下载管理代码。项目可能在安装时包括AssetBundles,有两个主要原因:

以减少项目构建时间并允许更简单的迭代开发。如果这些AssetBundles不需要与应用程序本身单独更新,那么可以通过将AssetBundles存储在Streaming Assets中,将AssetBundles包含在应用程序中。请参阅下面的Streaming Assets部分。

提供可更新内容的初始修订。这通常是为了节省最终用户在初始安装后的时间或用作以后修补的基础。对于这种情况,流式处理资产并不理想。但是,如果无法选择编写自定义下载和缓存系统,则可以将可更新内容的初始版本loaded into the Unity cache from Streaming Assets.。

4.2.1.1. Streaming Assets

在安装时将任何类型的内容(包括AssetBundles)包含在一个Unity应用程序中,最简单的方法是在构建项目之前将内容构建到/Asset/StreamingAsset/文件夹中。构建时StreamingAsset文件夹中包含的任何内容都将复制到最终的应用程序中。

本地存储上StreamingAssets文件夹的完整路径可在运行时通过属性Application.streamingAssetsPath访问。然后,可以在大多数平台上通过AssetBundle.LoadFromFile加载AssetBundle。

Android开发人员:在Android上,StreamingAsset文件夹中的资产存储在APK中,如果它们被压缩,可能需要更多的时间来加载,因为APK中存储的文件可以使用不同的存储算法。所使用的算法可能因Unity版本而异。您可以使用一个归档程序(如7-zip)来打开APK,以确定这些文件是否被压缩。如果是,您可以期望AssetBundle.LoadFromFile()执行得更慢。在本例中,可以使用UnityWebRequest.GetAssetBundle作为解决方案检索缓存版本。通过使用UnityWebRequest,AssetBundle将在第一次运行时被解压缩和缓存,从而使后续执行速度更快。注意,这将占用更多的存储空间,因为AssetBundle将被复制到缓存中。或者,您可以导出Gradle项目,并在构建时向AssetBundles添加一个扩展。然后,您可以编辑build.gradle文件并将该扩展名添加到noCompress部分。一旦完成,您应该能够使用AssetBundle.LoadFromFile(),而不必支付解压缩性能成本。

注:Streaming Assets不是某些平台上的可写位置。如果安装后需要更新项目的AssetBundle,请使用WWW.LoadFromCacheOrDownload或编写自定义下载程序。

4.2.2. Downloaded post-install

将AssetBundles传送到移动设备的最受欢迎的方法是在应用程序安装后下载它们。这也允许在安装后更新内容,而不必强迫用户重新下载整个应用程序。在许多平台上,应用程序二进制文件必须经过昂贵而漫长的重新认证过程。因此,开发一个良好的后安装下载系统是至关重要的。

交付AssetBundles最简单的方法是将它们放在Web服务器上,并通过UnityWebRequest交付。Unity将自动在本地存储上缓存下载的AssetBundles。如果下载的AssetBundle是LZMA压缩的,那么AssetBundle将以未压缩或重新压缩的形式存储在缓存中,就像LZ 4一样(依赖Caching.压缩Enable设置),以便将来更快地加载。如果下载的包是LZ4压缩的,AssetBundle将被压缩存储。如果缓存被填满,Unity将从缓存中删除最近使用最少的AssetBundle。有关详细信息,请参阅内置缓存部分。

通常建议在可能的情况下使用UnityWebRequest,或者只有在使用Unity5.2或更高版本时才使用WWW.LoadFromCacheOrDownload。只有当内置API的内存消耗、缓存行为或性能对于特定项目是不可接受的,或者项目必须运行特定于平台的代码才能满足其需求时,才需要对自定义下载系统进行投资。

可能阻止使用UnityWebRequest或WWW.LoadFromCacheOrDownload的情况示例:

  • 当需要对AssetBundle缓存进行细粒度控制时

  • 当项目需要实现自定义压缩策略时

  • 当项目希望使用特定于平台的API来满足某些需求时,例如在不活动时需要流数据。

--Example: 使用IOS"后台任务API在后台下载数据。

  • 当AssetBundles必须在不具备适当SSL支持(如PC)的平台上通过SSL交付时。

4.2.3. Built-in caching

Unity有一个内置的AssetBundle缓存系统,可以用来缓存通过UnityWebRequest API下载的AssetBundles,该API的重载接受一个AssetBundle版本号作为参数。这个数字不是存储在AssetBundle中,也不是由AssetBundle系统生成的。

缓存系统跟踪传递给UnityWebRequest的最后一个版本号。当使用版本号调用此API时,缓存系统通过比较版本号来检查是否存在缓存的AssetBundle。如果这些数字匹配,系统将加载缓存的AssetBundle。如果数字不匹配,或者没有缓存的AssetBundle,那么Unity将下载一个新的副本。此新副本将与新版本号相关联。

缓存系统中的AssetBundles只通过它们的文件名来标识,而不是通过下载它们的完整URL来标识。这意味着具有相同文件名的AssetBundle可以存储在多个不同的位置,例如内容传递网络。只要文件名相同,缓存系统就会将它们识别为相同的AssetBundle。

每个应用程序都需要确定将版本号分配给AssetBundles的适当策略,并将这些编号传递给UnityWebRequest。数字可能来自种类的唯一标识符,例如crc值。请注意,虽然AssetBundleManifest.GetAssetBundleHash()也可用于此目的,但我们不建议将此函数用于版本控制,因为它只提供了估计,而不是真正的哈希计算)。

有关更多细节,请参见“Unity Manual”中的“Patching with AssetBundles”部分。

在Unity2017.1中,通过允许开发人员从多个缓存中选择一个活动缓存,缓存API已经被扩展以提供更细粒度的控制。以前的Unity 版本只能修改Caching.expenationDelay和Caching.AvailableDiskSpace,以删除缓存项(这些属性保留在Cache类的Unity2017.1中)。

expirationDelay是自动删除AssetBundle之前必须经过的最小秒数。如果在此期间没有访问AssetBundle,则将自动删除它。

MaximumAvailableDiskSpace指定缓存在开始删除最近使用的AssetBundles之前可能使用的本地存储空间的大小(以字节为单位)。当达到限制时,Unity将删除缓存中最近打开的AssetBundle(或通过Caching.MarkAsUsed标记)。Unity将删除缓存的AssetBundles,直到有足够的空间完成新的下载。

4.2.3.1. Cache Priming

因为AssetBundles是通过它们的文件名来标识的,所以可以使用应用程序附带的AssetBundles来“初始化”缓存。为此,将每个AssetBundle的初始或基本版本存储在/Asset/StreamingAsset/中。该过程与随项目一起发送的详细流程相同。

在应用程序第一次运行时,可以通过从Application.StreingAssetsPath加载AssetBundles来填充缓存。从那时起,应用程序可以正常地调用UnityWebRequest(UnityWebRequest也可以用于最初从StreamingAsset路径加载AssetBundles)。

4.2.3. Custom downloaders

编写自定义下载器可以让应用程序完全控制如何下载、解压缩和存储AssetBundles。由于所涉及的工程工作并不简单,我们建议只对较大的团队采用这种方法。在编写自定义下载程序时有四个主要注意事项:

  • Download mechanism 下载机制

  • Storage location 存储位置,存储单元

  • Compression type 压缩类型

  • Patching 补丁

有关修补AssetBundle的信息,请参见使用Patching with AssetBundles

4.2.3.1. Downloading

对于大多数应用程序,HTTP是下载Assetbundle的最简单方法。但是,实现基于HTTP的下载器不是最简单的任务。自定义下载器必须避免过度的内存分配、过度的线程使用和过度的线程唤醒。Unity的WWW类不适合在此详尽描述的原因。

在编写自定义下载程序时,有三个选项:

  • C#'s HttpWebRequest and WebClient classes

  • Custom native plugins

  • Asset store packages

4.2.3.1.1. C# classes

如果应用程序不需要HTTPS/SSL支持,C#的WebClient类为下载AssetBundles提供了最简单的机制。它能够异步地将任何文件直接下载到本地存储,而不需要过多的托管内存分配。要下载带有WebClient的AssetBundle,请分配类的一个实例并将AssetBundle的URL传递给它以下载和目标路径。如果需要对请求的参数进行更多的控制,则可以使用C#‘s HttpWebRequest类编写一个下载器:

  1. HttpWebResponse.GetResponseStream获取字节流。

  2. 在堆栈上分配一个固定大小的字节缓冲区.

  3. 从响应流读取到缓冲区。

  4. 使用C#的File.IO API或任何其他流IO系统将缓冲区写入磁盘。

4.2.3.1.2. Asset Store Packages

几个AssetStore包提供本机代码实现,通过HTTP、HTTPS和其他协议下载文件。在编写用于Unity的自定义本机代码插件之前,建议对可用的AssetStore包进行评估。

4.2.3.1.3. Custom Native Plugins

编写自定义本机插件是最耗时的,但最灵活的方法是Unity下载数据。由于编程时间要求高,技术风险高,只有在没有其他方法能够满足应用要求的情况下,才推荐使用该方法。例如,如果应用程序必须在没有C#SSL支持的平台上使用SSL通信,则可能需要自定义本机插件。

自定义本机插件通常会包装目标平台的原生下载API。示例包括IOS上的NSURLConnection和Android上的java.net.HttpURLConnection。有关使用这些API的更多详细信息,请参阅每个平台的本机文档。

4.2.3.2. Storage

在所有平台上,Application.PersistentDataPath指向一个可写的位置,该位置应该用于存储在应用程序运行之间应该持久的数据。在编写自定义下载程序时,强烈建议使用Application.PersistentDataPath的子目录来存储下载的数据。

StreingAssetPath不可写,对于AssetBundle缓存来说是一个糟糕的选择。StreingAssetPath的示例位置包括:

  • OSX: Within .app package; not writable.

  • Windows: Within install directory (e.g. Program Files); usually not writable

  • iOS: Within .ipa package; not writable

  • Android: Within .apk file; not writable

4.3. Asset Assignment Strategies

决定如何将项目的资产划分为AssetBundles并不简单。采用一种简单化的策略是很有诱惑力的,比如将所有对象放在自己的AssetBundle中,或者只使用一个AssetBundle,但是这些解决方案有很大的缺点:

  • Having too few AssetBundles...

    1. Increases runtime memory usage 增加运行时内存使用

    2. Increases loading times 增加加载时间

    3. Requires larger downloads 需要更多下载量

  • Having too many AssetBundles...

    1. Increases build times 增加构建时间

    2. Can complicate development 复杂的发展

    3. Increases total download time 增加下载总时间

关键的决定是如何将对象分组到AssetBundles中。主要战略是:

  • Logical entities 逻辑实体

  • Object Types 对象类型

  • Concurrent content  并发内容

有关这些分组战略的更多信息可在“Manual”中找到。

4.4. Common pitfalls

本节描述了在使用AssetBundles的项目中常见的几个问题。

4.5.1. Asset duplication

Unity5的AssetBundle系统将发现一个对象的所有依赖时,该对象被构建到一个AssetBundle。此依赖关系信息用于确定将包含在AssetBundle中的对象集。

显式分配给AssetBundle的对象将只被构建到该AssetBundle中。当对象的AssetImporter将其AssetBundleName属性设置为非空字符串时,该对象将被“显式分配”。这可以通过在对象的检查器中选择AssetBundle来完成,也可以从Editor脚本中进行。

对象还可以通过将其定义为AssetBundle构建映射的一部分来分配给AssetBundle,该映射将与重载的BuildPiine.BuildAssetBundles()函数一起使用,该函数接受AssetBundleBuild数组。

在AssetBundle中没有显式分配的任何对象都将包含在包含引用未标记对象的1个或多个对象的所有AssetBundles中。

例如,如果两个不同的对象被分配给两个不同的AssetBundles,但都具有对公共依赖对象的引用,那么该依赖对象将被复制到两个AssetBundles中。重复的依赖关系也将被实例化,这意味着依赖对象的两个副本将被视为具有不同标识符的不同对象。这将增加应用程序的AssetBundles的总大小。这还会导致两个不同的对象副本加载到内存中,如果应用程序同时加载它的双亲。

有几种方法可以解决这个问题:

  1. 确保内置到不同AssetBundle中的对象不共享依赖关系。任何共享依赖关系的对象都可以放置到同一AssetBundle中,而不复制它们的依赖项。

    • 对于具有许多共享依赖项的项目,此方法通常不可行。它产生的单块资产绑定,必须重建和重新下载太频繁,以方便或有效。
  2. 划分 AssetBundles,这样就不会同时加载两个共享依赖项的AssetBundles。

    • 这种方法可能适用于某些类型的项目,例如基于级别的游戏。但是,它仍然不必要地增加了项目的AssetBundles的大小,并且增加了构建时间和加载时间。
  3. 确保所有依赖资产都内置到自己的AssetBundle中。这完全消除了重复资产的风险,但也带来了复杂性。应用程序必须跟踪AssetBundles之间的依赖关系,并确保在调用任何AssetBundle.LoadAssetAPI之前加载正确的AssetBundles。

通过位于UnityEditor命名空间中的AssetDatabaseAPI跟踪对象依赖关系。正如名称空间所暗示的那样,此API仅在UnityEditor中可用,而在运行时不能使用。GetDependents可用于定位特定对象或资产的所有直接依赖项。请注意,这些依赖项可能有它们自己的依赖项。此外,AssetImportAPI还可以用于查询分配任何特定对象的AssetBundle。

通过组合AssetDatabase和AssetImportAPI,可以编写一个编辑器脚本,以确保将AssetBundle的所有直接或间接依赖项分配给AssetBundles,或者确保没有两个AssetBundles共享未分配给AssetBundle的依赖项。由于复制资产的内存成本,建议所有项目都有这样的脚本。

4.5.2. Sprite atlas duplication

任何自动生成的sprite atlas都将被分配给AssetBundle,其中包含生成sprite 图集的sprite 对象。如果sprite对象被分配给多个AssetBundles,那么sprite 图集将不会分配给AssetBundle并将被复制。如果sprite 对象没有分配给AssetBundle,那么sprite图集也不会分配给AssetBundle。

为了确sprite 图集不被复制,请检查所有被标记在同一个sprite 图集中的sprite 都被分配到同一个资产包中。

请注意,在Unity5.2.2p3和更旧版本中,自动生成的sprite图集永远不会分配给AssetBundle。因此,它们将包括在任何包含其组成sprite的AssetBundle中,也包括引用其组成sprite的任何AssetBundle。由于这个问题,强烈建议所有Unity5项目使用Unity的sprite封隔器升级到团结5.2.2p4,5.3或任何更新版本的Unity。

4.5.3. Android textures

由于Android生态系统中的重型设备碎片,通常需要将纹理压缩成几种不同的格式。尽管所有Android设备支持ETC1,但ETC1不支持Alpha通道的纹理。如果应用程序不需要OpenGLES2支持,解决此问题的最干净方法是使用ETC2,它由所有AndroidOpenGLES3设备支持。

大多数应用程序需要在ETC 2支持不可用的旧设备上发布。解决这个问题的一种方法是使用Unity5的AssetBundle变体(有关其他选项的详细信息,请参阅联合的Android优化指南)。

要使用AssetBundle变体,所有不能使用ETC 1进行干净压缩的纹理必须隔离到只有纹理的AssetBundles中。接下来,使用DXT 5、PVRTC和ATITC等特定供应商的纹理压缩格式,创建这些AssetBundles的足够变体,以支持Android生态系统中不具备ETC 2功能的切片。对于每个AssetBundle变体,将包含的纹理的TextureImporter设置更改为适合该变体的压缩格式。

在运行时,可以使用SystemInfo.SupportsTextureFormatAPI检测到对不同纹理压缩格式的支持。此信息应用于选择和加载包含以支持格式压缩的纹理的AssetBundle变体。

更多关于Android纹理压缩格式的信息可以在here找到。

4.5.4. iOS file handle overuse

当前版本的Unity不受此问题的影响。

在Unity5.3.2p2之前的版本中,在加载AssetBundle的整个时间里,Unity都会为AssetBundle保存一个打开的文件句柄。在大多数平台上,这不是一个问题。但是,IOS限制进程的文件句柄的数量可以同时打开到255。如果加载AssetBundle导致超出此限制,则加载调用将失败,出现"Too Many Open File Handles"错误。

这对于尝试在数百个或数千个AssetBundle之间拆分其内容的项目来说,这是一个常见的问题。

对于无法升级到修补版本的团结的项目,临时解决方案是:

  • 通过合并相关AssetBundle来减少正在使用的AssetBundle的数量

  • 使用assetbunder.unload(false)关闭assetbundle的文件句柄,并手动管理加载的对象“生命周期”

4.5. AssetBundle Variants

AssetBundle 系统的一个关键特点是引入了AssetBundle 变体。变体的目的是允许应用程序调整其内容以更好地适应其运行时环境。变体允许不同AssetBundle文件中的不同UnityEngine.Objects在加载对象和解析实例ID引用时显示为“相同”对象。从概念上讲,它允许两个UnityEngine.Objects看起来共享相同的FileGUID&LocalID,并通过字符串变体ID标识实际的UnityEngine.Object。

该系统有两个主要用例:

  1. Variants simplify the loading of AssetBundles appropriate for a given platform.变量简化了适用于给定平台的资产开发的加载。

    • Example: A build system might create an AssetBundle containing high-resolution textures and complex shaders suitable for a standalone DirectX11 Windows build, and a second AssetBundle with lower-fidelity content intended for Android. At runtime, the project's resource loading code can then load the appropriate AssetBundle Variant for its platform, and the Object names passed into the AssetBundle.Load API do not need to change.示例:构建系统可能会创建一个包含高分辨率纹理和适用于独立Directx11 Windows构建的复杂明暗器的AssetBundle,以及一个针对Android的具有较低保真度内容的第二个AssetBundle。在运行时,项目的资源加载代码可以为其平台加载适当的assetbundle变量,而传递到assetbundle.load api的对象名不需要更改。
  2. Variants allow an application to load different content on the same platform, but with different hardware.变体允许应用程序在同一平台上加载不同的内容,但使用不同的硬件。

    • This is key for supporting a wide range of mobile devices. An iPhone 4 is incapable of displaying the same fidelity of content as the latest iPhone in any real-world application.这是支持多种移动设备的关键。iPhone4无法在任何实际应用程序中显示与最新iPhone相同的内容保真度。

    • On Android, AssetBundle Variants can be used to tackle the immense fragmentation of screen aspect ratios and DPIs between devices.在Android上,AssetBundle变体可用于解决屏幕纵横比和设备间dpi的巨大碎片问题。

4.5.1. Limitations

AssetBundle变体系统的一个关键限制是,它要求从不同的资产构建变体。即使这些资产之间的唯一变化是它们的导入设置,这个限制也是适用的。如果在变量A和变体B中构建的纹理之间的唯一区别是在统一纹理导入器中选择的特定纹理压缩算法,则变量A和变体B仍然必须是完全不同的资产。这意味着变量A和变体B必须是磁盘上的单独文件。

这一限制使大型项目的管理变得复杂,因为必须将特定资产的多个副本保存在源代码管理中。当开发人员希望更改资产的内容时,必须更新资产的所有副本。对于这个问题,没有现成的解决办法.

大多数团队都使用自己的AssetBundle变体。这是通过构建AssetBundle(其文件名后附加有定义的后缀)来完成的,以便识别给定AssetBundle表示的特定变体。在构建这些AssetBundle时,自定义代码以编程方式更改所包括资产的导入程序设置。一些开发人员扩展了他们的自定义系统,以便也可以更改连接到印前的组件上的参数。

4.6. Compressed or uncompressed?

是否压缩AssetBundles需要考虑几个重要问题,其中包括:

  • Loading time: Uncompressed AssetBundles are much faster to load than compressed AssetBundles when loading from local storage or a local cache.

  • Build time: LZMA and LZ4 are very slow when compressing files, and the Unity Editor processes AssetBundles serially. Projects with a large number of AssetBundles will spend a lot of time compressing them.

  • Application size: If the AssetBundles are shipped in the application, compressing them will reduce the application's total size. Alternatively, the AssetBundles can be downloaded post-install.

  • Memory usage: Prior to Unity 5.3, all of Unity's decompression mechanisms required the entire compressed AssetBundle to be loaded into memory prior to decompression. If memory usage is important, use either uncompressed or LZ4 compressed AssetBundles.

  • Download time: Compression may only be necessary if the AssetBundles are large, or if users are in a bandwidth-constrained environment, such as downloading on low-speed or metered connections. If only a few tens of megabytes of data are being delivered to PCs on high-speed connections, it may be possible to omit compression.

4.6.1. Crunch Compression

Bundles主要由DXT压缩纹理组成,使用Crunch压缩算法,应该建立非压缩纹理。

4.7. AssetBundles and WebGL

WebGL项目中的所有AssetBundle解压缩和加载必须发生在主线程上,因为Unity的WebGL导出选项目前不支持工作线程。使用XMLHttpRequest将AssetBundles的下载委托给浏览器。一旦下载,压缩的AssetBundles将被解压在Unity的主线程上,因此会根据包的大小而延迟Unity内容的执行。

Unity建议开发人员更喜欢小型AssetBundle,以避免出现性能问题。与使用大型AssetBundle相比,这种方法还具有更高的内存效率。UnityWebGL只支持LZ4-压缩和未压缩的资产包,但是,可以将gzip/brotli压缩应用于由Unity生成的包上。在这种情况下,您需要相应地配置Web服务器,以便在浏览器下载时解压文件。更多细节请看here

如果您使用的是Unity5.5或更旧,请考虑避免使用LZMA作为Assetbundle并使用LZ4进行压缩,而这是非常有效的按需解压缩的。Unity5.6将LZMA作为WebGL平台的压缩选项删除。

posted @ 2021-01-27 22:42  钢与铁  阅读(341)  评论(1编辑  收藏  举报