缓存池是 我在游戏构件中 用的比较多的一个框架模型,基于以下几点原因。
1、游戏性能的考虑。
在unity里,我们有大量的 生成(Instantiate)和 销毁(Destroy)物体的操作,在我们内存中,Destroy 了一个物体后,他虽然消失了,但是内存里还有着他的引用。
这样,当这样的操作不断进行,最后内存会越来越满,直接造成游戏卡顿等现象,这是十分不可取的。
2、框架包装,简单方便的调用。
如前几个模块一样, 缓存池包装 完毕后,也就是一两行代码,就能进行一个 小功能的 实现。
3、案例。
比如说 发射子弹类的 游戏。
我们来关注子弹。
子弹的使命 是生成出来,发射到目标,然后销毁。
通常一个 射击类 游戏不断的 会重复 上述操作,如过我们单纯的使用 Destroy 函数 绝对是不可取的,因为内存会被不断的占用。
通常我们会用 缓存池,来缓存我们的子弹,达到这样的效果:
1、 物体 需要销毁时 ,我们将他失活,存放起来。
2、物体 再次 需要被使用时,将他 激活,再次使用。
简而言之,缓存池起到了 废物利用 的作用,而这个 废物利用的 过程 非常快(相较于 重新 加载这个资源物体),不但提升了游戏性能, 也简便了 代码的 书写。
1、面向对象思想。
2、运用时,以 lambda 表达式为主
3、前面的资源加载模块 (要会用就行)
加载物体 调用 资源加载模块 的包装函数,有同步、异步加载。同时我们注意 回调函数(lambda表达式来实现) 的联系,来处理加载完毕后物体的操作。
缓存池不销毁(Destroy)物体,而是使物体失活,等到它再要被使用的时候,再把它激活,放到需要使用的地方。
而我们要处理的是怎么存放 失活的 物体,我们将他放到管理的 GameObject 下面,像文件夹一样存放即可,如下图所示:
我们新建C#脚本 ObjPool
public Class PoolData()
{
public GameObject fatherObj; // 文件夹 名字
public List<GameObject> poolList; //存放 一样的 物体
}
构造函数,根据上图示意,传递的是 该物体,以该物体的 名字做一个文件夹,文件夹 的 父对象就是我们的 池子:
public PoolData(GameObject objName,GameObject poolObj)
{
fatherObj = new GameObject(ObjName.name);
fatherObj.transform.parent = poolObj.transform;
//初始化 List
poolList = new List<GameObject>();
// 将 objName 这个物体 加进list
PushObj(objName);
}
功能函数,添加 和 移除 物体 :
public void PushObj(GameObject obj)
{
poolList.Add(obj);
obj.transform.parent = fatherObj.transform;
//首先失活 隐藏
obj.SetActive(false);
}
public GameObject GetObj()
{
GameObject obj = poolList[0];
// list里的物体都是一样的
poolList.RemoveAt(0);
obj.SetActive(true);
obj.transform.parent = null;
return obj;
}
成员有个 大文件夹,借用字典存放物体:
private GameObject poolObj;
public Dictionary<string,PoolData> poolDic =
new Dictionary<string, PoolData>();
接下来是 功能函数:
加载物体(生成),我们重载了一个 异步加载:
// 同步加载
public GameObject GetObj(string name)
{
GameObject obj = null;
if(poolDic.ContainsKey(name) && poolDic[name].poolList.Count>0)
{
obj = poolDic[name].GetObj();
}
else
{
//调用资源加载模块 加载物体
obj = ResMgr.GetInstance().Load<GameObject>(name);
obj.name = name; //命名 同 文件夹名字 一样
}
return obj;
}
//异步加载
public GameObject GetObj(string name,UnityAction<GameObject> callback)
{
GameObject obj = null;
if(poolDic.ContainsKey(name) && poolDic[name].poolList.Count>0)
{
//注意 异步加载 的回调函数
callback(poolDic[name].GetObj());
}
else
{
ResMgr.GetInstance().LoadAsync<GameObject>(name,(o)=>
{
//命名 同 文件夹名字 一样
o.name = name;
callback(o);
});
}
return obj;
}
接下来是 缓存 物体:
public void PushObj(string name,GameObject obj)
{
if (poolObj == null)
poolObj = new GameObject("Pool");
if (poolDic.ContainsKey(name))
{
poolDic[name].PushObj(obj);
}
else
{
poolDic.Add(name, new PoolData(obj,poolObj));
}
}
清空 缓存池:
///
/// 清空 缓存池
///
public void Clear()
{
poolDic.Clear();
poolObj = null;
}
下面是完整代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace ns
{
///
/// 缓存池 对象
///
///
///
public class PoolData
{
// 01-抽屉 对象挂载的父节点 文件夹/抽屉
public GameObject fatherObj;
// 02-存储容器 存放obj
public List<GameObject> poolList;
///
/// 构造函数, 将 obj 装进 poolobj里面
///
/// 小物体
/// 大池子
public PoolData(GameObject obj,GameObject poolObj)
{
//给抽屉 创建一个 父对象, 并且把他 作为我们 pool对象的子物体
fatherObj = new GameObject(obj.name);
fatherObj.transform.parent = poolObj.transform;
poolList = new List<GameObject>() { };
PushObj(obj);
}
///
/// 将一个 active 的obj disActive 放进抽屉(fatherOBj)
///
///
public void PushObj(GameObject obj)
{
poolList.Add(obj);
obj.transform.parent = fatherObj.transform;
//首先失活 隐藏
obj.SetActive(false);
}
///
/// 从 抽屉里面 取东西, 再激活
///
///
public GameObject GetObj()
{
GameObject obj = poolList[0];
poolList.RemoveAt(0);
obj.SetActive(true);
obj.transform.parent = null;
return obj;
}
}
///
///缓存池模块
///
public class ObjPool: BaseManager<ObjPool>
{
//衣柜
public Dictionary<string, PoolData> poolDic = new Dictionary<string, PoolData>();
private GameObject poolObj;//存放所有缓存物体 的 大文件夹
///
/// 获取物体
///
/// resources 里的路径
///
public GameObject GetObj
(string name, UnityEngine.Events.UnityAction<GameObject> callback)
{
GameObject obj = null;
if(poolDic.ContainsKey(name) && poolDic[name].poolList.Count > 0)
{
//obj = poolDic[name].GetObj();
callback(poolDic[name].GetObj());
}
else
{
ResMgr.GetInstance().LoadAsync<GameObject>(name, (o) =>
{
o.name = name;
callback(o);
//return o;
}
);
}
return obj;
}
public GameObject GetObj(string name)
{
GameObject obj = null;
if(poolDic.ContainsKey(name) && poolDic[name].poolList.Count > 0)
{
obj = poolDic[name].GetObj();
}
else
{
obj = ResMgr.GetInstance().Load<GameObject>(name);
obj.name = name;
}
return obj;
}
public void PushObj(string name,GameObject obj)
{
if (poolObj == null)
poolObj = new GameObject("Pool");
if (poolDic.ContainsKey(name))
{
poolDic[name].PushObj(obj);
}
else
{
poolDic.Add(name, new PoolData(obj,poolObj));
}
}
///
/// 清空 缓存池
///
public void Clear()
{
poolDic.Clear();
poolObj = null;
}
}
}
有了上述 框架后,我们可以 在这基础上进行 更多的延申。
如经常被使用的需求: 某一特效 ,想让他 出现 几秒 后消失(缓存 进 缓存池),我们可以附加如下脚本:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace ns
{
public class TXInPool : MonoBehaviour
{
// 公共变量 可以在 Unity Editor 界面 设置 值
public float time;
void OnEnable()
{
StartCoroutine(Push(time));
}
//延迟 time 秒 后 缓存进缓存池
public IEnumerator Push(float time)
{
yield return new WaitForSeconds(time);
ObjPool.GetInstance().PushObj(this.name, this.gameObject);
StopCoroutine(Push(time));
}
}
}
.
.
.
.
本文学于: [B站视频链接](https://www.bilibili.com/video/BV1C441117wU?from=search&seid=12140778858974302451) 可能视频有少许不全...写博客是为了将视频里的内容转化为可视化文本,省去看视频的麻烦。 同时将经验累积下来,方便日后观看。
感谢