资源管理之Resources和AssetBundle

参考文章:
https://unity3d.com/learn/tutorials/topics/best-practices/resources-folder
https://unity3d.com/learn/tutorials/topics/best-practices/assetbundle-fundamentals
https://unity3d.com/cn/learn/tutorials/topics/best-practices/assetbundle-usage-patterns?playlist=30089

一、Resources

通过使用Resources系统可以将Asset资源放在一个或多个名为 Resources 的文件夹中,并且在运行时利用Resources的相关API来加载或卸载Asset中的Object。

但是Unity官方强烈建议不要使用Resrouces:

  • Use of the Resources folder makes fine-grained memory management more difficult.
  • Improper use of Resources folders will increase application startup time and the length of builds.
    • As the number of Resources folders increases, management of the Assets within those folders becomes very difficult.
  • The Resources system degrades a project’s ability to deliver custom content to specific platforms and eliminates the possibility of incremental content upgrades.
    • AssetBundle Variants are Unity’s primary tool for adjusting content on a per-device basis.

官方给出两种可以使用Resources的情况:

  • Resources is an excellent system for during rapid prototyping and experimentation because it is simple and easy to use. However, when a project moves into full production, it is strongly recommended to eliminate uses of the Resources folder.
  • The Resources folder is also useful in trivial cases, when all of the following conditions are met:
    • The content stored in the Resources folder is not memory-intense
    • The content is generally required throughout a project’s lifetime
    • The content rarely requires patching
    • The content does not vary across platforms or devices.

所有放于Resources文件夹下的Assets和Objects资源在项目build后会被合并成一个文件。该文件包含有元数据及用于搜索用的索引信息。为了方便索引,这些索引信息是用平衡二叉树来存储的,其时间复杂度为O(nlog(n))。而要构造平衡二叉查找树需要花费的时间随着资源量的加大并不是简单的线性增长。因此如果Resources下的文件太多的话,所需要花费的build时间也会长很多。

二、AssetBundle

The AssetBundle system provides a method for storing one or more files in an archival format that Unity can index. The purpose of the system is to provide a data delivery method compatible with Unity’s serialization system. AssetBundles are Unity’s primary tool for the delivery and updating of non-code content after installation. This permits developers to reduce shipped asset size, minimize runtime memory pressure, and selectively load content that is optimized for the end-user’s device.

1. AssetBundle结构

AssetBundle由头文件和数据文件两部分构成

头文件包含有AssetBundle标识、压缩方式、及manifest文件。manifest文件是一张配置文件,它的存储结构是平衡二叉树。它用于记录该AssetBundle中各个Objects及Assets的信息及依赖关系。由于所使用的存储结构是二叉树,因此随着AssetBunlde中Asset数量的增长,构建配置文件所需的时间是大于线性增长的。

数据段中含有由序列化AssetBundle中的Asset而生成的原始数据。
5.3版本前数据文件使用LZMA方式压缩,同时AssetBundle中的Objects或Assets并不是单独压缩的,也就是说如果要读取一个或多个Objects的话,Unity需要对整个AssetBundle进行解压。
5.3版本后Unity提供了LZ4压缩方式,它允许AssetBundle中的Objects分开压缩。这样在读取某个Object时,Unity就不需要对整个AssetBundle进行解压了。

2. AssetBundle加载

AssetBundle可以通过不同的API来加载:

  • AssetBundle.LoadFromMemory(Async)
  • AssetBundle.LoadFromFile(Async)
  • WWW.LoadFromCacheOrDownload (Unity 5.6以及更高版本)
  • UnityWebRequest的DownloadHandlerAssetBundle
1)AssetBundle.LoadFromMemory(Async)

官方推荐不要使用该API接口,该接口需要AssetBundle一次性都加载到内存中。
AssetBundle.LoadFromMemory(Async) loads an AssetBundle from a managed-code byte array (byte[] in C#). It will always copy the source data from the managed-code byte array into a newly-allocated, contiguous block of native memory. If the AssetBundle is LZMA compressed, it will decompress the AssetBundle while copying. Uncompressed and LZ4-compressed AssetBundles will be copied verbatim.
The peak amount of memory consumed by this API will be at least twice the size of the AssetBundle: one copy in native memory created by the API, and one copy in the managed byte array passed to the API. Assets loaded from an AssetBundle created via this API will therefore be duplicated three times in memory: once in the managed-code byte array, once in the native-memory copy of the AssetBundle and a third time in GPU or system memory for the asset itself.

2)AssetBundle.LoadFromFile(Async)

