Unity 性能优化之写个简单的缓存池
November 10, 2016 在公司看源码的时候,发现一个投篮的场景中,玩家把篮球不断投出去,然后不断的创建和销毁对象。于是试着写个缓存池来管理。
有个叫PoolManager的工具类插件可以很方便的实现,可以看这里的介绍Unity3D研究院之初探PoolManager插件。
但是其实对象池就是预先创建一点对象,当我们需要用的时候,去拿就行了。如果没有,再创建。使用完毕后也并不销毁,方便下次使用。有点类似于Android中的listview的holder。看起来并不难,花了点时间写了个。
BufferPoolList.cs
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class BufferPoolList{
private List<GameObject> pool;
private GameObject prefab;
private Transform prefabParent;
public BufferPoolList(GameObject obj, Transform parent, int count)
{
prefab = obj;
pool = new List<GameObject>(count);
prefabParent = parent;
for (int i = 0; i < count; i++)
{
GameObject objClone = GameObject.Instantiate(prefab) as GameObject;
objClone.transform.parent = prefabParent;//为克隆出来的子弹指定父物体
objClone.name = "Clone0" + i.ToString();
objClone.SetActive(false);
pool.Add(objClone);
}
}
public GameObject GetObject()
{
//遍历缓存池 找空闲的物体
foreach (GameObject iter in pool)
{
if (iter.activeSelf == false)
{
iter.transform.SetParent(prefabParent);
iter.SetActive(true);
return iter;
}
}
GameObject newPrefab = GameObject.Instantiate(prefab) as GameObject;
newPrefab.transform.SetParent(prefabParent);
newPrefab.name = "Clone0" + pool.Count.ToString();
newPrefab.SetActive(true);
pool.Add(newPrefab);
return newPrefab;
}
}
在Player上的脚本或者控制类脚本上初始化之后,需要使用我们初始化的物体时,只需要GetObject()即可,用完将物体设置不可见即可。可以在实例化的物体上判断物体是否超出屏幕边界,超出 则 .SetActive(false);设置物体不可见。
这样写虽然不用每次都创建对象,缓存池的目的算是达到了,但是每次获取可用对象时都去做个循环,总感觉怪怪的。所以换种写法,不用list,改用 Queue 队列来写。代码如下:
BufferPool.cs
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class BufferPool
{
private Queue<GameObject> pool;
private GameObject prefab;
private Transform prefabParent;
//使用构造函数构造对象池
public BufferPool(GameObject obj,Transform parent,int count)
{
prefab = obj;
pool = new Queue<GameObject>(count);
prefabParent = parent;
for (int i = 0; i < count; i++)
{
GameObject objClone = GameObject.Instantiate(prefab) as GameObject;
objClone.transform.parent = prefabParent;//为克隆出来的子弹指定父物体
objClone.name = "Clone0" + i.ToString();
objClone.SetActive(false);
pool.Enqueue(objClone);
}
}
public GameObject GetObject()
{
GameObject obj = null;
if (pool.Count > 0)
{
obj = pool.Dequeue(); //Dequeue()方法 移除并返回位于 Queue 开始处的对象
obj.transform.position = prefabParent.position;
}
else
{
obj = GameObject.Instantiate(prefab) as GameObject;
obj.transform.SetParent(prefabParent);
}
obj.SetActive(true);
return obj;
}
//回收对象
public void Recycle(GameObject obj)
{
obj.SetActive(false);
pool.Enqueue(obj);//加入队列
}
}
这样获取对象时,就不用做循环了。每次使用时,出列。实例化的物体不再使用时,再让他加入队列。这样也有个不方便的地方,需要在游戏物体的脚本上,拿到 Player上的 BufferPool.cs 脚本 ,来回收对象,因为获取对象时,已经把该实例给移出队列了,所以当不再使用时,必须调用Recycle方法来将其加入到队列中去。
虽然一个用的List ,一个用的 Queue,但是道理是一样的。目的也都是为了解决Unity实例化对象慢的问题。