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;
}
}
我的疑问
就是原本我也没想到这个,但是我看的时候有弹幕提到:
频繁的切换父子关系会不会对性能也会造成一定的影响呢?