是一个高效的API接口,用于从本地读取AssetBundle。同时该接口在移动设备和Editor模式下的处理方式不一样:
Mobile devices: The API will only load the AssetBundle’s header, and will leave the remaining data on disk. The AssetBundle’s Objects will be loaded on-demand as loading methods (e.g. AssetBundle.Load) are called or as their InstanceIDs are dereferenced. No excess memory will be consumed in this scenario.
Unity Editor: The API will load the entire AssetBundle into memory, as if the bytes were read off disk and AssetBundle.LoadFromMemory(Async) was used. This API can cause memory spikes to appear during AssetBundle loading if the project is profiled in the Unity Editor. This should not affect performance on-device and these spikes should be re-tested on-device before taking remedial action.
注意:在Android上,Unity5.3及之前的版本是不能够直接使用该接口读取AssetBundle文件的。因为AssetBundle的资源路径是指向一个jar的压缩文件。
Unity 5.3.3之前,这个API名为AssetBundle.CreateFromFile,它的功能没有变化。

3)WWW.LoadFromCacheOrDownload

WWW.LoadFromCacheOrDownload is a useful API for loading Objects both from remote servers and from local storage. Files can be loaded from local storage via a file:// URL. If the AssetBundle is present in the Unity cache, this API will behave exactly like AssetBundle.LoadFromFile.
If an AssetBundle has not yet been cached, then WWW.LoadFromCacheOrDownload will read the AssetBundle from its source. If the AssetBundle is compressed, it will be decompressed using a worker thread and written into the cache. Otherwise, it will be written directly into the cache via the worker thread.
Once the AssetBundle is cached, WWW.LoadFromCacheOrDownload will load header information from the cached, decompressed AssetBundle. The API will then behave identically to an AssetBundle loaded with AssetBundle.LoadFromFile.
每次调用WWW.LoadFromCacheOrDownload都会在后台生成一个工作线程用于将资源下载到本地的缓存目录下,为了保证在内存受限的设备中能较好地运行,建议AssetBundle的文件要尽可能地小一点,尽量不要同时下载多个AssetBudnle文件。

提示:从Unity 2017.1开始,WWW.LoadFromCacheOrDownload被封装到了UnityWebRequest中。因此,在使用Unity 2017.1或者更高版本进行开发时,应该使用UnityWebRequest。WWW.LoadFromCacheOrDownload会在将来的发行版中被废弃。

4)AssetBundleDownloadHandler

在5.3及之后的版本,Unity提供了一个更为方便的接口来代替WWW接口,它就是UnityWebRequest。
开发者可以使用UnityWebRequest API明确指定Unity如何处理下载的数据,并且可以免除不必要的内存占用。使用UnityWebRequest下载AssetBundle的最简单的方法是调用UnityWebRequest.GetAssetBundle方法。

DownloadHandlerAssetBundle在需要从远端下载资源的时候,它的表现跟WWW.LoadFromCacheOrDownload是一样的,有一点区别就是DownloadHandlerAssetBundle会自建一个工作线程池,以确保在同一时间内开发者不会调用过多的下载任务。如果资源是从本地直接加载的话,DownloadHandlerAssetBundle的表现会像AssetBundle.LoadFromFile一样,从本地直接读取数据。

使用LZMA压缩的AssetBundle在下载时会被解压并使用LZ4压缩进行缓存。这一行为可以通过设置Caching.CompressionEnabled来修改。

在Unity 5.6之前,UnityWebRequest系统使用一个固定的工作线程池和内部任务系统来防止重复、并发下载,线程池的大小不可配置。在Unity 5.6中,该保护措施被移除了,目的是适应现代硬件以及更快的访问HTTP响应代码和数据头。

5)建议

如果资源是从本地读取的话,尽可能使用AssetBundle.LoadFromFile接口。
如果资源需从远端下载,Unity 5.3及更新版本使用UnityWebRequest、Unity 5.2及更早的版本中使用 WWW.LoadFromCacheOrDownload。
无论使用UnityWebRequest还是WWW.LoadFromCacheOrDownload,需要确保下载完AssetBundle后调用Dispose方法,或者将下载操作写于C#的using语块内。

3. 从AssetBundle加载Asset资源

AssetBundle提供三个接口来读取Objects资源:

  • LoadAsset(Async):读取Object资源
  • LoadAllAssets(Async): 读取所有Object资源
  • LoadAssetWithSubAssets(Async): 读取Object资源及它所带的子资源,比如有一张图片,它被分割成一个有N张的Sprite的图集。如果要读取这个图集,就需要使用该接口。

