正确使用AssetBundle加载和卸载

Posted 海洋_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了正确使用AssetBundle加载和卸载相关的知识,希望对你有一定的参考价值。

在使用Unity开发项目时,AssetBundle是必须使用的,对资源进行打包加载,因为移动端游戏包体的大小对用户体验非常重要,对内存的使用更加重要,下面我们就介绍一下关于AssetBundlle的使用,虽然网上关于这方面的介绍已经非常多了,自己还是总结一下:

资源卸载

当从本地加载AssetBundle文件时,可以通过缓存后者AssetBundle.LoadFromFile加载文件,它们对于内存的消耗比较少,但是,如果存在大量的AssetBundle,开销仍然成问题。因此,了解何时加载或卸载 AssetBundle 非常重要。如果 AssetBundle 未正确卸载,则可能导致内存中的对象重复。不正确地卸载 AssetBundles 在某些情况下也会导致不良行为,例如导致纹理丢失。下面通过案例解释一下:
假设我们有个模型Model,它是从AssetBundle加载的,并且该模型还在场景中,如果想卸载它,如果调用了 AssetBundle.Unload(true),则 Model 将从场景中删除,销毁并卸载。但是,如果调用了 AssetBundle.Unload(false),那么 AB 的头信息将被卸载,但是 M odel将保留在场景中并且仍然可以正常工作。调用 AssetBundle.Unload(false)会中断 M 和 AB 之间的链接,如果稍后再次加载 AB ,则 AB 中包含的对象的新副本将被加载到内存中。
如果稍后再次加载 AB ,则将重新加载 AssetBundle 标头信息的新副本,程序是不是从这个新副本加载 AB?Unity 不会在 AB 和 M 的新副本之间建立任何链接。
如果调用 AssetBundle.LoadAsset() 来重新加载 Model,Unity 将不会将 Model 的旧副本解释为 AB 中数据的实例。因此,Unity将加载 Model 的新副本,并且场景中将有两个相同的 Model副本。
因此,我们在卸载项目时应该使用AssetBundle.Unload(true)。这种方式就是彻底把对象销毁了,在使用该接口时还是要遵守一定的规则:
一:在程序运行中,在卸载AssetBundle时,可以在切换场景时候,这个大家经常使用;
二:使用单个对象的引用计数,这样就清楚知道再卸载对象时,是否被其他对象使用,合理利用内存;
有人说,如果我必须要用AssetBundle.Unload(false),该怎么操作?这个也是有办法的:
卸载的对象不被场景中任何对象引用,再调用 Resources.UnloadUnusedAssets。

管理

为了更好的管理AssetBundle,这里建议大家对打包的对象进行分组,比如:MMORPG游戏,可以按场景将各个地图和过场动画分组为 AssetBundle,但在大多数场景中都需要加载其他的一些对象,比如图片,UI以及不同的角色模型和纹理,可以将这些对象资源分组到第二组 AssetBundle 中,这些 AssetBundle 在启动时加载。
如果频繁的加载卸载同一个对象,这样可以将其隐藏,不必加载和卸载,这个还是要灵活运用。
我们在加载资源时,有两种情况:
一是,直接在程序里面;
二是,程序启动时下载;

存储

在程序里面的一般把资源放在StreamingAssets文件夹中,在运行时通过 Application.streamingAssetsPath 访问本地存储上 StreamingAssets 文件夹的完整路径。然后可以通过 AssetBundle.LoadFromFile 加载 AssetBundle 。

Unity 有一个内置的 AssetBundle 缓存系统,可以用来缓存通过 UnityWebRequest API 下载的 AssetBundles ,它有一个重载函数可以接收一个 AssetBundle 版本号作为参数,此数字不存储在 AssetBundle 内,并且不由 AssetBundle 系统生成。

缓存系统跟踪传递给 UnityWebRequest 的最新版本号,当使用版本号调用此 API 时,缓存系统会通过比较版本号来检查是否存在缓存的 AssetBundle,如果这些数字匹配,系统将加载缓存的 AssetBundle;如果数字不匹配,或者没有缓存的 AssetBundle,则 Unity 将下载新副本,此新副本将与新版本号相关联。
缓存系统中的 AssetBundle 仅由其文件名标识,只要文件名相同,缓存系统就会将它们识别为相同的 AssetBundle。

下载

我们从远程下载AssetBundle,对于大多数应用程序,HTTP 是下载 AssetBundles 的最简单方法。但是,这里不提倡使用HTTP,它是由缺陷的,这里就不讨论了。可以使用C#的WebClient,如果应用程序不需要 HTTPS / SSL 支持,C#的 WebClient 类提供了最简单的下载 AssetBundle 的机制,它能够将任何文件异步下载到本地存储,而无需过多的托管内存分配。
要使用 WebClient 下载AssetBundle,将要下载的 AssetBundle 的 URL 和目标路径传递给它,如果需要对请求的参数进行更多控制,则可以使用C#的 HttpWebRequest 类编写下载程序,步骤如下:
一:从 HttpWebResponse.GetResponseStream 获取字节流。
二:在堆栈上分配固定大小的字节缓冲区。
三:读入缓冲区。
四:使用 C# 的 File.IO API 或任何其他流 IO 系统将缓冲区写入磁盘。

下载资源的存储,在所有平台上,Application.persistentDataPath 都指向一个可写位置,该位置用于存储在应用程序运行期间保存的数据,编写自定义下载程序时,强烈建议使用 Application.persistentDataPath 的子目录来存储下载的数据。
由于 AssetBundles 由其文件名标识,因此可以使用应用程序附带的 AssetBundle “填充”缓存,可以通过在第一次运行应用程序时从 Application.streamingAssetsPath 加载 AssetBundles 来填充缓存。

