高效对象池设计:提升Unity性能的关键

缓冲池(对象池)模块

缓存池(对象池)的主要作用是优化资源管理,提高程序性能。主要通过重复利用已经创建的对象,避免频繁的创建和销毁过程,从而减少系统的内存分配和垃圾回收带来的开销。

cs 复制代码
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窗口中的布局,将对象和抽屉的关系可视化

cs 复制代码
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);
        //添加进list
        dataList.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个

而对象上限优化指的就是

我们希望控制对象数量有上限

对于不重要的资源我们没必要让其无限加量

而是将"使用最久"的资源直接抢来用

主要目的:

更加彻底的复用资源 ,对对象的数量上限加以限制 ,可以优化内存空间,甚至优化性能(减少数量上限,可以减小渲染压力)

cs 复制代码
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);
        //添加进list
        dataList.Add(obj);
        //这个对象已经不再使用 需要从使用中容器中移除
        usedList.Remove(obj);
    }

    /// <summary>
    /// 往使用中的容器添加对象
    /// </summary>
    /// <param name="obj"></param>
    public void PushUsedList(GameObject obj)
    {
        //添加进list
        usedList.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;
        //}
        #endregion
        if (!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));
            else
                poolDic[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;
    }
}
相关推荐
沉默的记录者3 小时前
unity 2021反向遮罩的毛边
unity·游戏引擎
是苏浙4 小时前
零基础入门C语言之深入了解指针2
c语言·开发语言
沧海归城4 小时前
Unity_Canvas_Canvas Scaler画布缩放器。
unity·游戏引擎
程序员黄同学4 小时前
Python中的列表推导式、字典推导式和集合推导式的性能和应用场景?
开发语言·python
AI小云4 小时前
【Python高级编程】类和实例化
开发语言·人工智能·python
道之极万物灭4 小时前
Python uv虚拟环境管理工具详解
开发语言·python·uv
OC溥哥9994 小时前
C++2D地铁跑酷代码
开发语言·c++
「QT(C++)开发工程师」5 小时前
【LUA教程】LUA脚本语言中文教程.PDF
开发语言·pdf·lua
程序定小飞5 小时前
基于springboot的民宿在线预定平台开发与设计
java·开发语言·spring boot·后端·spring