缓存池(对象池)
作用
缓存池(对象池)的主要作用是优化资源管理,提高程序性能。
主要通过重复利用已经创建的对象,避免频繁的创建和销毁过程,
从而减少系统的内存分配和垃圾回收带来的开销。
缓存池的基本实现
缓存池管理器
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 缓存池模块 管理器
/// </summary>
public class PoolMgr : BaseManager<PoolMgr> //BaseManager<PoolMgr>继承 不继承mono的单例管理器
{
//柜子容器放抽屉
private Dictionary<string, Stack<GameObject>> poolDic = new Dictionary<string, Stack<GameObject>>();
public GameObject GetObj(string name)
{
GameObject obj;
//有抽屉 并且 抽屉里有对象
if(poolDic.ContainsKey(name) && poolDic[name].Count > 0)
{
obj = poolDic[name].Pop();
//激活对象 再返回
obj.SetActive(true);
}
//否则去创建
else
{
obj = GameObject.Instantiate(Resources.Load<GameObject>(name));
}
return obj;
}
/// <summary>
/// 往缓存池放入对象
/// </summary>
/// <param name="name">抽屉名字</param>
/// <param name="obj">放入的对象</param>
public void PushObj(string name,GameObject obj)
{
//不直接移除对象 而是将对象失活 用的时候再激活
obj.SetActive(false);
//没有抽屉 创建抽屉
if (!poolDic.ContainsKey(name)) poolDic.Add(name, new Stack<GameObject>());
poolDic[name].Push(obj);
#region pushobj基础逻辑
/*//如果存储对应的抽屉 直接放入
if (poolDic.ContainsKey(name))
{
poolDic[name].Push(obj);
}
//没找到抽屉 创建一个出来 再放
else
{
poolDic.Add(name, new Stack<GameObject>());
poolDic[name].Push(obj);
}*/
#endregion
}
public void ClearPool()
{
poolDic.Clear();
}
}
对象销毁脚本(挂载在预设体上)(使用缓存池”销毁“)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DelayRemove : MonoBehaviour
{
public string poolName;
// Start is called before the first frame update
//当对象激活的时候执行的生命周期函数
private void OnEnable()
{
//延时函数
Invoke("RemoveMe", 1f);
}
private void RemoveMe()
{
PoolMgr.Instance.PushObj(poolName, this.gameObject);
}
}
进行窗口布局优化 + 对象上限优化
代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 抽屉(池子中的数据)对象
/// </summary>
public class PoolData
{
//用来存储抽屉中的对象 记录的是没有使用的对象
private Stack<GameObject> dataStack = new Stack<GameObject>();
//用来记录使用中的对象的
private List<GameObject> usedList = new List<GameObject>();
//抽屉根对象 用来进行布局管理的对象
private GameObject rootObj;
//获取容器中是否有对象
public int Count => dataStack.Count;
public int UsedCount => usedList.Count;
/// <summary>
/// 初始化构造函数
/// </summary>
/// <param name="root">柜子(缓存池)父对象</param>
/// <param name="name">抽屉父对象的名字</param>
public PoolData(GameObject root, string name, GameObject usedObj)
{
//开启功能时 才会动态创建 建立父子关系
if (PoolMgr.isOpenLayout)
{
//创建抽屉父对象
rootObj = new GameObject(name);
//和柜子父对象建立父子关系
rootObj.transform.SetParent(root.transform);
}
//创建抽屉时 外部肯定是会动态创建一个对象的
//我们应该将其记录到 使用中的对象容器中
PushUsedList(usedObj);
}
/// <summary>
/// 从抽屉中弹出数据对象
/// </summary>
/// <returns>想要的对象数据</returns>
public GameObject Pop()
{
//取出对象
GameObject obj;
if (Count > 0)
{
//从没有的容器当中取出使用
obj = dataStack.Pop();
//现在要使用了 应该要用使用中的容器记录它
usedList.Add(obj);
}
else
{
//取0索引的对象 代表的就是使用时间最长的对象
obj = usedList[0];
//并且把它从使用着的对象中移除
usedList.RemoveAt(0);
//由于它还要拿出去用,所以我们应该把它又记录到 使用中的容器中去
//并且添加到尾部 表示 比较新的开始
usedList.Add(obj);
}
//激活对象
obj.SetActive(true);
//断开父子关系
if (PoolMgr.isOpenLayout)
obj.transform.SetParent(null);
return obj;
}
/// <summary>
/// 将物体放入到抽屉对象中
/// </summary>
/// <param name="obj"></param>
public void Push(GameObject obj)
{
//失活放入抽屉的对象
obj.SetActive(false);
//放入对应抽屉的根物体中 建立父子关系
if (PoolMgr.isOpenLayout)
obj.transform.SetParent(rootObj.transform);
//通过栈记录对应的对象数据
dataStack.Push(obj);
//这个对象已经不再使用了 应该把它从记录容器中移除
usedList.Remove(obj);
}
/// <summary>
/// 将对象压入到使用中的容器中记录
/// </summary>
/// <param name="obj"></param>
public void PushUsedList(GameObject obj)
{
usedList.Add(obj);
}
}
/// <summary>
/// 缓存池(对象池)模块 管理器
/// </summary>
public class PoolMgr : BaseManager<PoolMgr>
{
//柜子容器当中有抽屉的体现
//值 其实代表的就是一个 抽屉对象
private Dictionary<string, PoolData> poolDic = new Dictionary<string, PoolData>();
//池子根对象
private GameObject poolObj;
//是否开启布局功能
public static bool isOpenLayout = true;
/// <summary>
/// 拿东西的方法
/// </summary>
/// <param name="name">抽屉容器的名字</param>
/// <returns>从缓存池中取出的对象</returns>
public GameObject GetObj(string name, int maxNum = 50)
{
GameObject obj;
//如果根物体为空 就创建
if (poolObj == null && isOpenLayout)
poolObj = new GameObject("Pool");
#region 加入了数量上限后的逻辑判断
if (!poolDic.ContainsKey(name) ||
(poolDic[name].Count == 0 && poolDic[name].UsedCount < maxNum))
{
//动态创建对象
//没有的时候 通过资源加载 去实例化出一个GameObject
obj = GameObject.Instantiate(Resources.Load<GameObject>(name));
//避免实例化出来的对象 默认会在名字后面加一个(Clone)
//我们重命名过后 方便往里面放
obj.name = name;
//创建抽屉
if (!poolDic.ContainsKey(name))
poolDic.Add(name, new PoolData(poolObj, name, obj));
else//实例化出来的对象 需要记录到使用中的对象容器中
poolDic[name].PushUsedList(obj);
}
//当抽屉中有对象 或者 使用中的对象超上限了 直接去取出来用
else
{
obj = poolDic[name].Pop();
}
#endregion
#region 没有加入 上限时的逻辑
////有抽屉 并且 抽屉里 有对象 才去直接拿
//if (poolDic.ContainsKey(name) && poolDic[name].Count > 0)
//{
// //弹出栈中的对象 直接返回给外部使用
// obj = poolDic[name].Pop();
//}
////否则,就应该去创造
//else
//{
// //没有的时候 通过资源加载 去实例化出一个GameObject
// obj = GameObject.Instantiate(Resources.Load<GameObject>(name));
// //避免实例化出来的对象 默认会在名字后面加一个(Clone)
// //我们重命名过后 方便往里面放
// obj.name = name;
//}
#endregion
return obj;
}
/// <summary>
/// 往缓存池中放入对象
/// </summary>
/// <param name="name">抽屉(对象)的名字</param>
/// <param name="obj">希望放入的对象</param>
public void PushObj(GameObject obj)
{
#region 因为失活 父子关系都放入了 抽屉对象中处理 所以不需要再处理这些内容了
////总之,目的就是要把对象隐藏起来
////并不是直接移除对象 而是将对象失活 一会儿再用 用的时候再激活它
////除了这种方式,还可以把对象放倒屏幕外看不见的地方
//obj.SetActive(false);
////把失活的对象(要放入抽屉中的对象) 父对象先设置为 柜子(缓存池)根对象
//obj.transform.SetParent(poolObj.transform);
#endregion
//没有抽屉 创建抽屉
//if (!poolDic.ContainsKey(obj.name))
// poolDic.Add(obj.name, new PoolData(poolObj, obj.name));
//往抽屉当中放对象
poolDic[obj.name].Push(obj);
////如果存在对应的抽屉容器 直接放
//if(poolDic.ContainsKey(name))
//{
// //往栈(抽屉)中放入对象
// poolDic[name].Push(obj);
//}
////否则 需要先创建抽屉 再放
//else
//{
// //先创建抽屉
// poolDic.Add(name, new Stack<GameObject>());
// //再往抽屉里面放
// poolDic[name].Push(obj);
//}
}
/// <summary>
/// 用于清除整个柜子当中的数据
/// 使用场景 主要是 切场景时
/// </summary>
public void ClearPool()
{
poolDic.Clear();
poolObj = null;
}
}