11.缓存池优化:窗口布局

一、缓存池层级布局优化

1. 为什么要优化 Hierarchy 布局

对象池中的对象通常不会真正销毁,而是:obj.SetActive(false),然后继续保留在场景中。当对象数量变多以后,Hierarchy 中可能出现大量失活对象。这样会导致:

  • Hierarchy 非常混乱
  • 不方便开发时查找对象
  • 无法直观看出对象属于哪个对象池
  • 调试对象池时不方便观察
    因此可以把对象池中的对象进行分层管理,让对象与对应池之间的关系更加清晰。

二、对象池布局结构

可以把整个对象池组织成:

复制代码
Pool
├── Bullet
│   ├── Bullet
│   ├── Bullet
│   └── Bullet
├── Enemy
│   ├── Enemy
│   └── Enemy
└── Effect
    ├── Effect
    └── Effect

三、PoolData

把每一种对象池的数据进一步封装为:PoolData

结构变成:Dictionary<string, PoolData>

PoolData 负责管理某一种对象池的数据和层级关系。

四、PoolData 的成员

复制代码
public class PoolData
{
    private Stack<GameObject> dataStack = new();
    private GameObject rootObj;
    public int Count => dataStack.Count;
}

dataStack

保存真正被回收的对象。

rootObj

表示当前子池在 Hierarchy 中的根对象。

Count

用于判断当前池中还有多少可复用对象。

五、创建子池根对象

复制代码
public PoolData(GameObject root, string name)
{
    if (PoolMgr.isOpenLayout)
    {
        rootObj = new GameObject(name);
        rootObj.transform.SetParent(root.transform);
    }
}

这样每一种对象都有自己的 Hierarchy 节点。

六、对象回收时的层级处理

对象放回池中:

复制代码
public void Push(GameObject obj)
{
    obj.SetActive(false);
    if (PoolMgr.isOpenLayout)
    {
        obj.transform.SetParent(rootObj.transform);
    }
    dataStack.Push(obj);
}

七、对象取出时的层级处理

从对象池取出:

复制代码
public GameObject Pop()
{
    GameObject obj = dataStack.Pop();
    obj.SetActive(true);
    if (PoolMgr.isOpenLayout)
    {
        obj.transform.SetParent(null);
    }
    return obj;
}

八、Pool 根对象

PoolMgr 中增加:private GameObject poolObj作为整个对象池的根节点。

当第一次回收对象时:

复制代码
if (poolObj == null && isOpenLayout)
{
    poolObj = new GameObject("Pool");
}

创建:Pool

之后所有子池:

复制代码
Bullet
Enemy
Effect

都会放到这个对象下面。

九、PushObj 的变化

PushObj()不再自己负责:obj.SetActive(false)和obj.transform.SetParent(...)

而是把这些逻辑交给:PoolData.Push()。所以:

复制代码
public void PushObj(GameObject obj)
{
    if (poolObj == null && isOpenLayout)
    {
        poolObj = new GameObject("Pool");
    }

    if (!poolDic.ContainsKey(obj.name))
    {
        poolDic.Add(obj.name,new PoolData(poolObj, obj.name));
    }
    poolDic[obj.name].Push(obj);
}

职责变成:

PoolMgr:找到对应 PoolData

PoolData:负责具体对象的保存和层级管理

这样比把所有逻辑都写在 PoolMgr 中更加清晰。

十、GetObj 的变化

获取对象时:

复制代码
if (poolDic.ContainsKey(name) &&poolDic[name].Count > 0)
{
    obj = poolDic[name].Pop();
}
else
{
    obj = Object.Instantiate(Resources.Load<GameObject>(name));
    obj.name = name;
}

现在:PoolData.Pop()

内部已经负责:

  • 从 Stack 取出
  • 激活对象
  • 解除父子关系
    因此 PoolMgr 只负责判断:
    池里有:Pop()
    池里没有:Instantiate()

十一、是否开启布局功能

复制代码
public static bool isOpenLayout = false;

用于控制是否启用 Hierarchy 布局。如果:isOpenLayout = true;则:

复制代码
Pool
├── Bullet
├── Enemy
└── Effect

并且回收对象会建立父子关系。

如果:isOpenLayout = false;则只进行对象池的数据管理,不进行额外的 Transform 父子关系操作。

十二、为什么把布局功能做成可选

