Unity对象池的学习
Unity对象池的学习
技术概述
对象池在Unity3D中是极为重要的技术,在遇到需要大量重复创建、销毁的对象时,对象池可以将其存放入池中,反复利用,从而尽可能的重复使用内存中驻留的资源。
技术详述
对象池的典型用法就是射击游戏中的子弹。在不使用对象池时,子弹的“一生”是这样的:创建→产生作用→销毁;而使用对象池后,子弹的“一生”是这样的:从对象池中取出使用→产生作用→存放进对象池→从对象池中取出使用···
显而易见的,该方法能避免重复对象的创建、销毁过程,节省内存空间的使用。以下为一个子弹对象池的大致创建过程
首先是对象池创建
//prefabPool = new PrefabPool(Resources.Load<Transform>("xxx"));加载本地预制
//prefabPool.cullDespawned = true;自动清理对象池
public static BulletPool bullet;//该部分用于初始化此对象池的配置
public GameObject bulletObj;
public int pooledAmount = 5;//初始化对象池中对象数量
public bool lockPoolSize = false;//取消锁定对象池大小
private List<GameObject> pooledObjects;//创建对象池链表
private int currentIndex = 0;
void Awake()
{
bullet = this;//实例化对象池
}
在start()中初始化对象池链表
void Start()
{
pooledObjects = new List<GameObject>();
for (int i = 0; i < pooledAmount; ++i)
{
GameObject obj = Instantiate(bulletObj);//创建子弹对象
obj.SetActive(false);//将子弹对象的激活状态Active设置为false
pooledObjects.Add(obj);//将子弹存放入对象池中
}
}
调用该子弹对象池中的可用子弹
public GameObject GetPooledObject()
{
for (int i = 0; i < pooledObjects.Count; ++i)//遍历对象池以寻找可用子弹
{
//从上一次调用的子弹的下一个开始寻找
//例如上一次发射的子弹是对象池中序号为2的子弹,则本次调用对象池中子弹时从3开始检查是否可用
int temI = (currentIndex + i) % pooledObjects.Count;
if (!pooledObjects[temI].activeInHierarchy)//检查该对象的Active状态
{
currentIndex = (temI + 1) % pooledObjects.Count;
return pooledObjects[temI];//如果Active为false则返回调用该对象
}
}
if(!lockPoolSize)//若没有false状态的子弹供我们使用,则生成新的对象并加入对象池
{
GameObject obj = Instantiate(bulletObj);
pooledObjects.Add(obj);
return obj;
}
return null;
}
遇到的问题和解决方案
在对象池中寻找可用对象时,最初只通过遍历进行地毯式搜索。在实际使用时,经过频繁的调用后,会产生略微影响体验的卡顿,一开始以为是对象过多,机器的机能限制,后来查看到了一篇文章,指明了这一问题产生的原因。
在寻找可用对象时,如果每次遍历都从头开始,如果对象池极大,且先前的对象仍然处于激活状态,我们将需要大量时间用来无谓的遍历,导致卡顿。因此,在代码中记录之前使用的对象序号,并从序号记录的下一个对象开始查找可用对象。
这一方法可以极大程度改善因遍历而浪费的机能、时间,对游戏性能进行优化。
总结
对象池是Unity中对性能优化极为重要的技术。在CPU、内存并非充满"Power"的情况下,对象池是让硬件的无谓重复尽可能降到最低,使有限的硬件资源用在最需要的地方,达到优化效果。
个人看来,对象池最大的优点在于复用、预载这两方面。
复用是对象池的典型特征、灵魂所在;而预载方面,作为玩家试想一下,玩家是愿意在加载界面多花1秒钟,还是在激烈战斗时突然卡顿0.1秒。结果是显而易见的。