Unity资源管理(四)-AssetBundle使用模式

发表于2018-06-06
评论7 1.27w浏览

Unity资源管理(四)-AssetBundle使用模式

参考文献:https://unity3d.com/cn/learn/tutorials/topics/best-practices/assetbundle-usage-patterns

本系列的前几篇文章讲述了AssetBundle的基本原理,包括几种加载API的底层行为。这一篇文章结合实践从多个方面讨论了在使用AssetBundle时可能遇到的问题和解决方案。这是Unity资源管理系列的最后一篇文章。

4.1 管理已加载的Asset

在内存敏感的环境中,仔细控制加载的Object的大小和数量十分重要。当Object被从活动Scene中移除时,Unity不会自动将其卸载。Asset清理会在特殊时期触发,但也可以手动触发。

需要对AssetBundle本身进行仔细的管理。一个由本地存储文件支撑的AssetBundle(在Unity缓存中或者由AssetBundle.LoadFromFile加载)的内存开销很小,很少会超过几十KB。但是,如果同时存在大量的AssetBundle,这一开销仍然可能引发问题。

因为大多数游戏允许玩家重新体验游戏内容(例如重玩某关),所以知晓该在什么时候对AssetBundle进行加载和卸载很重要。如果某个AssetBundle被不恰当地卸载,这可能造成Object在内存中产生重复的副本。在某些情况下,不恰当地卸载AssetBundle也会引起其他不良行为地产生,例如导致纹理丢失。想要了解产生这种情况的原因,请查看Object间的引用中的Asset、Object和序列化章节。

在调用[AssetBundle.Unload]方法时,传入的 unloadAllLoadedObjects 参数(true或false)会导致不同的行为,这对于管理资源和AssetBundle来说非常重要。

这一API会卸载调用方法的AssetBundle的数据头信息,是否同时卸载由此AssetBundle实例化的所有Object由 unloadAllLoadedObjects 参数决定。如果参数为true,来自于此AssetBundle的所有Object会被立即卸载——即使它们目前正在活动Scene中被使用着。

例如,假设材质M加载自AssetBundle AB,并且M目前正在活动Scene中。

如果调用了 AssetBundle.Unload(true),那么M会被从Scene中移除、销毁并卸载。然而,如果调用了 AssetBundle.Unload(false),AB的数据头信息会被卸载,但是M仍然会留在Scene中而且有效。调用 AssetBundle.Unload(false) 会破坏M和AB之间的链接关系。如果之后又加载了AB,会将AB中的Object的新的副本加载到内存中。

如果之后又加载了AB,那么会重新加载一份新的AssetBundle数据头信息副本,但是不会在新的AB副本中加载M。Unity不会再新的AB和M之间建立任何链接关系。

如果调用 AssetBundle.LoadAsset() 重新加载M,Unity不会将已有的M副本作为AB中的数据的实例,因此,Unity会加载一个新的M的副本,这样,Scene中就会有两个不同的M的副本。

对于大多数项目来说,这种行为是不好的,应该使用 AssetBundle.Unload(true) 并且采用一些方法来确保没有多余的Object副本。有两个常用的方法:

  1. 在应用程序生命周期中合理定义节点去卸载短期使用的AssetBundle,例如在关卡之间或者在加载画面时卸载。这是最简单也是最常用的选项。

  2. 维护每个Object的引用数量,并且只在AssetBundle的Object都没被使用时才卸载AssetBundle。这样应用程序就可以卸载和重新加载每个Obejct,而不会产生多余的副本。

如果必须在应用程序中使用 AssetBundle.Unload(false),那么只有在下列两种情况下可以卸载Object:

  1. 消除对不再使用的Object的所有引用,无论是在Scene中还是代码中,然后调用Resources.UnloadUnusedAssets

  2. 非附加式的加载Scene。这回销毁当前Scene中的所有Object并自动调用Resources.UnloadUnusedAssets

