一、简述
对象池是游戏开发过程中常用的一种优化对象创建和销毁的方式,外界只关心什么时候创建对象,对象的获取方式完全交给对象池来做。在多线程开发中使用到的线程池实际上就是用到了对象池的思想。
对象的创建和销毁是比较耗费性能的,对于一些经常需要创建的物体如果进行频繁的实例化的话是不合适的,所以可以创建一个对象池来保存一定数量的对象,当需要用到这个对象的时候不是实例化而是直接从对象池中获取,对象池如果发现自己的所有对象都被外界使用了,对象池自行扩容,或者也可锁定对象池的大小。同样道理,当外界不需要再使用这个对象的时候不要销毁它,给对象池一个信息,让对象池自行回收。
二、适用情况
最简单的例子就是子弹,这种需要大量创建的物体很时候用对象池来优化。当然,对象池的使用并不限于这种在场景中有具体表示的对象。
三、Unity对象池的实现
实现了一个简单的实体对象池 。
1.对象池GameObjectPool
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 对象池
/// </summary>
public class GameObjectPool
{
/// <summary>
/// 所有对象池的父物体
/// </summary>
private static Transform poolRoot;
/// <summary>
/// 对象池模板
/// </summary>
private GameObject template;
/// <summary>
/// 对象池链表
/// </summary>
private List<GameObject> pooledObjects;
/// <summary>
/// 初始化大小
/// </summary>
private readonly int initCount;
/// <summary>
/// 对象池数量
/// </summary>
private int currentCount;
/// <summary>
/// 对象池最大数量
/// </summary>
private readonly int maxCount;
/// <summary>
/// 是否锁定对象池大小
/// </summary>
private readonly bool lockPoolSize = false;
/// <summary>
/// 切换场景时是否卸载
/// 后面默认值要改为false
/// </summary>
private bool dontDestroyOnLoad = true;
public Transform objParent;
/// <summary>
/// 当前指向链表位置索引
/// </summary>
private int currentIndex = 0;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="_template"></param>
/// <param name="_initCount"></param>
/// <param name="_lookPoolSize"></param>
public GameObjectPool(GameObject _template, bool _dontDestroyOnLoad = false, int _initCount = 5, int _maxCount = 10, bool _lookPoolSize = false)
{
template = _template;
currentCount = initCount = _initCount;
lockPoolSize = _lookPoolSize;
dontDestroyOnLoad = _dontDestroyOnLoad;
objParent = new GameObject(template.name + "Pool").transform;
poolRoot = poolRoot ?? new GameObject("PoolRoot").transform;
objParent.SetParent(poolRoot);
if (dontDestroyOnLoad)
Object.DontDestroyOnLoad(objParent);
pooledObjects = new List<GameObject>(); // 初始化链表
for (int i = 0; i < currentCount; ++i)
{
GameObject obj = Object.Instantiate(template); // 创建对象
if (dontDestroyOnLoad)
Object.DontDestroyOnLoad(obj);
obj.SetActive(false); // 设置对象无效
obj.transform.SetParent(objParent);
pooledObjects.Add(obj); // 把对象添加到对象池中
}
}
/// <summary>
/// 获取对象池中可以使用的对象。
/// </summary>
/// <returns></returns>
public GameObject GetPooledObject()
{
for (int i = 0; i < pooledObjects.Count; ++i)
{
//每一次遍历都是从上一次被使用的对象的下一个
int Item = (currentIndex + i) % pooledObjects.Count;
if (!pooledObjects[Item].activeSelf)
{
currentIndex = (Item + 1) % pooledObjects.Count;
//返回第一个未激活的对象
return pooledObjects[Item];
}
}
//如果遍历完一遍对象库发现没有闲置对象且对象池未达到数量限制
if (!lockPoolSize || currentCount < maxCount)
{
GameObject obj = Object.Instantiate(template);
obj.transform.SetParent(objParent);
if (dontDestroyOnLoad)
Object.DontDestroyOnLoad(obj);
pooledObjects.Add(obj);
currentCount++;
return obj;
}
//如果遍历完没有而且锁定了对象池大小,返回空。
return null;
}
/// <summary>
/// 将对象池的数量回归5
/// </summary>
public void Clear()
{
for (int i = 0; i < pooledObjects.Count; i++)
{
if (i > initCount)
{
Object.Destroy(pooledObjects[i]);
}
else if (pooledObjects[i].activeInHierarchy)
{
pooledObjects[i].SetActive(false);
}
}
}
}
2.对象工厂 GameObjectFactory
对象工厂是个单例类,继承了之前的Singleton,请参考单例模板
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameObjectFactory : Singleton<GameObjectFactory>
{
/// <summary>
/// 利用GameObject的int型Hash值,索引对应的Pool对象
/// </summary>
private Dictionary<string, GameObjectPool> poolDic;
/// <summary>
/// 所有模型字典
/// </summary>
public Dictionary<string, GameObject> poolTemplateDic { get; private set; }
/// <summary>
/// 这个用于LoadingTest的测试
/// </summary>
private bool isLoad = true;
public GameObjectFactory()
{
poolTemplateDic = new Dictionary<string, GameObject>();
poolDic = new Dictionary<string, GameObjectPool>();
}
/// <summary>
/// 读取并预处理指定模型
/// </summary>
/// <returns></returns>
public void InitPool()
{
if (isLoad)
{
//从Resource制定路径下读取模型
List<UnityEngine.Object> objs = new List<UnityEngine.Object>();
objs.AddRange(Resources.LoadAll("GameObjectPool", typeof(GameObject)));
//objs.AddRange(Resources.LoadAll(" ***path*** ", typeof(GameObject)));
//便利所有模型进行处理
foreach (var _obj in objs)
{
//将模型加入到字典中
poolTemplateDic.Add(_obj.name, _obj as GameObject);
}
isLoad = false;
}
}
/// <summary>
/// 创建一个对象池
/// </summary>
/// <param name="template"></param>
public GameObjectPool CreatPool(GameObject template)
{
GameObjectPool _newPol = new GameObjectPool(template);
poolDic.Add(template.name, _newPol);
return _newPol;
}
/// <summary>
/// 通过名字实例化gameobj方法
/// </summary>
public GameObject Instantiate(string name, Vector3 pos = default, Quaternion quaternion = default)
{
if (!poolTemplateDic.ContainsKey(name))
{
throw new Exception("无法创建名为" + name + "的对象池");
}
GameObjectPool pool;
if (!poolDic.TryGetValue(name, out pool))
{
pool = CreatPool(poolTemplateDic[name]);
}
GameObject obj = pool.GetPooledObject();
obj.transform.position = pos;
obj.transform.rotation = quaternion;
obj.SetActive(true);
return obj;
}
/// <summary>
/// 清理对象池
/// </summary>
public void Clear()
{
foreach (var item in poolDic)
{
item.Value.Clear();
}
}
}
3. 使用
首先需要在程序开始或者合适的时候调用GameObjectFactory.Instance.Init(),这个是初始化预制体字典,默认需要将需要用对象池优化的预制体放入Resources/GameObjectPool 中。可以自行改写。
接着就是利用对象工厂获取对象。调用GameObjectFactory.Instance.Instantiate(),这个函数的第一个参数是预制体名,后两个参数是位置和角度。