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接口请求, 使用DownloadHandlerAssetBundlehandler进行处理.

压缩方式决定使用方式

根据Ab的压缩方式的不同, 我们对于Ab有不同的使用方式.

上一篇文章介绍过, Unity提供了LZMALZ4两种压缩方式.

其中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的内存占用进行分析, 同时顺便介绍内存分析工具和方法, 感兴趣的同学可以持续关注.

好了, 今天的内容就是这些, 希望对大家有所帮助.

猜你喜欢

转载自blog.csdn.net/woodengm/article/details/122177384