如果游戏中定义了良好的节点让用户等待Object加载和卸载,例如切换游戏模式和关卡时,应该在这些节点处尽量多的卸载无用的Object和加载新的Object。

实现上述操作的最简单的方法是,将游戏中不相关的部分分别打包到多个Scene中,然后分别将这些Scene连同它们的全部依赖打包到AssetBundle中。这样,游戏就可以进入到一个“加载”Scene中,完全卸载包含旧Scene的AssetBundle,然后加载包含新Scene的AssetBundle。

这是最简单的工作流程,但有些项目可能需要更复杂的AssetBundle管理,因为每个项目都不一样,并没有通用的AssetBundle模式。

在决定怎么将Object分配到AssetBundle中时,最好将那些需要同时加载或者更新的Object打包到同一个AssetBundle中。例如,在一个RPG游戏中,可以根据Scene将地图和场景打包到AssetBundle中,而那些在整个程序的生命周期中经常用到的Object,可以将它们打包到另外的AssetBundle中,并且在程序启动时就加载。

卸载AssetBundle后又从这个AssetBundle中重新加载Object可能会造成另一个问题——该Object加载失败而且在Unity编辑器的层级中显示为Missing。

这通常发生在Unity失去后又重新获得对它的图形上下文的控制时,例如,在移动应用上应用被挂起,或者在PC上用户锁定了电脑。在这种情况下,Unity必须重新将纹理和着色器上传到GPU。如果这些资源的源AssetBundle不可用了,应用程序会把Scene中的Object渲染成品红色。

4.2 分发

有两个分发AssetBundle的基本方法:和程序一同安装或者在安装程序后下载。

选择随程序安装还是安装后下载取决于程序运行平台的功能和限制。在移动平台上通常选择安装后下载,这样可以减小初始安装大小。在主机和PC上通常选择随程序安装。

具有合理的结构的项目可以在安装后发布新内容或添加补丁,并且不用关心AssetBundle是如何分发的。更多详细信息,请查看AssetBundle补丁手册

4.2.1 附带在项目中

分发AssetBundle的最简单方法是将其附带在项目,因为这样做不需要额外的下载管理代码。一个项目会在安装时包含AssetBundle的主要原因有两个:

  • 减少程序构建时间、加快开发迭代速度。如果这些AssetBundle不需要和应用程序本身分开进行更新,那么可以将它们包含在应用程序的StreamingAsset文件夹中。请查看下面的StreamingAssets章节。
  • 提供可更新内容的基础版本。这通常用于在初始安装后节省终端用户的时间,或者是作为之后添加补丁的基础服务。在这种情况下,使用StreamingAssets不是理想的解决方案。但是,如果不想开发自定义的下载和缓存系统,那么可以把可更新内容的初始版本从StreamingAssets加载到Unity缓存中,请查看下面的 预置缓存 章节。

4.2.1.1 StreamingAssets

将任意类型的内容包含进Unity程序的最简单方法是在构建工程之前将它们放入 /Assets/StreamingAsset/ 文件夹中。StreamingAsset 文件夹中的所有内容都会在构建时被复制到最终的应用程序中。

在运行时,可以通过Application.streamingAssetsPath属性获取AssetBundle.LoadFromFile在设备上的完整路径。在大多数平台上都可以通过 AssetBundle.LoadFromFile 加载其中的AssetBundle。

Android开发者:在Android中,StreamingAsset 里面的资源存储在APK中,如果它们被压缩过,可能会花费更多时间加载,在APK中存储文件可能使用不同的压缩算法。在不同版本的Unity中,压缩算法可能会不同。如果采用了压缩,AssetBundle.LoadFromFile()的执行速度会更慢,这时,可以使用UnityWebRequest.GetAssetBundle检索已缓存的版本。AssetBundle会在首次运行UnityWebRequest时被解压,这可以让后续的操作速度更快。需要注意的是,这样做会占用更多的存储空间,因为AssetBundle会被复制到缓存。一个替代方案是,在构建时导出Gradle项目并对AssetBundle添加扩展,然后再编辑 build.gradle 文件并将扩展添加到不压缩的部分。这样做以后,就可以使用 AssetBundle.LoadFromFile() 而不必产生解压开销。

