1、理解
对象池用于减少内存开销,其原理就是把可能用到到的对象,先存在一个地方(池),要用的时候就调出来,不用就放回去。而不是要用的时候创建,不用的时候销毁。
Eg:
我有个飞机,射击子弹,按传统的方法就是,创建子弹,子弹击中目标或出界等销毁子弹。就是不断的创建与销毁,要知道创建和销毁是要消耗许多内存以及时间的。如果把子弹存在一个地方(池),需要子弹时,就从里面拿出来,不需要的时候放回去。
如果还有多个飞机,可以公用一个子弹库。
这个方法虽然一直保持着存储子弹空间的一个最大值,但相比不断创建与销毁的代价,是非常值得考虑的。
而实际开发中,可以按照需要适当放大或缩小池大小。
对象池模式并不是游戏开发独有的设计模式,它的设计思路与其他开发中的数据库连接池、线程池的思路等是一样的。
其核心思想是,使用完不直接删除,而是将其放回池子里,需要用的时候再取出来。 对象池模式的出现主要优化两点:
1、防止对象被频繁的创建和删除,从而内存抖动、频繁GC(垃圾回收)
2、对象初始化成本较高
2、操作
下面用文字的方式简介对象池的基本操作
借用: 通俗点讲就是从池中获取物体,如果是第一次获取物体要初始化池。 如果池中没有想要的物体了,则创建一个该对象
归还: 通俗点讲就是物品用完了原本是要删除的,但是应用了对象池之后则是把物体归还到池内,前提是池中数量是不大于预设的最大数量的(防止太多内存炸了),如果池中数量已经大于了预设的最大数量,则直接删除
预热: 就是预加载一定数量的对象,我个人认为这是对象池中比较精髓的部分之一。如果不做预热的,那么第一次创建对象的时候还是直接涉及初始化问题。一个很容易懂道理是玩家宁愿在加载界面多等1秒,也不会愿意在游戏中卡顿0.1秒,特别是竞技类的游戏,玩家会想砸电脑的(笑)。所以我觉得如果不做预热的对象池优化只做了一半。
缩小: 差不多就像是预热反着来,上面在归还的时候说如果大于了设定的数量阈值就不返回池中而是直接删除,但实际上删除也有可能会带来时间成本,所以我们可以先不删除,在每次游戏中途的过关之类的加载界面的时候再删除缩小内存池。如果怕在加载界面之前内存爆了的话可以多设置一个必须删除的阈值,其作用跟上面归还时写的一样。(该功能我在我的DEMO当中没有做)
重置: 每个新拿出来的物体应该和新创建的一样时“崭新”的,不能明显带有上次使用过的状态,因此再每次物体出池的时候要对可能存在后效性的地方重置。在unity中则是在物体的OnEnable()中写物体手动初始化的内容,包括清空刚体的力等等,OnEnable()和Start()的区别就是Start()只在物体第一次启用的第一帧运行,OnEnable会在每次物体重新启用的时候运行。
对象池管理器
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 对象池管理器
/// </summary>
public class PoolTest : MonoBehaviour
{
/*
对象池的核心是集合
用栈搞一个集合
public Stack<GameObject> stack = new Stack<GameObject>();
也可以用list搞,比较简单
*/
public List<GameObject> list = new List<GameObject>();
//游戏预制体
public GameObject GoPrefab;
//对象池中的最大个数
public int MaxCount=10;
//对象放入对象池
public void Push(GameObject go)
{
//如果池子还能装,就往里面加,满了就不往里面加
if (list.Count < MaxCount)
{
list.Add(go);
}
else
{
Destroy(go);
}
}
//从对象池中取出对象(返回值就要标记为GameObject)
public GameObject Pop()
{
if (list.Count>0)//判断列表不为空
{
GameObject go = list[0];//如果有的话,就把第一项取出来
list.RemoveAt(0);//在list中把第一项删除
return go;
}
return Instantiate(GoPrefab);//没有的话就直接创建一个新的预制体
}
//删除对象池
public void Clear()
{
list.Clear();
}
}
测试代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Test : MonoBehaviour
{
//用来管理没有存放在对象池中的预制体
private List<GameObject> list = new List<GameObject>();
void Update()
{
//创建对象
if (Input.GetKeyDown(KeyCode.A))
{
GameObject go = GetComponent<PoolTest>().Pop();
list.Add(go);
go.SetActive(true);
}
//删除对象
if (Input.GetKeyDown(KeyCode.S))
{
if (list.Count>0)//判断当前场景中是否还有对象
{
GetComponent<PoolTest>().Push(list[0]);
list.RemoveAt(0);
list[0].SetActive(false);
}
}
}
}
将两个脚本挂到同一个空物体上
运行项目后我们可以很清楚的看到,创建出啊来的预制体被点击S键删除并没有被销毁掉,而是被放到了对象池中