Unity框架学习--对象池

       频繁创建和销毁对象会造成性能的开销。
        创建对象的时候,系统会为这个对象开辟一片新的空间。销毁对象的时候,这个对象会变成内存垃圾,当内存垃圾达到一定程度,就会触发垃圾回收机制,清理内存垃圾,由于此时在清理垃圾,所以程序有可能就会变卡。

        为了改善这个问题,我们就可以使用对象池。使用了它之后,程序的性能就能得到提升不那么容易变卡。

        对象池的原理:


        1、当要创建对象的时候,不直接创建,而是先从对象池里面找,如果对象池里有这种类型的对象,就从对象池中取出来用。如果对象池里面没有这种类型的对象,才创建该对象。
        2、当要销毁对象的时候,不直接销毁,而是把这个对象放进对象池里面存着,以便下一次创建对象时使用。

一个预制体对应一个对象池,代码如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 对象池
/// </summary>
public class ObjectPool : MonoBehaviour
{
    public GameObject prefabs;   //这个对象池存储的游戏对象的预制体

    public int capcity = 100;  //对象池的容量

    //从这个对象池中取出来正在使用的游戏对象
    public List<GameObject> usedGameObjectList = new List<GameObject>();

    //存在这个对象池中没有被使用的游戏对象
    public List<GameObject> unusedGameObjectList = new List<GameObject>();

    //这个池子总共拥有的游戏对象的个数
    public int TotalGameObjectCount { get => usedGameObjectList.Count + unusedGameObjectList.Count; }

    /// <summary>
    /// 从对象池中获取一个对象,并返回一个对象
    /// </summary>
    public GameObject Spawn(Vector3 position,Quaternion rotation,Transform parent=null)
    {
        GameObject go;

        //池子中有
        if (unusedGameObjectList.Count > 0)
        {
            go = unusedGameObjectList[0];
            unusedGameObjectList.RemoveAt(0);
            usedGameObjectList.Add(go);
            go.transform.localPosition = position;
            go.transform.localRotation = rotation;
            go.transform.SetParent(parent, false);
            go.SetActive(true);
        }
        else  //对象池中没有,实例化
        {
            go = Instantiate(prefabs, position, rotation, parent);
            usedGameObjectList.Add(go);
        }

        //如果该游戏对象身上继承MonoBehaviour的脚本中写了名叫OnSpwan的方法,则会执行它们一次
        //                                           就算没有接收者也不报错
        go.SendMessage("OnSpawn", SendMessageOptions.DontRequireReceiver);

        return go;
    }

    //回收进对象池中
    public void DesPawn(GameObject go)
    {
        if (go == null) return;

        //遍历这个对象池中所有正在使用的游戏对象
        for (int i = 0; i < usedGameObjectList.Count; i++)
        {
            if (usedGameObjectList[i] == go)
            {
                //如果这个对象池的容量不为负数,且容纳的游戏对象已经满了,则把0号游戏对象删除掉
                //确保之后新的游戏对象能放入池子中
                if (capcity >= 0 && usedGameObjectList.Count >= capcity)
                {
                    if (unusedGameObjectList.Count>0)
                    {
                        DesPawn(unusedGameObjectList[0]);
                        unusedGameObjectList.RemoveAt(0);

                    }
                }

                //把游戏对象回收到对象池中
                unusedGameObjectList.Add(go);
                usedGameObjectList.RemoveAt(i);

                //如果该游戏对象身上继承MonoBehaviour的脚本写了名叫OnDespawn的方法,则在回收的时候会执行一次
                //这个方法用来在回收前将游戏对象还原
                go.SendMessage("OnDespawn", SendMessageOptions.DontRequireReceiver);

                go.SetActive(false);
                go.transform.SetParent(transform, false);  //取消子物体

                return;
            }
        }
    }

    /// <summary>
    /// 把通过这个对象池生成的所有对象全部隐藏并放入对象池中
    /// </summary>
    public void DesPawnAll()
    {
        int count = usedGameObjectList.Count;

        for (int i = 1; i <= count; i++)
        {
            DesPawn(usedGameObjectList[0]);
        }

        usedGameObjectList.Clear();
    }

    /// <summary>
    /// 预加载的方法,预先在池子里放几个对象
    /// </summary>
    public void PreLoad(int amount = 1)
    {
        if (prefabs == null) return;

        if (amount <= 0) return;

        for (int i = 1; i <= amount ; i++)
        {
            GameObject go = Instantiate(prefabs, Vector3.zero, Quaternion.identity);
            go.SetActive(false);
            go.transform.SetParent(transform, false);

            unusedGameObjectList.Add(go);
            go.name = prefabs.name;  //更改一下生成的物体的名字
           
        }

    }

}

