本文主要是介绍Unity中的资源管理-AssetBundle(2),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
本文分享Unity中的资源管理-AssetBundle(2)
在上一篇文章中, 我们介绍了Ab
的基础知识, 并了解到如何构建Ab
, 最后还给出了几种不同的Ab
管理方案.
今天我们接着聊聊Ab
的加载和卸载.
Ab
的使用总的来说比较简单, 前提是我们不操心加载好的Ab
和从Ab
中加载出来的资材, 但整个资源管理的难点就在于此.
目前我们只是谈论Ab
最核心的使用而不涉及到基于此的各种资源管理方案. 这部分会在后面的文章给出.
好了, 下面开始今天的内容.
Ab的加载和使用
基本的加载接口
Unity提供了Ab
的同步和异步加载方式. 两者都是十分简单的. 总体遵循: 先加载Ab
, 然后从Ab
中加载资材, 清理资材, 卸载Ab
的流程.
这里以上篇文章打包出来的Ab
为例:
同步加载如下:
var ab = AssetBundle.LoadFromFile("Assets/Output/AssetBundle/AllAb/prefab");
var obj = ab.LoadAsset<GameObject>("obj.prefab");
Instantiate(obj);
异步加载如下:
void Start() {StartCoroutine(LoadAsync());
}IEnumerator LoadAsync() {var path = "Assets/Output/AssetBundle/AllAb/prefabs";var request = AssetBundle.LoadFromFileAsync(path);yield return request;var obj = request.assetBundle.LoadAsset<GameObject>("obj.prefab");Instantiate(obj);
}
上面两种方式都是从本地加载, Unity还提供了从网络加载, 准确的说是从URL加载, 而URL是包括本地的.
从URL加载Ab
URL是统一资源定位符的意思, 支持下面各种协议:
-
http://
-
https://
-
file://(访问本地文件)
-
ftp://(只支持匿名账号)
从Unity5.3以上, WWW相关接口被弃用, 而使用新的UnityWebRequest相关接口, 我们这里就不再介绍WWW的方式了.
从URL加载Ab
比起从本地加载略微复杂一点点. 所有相关接口基本都是异步的, 你也可以是当做同步使用, 自己定制.
首先介绍的是从URL加载资源, 包括所有的资源(这里依然从本地加载, 如果是网络, 只需要切换协议即可):
IEnumerator LoadWebRequest() {var request = UnityWebRequest.Get("file://C:/resourceManage/Assets/Output/AssetBundle/AllAb/prefabs");yield return request.SendWebRequest();if (request.isNetworkError || request.isHttpError) {Debug.LogError("网络错误!" + request.error);yield break;}var data = request.downloadHandler.data;var ab = AssetBundle.LoadFromMemory(data);var obj = ab.LoadAsset<GameObject>("obj.prefab");Instantiate(obj);
}
这种方式加载出来的是bytes
, 还需要各种资源提供的接口来构造资源, 比如AssetBundle.LoadFromMemory
.
基于第一种方式, 我们可以替换downloadHandler
, 告知下载的资源类型, 后续即可直接使用, 如下:
IEnumerator LoadWebRequest2() {var request = UnityWebRequest.Get("file://C:/resourceManage/Assets/Output/AssetBundle/AllAb/prefabs");// var handler = new DownloadHandlerAssetBundle(request.url, 2958429916);var handler = new DownloadHandlerAssetBundle(request.url, 0);request.downloadHandler = handler;yield return request.SendWebRequest();if (request.isNetworkError || request.isHttpError) {Debug.LogError("网络错误!" + request.error);yield break;}var ab = handler.assetBundle;var obj = ab.LoadAsset<GameObject>("obj.prefab");Instantiate(obj);
}
这里提供DownloadHandlerAssetBundle
作为downloadHandler
, 第二个参数为资源的crc
, 这可以让Unity为我们做资源效验, 如果传入0则不效验.
DownloadHandlerAssetBundle
告知要下载的资源为Assetbundle
, 还可以提供:
DownloadHandlerTexture
: 纹理DownloadHandlerAudioClip
: 声音DownloadHandlerBuffer
: 缓存DownloadHandlerFile
:文件DownloadHandlerScript
:自定义
downloadHandler
是一种Helper
类, 会附加到UnityWebRequest
对象上, 在接收到数据之后做一些跟类型紧密相关的操作, 以供后续方便使用.
最后, 针对AssetBundle
, Unity还提供了更加便利的封装接口UnityWebRequestAssetBundle
:
IEnumerator LoadWebRequest3() {var request = UnityWebRequestAssetBundle.GetAssetBundle("file://C:/resourceManage/Assets/Output/AssetBundle/AllAb/prefabs", 0);yield return request.SendWebRequest();if (request.isNetworkError || request.isHttpError) {Debug.LogError("网络错误!" + request.error);yield break;}var ab = DownloadHandlerAssetBundle.GetContent(request);var obj = ab.LoadAsset<GameObject>("obj.prefab");Instantiate(obj);
}
使用GetAssetBundle
接口请求, 使用DownloadHandlerAssetBundle
handler进行处理.
压缩方式决定使用方式
根据Ab
的压缩方式的不同, 我们对于Ab
有不同的使用方式.
上一篇文章介绍过, Unity提供了LZMA和LZ4两种压缩方式.
其中LZMA是基于流的压缩方式, 也就是是数据整体按照顺序一一排列, 输出为数据流之后再进行压缩的方式, 而这样压缩的效率比较高, 包体会比较小.
因为是按照顺序排列, 所以在使用时也有顺序要求, 也就是说, 我们在使用这种压缩方式出来的包时, 需要的流程为:
整体加载Ab
包, 整体解压所有资材, 然后从内存中取用资材, 此时整个Ab
中包含的资材都在内存中, 而不管我们使用不使用, 或者什么时候使用.
这就会造成首次加载和解压Ab
时比较耗时, 且内存占用也比较高. 当然, 好处也是显而易见的, 所有的资材都已经准备好了, 我们在使用的时候不需要进行IO操作, 会有比较流畅的体验.
为了解决内存占用过高的问题, Unity会在Ab
初次解压之后, 使用LZ4
重新压缩并存储在磁盘上, 下次加载加使用LZ4
方式解压使用.
一般在需要整体加载的资材使用这种压缩方式, 比如一整个模型, 不会使用单独的资材, 只要使用必然是整个一起使用.
而LZ4是基于块的压缩方式, 就是将资材一块一块的压缩后组合起来, 没有顺序要求, 压缩比也不高, 但是在使用的时候会带来巨大的好处:
可以只加载在Ab
头, 在实际使用的时候再解压具体的资材, 资材不用的时候也可以卸载, 也就是说加载资源超级快, 内存占用也比较小. 当然, 因为是即用即解压, 在首次解压某个资材的时候, 会有轻微的卡顿, 具体视资材大小而定.
总体上来说, 两种压缩方式各有千秋, 在Unity的发展历程中, 主要面向中小型项目(当然, 大型项目支持也是很好的), 所以默认的压缩方式是LZMA, 中小型项目资源不是很多, 可以在一开始就走一次Loading, 加载好所有的资源, 在使用过程中得到比较好的体验.
使用LZ4的加载速度与不压缩时区别不大, 额外的优势是减小了磁盘占用大小.
内存占用和卸载
Unity将Ab
相关的内容划分为三个部分:
Asset
: 资材AssetBundle
: 资材包Object
: 实例化对象(针对某些类型的资材)
总体的流程为: 使用各种方式加载Ab
, 然后从Ab
中加载资材, 如果资材支持实例化则实例化之后使用, 如果不支持则直接使用.
加载资材后, 有些资材可以被引用着使用(内存中只存在一份), 比如纹理, 材质, 声音等. 而有些资材需要复制后使用(内存中存在多份), 比如预制, 模型等.
所以资材的使用可以分为引用和实例化, 整个内存分为: Ab
所占内存, 资材所占内存, 实例化对象所占内存.
知道了以上的知识, 我们同时就知道如何卸载了, 反过来即可, 先摧毁实例化对象, 然后释放资材, 最后释放Ab
. 对应的接口分别为:
Destroy
: 摧毁实例化对象Resources.UnloadAsset
: 卸载非实例化资材, 预制, 模型, 游戏对象等需要实例化的资材不能使用Resources.UnloadUnusedAssets
: 卸载所有不使用的资材- 不使用指不存在强引用, 比如变量持有, 对象引用等
- 不需要实例化的资材, 只需要无引用即可
- 需要实例化的资材, 还需要先销毁其实例化的对象
AssetBundle.Unload(true or false)
: 卸载Ab
, 参数代表是否同时卸载所有的资材和其实例化出来的对象, 如果为true且还存在资材或者对象引用, 则会出现资材丢失, 即粉色现象.- 如果是通过URL相关接口加载
Ab
, 则可能会在内存中存在一份资源的原始数据, 根据不同的接口会有不同的表现.
下面给出网上比较常见的两张图, 基本上就是上面介绍的内容:
图片中已经表达的很清楚了, 这里简单的总结几句:
- 使用URL相关接口(包括WWW, UnityWebRequest), 有些会先在内存中构造一份资源数据, 然后再从这个数据中构造
Ab
, 有些则不会 - 加载
Ab
后, 会在内存中存在一份Ab
的数据, 根据压缩方式的不同, 这份数据大小不一
总结
Ab
的加载本身十分简单, 困难的点在于加载和使用后其内存分布, 并且如何在适当的实际进行卸载.
大家可能有些察觉, 从今天的内容开始慢慢开始复杂起来了. 不过不用担心, 我们先将理论做简单的介绍, 后续会更多的使用实例来论证这些内容, 两相印证后可能会有比较好的理解效果.
可能有些同学对内存部分还是有些云里雾里, 不用担心, 我们会在下一篇文章对Ab
的内存占用进行分析, 同时顺便介绍内存分析工具和方法, 感兴趣的同学可以持续关注.
好了, 今天的内容就是这些, 希望对大家有所帮助.
这篇关于Unity中的资源管理-AssetBundle(2)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!