Unity实现”对象池管理器“

前言:警告!这可能是坨屎,空闲时间写成,仅作娱乐

在Unity中生成或销毁一个物体会占用较大的资源,如果是制作FPS射击游戏,子弹生成更是雪上加霜。所以我自己写了一个PoolManager,不能和网上的各位大佬作比较,仅作娱乐。

该实例主要是通过一个PoolUtil对象作为一类游戏的对象池,再通过PoolManager来管理诸多PoolUtil。

 

PoolUtil的功能,主要还是靠队列实现的。调用者每次”销毁“物体时,将物体放入池中,并隐藏起来。再次生成时,查看池中有无待用的物体,若有则直接显示出来。若无,则继续生成新物体。

public class PoolUtil:MonoBehaviour
{
    public GameObject prefab;
    public string poolName;
    public Queue<GameObject> queue;
    
    /// <summary>
    /// 初始化方法
    /// </summary>
    /// <param name="prefab">要生成的预制体</param>
    /// <param name="poolName">池名</param>
    /// <param name="preLoadCount">预加载的数量</param>
    public void Init(GameObject prefab,string poolName, int preLoadCount)
    {
        queue = new Queue<GameObject>();
        this.prefab = prefab;
        gameObject.name = "PoolUtil_" + poolName;
        this.poolName = poolName;
        PreLoad(preLoadCount);
    }

    /// <summary>
    /// 预加载方法
    /// </summary>
    /// <param name="preLoadCount">预加载数量</param>
    void PreLoad(int preLoadCount)
    {
        for (int i = preLoadCount; i > 0; i--)
        {
            GameObject newObj = Instantiate(prefab);
            newObj.SetActive(false);
            SetGameObject(newObj.transform,GameObject.Find("Canvas").transform);
            queue.Enqueue(newObj);
        }
    }

    /// <summary>
    /// 生成方法
    /// </summary>
    /// <param name="pos">生成位置</param>
    /// <param name="rotation">生成时的旋转度数</param>
    /// <param name="parent">生成物体的父物体</param>
    /// <returns>返回生成的物体</returns>
    public GameObject Spawn(Vector3 pos,Quaternion rotation,Transform parent)
    {
        GameObject newObj = null;
        if (queue.Count!=0)
        {
            newObj = queue.Dequeue();
            newObj.SetActive(true);
            SetGameObject(newObj.transform, pos, rotation, parent);
            return newObj;
        }

        newObj = Instantiate(prefab);
        SetGameObject(newObj.transform, pos, rotation, parent);
        return newObj;
    }

    /// <summary>
    /// ”销毁“物体
    /// </summary>
    /// <param name="obj">要销毁的目标游戏物体</param>
    public void DeSpawn(GameObject obj)
    {
        obj.SetActive(false);
        queue.Enqueue(obj);
    }

    /// <summary>
    /// 设置物体的位置旋转和父物体
    /// </summary>
    /// <param name="obj"></param>
    /// <param name="pos"></param>
    /// <param name="rotation"></param>
    /// <param name="parent"></param>
    private void SetGameObject(Transform obj,Vector3 pos,Quaternion rotation,Transform parent)
    {
        obj.SetParent(parent);
        obj.localRotation = rotation;
        obj.localPosition = pos;
    }
    /// <summary>
    /// 下面三个是重载
    /// </summary>
    /// <param name="obj"></param>
    /// <param name="rotation"></param>
    /// <param name="parent"></param>
    private void SetGameObject(Transform obj,Quaternion rotation,Transform parent)
    {
        obj.SetParent(parent);
        obj.localRotation = rotation;
        obj.localPosition = Vector3.zero;
    }
    private void SetGameObject(Transform obj,Vector3 pos,Transform parent)
    {
        obj.SetParent(parent);
        obj.localRotation = Quaternion.identity;
        obj.localPosition = pos;
    }
    private void SetGameObject(Transform obj,Transform parent)
    {
        obj.SetParent(parent);
        obj.localRotation = Quaternion.identity;
        obj.localPosition = Vector3.zero;
    }
}
public class PoolManager : MonoBehaviour
{
    private Dictionary<string, PoolUtil> poolDic;
    public static PoolManager Instance;