问题及解决方案

问题描述:
如果将两个不同的对象分配给两个不同的 AssetBundle,但两者都引用了公共依赖关系对象,则该依赖关系对象将被复制到两个 AssetBundles 中。重复的依赖项也将被实例化,这意味着依赖项 Object 的两个副本将被视为具有不同标识符的不同对象。这将增加应用程序的 AssetBundles 的总大小。如果应用程序加载其父项,则还会导致将两个不同的 Object 副本加载到内存中。

解决方案:
第一:确保内置于不同 AssetBundle 的对象不共享依赖项。任何共享依赖项的对象都可以放在同一个 AssetBundle 中,而不会复制它们的依赖项。
缺陷:对于具有许多共享依赖项的项目,此方法通常不可行。它生成单个 AssetBundle,必须经常重建和重新下载才可以。
第二:对 AssetBundles 进行分组,以便不会同时加载共享依赖关系的两个 AssetBundle。
缺陷:此方法可能适用于某些类型的项目,例如练级的游戏,但是,它仍然不必要地增加了项目 AssetBundles 的大小,并增加了构建时间和加载时间。
第三:确保所有依赖项资产都内置在自己的 AssetBundle 中。
缺陷:这完全消除了重复加载的风险,但也带来了复杂性,程序必须跟踪 AssetBundle 之间的依赖关系,并确保在调用任何 AssetBundle.LoadAsset API 之前加载了正确的 AssetBundle 。

问题:
在使用ios时,IOS 限制进程可能同时打开 255 的文件句柄数。如果加载 AssetBundle 导致超出此限制,则加载调用将失败,并显示 “Too Many Open File Handles” 错误。
解决方案:
一:通过合并相关的 AssetBundle 减少正在使用的 AssetBundle 数量;
二:使用 AssetBundle.Unload(false) 关闭 AssetBundle 的文件句柄,并手动管理加载的对象的生命周期;
问题:
我们经常会压缩AssetBundle资源文件,在压缩时要考虑如下几个问题:
解决方案:
第一:加载时间:从本地存储或本地缓存加载时,未压缩的 AssetBundle 加载速度比压缩的 AssetBundle 快得多。
第二:构建时间:压缩文件时 LZMA 和 LZ4 非常慢,Unity Editor 按顺序处理 AssetBundles,具有大量 AssetBundle 的项目将花费大量时间来压缩它们。
第三:应用程序大小:如果 AssetBundle 在应用程序中提供,压缩它们将减少应用程序的总大小。或者,可以在安装后下AssetBundle。
第四:内存使用:所有 Unity 的解压缩机制都需要在解压缩之前将整个压缩的 AssetBundle 加载到内存中,如果内存使用很重要,请使用未压缩或 LZ4 压缩的 AssetBundle。
第五:下载时间:只有在 AssetBundle 较大或者用户处于带宽受限的环境中(例如在低速或计量连接上下载)时才可能需要压缩,如果在高速连接上只向 PC 传送几十兆字节的数据,则可以省略压缩。

AssetBundle Variants

AssetBundle 系统的一个关键特性是引入 AssetBundle Variants,Variants 的目的是允许应用程序调整其内容以更好地适应其运行时环境。变量允许不同 AssetBundle 文件中的不同 UnityEngine.Objects 在加载对象和解析实例 ID 引用时显示为“相同”对象。从概念上讲,它允许两个 UnityEngine.Objects 共享相同的文件 GUID 和本地 ID,并通过字符串 Variant ID 标识要加载的实际 UnityEngine.Object。主要应用如下所示:
第一,Variant 简化了适用于给定平台的 AssetBundle 的加载。
比如:构建系统可能会创建一个 AssetBundle,其中包含适用于独立 DirectX11 Windows 构建的高分辨率纹理和复杂着色器,以及另一个具有适用于 android 的低保真内容的 AssetBundle。在运行时,项目的资源加载代码可以为其平台加载适当的 AssetBundle Variant,而传递给 AssetBundle.Load API 的 Object 名称不需要更改。
第二:Variant 允许应用程序在同一平台上加载不同的内容,但使用不同的硬件。
这是支持各种移动设备的关键,在 Android 上,AssetBundle Variants 可用于解决设备之间屏幕宽高比。
缺陷
AssetBundle Variant 系统的一个关键限制是它要求 Variants 从不同的 Assets 构建。即使这些资源之间的唯一差异是其导入设置,此限制也适用。如果构建资源A 和资源B 中的纹理之间的唯一区别是在 Unity 纹理导入器中选择的特定纹理压缩算法,则资源A 和资源B 必须仍然是完全不同的资源。这意味着 Variant A 和 Variant B 必须是磁盘上的单独文件。
解决方案:
只能实施自己的 AssetBundle Variants ,通过构建 AssetBundles 来完成的,并在其文件名后附加明确定义的后缀,以便识别给定 AssetBundle 所代表的特定 Variants。

附上ETC原理

总结
作为学习笔记,希望对大家有所帮助,尤其在优化时,可以全面的帮助您解决AssetBundle的问题。

以上是关于正确使用AssetBundle加载和卸载的主要内容,如果未能解决你的问题,请参考以下文章

Unity5 AssetBundle系列——资源加载卸载以及AssetBundleManifest的使用

AssetBundle八:AssetBundle的获取加载方式

AssetBundle八:AssetBundle的获取加载方式

AssetBundle管理机制(上)

AssetBundle理论篇

Unity3D 5.3 新版AssetBundle使用方案及策略