注意:在某些平台上,StreamingAsset 是只读的。如果要在程序安装之后更新AssetBundle,请使用 WWW.LoadFromCacheOrDownload 或者自定义下载器。

4.2.2 在安装应用后下载

在移动设备上最受欢迎的分发AssetBundle的方式是在安装应用之后下载。这样做也能够让用户在更新时不用重新下载整个应用。在很多平台上,应用程序的二进制文件都必须通过昂贵且漫长的重新审核流程,因此,开发一个良好的用于安装后下载的系统很有用。

分发AssetBundle最简单的方法是将它们置于Web服务器上,然后通过UnityWebRequest下载。Unity会自动在本地存储上缓存下载的AssetBundle。如果下载的AssetBundle使用LZMA压缩,它会在解压或者重新以LZ压缩(取决于Caching.compressionEnabled设置)后再存储,以便在以后更快地加载。如果下载的AssetBundle使用LZ4压缩,它会被保留压缩格式存储。如果缓存区已满,Unity会移除最近一段时间内使用次数最少的AssetBundle。查看下一节 内置缓存 来了解细节。

一般情况下推荐优先使用 UnityWebRequest(在Unity 5.2以更早版本中则是 WWW.LoadFromCacheOrDownload)。只有在下列情况中才有必要开发自定义的下载系统:

  • 特定项目的内置API内存消耗和缓存行为性能不能满足需求
  • 项目必须运行依赖于特定平台的代码才能实现需求

例如,下面的这些情况可能不适合使用 UnityWebRequestWWW.LoadFromCacheOrDownload

  • 需要对AssetBundle缓存进行细粒度控制
  • 项目需要实现自定义压缩策略
  • 项目需要使用特定平台的API来实现特定需求,例如在非活动状态时流式传输数据。 
    • 例如:当程序进入后台时使用iOS的后台任务API下载数据。
  • 在Unity没有实现SSL支持的平台(例如PC)上使用SSL分发AssetBundle。

4.2.3 内置缓存

Unity内置了一个缓存系统,用于缓存通过 UnityWebRequest 下载的AssetBundle。UnityWebRequest 方法有一个接受AssetBundle版本号作为参数的重载,这个版本号并不存储在AssetBundle中,也不由AssetBundle系统生成。

缓存系统通过 UnityWebRequest 来位置最新的版本号,当调用带有版本号的重载方法时,缓存系统会通过对比版本号来检查是否已经有了缓存的AssetBundle。如果版本号匹配成功,系统会加载已缓存的AssetBundle,否则Unity会下载新的副本,作为参数传入的版本号会被分配给这个新的副本。

缓存系统中的AssetBundle仅以它们的文件名进行标识,而不是通过下载它们的完整URL标识。这意味着,一个AssetBundle可以被存储在多个不同的网络位置,例如内容分发网络(CDN)。只要文件名相同,Unity就会把它们认为是同一个AssetBundle。

应该根据每个应用程序的不同需求去设置合适的AssetBundle版本号分配策略,并在 UnityWebRequest 中使用这些版本号。版本号可以是各种唯一标识符,例如CRC值。AssetBundleManifest.GetAssetBundleHash() 的返回值也可以用作版本号,但不推荐这样做,因为他并没有进行真正的哈希计算,只是提供了一个近似值。

查看使用AssetBundle补丁手册来了解更多详细内容。

从Unity 2017.1开始,Caching API被加以扩展,来提供更多粒度控制,允许开发者在多个缓存中选择一个活动的缓存。在之前版本的Unity中只能通过修改Caching.expirationDelayCaching.maximumAvailableDiskSpace类移除已缓存的项目(在Unity 2017.1中,这两个属性保留在Cache类中)。

Cache 和 Caching 中的 expirationDelay 属性用于设置系统自动删除AssetBundle的等待时间(秒),如果这段时间内某个AssetBundle一直没被使用过,它会被自动删除。

