对象池(重复资源的循环利用)

在游戏里经常会用到重复资源,最常哪来举例的就是射击游戏中的子弹,像这样大量有共同特征的对象,就用一个容器来统一管理,需要的时候从容器里获取,销毁的时候也并不是真的Destroy,而是回收到容器里,如果容器中没有空闲对象,则创建,这样就可以避免大量的创建、销毁操作,实现代码的高效与低开销。
不同的项目中,可能会根据项目情况对对象池进行不同的优化或简化,不过原理都是一样。而且很多情况是一个项目中并不只有一种重复资源,可能会有很多种,这样就需要多组对象池来管理。如果是每一个对象池,都复制粘贴一遍代码,这无疑是各位大大们最不想看到的。最好是有一个管理器,来统一管理所有的对象池,这里我也根据最近几个小项目写了一个简易的对象池,由管理器来创建或销毁所有的对象池及其池对象,在此做一下总结。

//为了方便后期识别和管理,用枚举来标记对象在池中的状态

//对象的池状态
public enum PoolItemState
{
    Work,
    Idle
}

//管理对象池,原理也很简单,就是用一个容器,再来统一的管理所有的对象池,用管理器来创建或销毁对象池,以及对池的子对象进行的操作。

public class PoolManager : MonoBehaviour
{
    public static PoolManager instance;

    //Dictionary查找、添加、删除 速度较快,不过内存占用相对较多
    Dictionary<int, PoolItemBase> itemPrefabDic;
    Dictionary<int, Transform> workParentDic;
    Dictionary<int, Transform> idleParentDic;
    Dictionary<int, List<PoolItemBase>> workListDic;
    Dictionary<int, List<PoolItemBase>> idleListDic;

    void Awake()
    {
        instance = this;

        itemPrefabDic = new Dictionary<int, PoolItemBase>();
        workParentDic = new Dictionary<int, Transform>();
        idleParentDic = new Dictionary<int, Transform>();
        workListDic = new Dictionary<int, List<PoolItemBase>>();
        idleListDic = new Dictionary<int, List<PoolItemBase>>();
    }

    //注册一个对象
    public int InitNewPool(PoolItemBase poolItem, Transform parentTrans = null)
    {
        //判断是否已注册
        if (itemPrefabDic.ContainsValue(poolItem))
        {
            Debug.Log("<color=red> 注册的 poolIndex 已存在   </color>" + poolItem);
            return -1;
        }
        //生成对象池Index,在销毁时某个对象池,长度会缩减,获取的值将会重复,所以不能用itemPrefabDic.Count
        //int poolIndex = itemPrefabDic.Count;
        int poolIndex = GetNewPoolIndex();

        //记录PoolItemBase
        itemPrefabDic.Add(poolIndex, poolItem);
        //设置子对象的父物体
        if (parentTrans == null)
        {
            //如果未指定父物体,生成
            parentTrans = new GameObject(poolItem.name + "_WorkParent_CanDestroy").transform;
            parentTrans.parent = transform;
        }
        workParentDic.Add(poolIndex, parentTrans);
        Transform idleParentTrans = new GameObject(poolItem.name + "_IdleParent").transform;
        idleParentTrans.gameObject.SetActive(false);
        idleParentTrans.parent = transform;
        idleParentDic.Add(poolIndex, idleParentTrans);
        //用List保存子对象
        workListDic.Add(poolIndex, new List<PoolItemBase>());
        idleListDic.Add(poolIndex, new List<PoolItemBase>());
        //返回新注册对象池的Index
        return poolIndex;
    }

    //获取池对象
    public PoolItemBase GetOneItem(int poolIndex)
    {
        //判断是否在池内
        if (!itemPrefabDic.ContainsKey(poolIndex))
        {
            Debug.Log("<color=red> 获取的 poolIndex 不存在  </color>" + poolIndex);
            return null;
        }

        PoolItemBase tmpGetItem = null;
        if (idleListDic[poolIndex].Count > 0)
        {
            //如果有多余Item
            tmpGetItem = idleListDic[poolIndex][0];
            tmpGetItem.transform.SetParent(workParentDic[poolIndex]);
            idleListDic[poolIndex].RemoveAt(0);
        }
        else
        {
            //如果没有,加载
            tmpGetItem = Instantiate(itemPrefabDic[poolIndex], transform.position, Quaternion.identity, workParentDic[poolIndex]);
        }
        workListDic[poolIndex].Add(tmpGetItem);
        //初始化
        tmpGetItem.InitPool(poolIndex);
        //
        return tmpGetItem;
    }

