Unity中关于AssetBundle和Resources的教程
教程地址:https://learn.unity.com/tutorial/assets-resources-and-assetbundles#5c7f8528edbc2a002053b5a9
AssetBundle和Resources的内容主要包括4个方面:
- Assets(资产),Objects(对象)和serialization(序列化),主要包含Unity如何序列化Assets并处理Assets间的相互引用;
- Resources目录,内置的Resources的API;
- AssetBundle基础,如何操作AssetBundle,如何加载AssetBundle以及从AssetBundle中加载Assets;
- AssetBundle的使用模式,分配Assets到AssetBundle,如何管理加载的Assets。
- Assets(资产),Objects(对象)和serialization(序列化)
适当的Asset管理对快速加载和内存的少量使用非常重要,首先要先区分Assets和Objects
-
Assets和Objects内部
弄懂Unity标识和序列化数据的首要关键点是区分Assets和UnityEngine.Objects- Asset是硬盘中的文件,存储在Unity工程中的Assets目录下。比如Textures(纹理),3D模型,或者声音片段等。
- UnityEngine.Object是一组序列化数据,共同描述特定的资源实例。如mesh(网格),sprite等。
还有两种特殊的Object类型:ScriptableObject和MonoBehaviour
-
对象间引用
所有UnityEngine.Objects都可以引用其他UnityEngine.Objects。 这些其他对象可以驻留在同一资产文件中,也可以从其他资产文件中导入。
例如,一个材质对象通常具有一个或多个对纹理对象的引用。Unity使用File GUID和Local ID来辅助序列化,File GUID标识存储目标资源的资产文件,本地唯一的Local ID标识
资产文件中的每个对象。其中File GUID存储在.meta文件中。 -
为什么需要File GUID和Local ID
为了健壮性并提供灵活、平台无关的工作流程.File GUID提供了文件特定位置的抽象。 只要可以将特定的文件GUID与特定的
文件相关联,该文件在磁盘上的位置就变得无关紧要。 可以自由移动文件,而不必更新引用该文件的所有对象。如果与Asset文件管理的File GUID丢失了,那么资产文件中所有对象的引用也将丢失。 这就是为什么.meta文件必须保持与其关联
的Asset文件使用相同的文件名和相同文件夹存储的原因。Unity编辑器具有特定文件路径到已知文件GUID的映射。 每当加载或导入资产时,都会记录地图条目。
映射条目将资产的特定路径链接到资产的文件GUID。 -
复杂Asset和导入
Unity可以支持导入非本地资源,如FBX等,这些可以通过AssetImporter的API来处理。导入过程会将源Asset转换
为适合目标平台的格式。此过程适用于所有资产,而不仅仅是非本地资产。本地资产不需要冗长的转换过程或重新序列化。 -
序列化和实例
尽管File GUID和Local ID是健壮的,但GUID比较缓慢,并且在运行时需要性能更高的系统。 Unity在内部维护一个高速缓存,
该高速缓存将File GUID和Local ID转换为简单的,会话唯一的整数。这些整数称为Instance ID,并以简单的,
单调递增的顺序分配在缓存中注册新对象时。高速缓存维护给定实例ID,File GUID和Local ID之间的映射,这些映射定义了对象的源数据的位置以及对象在内存中的实例
(如果有)。这使UnityEngine.Objects能够可靠地维护彼此之间的引用。解析实例ID引用可以快速返回由实例ID表示的已加载对象。
如果尚未加载目标对象,则可以将File GUID和Local ID解析为对象的源数据,从而允许Unity即时加载对象。 -
加载大层次结构
当序列化Unity GameObjects的层次结构时,例如在预制序列化过程中,重要的是要记住整个层次结构将被完全序列化。
也就是说,层次结构中的每个GameObject和Component将在序列化数据中单独表示。这对加载和实例化GameObjects层次结构所需的
时间产生了有趣的影响。
创建任何GameObject层次结构时,CPU时间会以几种不同的方式花费:
a. 读取源数据(从存储,从AssetBundle,从另一个GameObject等)
b. 在新的变换之间设置父子关系
c. 实例化新的GameObjects和组件
d. 在主线程上唤醒新的GameObjects和Components从内存中读取数据要比从存储设备加载数据快的多,从存储设备加载数据的I/O操作就很费时。所以预制件的大小就需要把握好,因为
整个对象都被序列化,加载速度就很受影响了。所以一定要使用多层次结构的prefab,可以将其分块,然后运行时缝合起来。
- 资源目录
-
最佳实践
最好是不使用,因为:
a. 使用Resources文件夹会使更细粒度的内存管理更加困难
b. 资源文件夹使用不当会增加应用程序的启动时间和构建时间,随着“资源”文件夹的数量增加,在这些文件夹中管理资产变得非常困难
c. 资源系统降低了项目向特定平台交付自定义内容的能力,并消除了增量内容升级的可能性,AssetBundle Variants是Unity的主要工具,用于按设备调整内容 -
适当的使用
某些情况下,还是需要使用Resource的,比如需要三方配置数据等,不需要patch的内容等 -
Resource的序列化
项目构建时,Resources中的Assets和Objects会合并到一个序列化文件中,包含元数据和索引信息,类似于AssetBundle。这样,在内部就
需要构建查找结构,内部使用平衡搜索树,在splash播放的时候发生,这就需要消耗了。
- AssetBundle基础
-
布局
AssetBundle包含两个部分:头部和数据段。 -
加载AssetBundle
通过提供的API加载:
AssetBundle.LoadFromMemory(Async optional) - Unity不推荐该方式,因为需要使用3倍的字节数据的内存
AssetBundle.LoadFromFile(Async optional) - 最高效的加载没压缩的数据。
UnityWebRequest's DownloadHandlerAssetBundle
WWW.LoadFromCacheOrDownload (on Unity 5.6 or older) -
从AssetBundle加载Assets
LoadAsset (LoadAssetAsync)
LoadAllAssets (LoadAllAssetsAsync)
LoadAssetWithSubAssets (LoadAssetWithSubAssetsAsync) -
AssetBundle的依赖
Unity根据运行时环境使用两个不同的API来自动跟踪AssetBundle间的依赖。Editor下使用AssetDatabase的API查询依赖。
AssetImporter的API可以访问和修改AssetBundle的分配和依赖。在运行时,Unity提供了一个可选API,
以通过基于ScriptableObject的AssetBundleManifest API加载在AssetBundle构建期间生成的依赖项信息。当执行BuilePipeline.BuildAssetBundles的时候,Unity会序列化包含依赖信息的对象,然后单独存储,依据
AssetBundleManifest类型。文件名与文件夹名相同。
- AssetBundle使用模式
-
管理加载的Asset
对于内存敏感的环境下,小心控制加载对象的大小和数量就非常重要了。Unity并不会在对象被移除的时候自动卸载它们。
Asset的清理在特定时间触发,也可以手动触发。AssetBundle卸载如果不正确,会造成在对象在内存中的重复。AssetBundle的卸载通过AssetBundle.Unload(ture/false),当参数为true时会强制卸载所有,即便还有引用。参数为false时,
只是将AssetBundle与Object(对象)的引用链接断开,下次加载时,不会重新建立链接,这样就造成了内存中的重复对象。 -
分发
将项目的AssetBundle分发给客户端有两种基本方法:与项目同时安装它们或在安装后下载它们。