Cache 和 Caching 中的 maximumAvailableDiskSpace 属性用于设置缓存可占用的最大本地存储空间(字节),如果缓存已满,Unity会删除最近一段时间内使用次数最少的AssetBundle(可以使用 Caching.MarkAsUsed 将AssetBundle标记为在使用)。

4.2.3.1 预置(Priming)缓存

因为AssetBundle通过它们的文件名进行标识,所以可以通过随应用程序附带的AssetBundle来使缓存处于预备状态。要实现这种功能,需要将每个AssetBundle的初始(基础)版本放到 /Assets/StreamingAssets/ 文件夹中。其过程与上文中的 附带在项目中 章节相同。

在首次启动应用程序时,可以通过从 Application.streamingAssetsPath 加载AssetBundle来填充缓存。之后,应用程序就可以正常地调用 UnityWebRequest(在首次从 Application.streamingAssetsPath 加载AssetBundle时也可以使用 UnityWebRequest )。

4.2.4 自定义下载器

开发自定义的下载器可以完全控制应用程序如何下载、加压和存储AssetBundle。这一工程并非寻常项目,所以只推荐大型团队去尝试开发自定义的下载器。在开发自定义下载器时有4个需要着重考虑的内容:

  1. 下载机制
  2. 存储位置
  3. 压缩类型
  4. 添加补丁

关于使用AssetBundle添加补丁,请查看AssetBundle补丁手册来了解更多详细内容。

4.2.4.1 下载

对于大多数程序来说,HTTP是下载AssetBundle的最简单的方法。然而,实现一个基于HTTP的下载器并不是一项简单的任务。自定义下载器一定要避免多余的内存分配、线程使用和线程唤醒。Unity中的 WWW 类并不适用于实现自定义下载器,原因在这里

在开发自定义下载器时,可以选择三种途径:

  1. C#的 HttpWebRequest 和 WebClient 类
  2. 自定义的原生(Native)插件
  3. 使用Asset Store中的插件包
4.2.4.1.1 C#类

如果应用程序不需要HTTPS/SSL支持,那么C#的WebClient可以提供最简单的AssetBundle下载机制,它可以将任何类型的文件直接异步下载到本地存储中,不需要多余的内存开销。

使用 WebClient 下载AssetBundle时,为 WebClient 分配一个实例,然后将要下载的AssetBundle的URL地址和存储路径传递给实例即可。如果要通过请求参数进行更多控制,可以使用C#的HttpWebRequest类实现:

  1. 从 HttpWebResponse.GetResponseStream 获取字节流。
  2. 在栈中分配一个固定大小的字节缓冲区。
  3. 从缓冲区读取响应流数据。
  4. 使用C#的File.IO API或者其他IO流式传输系统将缓冲数据写入磁盘。
4.2.4.1.2 Asset Store中的插件包

在Asset Store中有几个插件包提供了由本地代码(Native-Code)实现的通过HTTP、HTTPS以及其他协议下载文件的功能。建议在开发自定义下载器之前先去Asset Store中查看是否有合适的插件包。

4.2.4.1.3 自定义原生插件(Native Plugins)

开发原生插件最为耗时,但灵活性最高。因为开发原生插件需要大量的编码时间,而且有技术风险,所以推荐只在其他几种方法无法满足需求时才去这样做。

原生插件通常会对目标平台的原生下载API进行封装。例如,iOS上的NSURLConnection和Android上的java.net.HttpURLConnection。使用这些方法时请查阅它们的文档。

4.2.4.2 存储

在所有的平台上,Application.persistentDataPath 都会指向一个可写的磁盘位置,这个位置用于存储在程序运行过程中需要持久化的数据。在开发自定义下载器时,强烈推荐将下载的数据存储在 Application.persistentDataPath 的子目录中。

