Unity游戏框架学习笔记——02缓存池

Unity游戏框架学习笔记——02缓存池

缓存池

一如既往指路牌:https://www.bilibili.com/video/BV1C441117wU?p=3.

缓存池,一般也称是缓冲池、对象池等等,顾名思义是一个用于存放对象的池子。
解决的是重复生成和销毁某一类对象,导致频繁GC(Garbage Collection)以至于游戏过程中卡顿的问题。

原理

将频繁生成和销毁的对象,在生成,然后经过一系列操作之后,要进行销毁时,不将其销毁,而是将其隐藏,保存在场景中。
在下一次需要生成该类对象时,并不重新实例化,而是直接在缓存池内取出,将其重新激活,显示在场景中。
对于缓存池内部,在第一次使用需要频繁创建和销毁的对象时,先判断缓存池内有无失活的对象,若无则创建一个用于存储该对象的池子。
缓存的概念其实是:对于不用的对象,失活隐藏在场景中,那么对与下一次的同类对象创建来说,就相当于是一种预生成的缓存辣。

数据结构

根据上述的特点,需要缓存池功能是:
区分对象的类别的标识
存储这些对象的空间

那么可以想到的是缓存池的数据结构,应该是:字典Directory<key, value>
关键字是对象的分类,或者是该类对象的名称 String
键值是存储 GameObject 类型的列表List< GameObject >

缓存池特性

根据上述的描述,不同的对象有其对应的空间来存储,但管理这些池子的类只能有一个,而不是各个不同的对象有相应对象池的类。
因此,缓存池在场景里面只能有一个,池子里面存储则各种不通过类型的缓存对象,那么缓存池就是具有单例的特性嘛。

缓存池的优化

为了便于查看场景中不同对象的使用情况,在对象失活,存入缓存池时,不仅仅是归入场景中的Pool空物体的子物体,还添加一个和该类对象同名的父物体来挂载失活的对象用以分类,即Pool->对象同名空物体->对象

可知,对象同名空物体是作为对象的父物体存在,那么在对象回收的时候,需要找到对应的父物体然后构建父子关系,因此对象和父物体应该存在对应关系,那么我么不妨把父物体和对象池封装在一起作为一个类,来实现再分类。

那么对应的数据字典也应该相应做出修改:
原来是Directory< String, List< GameObject > >
修改为Directory< String, SubPool >

SubPool 子对象池要包含:
回收时要挂载的父物体
相应的子对象池存储

代码

SubPool类

/// <summary>
/// 子对象池类
/// </summary>
public class SubPool
{
    // 子对象池的根对象
    public GameObject fatherObj;
    // 子对象池里的list,存储失活缓存对象
    public List<GameObject> poolList;

/// <summary>
/// 构造函数
/// 当归还对象但是子对象池不存在的时候时调用以初始化
/// </summary>
/// <param name="subObj">要归还的子对象</param>
/// <param name="poolObj">pool根对象</param>
    public SubPool(GameObject subObj, GameObject poolObj)
    {
        fatherObj = new GameObject(subObj.name);
        poolList = new List<GameObject>();
        
        // 父对象设置 和 pool 根节点的关系
        fatherObj.transform.parent = poolObj.transform;
        RepayObjectToSubPool(subObj);
    }

    /// <summary>
    /// 将对象放回子对象池中
    /// </summary>
    /// <param name="subObj">归还的子对象</param>
    public void RepayObjectToSubPool(GameObject subObj)
    {
        // 失活以隐藏
        subObj.SetActive(false);
        // 加入子对象池中
        poolList.Add(subObj);
        // 设置父节点
        subObj.transform.parent = fatherObj.transform;
    }

    /// <summary>
    /// 从子对象池中取出对象
    /// </summary>
    /// <returns>取出的对象</returns>
    public GameObject GetObjectFromSubPool()
    {
        // 从子对象池中取出对象
        GameObject gameObj = poolList[0];
        gameObj.name = fatherObj.name;
        //将取出的对象从列表移除
        poolList.RemoveAt(0);
        // 解除父子关系
        gameObj.transform.parent = null;
        // 激活以显示
        gameObj.SetActive(true);

        return gameObj;
    }
}

缓存池类

/// <summary>
/// 缓存池类
/// 单例,用于管理所有的缓存池
/// </summary>
public class PoolManger : Singleton<PoolManger>
{
    // 数据字典
    public Dictionary<string, SubPool> poolDictonary = new Dictionary<string, SubPool>();
    // pool空物体,便于检查
    private GameObject poolObj;

    /// <summary>
    /// 从缓存池中取出name对应子对象池里面的对象
    /// </summary>
    /// <param name="name">子对象池名字</param>
    /// <returns></returns>
    public GameObject GetGameobjectInPool(string name)
    {
        GameObject gameObj;

        //如果缓存池内有名为 name 的子对象池,并且子对象池内有对象
        if (poolDictonary.ContainsKey(name) && poolDictonary[name].poolList.Count > 0)
        {
            // 赋予,从name对应的子对象池中移除
            gameObj = poolDictonary[name].GetObjectFromSubPool();
        }
        // name 子缓存池内没有对象,就实例化一个新的
        else
        {
            gameObj = GameObject.Instantiate(Resources.Load<GameObject>(name));
            // 将新对象命名和对象池一致,便于归还
            gameObj.name = name;
        }
        
        return gameObj;
    }

    /// <summary>
    /// 将游戏对象归还给缓存池
    /// </summary>
    /// <param name="name">对象池名字</param>
    /// <param name="gameObj">归还的游戏对象</param>
    /// <returns></returns>
    public void RepayGameobjectToPool(string name, GameObject gameObj)
    {
        // 将要回收的对象设置父节点
        if(poolObj == null)
        {
            poolObj = new GameObject();
            poolObj.name = "Pool";
        }

        // 字典内不存在name对应的list,就创建出新的对应子对象池
        if (!poolDictonary.ContainsKey(name))
        {
            poolDictonary.Add(name, new SubPool(gameObj, poolObj));
        }
        // 存在 name 的子对象池,调用其对应的回收方法
        else
        {
            poolDictonary[name].RepayObjectToSubPool(gameObj);
        }
    }

    /// <summary>
    /// 清空缓存池内的对象
    /// 用于场景切换时
    /// </summary>
    public void ClearPool()
    {
        poolDictonary.Clear();
        poolObj = null;
    }
}

我的疑问

就是原本我也没想到这个,但是我看的时候有弹幕提到:
频繁的切换父子关系会不会对性能也会造成一定的影响呢?

原创文章 7 获赞 6 访问量 936

猜你喜欢

转载自blog.csdn.net/LAUGINJI/article/details/105921789
今日推荐