ResoucesMng Model In Unity
目录
- 引言
- 问题与解决过程
- 总结
引言
读者,欢迎来到这一篇文章,你发现了吧 :) ,这一章采用了新的排版,希望你能够喜欢。
这篇文章标题叫做Resources Mng Model In Unity,即Unity中的资源管理器模块。在本文,我会介绍Unity中如何动态地加载资源,而不是像以往一样用手拖动资源。
重在尝试,放手去做吧,少年!
问题与解决过程
Q1: Unity里,如何动态加载资源?
A: 在这之前,我先告诉你一个重要文件夹,Resources文件夹,这个文件夹需要自己在项目工程中手动创建,文件夹里的内容有以下特点:
- 可以用
ResourcesAPI加载;- 打包时会压缩;
- 打包时还会加密;
- 打包后只可读,可以用
ResourcesAPI加载。简单来讲,这个文件夹的内容可以被我们用
ResourcesAPI动态加载出来供我们使用。
Q2: 有哪些常用资源文件?
A: 像GameObject、AudioClip、TextAsset和Texture等都是常用的,预制体就是GameObject类型,音频文件就是AudoClip类型,文本文件就是TextAsset类型,纹理图片就是Texture类型。
Q3: 这个API怎么用?
A: 你可以查阅文档,Unity文档页面[Unity Resources内容](Unity - Scripting API: Resources)
我主要介绍同步加载资源与异步加载资源的流程。
同步加载
步骤一般都是:
- 用
Resources.Load<T>(string path)加载资源;用泛型重载的原因是减少装箱拆箱操作。
- 用
T类型变量存储加载的资源; - 使用。
其中有点特殊的是GameObject类型资源。我们需要将其加载到场景中,因此对于GameObject类型资源,应该:
- 用
Resources.Load<T>(string path)加载资源; - 用
T类型变量存储加载的资源; - 使用
Instantiate()实例化GameObject类型资源。
1 | //生命周期函数,在开始前一帧执行 |
注意:路径要求使用正斜杠
/,路径名中不要包含文件的后缀,比如.prefab,.txt,可以直接填资源的名字,Unity会在子文件夹找资源。如果在Resources文件夹里有两个子文件夹,而且两个子文件夹中有同名资源,那么请保证路径名完整,不然你不知道加载的是哪个文件。
异步加载
异步加载有两种方式,直接使用API和利用协程间接异步加载。
直接使用API
上图中,API返回值类型是什么?——ResourceRequest。在编辑器中按F12查看定义,
asset属性这个属性就是我们拿资源的地方。ResourceRequest可以看作一个回应,回应Resources.LoadAsync(),在这个回应里就有我们要的资源。但是这个资源是Object类型的,因此我们需要将其转换为我们需要的类型才能够使用。
ResourceRequest继承的类里面有一个completed事件,这个事件其实就是资源加载完后执行的事件。
还有一个isDone属性,表示加载过程有没有结束。progress属性,表示的是当前的加载进度。
代码展示如何使用这种方式进行异步加载资源(非泛型版本):
1 | //生命周期函数,在开始前一帧执行 |
代码展示如何使用这种方式进行异步加载资源(泛型版本):
1 | //生命周期函数,在开始前一帧执行 |
插入:Q4: 为啥要用异步加载?
A:同步加载都会在一帧里执行加载逻辑,然后当前帧就能获得资源,但是如果要加载的资源特别大呢?这时候资源要加载的时间就变长了,如果你是帧数很高,说明每一帧的时间间隔很短,这样一来这一帧做不完的事情就要用到很多帧,就会造成卡顿。因此使用异步加载的方式,缓解卡顿,但是不知道何时能够获得资源。
Q5:怎么知道已经获得资源并可以使用它了呢?
A:读者,还记得介绍的isDone属性吗?
1 | //了解何时获得了资源 |
协程间接异步加载资源
这种方式更常用、更灵活。可以在加载资源完毕后做一些事情。我在这里默认读者你已经接触过协程相关内容。
过程:
- 首先声明一个协程函数;
- 在协程函数中异步加载资源;
- 外部开启协程。
代码如下:
1 | //协程函数 |
yield return request!Q6: 为什么能够这样使用?
A: 哈哈哈,读者,请往上看图片,你记得吗?ResourceRequest的父类是AsyncOperation,AsyncOperation的父类是YieldInstruction,没错,正是YieldInstruction类,yield return语句后接上一个继承自该类的对象,会告诉Unity协程函数什么时候继续执行代码。在这里,Unity协程函数会在request已经加载完后继续执行下面的代码。
Q7: 我怎么操作对象呢?
A: 听说过回调函数吗,用这个把资源传出去操作。
1 | //协程函数 |
看到这里,读者你已经明白了如何使用Resources中的API加载资源了。接下来我将介绍一个资源加载模块,提供给外部加载资源。
分析一下资源加载模块需要具备的功能:
- 同步加载资源
- 异步加载资源
OK,接下来我将逐步完善一个资源加载模块,
1 | //资源加载模块:BaseMng<T>是之前实现的单例模式基类 |
Q8: 为什么创建了一个副本引用实例化的资源呢?
A: 读者,我很高兴能为你解释这个问题,我在开发过程中遇到了一个问题,和这个相关。我用资源加载模块加载了一个预制体对象,我通过回调函数把这个对象给外部使用,我在外部修改这个对象的时候,报错了——我修改的是Transform组件,给这个预制体对象一个父节点,报错显示“为了避免数据冲突,在预制体中的Transform组件不能被设置”。
重点在Resources.Load<GameObject>()和Resources.LoadAsync<GameObject>(),前者返回的是预制体对象 ,后者返回的asset中存的也是预制体对象,我们修改相当于修改预制体了,所以我们要创建一个副本,返回的也是这个副本。
意思就是我们修改的是实例化后的副本,而不是预制体本身(会有冲突)。
好的,我们继续完善异步加载资源的方法:
1 | //资源加载模块:BaseMng<T>是之前实现的单例模式基类 |
Q9: 异步加载资源时,如果是一个预制体GameObject,我可以给他一个名字吗,方便找?
A: 当然可以,这是一个明智的决定。你只需要在代码中添加:
1 | obj.name = path; |
或者你可以制定一套自己的命名规则。
这样一来,我们的资源管理模块就完成了:)
总结
资源管理模块的功能是为了封装好API,供外部使用,快速加载资源,减少一些重复的工作,如实例化预制体,绑定音频文件等。
资源管理模块具备:
- 同步加载资源,加载较小的资源;
- 异步加载资源,加载较大的资源。
要注意的坑:
- 加载预制体时,需要创建一个GameObject副本存储实例化的对象,对这个副本进行修改,不然会修改预制体;
- 回调函数的使用,因为异步加载资源不会在同一帧就把资源交给我们,所以需要等资源加载完毕后交给外部处理,就使用了回调函数。