将AssetBundle缓存放到 Application.streamingAssetPath 中是一个糟糕的选择,而且有些时候这个文件夹是不可写的,例如:

  • OSX:在 .app 包中,不可写
  • Windows:在安装目录中(例如 Program Files),经常不可写
  • iOS:在 .ipa 包中,不可写
  • *Android:在 .apk 文件中,不可写

4.3 Asset分配策略

决定怎样将项目中的资源划分到不同的AssetBundle中不是一项简单的工作。很容易想到的两个方案是把每个Object单独放到一个AssetBundle中和把所有Object放到同一个AssetBundle中。但这两种做法都有很明显的缺点:

  • AssetBundle太少: 
    • 增加运行时内存占用
    • 增加加载时间
    • 需要下载的内容更大
  • AssetBundle太多: 
    • 增加构建时间
    • 使开发更复杂
    • 增加整体下载时间

分组Object的基本策略是:

  • 逻辑实体
  • Object类型
  • 同时使用的内容

关于分组策略的更多信息,请查看Asset准备手册

4.4 常见陷阱

这一节讨论了在使用了AssetBundle的项目中经常会遇到的几个问题。

4.4.1 Asset重复

当把Object打包进AssetBundle时,Unity 5的AssetBundle系统会检测Object的所有依赖,这个依赖信息用于确定将要被包含到AssetBundle中的Object的集合。

如果Object的 assetBundleName 属性不为空,那么它会被打包到指定的AssetBundle中。可以在Object的Inspector面板或者Editor脚本中设置 assetBundleName 属性。

也可以通过定义Object的AssetBundle构建图来分配它们所属的AssetBundle,这需要配合BuildPipeline.BuildAssetBundles()的接受 AssetBundleBuild 数组作为参数的重载方法使用。

Any Object that is not explicitly assigned in an AssetBundle will be included in all AssetBundles that contain 1 or more Objects that reference the untagged Object.什么鬼??!!看不懂,不瞎翻译了。

例如,Object A和Object B被分配到了两个不同的AssetBundle中,但A和B都依赖另一个Object C,那么在两个AssetBundle中都会复制一份C的副本。被依赖的C同样会被实例化,这意味着它会有两份被标识为不同的Object的副本。这会增加应用程序的AssetBundle的整体大小,而且当应用程序同时加载了A和B时,会在内存中加载两个C的实例。

有几种方式可以定位这种问题:

  1. 确保打包进不同AssetBundle的Object间没有共同的依赖。把含有共同依赖的Object打包进同一个AssetBundle中。 
    • 这种做法不适合含有很多共享依赖的项目。
  2. 分离AssetBundle,确保含有共同依赖的AssetBundle不会被同时加载。 
    • 这种做法适合某些特定类型的项目,例如基于关卡的游戏,不过它同时也会增加AssetBundle的整体大小、构建时间和加载时间。
  3. 确保所有被依赖的资源被打包到了AssetBundle中。这样可以完全消除产生重复资源的风险,但会增加复杂性。应用程序必须追踪AssetBundle之间的依赖关系,并且在调用 AssetBundle.LoadAsset 之前确保加载了正确的AssetBundle。

Object的依赖关系可以通过 UnityEditor 命名空间中的 AssetDatabase API进行追踪。通过这个命名空间的名称可以看出,这个API只能用于Unity编辑器,而不能在运行时使用。AssetDatabase.GetDependencies 可以用来定位指定Object或Asset的所有直接依赖,注意,被依赖的内容也可能依赖其他内容。另外,AssetImporter 可用于指定Object被分配到了哪个AssetBundle。

结合 AssetDatabase 和 AssetImporter 可以开发一个编辑器脚本,用于确保所有AssetBundle的直接和间接依赖都被分配到了AssetBundle中,或者确保没有任意两个AssetBundle共同依赖了未被分配到AssetBundle中的对象。因为重复资产会增加内存开销,所以推荐在每个项目中都编写一个这样的脚本。

4.4.2 精灵图集(Sprite Atlas)重复