    private void Start()
    {
        //单例模式好调用
        Instance = this;
        DontDestroyOnLoad(gameObject);
        poolDic = new Dictionary<string, PoolUtil>();
    }

    /// <summary>
    /// 暴露给调用者的生成方法
    /// </summary>
    /// <param name="needPool">是否需要生成池</param>
    /// <param name="prefab">要生成的物体</param>
    /// <param name="pos">位置</param>
    /// <param name="rotation">旋转</param>
    /// <param name="parent">父物体</param>
    public void CreateObj(bool needPool,GameObject prefab,Vector3 pos,Quaternion rotation,Transform parent)
    {
        if (needPool)//需要对象池
        {
            QueryPool(prefab).Spawn(pos,rotation,GameObject.Find("Canvas").transform);
        }
        else
        {
            GameObject temp = Instantiate(prefab);
            temp.transform.SetParent(parent);
            temp.transform.localPosition = pos;
            temp.transform.localRotation = rotation;
        }
    }

    /// <summary>
    /// 暴露给调用者的“销毁”方法
    /// </summary>
    /// <param name="deleteObj">销毁的目标物体</param>
    public void DestroyObj(GameObject deleteObj)
    {
        QueryPool(deleteObj).DeSpawn(deleteObj);
    }

    /// <summary>
    /// 查询字典中有无该物体的池
    /// </summary>
    /// <param name="prefab">目标物体</param>
    /// <returns>返回该物体的池</returns>
    private PoolUtil QueryPool(GameObject prefab)
    {
        string key = prefab.name.Split('(')[0];
        if (!poolDic.ContainsKey(key))
        {
            CreatePool(prefab);
        }
        return poolDic[key];
    }

    /// <summary>
    /// 创建目标物体的池
    /// </summary>
    /// <param name="prefab">目标物体</param>
    private void CreatePool(GameObject prefab)
    {
        GameObject obj = new GameObject();
        PoolUtil util = obj.AddComponent<PoolUtil>();
        util.Init(prefab,prefab.name,1);
        poolDic.Add(prefab.name, util);
        obj.transform.SetParent(transform);
    }
}

PoolManager主要通过一个池的字典,每次创建物体时,根据调用者的实际情况,判断是否需要创建池?然后加入到字典。这样,调用者再次创建时,即可查找指定池,并将目标物体放入该池中,方便复用。

 

下面是测试代码

public class TestPool : MonoBehaviour
{
    public GameObject prefab;
    public void ButtonFunc()
    {
        PoolManager.Instance.CreateObj(true,prefab,Vector3.zero,Quaternion.identity,GameObject.Find("Canvas").transform);
    }
}

在Unity新建一个Canvas,将该代码添加到Canvas上。新建一张图片改成红色,然后拖拽到Assets下作为预制体。为测试”销毁“功能,另外新建脚本:

public class ImageFunc : MonoBehaviour
{
    private float timer = 0;
    private float interval = 5f;
    void OnEnable()
    {
        timer = Time.time;
    }

    // Update is called once per frame
    void Update()
    {
        if(Time.time-timer>=interval)
        {
            PoolManager.Instance.DestroyObj(this.gameObject);
        }
    }
}

此代码作为预制体自身的脚本,生成时就开始计时,到达五秒后销毁自己。再次生成时会重置时间为当前时间。

 

 

 

 

关于该对象池的优化方向:

1.可以从Hierachy面板上点击PoolUtil,观察其Insperctor面板,可查询场景中有哪些物体归这个池管?

2.添加功能销毁池本身,如果长时间不再新生成该对象,应该逐步销毁池中物体,直至池中物体数量归0,然后销毁池

目前就想到这么多了

posted @ 2021-09-07 22:11  军酱不是酱  阅读(166)  评论(0编辑  收藏  举报