版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/chy_xfn/article/details/52524632
关于对象池的概念网上很多资料都已经介绍过来了,而且网上也有大神写的插件PoolManager。我比较少使用,因为平时做的项目偏小型,所以比较排斥把各种插件放到项目里(个人比较崇尚精简主义)。而我更喜欢学习其原理方法,根据自己的需求去重新实现功能(去繁取简)。
对象池原理
在游戏中我们都知道像怪物、子弹和特效等是需要频繁使用的(创建和销毁)。那么,毋庸置疑这一过程是会消耗性能的,所以引用对象池技术的意思就是在游戏开始前预先初始化一个含一定数量对象的池子,当游戏中需要时就从池子中取,不需要时就放回池中。这样就能避免游戏中频繁的创建和销毁对象。
下面代码将简单实现一个对象池
using UnityEngine;
using System.Collections;
public class MyObjectPool<T> {
// 方法的委托
public delegate T CreateDelegate();
private CreateDelegate dCreate;
private T[] objList; // 使用数组存储存取速度快,减少内存碎片
private int size = 0;
private int poolSize = 1;
/// <summary>
/// 对象池初始化对象,size为对象池初始大小
/// </summary>
/// <param name="create"></param>
public MyObjectPool(CreateDelegate create,int size)
{
poolSize = size;
dCreate = create;
objList = new T[poolSize];
for (int i = 0; i < poolSize; i++)
{
Push(dCreate());
}
}
/// <summary>
/// 对象池取对象,如果取完了,扩展对象池大小
/// </summary>
/// <returns></returns>
public T Pop()
{
T newObj = default(T);
if (objList != null && size != 0){
newObj = objList[--size];
objList[size] = default(T);
Debug.Log("从对象池取一个对象,对象池还剩" + size + "个对象");
return newObj;
}
else {
newObj = dCreate();
objList = new T[++poolSize];
Debug.Log("对象池对象不够,扩展池大小为" + poolSize);
}
return newObj;
}
/// <summary>
/// 对象池回收对象
/// </summary>
/// <param name="t"></param>
public void Push(T t)
{
if (t != null && size < poolSize)
{
objList[size++] = t;
t = default(T);
Debug.Log("回收对象,当前对象池大小为" + size + "个对象");
}
else {
Debug.Log("没有可回收对象");
}
}
}
在另一脚本创建对象池
// 游戏场景中用来调用所创建的对象池
// 需要using的
using System.Collections.Generic;
public MyObjectPool<GameObject> monsterPool;
public GameObject obj;
// 测试使用,正式不需要用list缓存
private List<GameObject> objList = new List<GameObject>();
void Start ()
{
// 初始化对象池
monsterPool = new MyObjectPool<GameObject>(InitObjPool,10);
}
// 通过委托调用的方法
private GameObjcet InitObjPool()
{
// 初始化怪物对象池,obj可以为怪物、子弹和特效等预设
GameObject go = MonoBehaviour.Instantiate(obj) as GameObject;
return go;
}
private void OnGUI()
{
if (GUI.Button(new Rect(10, 10, 100, 50), "Pop")){
GameObject go = monsterPool.Pop();
if (go != null)
{
go.SetActive(true);
objList.Add(go);
}
}
if (GUI.Button(new Rect(10, 70, 100, 50), "Push"))
{
if (objList.Count > 0) {
GameObject go = objList[objList.Count - 1];
go.SetActive(false);
objList.Remove(go);
monsterPool.Push(go);
}
}
}
小结:
- 这里简单的实现了对象池的用就取,不用放回功能。对象池的创建放在和场景初始化一起就行了(搭配上一篇的异步加载使用,即异步加载场景完之后对象池也建好了)。
- 关于对象使用后的数据重置问题,如果对象是使用挂脚本方式,在使用过程中数据可能会改变,那么这些对象就是独立的个体(数据不唯一),所以我们应该尽量避免在对象身上挂脚本。比如对象为怪物,那么我们就是创建一个Monster类(不继承MonoBehaviour),关于怪物的属性、初始化方法都放在这个类里面,对象实例只是其中的一个GameObject,游戏中使用的是Monster类,对象实例只是一个模型,没有任何数据。那么将其放回对象池就不需要作数据重置了。我们在新建一个怪物时只需new一个Monster实例,加上从对象池取一个怪物对象就可以完成一个怪物的创建了。
技术扩展
- 由于没有真正的销毁对象,而且随着数量的增多还不断扩展对象池大小,是很占用内存的,特别是手机的。
- 2018/01/10 更新修改
2.1 如果是GameObject对象
2.2 回收时不要调用SetActive(false)隐藏对象,而是设置其坐标远离当前屏幕,减少渲染消耗
2.3 大退时,清除对象池的所有GameObject对象,减少内存占用