所有自动生成的精灵图集都会被分配到用于生成精灵图集的Object所在的AssetBundle中。如果这些精灵Object被分配到了不同的AssetBundle中,那么对应的精灵图集就不会被分配到AssetBundle中,这时它们可能会产生副本。如果这些精灵Object没有被分配到AssetBundle中,它们对应的精灵图集也不会被分配到AssetBundle中。

要确保精灵图集没有重复,请确保同一个精灵图集中的所有精灵都被打包到了同一个AssetBundle中。

注意,在Unity 5.2.2p3以及更早的版本中,自动生成的精灵图集从不会被分配到AssetBundle中。因此,所有包含或引用了某个精灵图集内的精灵的AssetBundle中都会包含一份这个精灵图集。强烈推荐把所有使用了Unity的Sprite Packer的项目升级到Unity 5.2.2p4、Unity 5.3或者更高版本的Unity。

4.4.3 Android纹理

因为Android生态系统碎片化十分严重,通常需要将纹理以几种不同的格式进行压缩。所有的Adnroid设备都支持ETC1,但是ETC1不支持带有Alpha通道的纹理。如果应用程序不需要OpenGL ES 2支持,那么解决这一问题的最简洁的方案是使用ETC2,所有的Android OpenGL ES 3设备都支持这种格式。

大多数应用程序都需要兼容不支持ETC 2的老旧设备,有一种解决方案是使用Unity 5中的AssetBundle Variant(其他方案请参考Unity的Android性能优化指南)。

要使用AssetBundle Variant,所有不能够使用ETC1进行规则压缩(cleanly compressed)的纹理必须隔离打包到只含有纹理的AssetBundle中。然后,使用设备厂商指定的纹理压缩格式(例如DXT5、PVRTC和ATITC)创建足够多的变体来适配那些不支持ETC2的设备。对于每个AssetBundle Variant,把它们里面的纹理的 TextureImporter 设置修改为所使用的压缩格式。[?1]

[?1]:原文:To use AssetBundle Variant, all textures that cannot be cleanly compressed using ETC1 must be isolated into texture-only AssetBundles. Next, create sufficient Variant of these AssetBundles to support the non-ETC2-capable slices of the Android ecosystem, using vendor-specific texture compression formats such as DXT5, PVRTC and ATITC. For each AssetBundle Variant, change the included textures’ TextureImporter settings to the compression format appropriate to the Variant.

在运行时,可以使用SystemInfo.SupportsTextureFormat检测某种纹理格式是否受支持。应该使用这一信息来选择和加载包含受支持的压缩纹理的AssetBundle Variant。

可以在这里查看更多有关于Android纹理压缩格式的信息。

4.4.4 iOS文件句柄过度使用

当前版本的Unity不受这一问题的影响

在Unity 5.3.2p2之前版本中,在加载AssetBundle后,Unity会一直持有它的打开文件句柄(open file handle)。在大多数平台上这并不成问题,但是iOS会把进程同时打开的文件句柄数量限制在255以内。如果加载AssetBundle导致文件句柄数超出限制,则会加载失败,并且引发 Too Many Open File Handles 错误。

如果项目中含有成百上千的AssetBundle,则很容易出现这个问题。

对于那些不能升级Unity版本的项目,临时的解决方案是:

  • 合并相关AssetBundle来减少AssetBundle数量
  • 使用 AssetBundle.Unload(false) 关闭其文件句柄,并且手动管理已加载的Object的生命周期

4.5 AssetBundle Variant

AssetBundle系统的一个关键功能是对AssetBundle Variant的引入。AssetBundle Variant的目的是让应用程序调整其内容来更好的适配运行时环境。在加载Object和处理Instance ID引用时,变体可以让来自不同AssetBundle中的不同Object表现为同一个Object。从理论上讲,这可以使两个Object共享同样的File GUID和Local ID,并且用一个字符串形式的Variant ID标识要实际加载的Object。

