Unity 对象池技术
为什么使用对象池?
在我们开发中,往往会遇见需要不断创建和销毁同一物体的情况。(如飞机大战,许多FPS游戏,三消类游戏等),这时我们系统不断的实例化资源和销毁资源对于内存以及性能的消耗是非常大的。对于这种我们可以使用对象池技术进行优化。效果十分明显。
对象池及时的原理:
适用范围:有大量的物体需要被不断的创建和销毁的时候。
关键点:
从对象池中放入对象。
从对象池中取出对象。
使用何种数据类型来对这些对象进行储存。
关键思想:
相比于Instantiate和Destroy,不断的实例化和销毁,对象池只是对它们实例化出的对象进行激活和失活的处理。
我们配合Unity来进行实际的演示。
以最简单的打砖块案例进行讲解,具体自己的项目自己进行分析和解决。
首先通过脚本动态创建出一堵墙。
Paste_Image.png
通过射线实现子弹的发射。点击时创建预设并拿到预设的刚体组件添加力。
如果我们不进行销毁,那子弹将会一直存在于我们的场景,对我们的内存造成巨大的负担,这里我们可以是Destroy方法进行延时销毁。
但问题又来了这只是一个小小的Demo,如果我们的游戏场景里的资源有许多许多加上特效加上各种开销,我们如果再这样不断的向内存申请空间创建对象在销毁对象,这样使性能十分的低效。
通过上述例子我们可以发现,子弹是不断的被创建又不断的被销毁。符合我们的对象池技术需求。
使用对象池。
首先在场景中的游戏控制器上添加ObjectPool脚本。
ObjectPool的核心思想为如果存入对象池中,如何取出,用什么数据类型存储
我们来对ObjectPool类进行实现
如何存储
由于对象池是管理整个游戏的,并且我们的对象池只需要一个,我们需要单例模式。
我们的对象池中可能不仅仅只有Bullet子弹这一种需要管理的资源,还有许多可能需要储存的资源,我们需要使用一个数组来进行存储,但是每种资源的类型都是不同的,所以这里我们可以使用ArrayList这种数据类型来进行存储。
我们的每一种资源则对应了储存这种资源的ArrayList,所以我们可以使用键值对来进行一一对应。
思路逻辑清晰以后,我们来开始进行实现。
构建ObjectPool类。使用单例模式并添加键值对来进行储存。(这里为了简单方便讲解使用string类型作为key)
Paste_Image.png
接着我们需要对资源进行储存。(将数据存入对象池)
这里构建一个Return方法来实现储存。
首先跟着游戏对象的名字作为key
如果pool键值对中有key的值,我们将它Add在key所对应的ArrayList中。
如果pool键值对中没有key的值,我们则为这个key开辟一个ArrayList用于存储它。
并将它的Active设置为false(场景中的表现为消失,效果同Destroy但是这个资源并没有被回收)
Paste_Image.png
我们定义一个Get方法用于往对象池里取出对象。
Get类的设计思路,由于我们实例化出来的预设都是作为(Clone)来存在,所以我们将键值对的Key定义为一个原预设名加上(Clone)的形式。
声明一个Object的对象o。
判断键值对中是否有这个键,如果有,再判断这个键所对应的值是否为空。如果不为空,则将这个对象从ArrayList中取出后并在Arrarlist中删除。
作为我们需要使用的对象,由于之前储存的时候,我们将对象失活,现在我们需要将它激活,并设置它相应的position,和rotation。
如果不是上述的情况,说明我们的对象现在还没有被实例化出来(或者是需要从对象池中取出的数量小于场景中需要使用的数量时),我们就需要实例化对象。这里使用Resources.Load方法。
Paste_Image.png
Paste_Image.png
对象池构建完毕以后,我们将对象池进行应用。
直接使用对象池的对象来获取对象,而不是直接实例化出来对象。
Paste_Image.png
在Destroy方法处,进行相应的修改。
首先我们要淘汰之前的Destroy方法。
Paste_Image.png
并声明一个初始化的方法,用于开启协程。(每隔两秒将实例化出来的对象失活并储存入对象池中)
接着我们就不能再Srart中去调用这个方法,因为Start只能在对象实例化之后运行一次,而我们进行的是不断的失活和激活的动作。对象从对象池中使用是,不断的失活和激活过程(拿出来使用是激活,使用过后放回去失活),所以我们需要把这个失活的操作放在OnEnable()回调方法中,让它延时2f进行失活而不能放在Start中。
对象池完成。
这样对象池就会根据我们子弹的生命周期(这里是2f),初始化和复用相应数量的游戏对象,使用时就激活,不使用时就失活。不会再进行不断的初始化和销毁。