什么是对象池?
对象池,简单的说就是一种为了避免重复创建,删除对象的解决方案。它可以通过复用游戏对象,一定程度上提高游戏性能,避免内存消耗,特别针对任何频繁创建删除对象的情景。这符合了性能优化中对脚本优化的理念。是居家旅行...哦不...游戏开发必备技能。
对象池的实现原理?
创建复用对象的集合,通过查找和设置集合元素的active状态,达到和创建、删除一样的视觉效果。
这里先通过一个射击子弹的简单例子,从不用对象池,到给子弹创建对象池,去繁就简的一步步呈现对象池使用过程,深化理解。
不用对象池的小白脚本
Control.cs
...
public class Control:MonoBehaviour{
public GameObject bulletPrb;
void Update(){
if(Input.GetMouseButton(0))
Fire();
}
void Fire(){
Instantiate(bulletPrb);
}
}
Bullet.cs
...
public class Bullet:Monobehaviour{
void Start(){
Invoke("Destroy",2);
}
void Destroy(){
Destroy(gameObject);
}
}
这里很容易的脑补出Hierarchry下,bullet对象一直在创建,销毁,创建,销毁。。。
使用对象池的一阶脚本
Control.cs
...
using System.Collections.Generic;
public class Control:MonoBehaviour{
public GameObject bulletPrb;
public int poolingCapacity = 20;
public List<GameObject> pool;
void Start(){ //创建对象池,并将对象设为不可见
pool = new List<GameObject>()
for(int i=0 ; i<poolingCapacity;i++)
{
GameObject bullet = Instantiate(bulletPrb);
pool.Add(bullet);
pool[i].SetActive(false);
}
}
void Update(){
if(Input.GetMouseButton(0))
Fire();
}
void Fire(){
//Instantiate(bulletPrb,transform.position,Quternion.identity);
for(int i = 0 ; i < pool.Count; i++)
{ //自动搜索对象池中失活的对象并激活显示
if ( !pool[i].activeinHierarchy )
{
pool[i].transform.position = transform.position;
pool[i].transform.rotation = transform.rotation;
pool[i].SetActive(true);
break;
}
}
}
}
Bullet.cs
...
public class Bullet:Monobehaviour{
void OnEnable(){
Invoke("Destroy",2);
}
void Destroy(){
//Destory(gameObject);
gameObject.SetActive(false); //只要隐藏掉就好,对象还在对象池中
}
void OnDisable{
CancelInvoke();
}
}
这样对象池的功能算是实现了。然而它是被构建在控制脚本中的,这样显然不行。这就像编程小白把所有语句写在主函数中一样,这不符合面向对象的思想!因此,需要第三步
使用对象池的二阶脚本
新建一个ObjectBulletPool.cs 并挂载到新建的空物体ObjectPooler上
...
using System.Collections.Generic
public class ObjectPool: MonoBehaviour{
public static ObjectPool current;
public GameObject objectPrb; //将被放入对象池中的预制体
public int poolCapacity = 20;
public List<GameObject> pool;
public bool willgrow = true; //池子容量不够时,是否自动扩展池子
void Awake(){
current = this;
}
void Start(){
for(int i = 0 ; i<poolCapacity ; i++)
{
GameObject obj = Instantiate(objectPrb);
pool.Add(obj);
obj.SetActive(false);
}
}
public GameObject GetPooledObject(){
for( int i = 0 ; i<pool.Count ; i++) //遍历对象池,将未激活的对象传递出去
{
if ( ! pool[i].activeInHierarchy )
return pool[i];
}
if ( willgrow ) //当池子中所有对象都激活了,但是还想激活显示对象时,扩展池子
{
GameObject obj = Instantiate(objectPrb);
pool.Add(obj);
obj.SetActive(false);
return obj;
}
return null;
}
}
简化 Control.cs
...
using System.Collections.Generic;
public class Control:MonoBehaviour{
/*
public GameObject bulletPrb;
public int poolingCapacity = 20;
public List<GameObject> pool;
void Start(){ //创建对象池,并将对象设为不可见
pool = new List<GameObject>()
for(int i=0 ; i<poolingCapacity;i++)
{
GameObject bullet = Instantiate(bulletPrb);
pool.Add(bullet);
pool[i].SetActive(false);
}
}
*/
void Update(){
if(Input.GetMouseButton(0))
Fire();
}
void Fire(){
//Instantiate(bulletPrb,transform.position,Quternion.identity);
/*
for(int i = 0 ; i < pool.Count; i++)
{ //自动搜索对象池中失活的对象并激活显示
if ( !pool[i].activeinHierarchy )
{
pool[i].transform.position = transform.position;
pool[i].transform.rotation = transform.rotation;
pool[i].SetActive(true);
break;
}
}
*/
//获取未激活对象,激活显示
GameObject obj = ObjectPool.current.GetPooledObject();
if (obj == null) return;
obj.transform.position = transform.position;
obj.transform.rotation = transform.rotation;
obj.SetActive(true);
}
}
不用改 Bullet.cs
...
public class Bullet:Monobehaviour{
void OnEnable(){
Invoke("Destroy",2);
}
void Release(){
//Destory(gameObject);
gameObject.SetActive(false); //只要隐藏掉就好,对象“回到”对象池中
}
void OnDisable{
CancelInvoke();
}
}
多说一句,如果你能发射不同类型的子弹怎么办?
解决思路:将子弹类型bulletPrb对象改成数组,在GetPooledObject()函数中传入子弹类型的参数,然后遍历数组匹配子弹类型,return出去。
小结:
这个对象池其实是固定的,一旦创建就不用删除,这样便于复用创建好的对象。使用对象其实就是在对象池中搜索没有active的对象并激活获取到它好进行操作。释放对象就是让他失活,好被以后搜索复用。
Ok,看到这里,对象池的基本用法就掌握了,在实际项目中需求会更加多变,但是万变不离本质。高阶运用也是从低阶进化的!
高阶运用请看这位老兄的文章:unity中的通用对象池
网友评论