对象池解决的问题:1.内存碎片化.,2.减少实例化和销毁带来的开销,3.减少GC回收机制
应用场景:在unity中要知道创建游戏对象和销毁对象都是需要占用资源的,如果当你需要频繁创建和销毁对象的时候(比如英雄联盟的小兵,如果一局游戏时间很长的话,就会一直创建小兵和销毁小兵,就会造成资源的浪费),这个时候就需要引入对象池的概念,比如吃饭用筷子,如果你家用一次性筷子,那么每一次吃饭就需要去买一次性筷子(创建对象),买筷子需要花钱(消耗资源),吃完后就会把一次性筷子扔了(销毁对象),这样就会浪费钱(浪费资源)。对象池可以把它想象成你家吃饭的筷子篓,筷子篓(对象池)里面存在一些筷子(对象),在需要的时候就从筷子篓里面拿筷子(从对象池里面取出对象),吃完了需要清洗筷子(对象数据还原),洗完后有放回到筷子篓(将对象放回筷子篓),这样就不会造成资源的消耗
举个例子:创建,销毁的方式创建出很多球,可以用Instantiate()方法创作出球,然后在球上面挂载 Destroy(gameObject,3f)函数,就可以达到创建球三秒后自动销毁的效果,因为比较简单这里就不具体展示代码
大概效果就是这样
接下来使用对象池,一般对象池需要有这几个方法,1.获取一个对象,2.回收一个对象,3.初始化方法(预先生成一些对象供使用),第3个方法也可以不一定要写,等到需要使用对象池的时候再往对象池里面添加对象也可以
这里就大概需要三个脚本,一个对象脚本,一个对象池的脚本,一个创建对象放在对象池的脚本
对象池脚本:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ObjectPool : MonoBehaviour
{
public GameObject Object;//首先要知道对象池里面存放的对象
public int ObjecNumber = 100;//初始化对象数量
private Queue<GameObject> pool = new Queue<GameObject>();//用队列来做对象池,也可以用list
public Transform PoolTransform;//对象池的位置,用来存放对象
public static ObjectPool instance { get; private set; }//使用单例模式来调用对象池
private void Awake()
{
instance = this;
}
private void Start()
{
InitPool();
}
//初始化对象池,再对象池中存放一定数量的对象
void InitPool()
{
GameObject Temp;
for(int i=0; i< ObjecNumber; i++)
{
Temp= Instantiate(Object, PoolTransform);//生成一个对象
pool.Enqueue(Temp);//将对象放在对象池中
Temp.SetActive(false);
}
}
//对象池,对外提供的方法
//获取一个对象
public GameObject GetObject()
{
GameObject temp;
if(pool!=null&&pool.Count>0)//这里是判断对象池存在且对象池里面有对象
{
temp= pool.Dequeue();
temp.SetActive(true);
return temp;
}
else//如果池子里面没有对象了
{
temp = Instantiate(Object);
return temp;
}
}
//放回对象,将对象放入对象池中
public void ReturnObject(GameObject Object)
{
Object.SetActive(false);
pool.Enqueue(Object);//入队
}
}
创建对象放在对象池的脚本:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CreatObjectAtPool : MonoBehaviour
{
void Update()
{
GameObject Object = ObjectPool.instance.GetObject();//从对象池中取出
}
}
对象脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ObjectAtPool : MonoBehaviour
{
private void OnEnable()//!!主要这里一定要在OnEnable的声明周期函数中调用放回对象池的操作,因为控制对象是靠对象的激活与失活
//如果在start中就只会在创建的第一次才会调用延时后再放回,当第二次激活后就不会再调用了
{
StartCoroutine(HideObject());
}
private void OnDisable()
{
//这一步是清理对象数据的工作,放在对象失活函数中,那就是还原数据的状态,这里就把对象的位置还原
this.gameObject.transform.position=ObjectPool.instance.PoolTransform.position;
}
IEnumerator HideObject()//隐藏对象
{
yield return new WaitForSeconds(3);
ObjectPool.instance.ReturnObject(this.gameObject);//将对象放回对象池
}
private void Start()
{
if (this.gameObject.transform.parent==null) //这一步是为了销毁超出对象池,通过实例化创建出来的对象
{
Destroy(gameObject,3f);
}
}
}
这是使用自定义的对象池,在unity2021以上的版本中,有自带的对象池功能,引入命名空间using UnityEngine.Pool;即可使用,同样,使用unity自带的对象池也最好需要创键三个脚本,一个对象脚本,一个对象池脚本,一个生成对象脚本
这里重点介绍对象池脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Pool;
public class UnityObjectPool : MonoBehaviour
{
public ObjectPool<GameObject> pool;//使用unity自带的对象池
public GameObject ObjectAtUnityPool;//对象池中的对象
public Transform UnityObjectPoolTrans;
bool ReturnCheck=false;
private int defaultCapacity = 10;
private int maxSize = 1000;
public static UnityObjectPool instance { get; private set; }
private void Awake()
{
instance = this;
}
// Start is called before the first frame update
void Start()
{
CreatPool();
}
void CreatPool()//创建对象池
{
//CreatObject是一个委托,它与defaultCapacity就会一起初始化对象池,它往对象池创造对象时调用的方法
//GetObject是一个委托,从对象池获取对象
//ReturnObject是一个委托,将对象放回对象池
//DestroyExtraObject是一个委托,这是方法是如果创造的对象超出了对象池的最大容量,应该怎么销毁额外的对象
//ReturnCheck是一个布尔变量,返回放回对象时,true就会对对象池中是否已近存在该对象进行检测,避免重复放回
//defaultCapacity默认容量,初始对象池中对象的个数
//maxSize最大容量
pool = new ObjectPool<GameObject>(CreatObject, GetObject, ReturnObject, DestroyExtraObject, ReturnCheck, defaultCapacity, maxSize);
}
GameObject CreatObject()//创建对象的函数
{
GameObject Object = Instantiate(ObjectAtUnityPool, UnityObjectPoolTrans);
Object.transform.position = UnityObjectPoolTrans.position;
return Object;
}
void GetObject(GameObject Object)//从对象池中取对象要执行的事就只需要将该对象设置为激活状态
{
Object.gameObject.SetActive(true);
}
void ReturnObject(GameObject Object)//将对象放回对象池
{
Object.gameObject.SetActive(false);
}
void DestroyExtraObject(GameObject Object) //销毁额外的对象
{
Destroy(Object, 3f);
}
}
可以看见与自己写对象池最大的不同就是代码显得简洁,不用自己写出队入队操作,只需要写在
1.拿出对象池的时候需要做什么,
2.放回对象池的时候需要做什么,
3.创造对象的时候需要做什么和
4.销毁额外的对象的时候需要做什么还有一些参数即可,
不过需要注意,从对象池取对象的操作是pool.Get(),放回操作是pool.Release(GameObject Object);为什么取出来不需要参数,而放进去需要传入对象呢,因为取出来是从对象池里面直接拿出来,不需要告诉取谁(队列先进先出的思想,取排在前面的)所以不用传参数,但是放回去就需要知道到底要把哪一个对象放回到对象池里面。具体可以参考自己写的对象池的取对象方法
另外再附上
对象脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ObjetcAtUnityPool : MonoBehaviour
{
private void OnEnable()
{
StartCoroutine(HideObject());
}
private void OnDisable()
{
//还原数据
this.gameObject.transform.position = gameObject.transform.parent.position;
}
IEnumerator HideObject()
{
yield return new WaitForSeconds(3);
UnityObjectPool.instance.pool.Release(this.gameObject);//放回对象池
}
}
创造对象
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CreatObjectAtUnityPool : MonoBehaviour
{
// Update is called once per frame
void Update()
{
UnityObjectPool.instance.pool.Get();//从对象池中取对象,,如果对象池中没有对象了就会先创建对象,再取出来
}
}
等以后能够熟练使用对象池了再来提炼总结