一个预制体一个池子,然后一个大的池子管理所有的小对象池

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 对象池管理,可以通过这个管理器从对象池生成游戏对象,也可以回收游戏对象进对象池
/// </summary>
public class ObjectPoolsManager : SingletonPatternBase<ObjectPoolsManager>
{
    GameObject poolsParent;  //所有对象池的父物体
    string poolsParentName = "ObjectPools";  //大对象池的名字

    //当前所有小对象池的列表
    public List<ObjectPool> objectPoolsList = new List<ObjectPool>();

    public Dictionary<GameObject, ObjectPool> objectDictionary = new Dictionary<GameObject, ObjectPool>();

    /// <summary>
    /// 从对象池生成游戏对象
    /// 如果对象池有,从对象池中取出来用
    /// 如果对象池没有,实例化该对象
    /// </summary>
    public GameObject Spawn(GameObject prefab,Vector3 position,Quaternion rotation,Transform parent=null)
    {
        if (prefab == null) return null;

        //如果场景中没有对象池的父物体,则生成一个空物体作为所有对象池的父物体
        CreatePoolsParentIfNull();

        //先通过预制体查找它所属的小对象池,如果找到了,则返回这个小对象池
        //如果找不到,则创建一个小对象池,用来存放这种预制体
        ObjectPool objectPool = FindPoolByPrefabOrCreatePool(prefab);

        GameObject go = objectPool.Spawn(position, rotation, parent);

        objectDictionary.Add(go, objectPool);

        return go;
    }

    /// <summary>
    /// 回收的方法
    /// </summary>
    public void DesPawn(GameObject go,float delayTime=0)
    {
        if (go == null) return;

        MonoManager.Instance.StartCoroutine(DespawnCoroutine(go, delayTime));
    }

    IEnumerator DespawnCoroutine(GameObject go, float delayTime = 0)
    {
        //等待指定的秒数
        yield return new WaitForSeconds(delayTime);

        if (objectDictionary.TryGetValue(go, out ObjectPool pool))
        {
            objectDictionary.Remove(go);
            pool.DesPawn(go);
        }
        else
        {
            //获取这个游戏对象所属的对象池
            pool = FindPoolByUsedGameObject(go);

            if (pool != null)
            {
                pool.DesPawn(go);
            }
        }
    }

    /// <summary>
    /// 通过正在使用的物体获取这个游戏对象所属的对象池
    /// </summary>
    ObjectPool FindPoolByUsedGameObject(GameObject go)
    {
        if (go == null) return null;

        for (int i = 0; i < objectPoolsList.Count; i++)
        {
            ObjectPool pool = objectPoolsList[i];

            for (int j = 0; j < pool.usedGameObjectList.Count; j++)
            {
                if (pool.usedGameObjectList[j]==go)
                {
                    return pool;
                }
            }
        }
        return null;
    }


    /// <summary>
    /// 如果场景中没有对象池的父物体,则生成一个空物体作为所有对象池的父物体
    /// </summary>
    void CreatePoolsParentIfNull()
    {
        if (poolsParent == null)
        {
            objectPoolsList.Clear();
            objectDictionary.Clear();

            poolsParent = new GameObject(poolsParentName);
        }
    }

    /// <summary>
    /// 先通过预制体查找它所属的小对象池,如果找到了,则返回这个小对象池
    /// 如果找不到,则创建一个小对象池,用来存放这种预制体
    /// </summary>
    /// <returns></returns>
    ObjectPool FindPoolByPrefabOrCreatePool(GameObject prefab)
    {
        //确保大对象池存在
        CreatePoolsParentIfNull();

        //查找并返回该预制体对象的对象池
        ObjectPool objectPool = FindPoolByPrefab(prefab);

        if (objectPool == null)
        {
            objectPool = new GameObject($"ObjectPool{prefab.name}").AddComponent<ObjectPool>();
            objectPool.prefabs = prefab;
            objectPool.transform.SetParent(poolsParent.transform);
            objectPoolsList.Add(objectPool);
        }
        return objectPool;
    }

    /// <summary>
    /// 查找并返回该预制体对象的对象池
    /// </summary>
    ObjectPool FindPoolByPrefab(GameObject prefab)
    {
        if (prefab == null) return null;

        for (int i = 0; i < objectPoolsList.Count; i++)
        {
            if (objectPoolsList[i].prefabs == prefab)
            {
                return objectPoolsList[i];
            }
        }

        return null;
    }
}

物体身上最好写一个这样的方法将物体状态初始化,清楚一些脏数据

void OnDespawn()
    {
        transform.position = Vector3.zero;
        GetComponent<Rigidbody>().velocity = Vector3.zero;
    }

猜你喜欢

转载自blog.csdn.net/zaizai1007/article/details/132365705