这里先简述一下对象池的目的以及什么情况下需要对象池:
将部分需要持续性复用的物体(或对象)以隐藏/修改位置等方式先收集起来,等需要的时候直接打开而不需要重新实例化出来。
牺牲部分内存,降低CG垃圾回收频率(因为CG比较影响帧数)。
举个例子比如Fps游戏中的子弹,需要高频率生成,这时候子弹用完了直接藏起来,再用的时候打开就是了,弹痕也一样。
再比如一些Moba游戏中的小兵,其实都是重复的,在他们“死亡”时关闭就行了,生成时再重置一下参数打开即可。
什么情况下不需要使用对象池呢:
某些物体并非高频使用的,比如第一关的大Boss,他并不会在后面出现,因此不需要把他放到对象池中。
本人常做的一些展示类项目中,其实并不太需要使用到对象池,因为展示类模型一般会比较大,也很少出现来回切换的情况,这时候在加载过程中牺牲一些性能释放掉反而比你一直将模型放在内存中的好,但这里我们还是来创建一个比较好的对象池。
市面上的对象池大多用Dictionary来处理,目的是把不同的物体放到一个池子里,然后通过名字来检索。
个人认为这并不是对象池正确的用法,应该是同样的物体,放到单独的一个池子里。如果物体类型不同,没必要放到一个大的储存池里。因此每一个类型的物体会有一个单独的Pool。
调用方式:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Test : MonoBehaviour
{
//一个用于管理生成物体的List
public List<GameObject> m_CubeList = new List<GameObject>();
//需要实例化出来的物体
public GameObject m_Cube;
//对象池
ObjectPool cubePool;
//临时变量
GameObject tempCube;
private void Start()
{
//创建一个对象池,方法一
cubePool = new ObjectPool();
cubePool.ElementObject = m_Cube;
//因为写了三种构造函数,也可以这么创建,方法二
cubePool = new ObjectPool(m_Cube);
//或者这样,自定义对象池场景中GameObject父物体的名字,方法三
cubePool = new ObjectPool("子弹暂存列表");
cubePool.ElementObject = m_Cube;
}
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
//按下鼠标左键获取一个对象,但该对象的实例化方法还是本类实现的
tempCube = cubePool.GetObject(InstantiateCube);
//操作生成的这个对象
tempCube.transform.parent = this.transform;
//存起来,用于还回去的时候调用
m_CubeList.Add(tempCube);
}
if (Input.GetMouseButtonDown(1))
{
if (m_CubeList.Count > 0)
{
//此处仅写了一个简单的还回去的示例,可根据需要自行执行销毁
cubePool.PushObject(m_CubeList[0]);
m_CubeList.RemoveAt(0);
}
}
}
/// <summary>
/// 此方法为GetObject的参数,可在这里自行实现加载及序列化方法
/// </summary>
/// <param name="gameObject"></param>
/// <returns></returns>
GameObject InstantiateCube(GameObject gameObject)
{
return Instantiate(gameObject);
}
}
对象池代码:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 对象池(重复调用/使用的游戏物体)
/// </summary>
public class ObjectPool
{
//用于储存的隐藏父物体
private GameObject m_RootObject;
//用于实例化的物体
private GameObject m_ElementObject;
//用于储存的对象池
private Stack<GameObject> m_ObjectPoolStack = new Stack<GameObject>();
/// <summary>
/// 构造函数,要求必须设置一个ElementObject物体,即用于复用的物体
/// </summary>
/// <param name="elementObject"></param>
public ObjectPool(GameObject elementObject)
{
//此处使用了StringEx
//如不使用StringEx可用下面一行代替
//m_RootObject = new GameObject(elementObject.name + "Stack");
m_RootObject = new GameObject(StringEx.Concat(elementObject.name, "Stack"));
m_RootObject.gameObject.SetActive(false);
m_ElementObject = elementObject;
}
/// <summary>
/// 设置收集父物体的名字
/// </summary>
/// <param name="name"></param>
public ObjectPool(string name)
{
m_RootObject = new GameObject(name);
m_RootObject.gameObject.SetActive(false);
}
/// <summary>
/// 构造函数
/// </summary>
public ObjectPool()
{
m_RootObject = new GameObject("UnnamedObjectPool");
m_RootObject.gameObject.SetActive(false);
}
/// <summary>
/// 需要生成的物体
/// </summary>
public GameObject ElementObject
{
get { return m_ElementObject; }
set { m_ElementObject = value; }
}
/// <summary>
/// 获得一个物体
/// </summary>
/// <param name="instantiateAction">需实现一个返回值为GameObject的实例化函数</param>
/// <returns></returns>
public GameObject GetObject(Func<GameObject, GameObject> instantiateAction)
{
if (m_ObjectPoolStack.Count == 0)
{
return instantiateAction(m_ElementObject);
}
else
{
return m_ObjectPoolStack.Pop();
}
}
/// <summary>
/// 归还一个物体
/// </summary>
/// <param name="pushObject"></param>
public void PushObject(GameObject pushObject)
{
m_ObjectPoolStack.Push(pushObject);
pushObject.transform.parent = m_RootObject.transform;
}
/// <summary>
/// 清除当前列表
/// </summary>
public void Clear()
{
m_ObjectPoolStack.Clear();
}
}
虽然注释写的很清楚了,但还是担心小白会看不懂,这里我的主要思路是每个池子只管自己类型的对象(比如子弹),new一个Pool就是一个子弹的Pool,如果弹痕也需要,那就再new一个弹痕的Pool,有关这些Pool本身的管理我觉得可以根据用户自己的需要去实现就行。
因此我这里把实例化的代码放在使用类当中,略微提升一下通用性。这个对象池其实只是实现了一个Stack的暂存和取用。当然了,本人对对象池的需求不多,因此仅理解到这里应该能满足自身需要了,其他小伙伴如果更倾向于Dictionary来处理,序中的链接中有相对应的案例,直接百度也能搜到很多。这里就不赘述了。
网友评论