Unity 性能优化之写个简单的缓存池

2016/11/10 Unity

​ 在公司看源码的时候,发现一个投篮的场景中,玩家把篮球不断投出去,然后不断的创建和销毁对象。于是试着写个缓存池来管理。

​ 有个叫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实例化对象慢的问题。