对象池是在游戏开发中的常见优化技术,目的在于避免频繁的创建、销毁对象带来的内存消耗
下面是官网的介绍
打开工程,建立ObjectPool文件夹,然后再建立4个文件
其中
ObjectBase是池对象基类,所有需要被对象池管理的对象都需要继承该类(实际使用的对象被封装在这个类的派生类中,因为需要被对象池管理的对象可能是不同类型的,这样封装方便调用池对象的生命周期)
IObjectPool是对象池接口,ObjectPool是对象池类,使用链表维护指定类型的池对象
ObjectPoolManager是对象池管理器,使用字典维护所有对象池
打开ObjectBase,将其修改为抽象类,添加字段与属性,以及构造方法
/// <summary> /// 池对象基类 /// </summary> public abstract class ObjectBase { /// <summary> /// 对象名称 /// </summary> public string Name { get; set; } /// <summary> /// 对象(实际使用到的对象放到这里) /// </summary> public object Target { get; set; } /// <summary> /// 对象上次使用时间 /// </summary> public DateTime LastUseTime { get; private set; } /// <summary> /// 对象的获取计数 /// </summary> public int SpawnCount { get; set; } /// <summary> /// 对象是否正在使用 /// </summary> public bool IsInUse { get { return SpawnCount > 0; } } public ObjectBase(object target, string name = "") { Name = name; Target = target; } }
添加上池对象的生命周期方法
/// <summary> /// 获取对象时 /// </summary> protected virtual void OnSpawn() { } /// <summary> /// 回收对象时 /// </summary> protected virtual void OnUnspawn() { } /// <summary> /// 释放对象时 /// </summary> public abstract void Release();
添加对象的获取与回收方法
/// <summary> /// 获取对象 /// </summary> public ObjectBase Spawn() { SpawnCount++; LastUseTime = DateTime.Now; OnSpawn(); return this; } /// <summary> /// 回收对象 /// </summary> public void Unspawn() { OnUnspawn(); LastUseTime = DateTime.Now; SpawnCount--; }
打开IObjectPool,将其修改为接口,并添加一些属性
/// <summary> /// 对象池接口 /// </summary> public interface IObjectPool{ /// <summary> /// 对象池名称 /// </summary> string Name { get; } /// <summary> /// 对象池对象类型 /// </summary> Type ObjectType { get; } /// <summary> /// 对象池中对象的数量。 /// </summary> int Count { get; } /// <summary> /// 对象池中能被释放的对象的数量。 /// </summary> int CanReleaseCount { get; } /// <summary> /// 对象池自动释放可释放对象的间隔秒数(隔几秒进行一次自动释放) /// </summary> float AutoReleaseInterval { get; set; } /// <summary> /// 对象池的容量。 /// </summary> int Capacity { get; set; } /// <summary> /// 对象池对象过期秒数(被回收几秒钟视为过期,需要被释放) /// </summary> float ExpireTime { get; set; } }
添加释放对象的方法
/// <summary> /// 释放超出对象池容量的可释放对象 /// </summary> void Release(); /// <summary> /// 释放指定数量的可释放对象 /// </summary> /// <param name="toReleaseCount">尝试释放对象数量。</param> void Release(int toReleaseCount); /// <summary> /// 释放对象池中的所有未使用对象 /// </summary> void ReleaseAllUnused();
最后添加轮询与关闭的方法
/// <summary> /// 轮询对象池 /// </summary> void Update(float elapseSeconds, float realElapseSeconds); /// <summary> /// 清理并关闭对象池 /// </summary> void Shutdown();
然后打开ObjectPool,为其添加泛型(这里的泛型代表这个对象池所管理的池对象的类型)与约束,以及实现IObjectPool接口
/// <summary> /// 对象池 /// </summary> public class ObjectPool<T> : IObjectPool where T : ObjectBase { }
这时先不急着继续编写,而是在这个类上头定义一个释放对象筛选方法的委托模板
/// <summary> /// 释放对象筛选方法 /// </summary> /// <typeparam name="T">对象类型</typeparam> /// <param name="candidateObjects">要筛选的对象集合</param> /// <param name="toReleaseCount">需要释放的对象数量</param> /// <param name="expireTime">对象过期参考时间</param> /// <returns>经筛选需要释放的对象集合</returns> public delegate LinkedList<T> ReleaseObjectFilterCallback<T>(LinkedList<T> candidateObjects, int toReleaseCount, DateTime expireTime) where T : ObjectBase;
这个委托等会编写释放对象的方法时需要用到
现在可以回到ObjectPool的编写了,添加一些字段与属性
/// <summary> /// 对象池容量 /// </summary> private int m_Capacity; /// <summary> /// 对象池对象过期秒数 /// </summary> private float m_ExpireTime; /// <summary> /// 对象池名称 /// </summary> public string Name { get; private set; } /// <summary> /// 池对象的链表 /// </summary> private LinkedList<ObjectBase> m_Objects; /// <summary> /// 池对象类型 /// </summary> public Type ObjectType { get { return typeof(T); } } /// <summary> /// 池对象的数量 /// </summary> public int Count { get { return m_Objects.Count; } } /// <summary> /// 池对象是否可被多次获取 /// </summary> public bool AllowMultiSpawn { get; private set; } /// <summary> /// 可释放的对象的数量 /// </summary> public int CanReleaseCount { get { return GetCanReleaseObjects().Count; } } /// <summary> /// 对象池自动释放可释放对象计时 /// </summary> public float AutoReleaseTime { get; private set; } /// <summary> /// 对象池自动释放可释放对象的间隔秒数 /// </summary> public float AutoReleaseInterval { get; set; } /// <summary> /// 对象池容量 /// </summary> public int Capacity { get { return m_Capacity; } set { if (value < 0) { Debug.LogError("设置对象池容量<0,无法设置"); } if (m_Capacity == value) { return; } m_Capacity = value; } } /// <summary> /// 池对象过期秒数 /// </summary> public float ExpireTime { get { return m_ExpireTime; } set { if (value < 0) { Debug.LogError("设置对象过期秒数<0,无法设置"); } if (m_ExpireTime == value) { return; } m_ExpireTime = value; } }
上面这段代码里会有一个地方因为缺少对应方法报错,先不去管它,添加构造方法,在里面进行数据初始化
public ObjectPool(string name, int capacity, float expireTime, bool allowMultiSpawn) { Name = name; m_Objects = new LinkedList<ObjectBase>(); Capacity = capacity; AutoReleaseInterval = expireTime; ExpireTime = expireTime; AutoReleaseTime = 0f; AllowMultiSpawn = allowMultiSpawn; }
添加检查对象的方法
/// <summary> /// 检查对象 /// </summary> /// <param name="name">对象名称</param> /// <returns>要检查的对象是否存在</returns> public bool CanSpawn(string name) { foreach (ObjectBase obj in m_Objects) { if (obj.Name != name) { continue; } if (AllowMultiSpawn || !obj.IsInUse) { return true; } } return false; }
添加对象的注册,获取与回收的方法
/// <summary> /// 注册对象 /// </summary> /// <param name="obj">对象</param> /// <param name="spawned">对象是否已被获取</param> public void Register(T obj, bool spawned = false) { if (obj == null) { Debug.LogError("要放入对象池的对象为空:" + typeof(T).FullName); return; } //已被获取就让计数+1 if (spawned) { obj.SpawnCount++; } m_Objects.AddLast(obj); } /// <summary> /// 获取对象 /// </summary> /// <param name="name">对象名称</param> /// <returns>要获取的对象</returns> public T Spawn(string name = "") { foreach (ObjectBase obj in m_Objects) { if (obj.Name != name) { continue; } if (AllowMultiSpawn || !obj.IsInUse) { Debug.Log("获取了对象:" + typeof(T).FullName + "/" + obj.Name); return obj.Spawn() as T; } } return null; } /// <summary> /// 回收对象 /// </summary> public void Unspawn(ObjectBase obj) { Unspawn(obj.Target); } /// <summary> /// 回收对象 /// </summary> public void Unspawn(object target) { if (target == null) { Debug.LogError("要回收的对象为空:" + typeof(object).FullName); } foreach (ObjectBase obj in m_Objects) { if (obj.Target == target) { obj.Unspawn(); Debug.Log("对象被回收了:" + typeof(T).FullName + "/" + obj.Name); return; } } Debug.LogError("找不到要回收的对象:" + typeof(object).FullName); }
添加获取可释放对象的方法(刚才报错时缺少的对应方法)
/// <summary> /// 获取所有可以释放的对象 /// </summary> private LinkedList<T> GetCanReleaseObjects() { LinkedList<T> canReleaseObjects = new LinkedList<T>(); foreach (ObjectBase obj in m_Objects) { if (obj.IsInUse) { continue; } canReleaseObjects.AddLast(obj as T); } return canReleaseObjects; }
添加释放对象的主要方法
/// <summary> /// 释放对象池中的可释放对象 /// </summary> /// <param name="toReleaseCount">尝试释放对象数量</param> /// <param name="releaseObjectFilterCallback">释放对象筛选方法</param> public void Release(int toReleaseCount, ReleaseObjectFilterCallback<T> releaseObjectFilterCallback) { //重置计时 AutoReleaseTime = 0; if (toReleaseCount <= 0) { return; } //计算对象过期参考时间 DateTime expireTime = DateTime.MinValue; if (m_ExpireTime < float.MaxValue) { //当前时间 - 过期秒数 = 过期参考时间 expireTime = DateTime.Now.AddSeconds(-m_ExpireTime); } //获取可释放的对象和实际要释放的对象 LinkedList<T> canReleaseObjects = GetCanReleaseObjects(); LinkedList<T> toReleaseObjects = releaseObjectFilterCallback(canReleaseObjects, toReleaseCount, expireTime); if (toReleaseObjects == null || toReleaseObjects.Count <= 0) { return; } //遍历实际要释放的对象 foreach (ObjectBase toReleaseObject in toReleaseObjects) { if (toReleaseObject == null) { Debug.LogError("无法释放空对象"); } foreach (ObjectBase obj in m_Objects) { if (obj != toReleaseObject) { continue; } //释放对象 m_Objects.Remove(obj); obj.Release(); Debug.Log("对象被释放了:" + obj.Name); break; } } }
添加默认的筛选方法
/// <summary> /// 默认的释放对象筛选方法(未被使用且过期的对象) /// </summary> private LinkedList<T> DefaultReleaseObjectFilterCallBack(LinkedList<T> candidateObjects, int toReleaseCount, DateTime expireTime) { LinkedList<T> toReleaseObjects = new LinkedList<T>(); if (expireTime > DateTime.MinValue) { LinkedListNode<T> current = candidateObjects.First; while (current != null) { //对象最后使用时间 <= 过期参考时间,就需要释放 if (current.Value.LastUseTime <= expireTime) { toReleaseObjects.AddLast(current.Value); LinkedListNode<T> next = current.Next; candidateObjects.Remove(current); toReleaseCount--; if (toReleaseCount <= 0) { return toReleaseObjects; } current = next; continue; } current = current.Next; } } return toReleaseObjects; }
主方法编写完成后,添加3个释放对象的相关方法
/// <summary> /// 释放超出对象池容量的可释放对象 /// </summary> public void Release() { Release(m_Objects.Count - m_Capacity, DefaultReleaseObjectFilterCallBack); } /// <summary> /// 释放指定数量的可释放对象 /// </summary> /// <param name="toReleaseCount"></param> public void Release(int toReleaseCount) { Release(toReleaseCount, DefaultReleaseObjectFilterCallBack); } /// <summary> /// 释放对象池中所有未使用对象 /// </summary> public void ReleaseAllUnused() { LinkedListNode<ObjectBase> current = m_Objects.First; while (current != null) { if (current.Value.IsInUse) { current = current.Next; continue; } LinkedListNode<ObjectBase> next = current.Next; m_Objects.Remove(current); current.Value.Release(); Debug.Log("对象被释放了:" + current.Value.Name); current = next; } }
最后在添加轮询与关闭的方法,这个类就编写完成了
/// <summary> /// 对象池的定时释放 /// </summary> public void Update(float elapseSeconds, float realElapseSeconds) { AutoReleaseTime += realElapseSeconds; if (AutoReleaseTime < AutoReleaseInterval) { return; } Release(); } /// <summary> /// 清理对象池 /// </summary> public void Shutdown() { LinkedListNode<ObjectBase> current = m_Objects.First; while (current != null) { LinkedListNode<ObjectBase> next = current.Next; m_Objects.Remove(current); current.Value.Release(); Debug.Log("对象被释放了:" + current.Value.Name); current = next; } }
现在打开ObjectPoolManager,使其继承ManagerBase,添加字段与属性,并在构造方法中初始化
public class ObjectPoolManager : ManagerBase{ /// <summary> /// 默认对象池容量 /// </summary> private const int DefaultCapacity = int.MaxValue; /// <summary> /// 默认对象过期秒数 /// </summary> private const float DefaultExpireTime = float.MaxValue; /// <summary> /// 对象池字典 /// </summary> private Dictionary<string, IObjectPool> m_ObjectPools; public override int Priority { get { return 90; } } /// <summary> /// 对象池数量 /// </summary> public int Count { get { return m_ObjectPools.Count; } } public ObjectPoolManager() { m_ObjectPools = new Dictionary<string, IObjectPool>(); } }
添加Manager的生命周期方法
public override void Init() { } public override void Shutdown() { foreach (IObjectPool objectPool in m_ObjectPools.Values) { objectPool.Shutdown(); } m_ObjectPools.Clear(); } public override void Update(float elapseSeconds, float realElapseSeconds) { foreach (IObjectPool objectPool in m_ObjectPools.Values) { objectPool.Update(elapseSeconds, realElapseSeconds); } }
添加检查对象池的方法
/// <summary> /// 检查对象池 /// </summary> public bool HasObjectPool<T>() where T : ObjectBase { return m_ObjectPools.ContainsKey(typeof(T).FullName); }
添加对象池的创建、获取与销毁的方法
/// <summary> /// 创建对象池 /// </summary> public ObjectPool<T> CreateObjectPool<T>(int capacity = DefaultCapacity, float exprireTime = DefaultExpireTime, bool allowMultiSpawn = false) where T : ObjectBase { string name = typeof(T).FullName; if (HasObjectPool<T>()) { Debug.LogError("要创建的对象池已存在"); return null; } ObjectPool<T> objectPool = new ObjectPool<T>(name, capacity, exprireTime, allowMultiSpawn); m_ObjectPools.Add(name, objectPool); return objectPool; } /// <summary> /// 获取对象池 /// </summary> public ObjectPool<T> GetObjectPool<T>() where T : ObjectBase { IObjectPool objectPool = null; m_ObjectPools.TryGetValue(typeof(T).FullName, out objectPool); return objectPool as ObjectPool<T>; } /// <summary> /// 销毁对象池 /// </summary> public bool DestroyObjectPool<T>() { IObjectPool objectPool = null; if (m_ObjectPools.TryGetValue(typeof(T).FullName, out objectPool)) { objectPool.Shutdown(); return m_ObjectPools.Remove(typeof(T).FullName); } return false; }
最后添加释放对象池对象的方法
/// <summary> /// 释放所有对象池中的可释放对象。 /// </summary> public void Release() { foreach (IObjectPool objectPool in m_ObjectPools.Values) { objectPool.Release(); } } /// <summary> /// 释放所有对象池中的未使用对象。 /// </summary> public void ReleaseAllUnused() { foreach (IObjectPool objectPool in m_ObjectPools.Values) { objectPool.ReleaseAllUnused(); } }
到这里整个ObjectPool模块已经编写完成了,该开始测试了
按照惯例,建立测试需要的文件夹,场景,空物体和脚本
打开TestObject,使其继承ObjectBase
public class TestObject : ObjectBase { public TestObject(object target, string name = "") : base(target, name) { } public override void Release() { } }
然后打开测试脚本,编写测试代码
public class ObjectPoolTestMain : MonoBehaviour { private ObjectPool<TestObject> m_testPool; private void Start() { //创建对象池 ObjectPoolManager m_objectPoolManager = FrameworkEntry.Instance.GetManager<ObjectPoolManager>(); m_testPool = m_objectPoolManager.CreateObjectPool<TestObject>(); //注册对象 TestObject testObject = new TestObject("hello ObjectPool","test1"); m_testPool.Register(testObject, false); } private void Update() { if (Input.GetMouseButtonDown(0)) { TestObject testObject = m_testPool.Spawn("test1"); Debug.Log(testObject.Target); m_testPool.Unspawn(testObject.Target); } } }
启动游戏,点击鼠标左键,测试成功