在Untiy中,如果在短时间内有大量GameObject被创建销毁,会造成性能下降,游戏卡顿。
根据线程池的思想,我们可以建立一个对象池来暂存暂时不用的GameObject,要用时再从对象池取出,实现GameObject的复用,减少创建,销毁。
以最近项目中的弹幕池为例。
要点:
1、编写弹幕池类,其拥有一个队列作为池来保存对弹幕GameObject的引用。
2、将该类设为单例,避免多次创建,管理混乱。
3、场景中设有作为弹幕根的Empty GameObject,将不用的弹幕设为其孩子,方便调试时查看。
using UnityEngine;
using System.Collections.Generic;
public class BarragePool
{
private static BarragePool _instance = new BarragePool();
//对象池的真正本体,一个存放弹幕GameObject的队列(也可以用其他容器实现)
Queue<GameObject> pool = new Queue<GameObject>();
//暂存未激活的不用的弹幕GameObject的场景内对象(需要在场景中预设)
GameObject PoolRoot;
//BarragePool作为单例,这是一种简单的实现方式,更高级的实现方式可以参考我另一个博客。
public static BarragePool Instance
{
get
{
if (_instance == null)
{
_instance = new BarragePool();
}
return _instance;
}
}
/// <summary>
/// 获取弹幕GameObject
/// </summary>
/// <returns></returns>
public GameObject GetBarrage()
{
GameObject result = null;
//若池中没有弹幕对象
if (pool.Count == 0)
{
//使用"Prefabs/Barrage"的弹幕预设体
GameObject prefab = Resources.Load<GameObject>("Prefabs/Barrage");
if (prefab != null)
{
//创建弹幕GameObject
result = GameObject.Instantiate(prefab);
}
else
{
//如果未找到"Prefabs/Barrage"的预设体,则报错
Debug.LogError("load >Prefabs/Barrage< failed!");
return null;
}
}
else
{
//从池中取出已有的弹幕GameObject
result = pool.Dequeue();
}
//激活弹幕GameObject
result.SetActive(true);
return result;
}
/// <summary>
/// 回收弹幕GameObject,并放入场景中BarragePoolRoot下
/// </summary>
/// <param name="barrage"></param>
public void ResetBarrage(GameObject barrage)
{
//将回收的弹幕设为未激活
barrage.SetActive(false);
//PoolRoot初始化
if(PoolRoot == null)
{
PoolRoot = GameObject.Find("BarragePoolRoot");
//如果场景中没有BarragePoolRoot就报错
if (PoolRoot == null)
{
Debug.LogError("Cannot find 'BarragePoolRoot'!");
}
}
//放回PoolRoot下
barrage.transform.SetParent(PoolRoot.transform);
//收入池中
pool.Enqueue(barrage);
}
}
以上的对象池实现对普通的游戏够用了。
如果有更高要求,可以参考线程池的设计:
1、给池加大小限制,超出大小时,拒绝创建新对象,用于维持性能。然后可以设置阻塞队列让被拒绝的“任务”阻塞等待(这个貌似在Untiy这种环境下是不行的),或者直接报错。
2、如果对象有很多初始化的工作(不仅仅是根据预设体创建对象),或者要求扩展性,就去实现对象工厂。
3、给对象设置生存时间,以免长时间不用而空占资源。或者提供销毁方法,调用时销毁一定数量的不用的对象。
4、提供对象池的关闭方法,等待所有阻塞的“任务”执行完后销毁池中对象和池。
5、使用泛型编写对象池,使其能够通用。