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

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