这个系统由两个基本用例:

  1. 针对给定的平台适当地简化AssetBundle的加载。 
    • 例如:构建系统可以为支持DirectX的Windows系统创建一个含有高分辨率纹理贴图和复杂的着色器的AssetBundle,为Android系统创建另一个含有低精度内容的AssetBundle。在运行时,项目的资源加载代码会根据平台加载合适的AssetBundle Variant,但传递给 AssetBundle.Load 的Object名称不需要改变。
  2. 使应用程序可以在同一平台的不同硬件上加载不同内容。 
    • 这是大范围适配各种移动设备的关键。在任何模拟真实世界的应用中,iPhone 4都不能和最新的iPhone显示同样高精度的画面。
    • 在碎片化十分严重的Android上,AssetBundle Variant可以用于处理对不同屏幕长宽比和像素密度的适配。

4.5.1 局限性

AssetBundle Variant系统的一个关键性限制是它需要从不同的Asset来构建变体。即使这些Asset之间唯一的区别只是它们的导入设置不同,它们也会受到这种限制。如果一个纹理被打包到变体A和变体B中,而其中仅有的区别是导入时选择的纹理压缩算法不同,那A和B也会是完全不同的变体,即A和B会是磁盘上的两个不同的文件。

这一限制增加了大型项目的管理难度,因为必须在版本控制系统中维护特定Asset的多个副本。当开发者修改了这些Asset时,它们的所有副本进行更新。这种问题没有内建的解决办法。

很多团队实现了他们自己的AssetBundle Variant格式——定义良好的后缀名并附加到文件名中,以此来标识AssetBundle的特定变体。自定义程序代码,在打包AssetBundle的时候修改它所包含的Asset的导入设置。有些开发者扩展了它们的自定义系统,这样就可以修改父子到预制体上的组件上的参数。

4.6 压缩还是不压缩?

是否对AssetBundle进行压缩需要考虑下列的几个重要的因素:

  • 加载时间:从本地存储或本地缓存中加载未压缩的AssetBundle要比加载压缩过的AssetBundle的速度快得多。
  • 构建时间:LZMA和LZ4压缩文件的速度非常慢,而且Unity会连续的处理AssetBundle。含有大量AssetBundle的项目会花费大量的时间进行压缩。
  • 应用大小:如果AssetBundle被附带在项目中,将它们压缩可以减小应用程序的体积。不过AssetBundle可以选择在安装之后再下载。
  • 内存占用:再Unity 5.3之前,所有的解压机制都需要将被压缩的AssetBundle整个加载到内存中进行解压。如果考虑内存占用量,请使用不压缩或者以LZ4压缩的AssetBundle。
  • 下载时间:如果AssetBundle很大或者用户网络带宽有限,那可能需要压缩。

4.6.1 紧密压缩(Crunch Compression)

如果AssetBundle中的主要内容是使用紧密压缩算法进行压缩的DXT压缩纹理,那么这个AssetBundle不应该再被压缩。

4.7 AssetBundle和WebGL

强烈推荐开发者不要在WebGL项目中使用压缩的AssetBundle.

在WebGL项目中所有AssetBundle的解压和加载操作都会发生在主线程中。因为目前Unity的WebGL导出选项不支持工作线程。AssetBundle的下载使用 XMLHttpRequest 委托给了浏览器,它会在Unity的主线程中执行。

如果在使用Unity 5.5或更早地版本,尽量避免使用LZMA压缩,使用按需进行压缩的LZ4来取代它。如果需要比LZ4还小的分发文件,可以配置Web服务器在HTTP协议层面对文件进行GZip压缩。在Unity 5.6中,移除了WebGL平台的LZMA压缩选项。[?2]

[?2]:原文:If you are using Unity 5.5 or older, consider avoiding LZMA for your AssetBundles and compress using LZ4 instead, which is decompressed very efficiently on-demand. If you need smaller compression sizes then LZ4 delivers, you can configure your web server to gzip-compress the files on the HTTP protocol level (on top of LZ4 compression). Unity 5.6 removes LZMA as an compression option for the WebGL platform.


如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引