using System.Collections.Generic; using UnityEngine; /// /// 抽屉(池子中的数据)对象 /// public class PoolData { //用来存储抽屉中的对象 记录的是没有使用的对象 private Stack dataStack = new Stack(); //用来记录使用中的对象的 private List usedList = new List(); //抽屉上限 场景上同时存在的对象的上限个数 private int maxNum; //抽屉根对象 用来进行布局管理的对象 private GameObject rootObj; //获取容器中是否有对象 public int Count => dataStack.Count; public int UsedCount => usedList.Count; /// /// 进行使用中对象数量和最大容量进行比较 小于返回true 需要实例化 /// public bool NeedCreate => usedList.Count < maxNum; /// /// 初始化构造函数 /// /// 柜子(缓存池)父对象 /// 抽屉父对象的名字 public PoolData(GameObject root, string name, GameObject usedObj) { //开启功能时 才会动态创建 建立父子关系 if(PoolMgr.isOpenLayout) { //创建抽屉父对象 rootObj = new GameObject(name); //和柜子父对象建立父子关系 rootObj.transform.SetParent(root.transform); } //创建抽屉时 外部肯定是会动态创建一个对象的 //我们应该将其记录到 使用中的对象容器中 PushUsedList(usedObj); PoolObj poolObj = usedObj.GetComponent(); if (poolObj == null) { Debug.LogError("请为使用缓存池功能的预设体对象挂载PoolObj脚本 用于设置数量上限"); return; } //记录上限数量值 maxNum = poolObj.maxNum; } /// /// 从抽屉中弹出数据对象 /// /// 想要的对象数据 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; } /// /// 将物体放入到抽屉对象中 /// /// public void Push(GameObject obj) { //失活放入抽屉的对象 obj.SetActive(false); //放入对应抽屉的根物体中 建立父子关系 if (PoolMgr.isOpenLayout) obj.transform.SetParent(rootObj.transform); //通过栈记录对应的对象数据 dataStack.Push(obj); //这个对象已经不再使用了 应该把它从记录容器中移除 usedList.Remove(obj); } /// /// 将对象压入到使用中的容器中记录 /// /// public void PushUsedList(GameObject obj) { usedList.Add(obj); } } /// /// 方便在字典当中用里式替换原则 存储子类对象 /// public abstract class PoolObjectBase { } /// /// 用于存储 数据结构类 和 逻辑类 (不继承mono的)容器类 /// /// public class PoolObject : PoolObjectBase where T:class { public Queue poolObjs = new Queue(); } /// /// 想要被复用的 数据结构类、逻辑类 都必须要继承该接口 /// public interface IPoolObject { /// /// 重置数据的方法 /// void ResetInfo(); } /// /// 缓存池(对象池)模块 管理器 /// public class PoolMgr : BaseManager { //柜子容器当中有抽屉的体现 //值 其实代表的就是一个 抽屉对象 private Dictionary poolDic = new Dictionary(); /// /// 用于存储数据结构类、逻辑类对象的 池子的字典容器 /// private Dictionary poolObjectDic = new Dictionary(); //池子根对象 private GameObject poolObj; //是否开启布局功能 public static bool isOpenLayout = true; private PoolMgr() { //如果根物体为空 就创建 if (poolObj == null && isOpenLayout) poolObj = new GameObject("Pool"); } /// /// 拿东西的方法 /// /// 抽屉容器的名字 /// 从缓存池中取出的对象 public GameObject GetObj(string name) { //如果根物体为空 就创建 if (poolObj == null && isOpenLayout) poolObj = new GameObject("Pool"); GameObject obj; #region 加入了数量上限后的逻辑判断 if(!poolDic.ContainsKey(name) || (poolDic[name].Count == 0 && poolDic[name].NeedCreate)) { //动态创建对象 //没有的时候 通过资源加载 去实例化出一个GameObject obj = GameObject.Instantiate(Resources.Load(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(name)); // //避免实例化出来的对象 默认会在名字后面加一个(Clone) // //我们重命名过后 方便往里面放 // obj.name = name; //} #endregion return obj; } /// /// 获取自定义的数据结构类和逻辑类对象 (不继承Mono的) /// /// 数据类型 /// public T GetObj(string nameSpace = "") where T:class,IPoolObject,new() { //池子的名字 是根据类的类型来决定的 就是它的类名 string poolName = nameSpace + "_" + typeof(T).Name; //有池子 if(poolObjectDic.ContainsKey(poolName)) { PoolObject pool = poolObjectDic[poolName] as PoolObject; //池子当中是否有可以复用的内容 if(pool.poolObjs.Count > 0) { //从队列中取出对象 进行复用 T obj = pool.poolObjs.Dequeue() as T; return obj; } //池子当中是空的 else { //必须保证存在无参构造函数 T obj = new T(); return obj; } } else//没有池子 { T obj = new T(); return obj; } } /// /// 往缓存池中放入对象 /// /// 抽屉(对象)的名字 /// 希望放入的对象 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()); // //再往抽屉里面放 // poolDic[name].Push(obj); //} } /// /// 将自定义数据结构类和逻辑类 放入池子中 /// /// 对应类型 public void PushObj(T obj, string nameSpace = "") where T:class,IPoolObject { //如果想要压入null对象 是不被允许的 if (obj == null) return; //池子的名字 是根据类的类型来决定的 就是它的类名 string poolName = nameSpace + "_" + typeof(T).Name; //有池子 PoolObject pool; if (poolObjectDic.ContainsKey(poolName)) //取出池子 压入对象 pool = poolObjectDic[poolName] as PoolObject; else//没有池子 { pool = new PoolObject(); poolObjectDic.Add(poolName, pool); } //在放入池子中之前 先重置对象的数据 obj.ResetInfo(); pool.poolObjs.Enqueue(obj); } /// /// 用于清除整个柜子当中的数据 /// 使用场景 主要是 切场景时 /// public void ClearPool() { poolDic.Clear(); poolObj = null; poolObjectDic.Clear(); } }