一、对象池原理
创建一个 池子,池子 预先生成 有一定数量的需要大量重复使用的物体(prefab),在使用的时候,直接从池子中 取出 SetActive(true)) 即可,用完后再 回收(SetActive(false)) 到池中。
这样省去了部分繁琐的 Instantiate 以及 Destroy 操作,提高了程序运行效率,甚至可以减少运行时的卡顿
二、实现对象池
1.实现
先直接上代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ObjectPool : MonoBehaviour
{
// 单例
public static ObjectPool poolInstance;
// 池子要存储的物体
public GameObject Object;
// 内存区(队列)
public Queue<GameObject> objectPool = new Queue<GameObject>();
// 池子的初始容量
public int defaultCount = 16;
// 池子的最大容量
public int maxCount = 25;
private void Awake()
{
poolInstance = this;
}
// 对池子进行初始化(创建初始容量个数的物体)
public void Init()
{
GameObject obj;
for (int i = 0; i < defaultCount; i++)
{
obj = Instantiate(Object, this.transform);
// 将生成的对象入队
objectPool.Enqueue(obj);
obj.SetActive(false);
}
}
// 从池子中取出物体
public GameObject Get()
{
GameObject tmp;
// 如果池子内有物体,从池子取出一个物体
if (objectPool.Count > 0)
{
// 将对象出队
tmp = objectPool.Dequeue();
tmp.SetActive(true);
}
// 如果池子中没有物体,直接新建一个物体
else
{
tmp = Instantiate(Object, this.transform);
}
return tmp;
}
// 将物体回收进池子
public void Remove(GameObject obj)
{
// 池子中的物体数目不超过最大容量
if (objectPool.Count <= maxCount)
{
// 该对象没有在队列中
if (!objectPool.Contains(obj))
{
// 将对象入队
objectPool.Enqueue(obj);
obj.SetActive(false);
}
}
// 超过最大容量就销毁
else
{
Destroy(obj);
}
}
}
2.分析
(根据上面的原理,将代码的各部分一一对应起来)
1. 池子 :就是 ObjectPool.cs 类本身
public class ObjectPool : MonoBehaviour
其中池子中有一些重要属性(类中的字段)
// 池子要存储的物体
public GameObject Object;
// 内存区
public Queue<GameObject> objectPool = new Queue<GameObject>();
// 池子的初始容量
public int defaultCount = 16;
// 池子的最大容量
public int maxCount = 25;
- 其中最核心的部分就是由队列(Queue)实现的内存区(当然也可以用列表、栈来实现),用来同步记录池中对象的出入
- 设置最大容量的目的就是防止对象池无节制的占用内存资源
2. 预先生成 :初始化对象池,在程序开始运行的时候生成一定数量(defaultCount)的物体
// 对池子进行初始化(创建初始容量个数的物体)
public void Init()
{
GameObject obj;
for (int i = 0; i < defaultCount; i++)
{
obj = Instantiate(Object, this.transform);
// 将生成的对象入队
objectPool.Enqueue(obj);
obj.SetActive(false);
}
}
3. 取出 :从对象池中取出物体,通过 SetActive(true) 激活场景中的物体,而不是重新生成,替代了 Instantiate 操作
public GameObject Get()
{
GameObject tmp;
// 如果池子内有物体,从池子取出一个物体
if (objectPool.Count > 0)
{
// 将对象出队
tmp = objectPool.Dequeue();
tmp.SetActive(true);
}
// 如果池子中没有物体,直接新建一个物体
else
{
tmp = Instantiate(Object, this.transform);
}
return tmp;
}
- 如果初始生成的全部对象都拿出对象池去场景中使用了,对象池内此时没有物体;场景中却还需要更多的该物体,可以继续生成
4. 回收 :将物体回收到对象池中,通过 SetActive(false) 禁用场景中的物体,而不是直接销毁,替代了 Destroy 操作
// 将物体回收进池子
public void Remove(GameObject obj)
{
// 池子中的物体数目不超过最大容量
if (objectPool.Count <= maxCount)
{
// 该对象没有在队列中
if (!objectPool.Contains(obj))
{
// 将对象入队
objectPool.Enqueue(obj);
obj.SetActive(false);
}
}
// 超过最大容量就销毁
else
{
Destroy(obj);
}
}
- 因为在场景中的物体数量可能超出对象池初始容量甚至是最大容量(maxCount),所以在回收的时候不应该全部回收,超出最大容量的那部分物体就直接销毁(Destroy)
这里放一张Demo截图:点击按钮,在粉色大格子中生成白色小格子,每个白色小格子在两秒后销毁(当然不是真的销毁)
本文仅仅简单介绍了对象池的思路与实现方法
对象池的写法千千万,就上面提到的对象池实现方法而言,还有许多更好的实现方法,欢迎留言讨论!