高效对象池设计:提升Unity性能的关键
缓冲池(对象池)模块
缓存池(对象池)的主要作用是优化资源管理,提高程序性能。主要通过重复利用已经创建的对象,避免频繁的创建和销毁过程,从而减少系统的内存分配和垃圾回收带来的开销。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;/// <summary>
/// 缓存池(对象池)模块 管理器
/// </summary>
public class PoolMgr : BaseManager<PoolMgr>
{//声明柜子(Dictionary)和抽屉(List)容器private Dictionary<string,List<GameObject>> poolDic = new Dictionary<string,List<GameObject>>();private PoolMgr() { }/// <summary>/// 拿东西的方法/// </summary>/// <param name="name"> 抽屉容器的名字 </param>/// <returns></returns>public GameObject GetObjFromPool(string name){GameObject obj;//有抽屉 并且 抽屉中有对象if (poolDic.ContainsKey(name) && poolDic[name].Count > 0){//直接返回第一个对象obj = poolDic[name][0];poolDic[name].RemoveAt(0);obj.SetActive(true);}//否则应该创建else{//没有的话 通过资源加载 实例化一个对象并返回obj = GameObject.Instantiate(Resources.Load<GameObject>(name));obj.name = name;}return obj;}/// <summary>/// 往缓存池放入对象/// </summary>/// <param name="obj">希望放入的对象</param>public void PushObjToPool(GameObject obj){//并不是直接移除对象,而是将对象失活 再用时再激活obj.SetActive(false);//如果存在对应的抽屉容器 可以直接放if (poolDic.ContainsKey(obj.name)){poolDic[obj.name].Add(obj);}//如果没有对应的容器可以先创建再放else{poolDic.Add(obj.name, new List<GameObject>());poolDic[obj.name].Add(obj);}}/// <summary>/// 用于清除整个柜子当中的数据/// 使用场景主要好是 切换场景/// </summary>public void ClearPool(){ poolDic.Clear();}
}
窗口布局优化
现在直接关活对象,当之后项目做大了,抽屉多了,对象多了,游戏中成百上千个对象,在开发测试时不方便从Hierarchy窗口中查看对象获取信息,因此我们希望能优化一下Hierarchy窗口中的布局,将对象和抽屉的关系可视化
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 抽屉(池子中的数据)对象
/// </summary>
public class PoolData
{ //用来存储抽屉中的对象private List<GameObject> dataList = new List<GameObject>();//抽屉跟对象,用来布局private GameObject rootObj;//获取容器中对象的数量public int Count => dataList.Count;public PoolData(GameObject root,string name) {if (PoolMgr.isOpenLayout){//创建抽屉父对象rootObj = new GameObject(name);//将抽屉父对象与柜子父对象设置为父子关系rootObj.transform.SetParent(root.transform);}}/// <summary>/// 取出抽屉中的对象/// </summary>/// <returns></returns>public GameObject GetGameObject(){//取出对象GameObject obj = dataList[0];dataList.RemoveAt(0);//激活对象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);//添加进listdataList.Add(obj);}
}/// <summary>
/// 缓存池(对象池)模块 管理器
/// </summary>
public class PoolMgr : BaseManager<PoolMgr>
{//声明柜子(Dictionary)和抽屉(List)容器private Dictionary<string, PoolData> poolDic = new Dictionary<string, PoolData>();//池子根对象private GameObject poolObj;//是否开启布局功能public static bool isOpenLayout = true;private PoolMgr() { }/// <summary>/// 拿东西的方法/// </summary>/// <param name="name"> 抽屉容器的名字 </param>/// <returns></returns>public GameObject GetObjFromPool(string name){GameObject obj;//有抽屉 并且 抽屉中有对象if (poolDic.ContainsKey(name) && poolDic[name].Count > 0){//取出抽屉中的对象obj = poolDic[name].GetGameObject();}//否则应该创建else{//没有的话 通过资源加载 实例化一个对象并返回obj = GameObject.Instantiate(Resources.Load<GameObject>(name));obj.name = name;}return obj;}/// <summary>/// 往缓存池放入对象/// </summary>/// <param name="obj">希望放入的对象</param>public void PushObjToPool(GameObject obj){//如果根对象为空才创建if (poolObj == null && isOpenLayout)poolObj = new GameObject("Pool");//并不是直接移除对象,而是将对象失活 再用时再激活obj.SetActive(false);//失活之后设置父对象if(isOpenLayout)obj.transform.SetParent(poolObj.transform);//如果没有对应的容器可以先创建再放if (!poolDic.ContainsKey(obj.name))poolDic.Add(obj.name, new PoolData(poolObj, obj.name));poolDic[obj.name].Push(obj);}/// <summary>/// 用于清除整个柜子当中的数据/// 使用场景主要好是 切换场景/// </summary>public void ClearPool(){ poolDic.Clear();poolObj = null;}
}
对象上限优化
目前我们制作的缓存池模块
理论上来说,当动态创建的对象长时间不放回抽屉
每次从缓存池中动态获取对象时,会不停的新建对象
那么也就是对象的数量是没有上限的
场景上的某种对象可以存在n个
而对象上限优化指的就是
我们希望控制对象数量有上限
对于不重要的资源我们没必要让其无限加量
而是将“使用最久”的资源直接抢来用
主要目的:
更加彻底的复用资源 ,对对象的数量上限加以限制 ,可以优化内存空间,甚至优化性能(减少数量上限,可以减小渲染压力)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 抽屉(池子中的数据)对象
/// </summary>
public class PoolData
{ //用来存储抽屉中的对象 记录的是没有使用的对象private List<GameObject> dataList = new List<GameObject>();//抽屉跟对象,用来布局private GameObject rootObj;//抽屉上限,场景上同时存在的对象的上限个数private int maxNum;//记录使用中的对象private List<GameObject> usedList = new List<GameObject>();//获取容器中对象的数量public int Count => dataList.Count;public int UsedCount => usedList.Count;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);}//创建抽屉时 外部会动态创建一个对象//我们需要将它记录在使用中的容器中usedList.Add(usedObj);//DelayRemove 挂载在需要对象池创建的对象身上的脚本//主要定义了maxNum,对象在对象池中的上限以及延迟调用PushObjToPool函数让对象失活DelayRemove delayRemove = usedObj.GetComponent<DelayRemove>();if (delayRemove == null){Debug.LogError("请为使用对象池的预设体挂载DelayRemove脚本");return;}maxNum = delayRemove.maxNum;}/// <summary>/// 取出抽屉中的对象/// </summary>/// <returns></returns>public GameObject GetGameObject(){//取出对象GameObject obj;if (Count > 0) //如果抽屉中有未使用的话直接使用{obj = dataList[0];dataList.RemoveAt(0);usedList.Add(obj);}else //抽屉中没有未使用的{//取使用中的List抽屉中的第一个,代表使用时间最长的obj = usedList[0];//并把它从list中移除掉usedList.RemoveAt(0);//由于还要使用,再把它记录到List的最后一个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);//添加进listdataList.Add(obj);//这个对象已经不再使用 需要从使用中容器中移除usedList.Remove(obj);}/// <summary>/// 往使用中的容器添加对象/// </summary>/// <param name="obj"></param>public void PushUsedList(GameObject obj){//添加进listusedList.Add(obj);}
}/// <summary>
/// 缓存池(对象池)模块 管理器
/// </summary>
public class PoolMgr : BaseManager<PoolMgr>
{//声明柜子(Dictionary)和抽屉(List)容器private Dictionary<string, PoolData> poolDic = new Dictionary<string, PoolData>();//池子根对象private GameObject poolObj;//是否开启布局功能public static bool isOpenLayout = true;private PoolMgr() { }/// <summary>/// 拿东西的方法/// </summary>/// <param name="name"> 抽屉容器的名字 </param>/// <returns></returns>public GameObject GetObjFromPool(string name){if (poolObj == null && isOpenLayout)poolObj = new GameObject("Pool");GameObject obj;#region 没有加入上限时的逻辑////有抽屉 并且 抽屉中有对象//if (poolDic.ContainsKey(name) && poolDic[name].Count > 0)//{// //取出抽屉中的对象// obj = poolDic[name].GetGameObject();//}////否则应该创建//else//{// //没有的话 通过资源加载 实例化一个对象并返回// obj = GameObject.Instantiate(Resources.Load<GameObject>(name));// obj.name = name;//}#endregionif (!poolDic.ContainsKey(name) ||(poolDic[name].Count == 0 && poolDic[name].NeedCreate)){//没有的话 通过资源加载 实例化一个对象并返回obj = GameObject.Instantiate(Resources.Load<GameObject>(name));obj.name = name;if (!poolDic.ContainsKey(name))//创建抽屉poolDic.Add(name, new PoolData(poolObj, name, obj));elsepoolDic[name].PushUsedList(obj);//实例化出来的对象需要添加到使用中的List}//当抽屉中有对象 或者 使用中的对象超上限的情况下else{obj = poolDic[name].GetGameObject();}return obj;}/// <summary>/// 往缓存池放入对象/// </summary>/// <param name="obj">希望放入的对象</param>public void PushObjToPool(GameObject obj){//如果根对象为空才创建if (poolObj == null && isOpenLayout)poolObj = new GameObject("Pool");//并不是直接移除对象,而是将对象失活 再用时再激活obj.SetActive(false);//失活之后设置父对象if(isOpenLayout)obj.transform.SetParent(poolObj.transform);//如果没有对应的容器可以先创建再放poolDic[obj.name].Push(obj);}/// <summary>/// 用于清除整个柜子当中的数据/// 使用场景主要好是 切换场景/// </summary>public void ClearPool(){ poolDic.Clear();poolObj = null;}
}