    //回收池对象
    public void DestroyOneItem(int poolIndex, PoolItemBase item)
    {
        if (!itemPrefabDic.ContainsKey(poolIndex))
        {
            Debug.Log("<color=red> 回收的 poolIndex 不存在   </color>" + poolIndex);
            return;
        }
        //将工作的Item回收
        if (!idleListDic[poolIndex].Contains(item))
            idleListDic[poolIndex].Add(item);
        if (workListDic[poolIndex].Contains(item))
            workListDic[poolIndex].Remove(item);
        item.transform.SetParent(idleParentDic[poolIndex]);
        //重置
        item.ResetPool();
    }

    //清空对象池
    public void ClearPool(int poolIndex)
    {
        if (!itemPrefabDic.ContainsKey(poolIndex))
        {
            Debug.Log("<color=red> 回收的 pool 不存在   </color>" + poolIndex);
            return;
        }
        //遍历,回收
        for (int i = 0; i < workListDic[poolIndex].Count; i++)
        {
            DestroyOneItem(poolIndex, workListDic[poolIndex][i]);
        }
        workListDic[poolIndex].Clear();
    }

    //销毁一个对象池
    public void DestroyPool(int poolIndex)
    {
        if (!itemPrefabDic.ContainsKey(poolIndex))
        {
            Debug.Log("<color=red> 销毁的的 pool 不存在   </color>" + poolIndex);
            return;
        }
        //销毁/清空
        if (workParentDic[poolIndex].name.EndsWith("CanDestroy", System.StringComparison.Ordinal))
            Destroy(workParentDic[poolIndex].gameObject);
        else
        {
            ClearPool(poolIndex);
        }
        Destroy(idleParentDic[poolIndex].gameObject);
        //数据移除
        itemPrefabDic.Remove(poolIndex);
        workParentDic.Remove(poolIndex);
        idleParentDic.Remove(poolIndex);
        workListDic.Remove(poolIndex);
        idleListDic.Remove(poolIndex);
    }

    int indexNum = 0;
    int GetNewPoolIndex()
    {
        return indexNum++;
    }

}

//因为是对池对象的统一管理,给其添加一个父类,关于继承和多态,能搜到各种详细的资料~~

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

//池对象父类
public abstract class PoolItemBase : MonoBehaviour
{

    protected PoolItemState itemState;
    protected int poolIndex;

    //初始化
    public void InitPool(int poolInde)
    {
        itemState = PoolItemState.Work;
        poolIndex = poolInde;

        gameObject.name = "item_" + poolIndex;

        Init();
    }

    //重置,销毁时调用
    public void ResetPool()
    {
        itemState = PoolItemState.Idle;
        transform.localPosition = Vector3.zero;

        Reset();
    }

    public abstract void Init();

    public abstract void Reset();

}

//到这里对象池就算写完咯,关于其中的数据结构,需要根据实际项目的数据量及数据操作方式,选择合适的方案,如果数据比较多,字典就不是最佳选择了···

//接下来是一个简单的小测试,看一下实际效果
//对象类继承父类,并重写方法

public class CubeItem : PoolItemBase
{
    public override void Init()
    {
        StartCoroutine(Move());
    }

    public override void Reset()
    {
        StopAllCoroutines();
    }

    IEnumerator Move()
    {
        float timer = 0;
        while(timer < 2)
        {
            transform.position += Vector3.forward * 5;
            timer += 0.02f;
            yield return new WaitForSeconds(0.02f);
        }
        PoolManager.instance.DestroyOneItem(poolIndex, this);
    }
}

//对象的使用者,注册一个对象池,获取对象池的Index,然后就可以创建对象啦

public class LauncherControl : MonoBehaviour 
{
    [SerializeField] CubeItem CubePrefab;

    int poolIndex;

	void Start () 
    {
        poolIndex = PoolManager.instance.InitNewPool(CubePrefab);
        StartCoroutine(LaunchCube());
	}

    void Update()
    {
        if (Input.GetMouseButtonDown(1))
            PoolManager.instance.ClearPool(poolIndex);
        else if (Input.GetMouseButtonDown(2))
            PoolManager.instance.DestroyPool(poolIndex);
    }
    
    IEnumerator LaunchCube()
    {
        while(true)
        {
            PoolManager.instance.GetOneItem(poolIndex);
            yield return new WaitForSeconds(0.5f);
        }
    }
}

感谢大家的鼓励与支持,本人目前正处于我工作 我学习 我快乐的学习阶段,在技术上还需要继续努力,分享的Demo或案例或多或少都存在有待优化的地方,欢迎指点~~~

猜你喜欢

转载自blog.csdn.net/qq_39108767/article/details/81914757