Object的加载并不是在主线程中执行,而是在工作线程中进行读取。Unity系统中任何非线程敏感的资源数据或对象都是在工作线程中处理。例如,由网格创建VBO、解压纹理等。

从Unity5.3起,Object开始并行加载,在多个工作线程中对多个Object进行反序列化、处理和继承。当一个Object完成加载后,会调用它的 Awake 回调,并且在下一帧中对Unity引擎可用。

同步的 AssetBundle.Load 方法会暂停主线程,直到Object加载完成。它也会对Object加载进行时间切片,这样Object集成占用的每帧时间就不会超过指定的毫秒数。这一毫秒数可以通过Application.backgroundLoadingPriority属性设置:

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

4. AssetBundle依赖

AssetBundle中资源跟资源间存在依赖关系,一个Object可能跟相同或不同的AssetBundle中的一个或多个Object产生依赖关系。而Unity在加载Object时,是不会主动加载该Object的依赖资源。所以,在项目中,我们加载一个AssetBundle时,需要判断该AssetBundle是否跟其它AssetBundle有依赖关系,如果有的话需要在加载该AssetBundle之前加载其它所依赖的一个或多个AssetBundle。

当通过使用BuildPipeline.BuildAssetBundles接口导出AssetBundle后,Unity除了生成元数据文件外,还会为每个AssetBundle生成一个manifest文件,同时还会有一个跟根目录名称一样的manifest文件(假设所有的AssetBundle都导出到一个叫MyAssets的文件夹下的话,则该文件夹内会有一个叫MyAssets.minifest文件)。加载该文件,通过AssetBundleManifest类型,可以获取所有AssetBundle的依赖信息。

下面两接口可以读取依赖的AssetBundle的名称:

  • AssetBundleManifest.GetAllDependencies returns all of an AssetBundle’s dependencies, including the dependencies of the AssetBundle’s direct children, its children’s children, etc.
  • AssetBundleManifest.GetDirectDependencies returns only an AssetBundle’s direct children.

5. AssetBundle资源的管理

It is critical to carefully control the size and number of loaded Objects in memory-sensitive environments. Unity does not automatically unload Objects when they are removed from the active scene. Asset cleanup is triggered at specific times, and it can also be triggered manually.
As most projects allow users to re-experience content (such as replaying a level), it’s important to know when to load or unload an AssetBundle. If an AssetBundle is unloaded improperly, it can cause Object duplication in memory. Improperly unloading AssetBundles can also result in undesirable behavior in certain circumstances, such as causing textures to go missing.

AssetBundle.Unload的使用需要格外小心:
This API unloads the header information of the AssetBundle being called. The argument indicates whether to also unload all Objects instantiated from this AssetBundle. If it is true, then all Objects originating from the AssetBundle will also be immediately unloaded - even if they are currently being used in the active scene.

例如,假设材质球M是从名为AB的AssetBundle包中加载的,周时M应用于当前场景
在这里插入图片描述
如果AB.Unload(true)被调用, M将会从当前场景被移除,然后卸载和销毁掉。如果AB.Unload(false)被调用, 名为AB的AssetBundle头文件被卸载,而M依旧活跃在当前场景。所以调用AssetBundle.Unload(false)只是断开了资源M和AB包的关联。如果AB资源包重新加载一次,AB包的所有资源将会重新被加载到内存中。
在这里插入图片描述
新的AB资源包被加载后,内存中会有一份新的AB资源的头文件数据,但是M并不是从这份新的AB资源中加载的,所以UNITY是没有办法在新的AB资源和旧的M之前建立联系的。
在这里插入图片描述
如果调用AB.LoadAsset()来加载M资源,UNITY没办法知道当前内存中的M就是现在AB资源包中的数据实例。因此,UNITY会加载一份全新的M,这样内存中就存在了两份M资源。
在这里插入图片描述
For most projects, this behavior is undesirable. Most projects should use AssetBundle.Unload(true) and adopt a method to ensure that Objects are not duplicated. Two common methods are:

  1. Having well-defined points during the application’s lifetime at which transient AssetBundles are unloaded, such as between levels or during a loading screen. This is the simpler and most common option.
  2. Maintaining reference-counts for individual Objects and unload AssetBundles only when all of their constituent Objects are unused. This permits an application to unload & reload individual Objects without duplicating memory.

If an application must use AssetBundle.Unload(false), then individual Objects can only be unloaded in two ways:

  • Eliminate all references to an unwanted Object, both in the scene and in code. After this is done, call Resources.UnloadUnusedAssets.
  • Load a scene non-additively. This will destroy all Objects in the current scene and invoke Resources.UnloadUnusedAssets automatically.
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值