
最近在做一个弹幕游戏,如果你看过东方之类的视频就会发现这种游戏的弹幕几乎占满了屏幕,不做优化的话自然就会卡成一坨,原因是大量Instantitate和Destroy的内存开销实在太大了。
弹幕有时需要成百上千的生成,一会就飞出屏幕了,这样在短时间内频繁生成和销毁是很浪费内存的。
那么,如果我们不销毁弹幕,而是暂时将它“关闭”保存下来,等需要生成时再次调用呢?
这就是对象池,一种基于学校厕所水过滤后进入饮水机的循环系统。
对象池到底是啥?
顾名思义,对象池就是对象的游泳池存放游戏对象的缓冲区域,我们可以从对象池中放入/取出游戏对象。
对象池的放入,就是将放入的物体隐藏起来放进对象池备用,对象池的取出,就是在对象池里取出一个物体,将它激活。
例如我的弹幕游戏,我将生成弹幕的Instantitate换成对象池的取出操作,将销毁弹幕的Destroy换成对象池的放入操作,大大节省了内存。
不仅是弹幕游戏,生成数量庞大的敌人,宝箱;音游中的tap,hold…只要需要进行频繁的生成和销毁,就可以使用对象池进行优化。(我去太牛逼了)
对象池怎么实现?
从对象池中取出对象,本质上就是SetActive(true),相对的,从对象池中放入对象,本质上就是SetActive(false)
那有人就要问了,如果池子里一个对象没有,该怎么取出呢?
那就只有用Instantitate生成一个了,毕竟你不能指望Unity能无中生有(热力学不存在了!)
弄懂了对象池的原理,我们直接开搞!
创建ObjectPool.cs,这是一个对象池脚本。
using System.Collections;using System.Collections.Generic;using UnityEngine;
public class ObjectPool : MonoBehaviour{ public GameObject Object;//对象池要取出/放入的物体 public Queue<GameObject> Pool = new Queue<GameObject>();//对象池,用队列实现 public int DefaultCount = 30;//对象池基础容量 public int MaxCount = 50;//对象池最大容量
public void Init()//一开始对象池啥也没有,初始化将池子装满 { GameObject o; for (int i = 0; i < DefaultCount; i++) { o = Instantiate(Object);//生成物体填充对象池 Pool.Enqueue(o);//Queue.Enqueue(Object obj):将obj放入队尾 o.SetActive(false);//不激活在池子里的对象 } } public GameObject Get()//池子的取出操作 { GameObject o; if (Pool.Count > 0)//如果对象池不是空的 { o = Pool.Dequeue();//Queue.Dequeue():取出队首元素 o.SetActive(true);//激活出池对象 } // 池子是空的,直接新建一个物体 else { o = Instantiate(Object); } return o; } public void Remove(GameObject o)//池子的放入操作 { if (Pool.Count <= MaxCount)//池子不溢出(池中对象总数不超过最大容量) { if (!Pool.Contains(o))//如果这个对象不在池子里 { Pool.Enqueue(o);//放入池子 o.SetActive(false);//不激活在池子里的对象 } } // 超过最大容量,既然这个物体没用了,就销毁这个对象 else { Destroy(o); } }}
这里贴一下C#的Queue队列用法(来自菜鸟教程 (runoob.com))
原文链接:[菜鸟教程](C# 队列(Queue) | 菜鸟教程 (runoob.com))
下表列出了 Queue 类的一些常用的 属性:
属性名称 | 类型 | 描述 |
---|---|---|
Count | int | 获取队列中的元素个数。 |
SyncRoot | object | 获取一个对象,用于同步对队列的访问(非泛型)。 |
IsSynchronized | bool | 指示队列的访问是否同步(线程安全,始终为 false )。 |
下表列出了 Queue 类的一些常用的 方法:
方法名称 | 返回类型 | 描述 |
---|---|---|
元素操作 | ||
Enqueue(object item) | void | 将元素添加到队列的末尾。 |
Dequeue() | object | 移除并返回队列开头的元素。 |
Peek() | object | 返回队列开头的元素,但不移除。 |
Clear() | void | 移除队列中的所有元素。 |
检查与复制 | ||
Contains(object item) | bool | 确定某元素是否存在于队列中。 |
ToArray() | object[] | 将队列中的元素复制到新数组中。 |
Clone() | object | 创建当前队列的浅表副本。 |
CopyTo(Array array, int index) | void | 将队列中的元素复制到现有数组,从指定索引开始。 |
对象池的优缺点
最后讲一下对象池的优缺点吧:
优点!
-
大大节省性能开销。
-
结构相对简单,代码量少。
-
循环效率高,可重复使用。
缺点!
-
需要提前定好池子容量,容量太大用不上,太小不够用
-
需要生成的数目太小没卵用
如果你
-
需要频繁创建/销毁对象
-
有空间大小差不多的对象
-
有可重用,但生成和销毁消耗的对象
请毫不犹豫选择对象池
参考文章: CSDN 五莲神卫:Unity 对象池(Object Pool)简单实现_unity objectpool-CSDN博客