修改:Transform.SetParent()

本身也需要一定的处理。如果对象池中的对象大量、频繁地:

取出:SetParent(null)

回收:SetParent(root)

就会产生额外的层级调整。

因此布局功能主要方便:

复制代码
开发
调试
观察对象池

如果不需要在 Hierarchy 中观察对象关系,可以关闭:

复制代码
isOpenLayout = false;

十三、ClearPool

复制代码
public void ClearPool()
{
    poolDic.Clear();
    poolObj = null;
}

因为下一次重新使用对象池时,需要重新建立池的布局结构。

十四、PoolData.cs

复制代码
using System.Collections;

using System.Collections.Generic;

using UnityEngine;

  

public class PoolData

{

    private readonly GameObject objRoot;//抽屉------父物体

    public Stack<GameObject> objStack=new ();//抽屉中所有保留的对象们

    public int Count=>objStack.Count;//抽屉中的对象数量

  

    public PoolData(GameObject poolRoot, string poolName)

    {

        if (PoolMgr2.Instance.IsOpenLayout)

        {

            objRoot = new GameObject(poolName);

            objRoot.transform.SetParent(poolRoot.transform);

        }

    }

    public GameObject Pop()

    {

        GameObject obj;

        obj=objStack.Pop();

        obj.SetActive(true);

        if (PoolMgr2.Instance.IsOpenLayout)

        {

            obj.transform.SetParent(null);

        }

        return obj;

    }

  

    public void Push(GameObject obj)

    {

        objStack.Push(obj);

        obj.SetActive(false);

        if (PoolMgr2.Instance.IsOpenLayout)

        {

            Debug.Log("保存到缓存池中");

            obj.transform.SetParent(objRoot.transform);

        }

    }

}

十五、PoolMgr.cs

复制代码
using System.Collections;

using System.Collections.Generic;

using UnityEngine;

  

public class PoolMgr2 : Singleton2<PoolMgr2>

{

    private PoolMgr2()

    {

    }  

  

    private Dictionary<string,PoolData> poolDic=new ();

    private  GameObject poolRoot;

    public bool IsOpenLayout=true;

    public GameObject GetObj(string name)

    {

        GameObject obj;

        //存在抽屉并且有对象

        if (poolDic.ContainsKey(name) && poolDic[name].Count > 0)

        {

            obj=poolDic[name].Pop();

        }

        else

        {

            obj=GameObject.Instantiate(Resources.Load<GameObject>(name));

            obj.name=name;

        }

        return obj;

    }

  

    public void PushObj(GameObject obj)

    {

       if (obj == null)

            return;

        // 第一次需要布局时创建总根对象

        if (IsOpenLayout && poolRoot == null)

        {

            poolRoot = new GameObject("Pool");

        }

  

        // 没有对应的子池就创建

        if (!poolDic.TryGetValue(obj.name, out PoolData poolData))

        {

            poolData = new PoolData(poolRoot, obj.name);

            poolDic.Add(obj.name, poolData);

        }

        poolData.Push(obj);

    }

  

    public void ClearPool()

    {

        poolDic.Clear();

        poolRoot = null;

    }

}
相关推荐
格林威1 小时前
C# 图像异步落盘存储:基于Channel 配合 ArrayPool 实现异步落盘
开发语言·人工智能·数码相机·机器学习·计算机视觉·c#·视觉检测
深圳市益普科技有限公司1 小时前
半导体MES的数字孪生:虚拟调试如何把上线风险提前清零
java·开发语言
低代码布道师1 小时前
外包项目管理平台全栈实战课02搭建数据底座:Next.js + PostgreSQL + Prisma 7
开发语言·javascript·postgresql·全栈开发
格林威1 小时前
C# 图像使用AVX2指令集:使用OpenCvSharp实现字节图像解压缩速度和map_image算子速度提升
开发语言·图像处理·人工智能·计算机视觉·c#·视觉检测·工业相机
(Charon)1 小时前
【C++】网络缓冲区设计(四):epoll + Reactor中Buffer的完整读写流程
开发语言·c++
西西弗Sisyphus2 小时前
Qt 实现一个 Windows 启动项查看器
开发语言·windows·qt
FfHUCisI2 小时前
Golang 函数调用的瞬间:栈帧、拷贝栈与逃逸分析
开发语言·后端·golang