[个人框架 C_Framework] C_Pool快速对象池

2018-11-05  本文已影响0人  hh5460

对象池 作为游戏开发过程中最常用的手段之一,在C_Framework框架中也必然会有,那么什么是池呢,池实际上可以理解为一个容器,需要的时候就从里面拿,用完了的时候就放回去,再需要的时候再拿出来,不停的复用来做到用池中有限的物体模拟出无限的物体,而不是使用的时候Instantiate,不用的时候Destory,因为这样做的性能效率很低...
一个简单的对象池写起来很容易,那么我们写一个稍微进阶一点的对象池,我们需要的功能有:

1.一个基本对象池
2.可以手动拖拽预制到面板设置属性创建对象池
3.可以代码创建对象池
4.不拖拽也不用代码,直接指定Resources 或者 AssetBunlde 路径,通过它们直接创建对象池
5.支持预加载,限制最大个数,保留个数,定时回收
6.支持安全模式,即当我限制了池中A物体最多只能有2个的时候A1 A2,如果前两个A1A2还没有用完,就请求第三个物体A3,在非安全模式下会正常复用直接把没用完的A1拿过来,但是在安全模式下,会抛出异常...

效果Demo:
我们先创建一个sphere,添加一个Rigidbody组件,再为其挂载一个脚本,让它每次OnEnable后2秒关闭自己active(具体的业务逻辑自己结合项目即可,这里是为了演示效果,模拟子弹2秒消失),并重命名为bullet并保存到project层级下作为预制,然后我们将C_Pool脚本挂载在Main Camera下,具体挂在哪无所谓,然后看到C_Pool面板下有Pools集合属性,我们将其设为1并添加绑定刚刚的bullet预制,然后设置其属性

Target Name : 对象的名称,当要从对象池中取出对象时,用这个名字来取
Target : 具体绑定的需要复用的对象,当这里不绑定时就需要指定下面两个参数
Resources Directory : Resourecs 路径,当上面Target属性没有绑定时,而这个属性不为空时,就从对应的Resources路径加载
Asset Bundle Pageage Name : 同上,在PersistentPath路径下自动加载包中物体
Preload Nums : 预加载,用之前就预先加载几个
Stay Nums : 保持个数,即至少要保持有几个可用的,回收也回收不掉
Limit Nums : 限制个数,即不可以超过这个数量
Is Use GC : 是否开启定时回收
Interval Time GC : 每隔多少秒回收一次
One Shot GC Num : 每次回收几个
Is Safe Mode : 是否开启安全模式,参考上方第6条

然后我们再创建一个测试脚本,不停的从池中获取bullet然后发射即可,看下方gif演示效果,具体看下对象池设置参数和使用过程中对象池的变化情况,这里我们预加载2个bullet,保持始终有2个,限制最多有8个,每2秒回收一次,每次回收2个


C_Pool

代码(SingleFramework不明白的请参考我之前的文章SingleFramework单例篇)

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

public class C_Pool : SingleFramework<C_Pool>
{
    public bool isNeedGlobalGC = false;
    public float InvokeRepGCTime = 300f;   //每隔300s后自动GC回收一次  

    public PoolItem this[string indexString]
    {
        get
        {
            return GetPoolItem(indexString);
        }
    }

    //界面属性绑定
    public PoolItemInfo[] Pools;
    Dictionary<string, PoolItem> dicPoolItems = new Dictionary<string, PoolItem>();

    [HideInInspector]
    public GameObject gameObjectPools;

    //GC回收
    void PoolGC()
    {
        Resources.UnloadUnusedAssets();
        GC.Collect();
    }

    public override void Init()
    {
        gameObjectPools = new GameObject("Pools");

        //有面板预加载的项
        if (Pools.Length > 0)
        {
            for (int i = 0; i < Pools.Length; i++)
            {
                Add2Pool(Pools[i]);        //初始化池信息
            }
        }

        //是否需要开启全局GC
        if (isNeedGlobalGC)
        {
            InvokeRepeating("PoolGC", InvokeRepGCTime, InvokeRepGCTime);
        }
    }

    /// <summary>
    /// !!!每次当该脚本在Editor环境下Inspector的属性变化时会调用
    /// </summary>
    void OnValidate()
    {
        if (Pools != null)
        {
            for (int i = 0; i < Pools.Length; i++)
            {
                if (Pools[i].Target != null && string.IsNullOrEmpty(Pools[i].TargetName))
                {
                    Pools[i].TargetName = Pools[i].Target.name;
                }
            }
        }

    }

    //将当前对象返回到池中
    internal static void ReturnPool(GameObject target)
    {
        target.gameObject.SetActive(false);
        target.transform.SetParent(Instance.gameObjectPools.transform);
    }

    public void Add2Pool(PoolItemInfo poolItemInfo)
    {
        if (string.IsNullOrEmpty(poolItemInfo.TargetName))
        {
            throw new Exception("Pool池中不允许存在TargetName为空的项...");
        }
        if (poolItemInfo.Target == null && string.IsNullOrEmpty(poolItemInfo.ResourcesDirectory) && string.IsNullOrEmpty(poolItemInfo.AssetBundlePackageName))      //未绑定
        {
            throw new Exception(poolItemInfo.TargetName + "未绑定任何创建方式...");
        }
        if (dicPoolItems.ContainsKey(poolItemInfo.TargetName))
        {
            throw new System.Exception("CPool中元素" + poolItemInfo.Target.name + "池中已经存在了!");
        }

        PoolItem poolItem = new PoolItem();
        poolItem.poolItemInfo = poolItemInfo;
        poolItem.Preload();                                 //处理预加载
        dicPoolItems.Add(poolItemInfo.TargetName, poolItem);
    }

    public PoolItem GetPoolItem(string prefabKey)
    {
        if (dicPoolItems.ContainsKey(prefabKey))
        {
            return dicPoolItems[prefabKey];
        }
        else
        {
            throw new System.Exception("池中不存在" + prefabKey + "物体");
        }
    }

    public bool ContainsPool(string PoolName)
    {
        if (dicPoolItems.ContainsKey(PoolName))
        {
            return true;
        }
        return false;
    }

    /// <summary>
    /// 启用对应Pool的携程
    /// </summary>
    /// <param name = "poolItem" > 对应Pool项 </ param >
    public void StartPoolItemCoroutine(PoolItem poolItem)
    {
        if (poolItem.poolItemInfo.IntervalTimeGC != 0 && poolItem.poolItemInfo.OneShotGCNum != 0)
        {
            poolItem.coroutine = StartCoroutine(PoolItemCoroutine(poolItem));
            poolItem.IsGCCoroutining = true;
        }
    }

    /// <summary>
    /// 停止对应Pool的协成
    /// </summary>
    /// <param name = "poolItem" ></ param >
    public void StopPoolItemCoroutine(PoolItem poolItem)
    {
        StopCoroutine(poolItem.coroutine);
        poolItem.IsGCCoroutining = false;
    }

    /// <summary>
    /// PoolItem携程执行函数
    /// </summary>
    /// <param name = "poolItem" ></ param >
    /// < returns ></ returns >
    IEnumerator PoolItemCoroutine(PoolItem poolItem)
    {
        yield return new WaitForFixedUpdate();      //这里停留一帧给库预加载
        while (poolItem.LstOffs.Count > 0 && poolItem.LstOffs.Count > poolItem.poolItemInfo.StayNums)   //Pool中有
        {
            yield return poolItem.poolItemInfo.ws_IntervalTimeGC;
            int GCNum = 0;
            for (int i = 0; i < poolItem.LstOffs.Count; i++)
            {
                if (poolItem.LstOffs[i].gameObject.activeSelf == false)
                {
                    Destroy(poolItem.LstOffs[i].gameObject);
                    poolItem.LstOffs.RemoveAt(i--);
                    GCNum++;
                    if (GCNum == poolItem.poolItemInfo.OneShotGCNum || poolItem.LstOffs.Count == poolItem.poolItemInfo.StayNums)
                    {
                        break;
                    }
                }
            }
        }
        StopPoolItemCoroutine(poolItem);
    }
}

public class PoolItem
{
    public PoolItemInfo poolItemInfo;       //Pool所包含的信息项
    public Coroutine coroutine;             //当前PoolItem对象所关联的coroutine
    bool isGCCoroutining = false;           //当前PoolItem池是否在GC中
    public bool IsGCCoroutining
    {
        get
        {
            return isGCCoroutining;
        }
        set
        {
            if (value != isGCCoroutining)
            {
                isGCCoroutining = value;
            }
        }
    }

    public List<GameObject> LstOffs = new List<GameObject>();

    //出库
    public GameObject DePool()  //出cool
    {
        //目标GameObject
        GameObject target = null;
        //当前池中的个数已经大于等于限制个数了,则不再Instantiate该物体,开始复用
        //(LstOffs.Count > 0 && !LstOffs[0].gameObject.activeSelf)表示的是当最初有未使用的时候就可以开始复用了
        if (poolItemInfo.LimitNums != 0 && LstOffs.Count >= poolItemInfo.LimitNums || (LstOffs.Count > 0 && !LstOffs[0].gameObject.activeSelf))
        {
            //先去搜索关闭着的物体,如果找不到则强制第一个物体
            int index = -1;
            for (int i = 0; i < LstOffs.Count; i++)
            {
                if (!LstOffs[i].activeSelf)
                {
                    index = i;
                    break;
                }
            }

            //检测是否启用了安全模式
            if (index == -1)
            {
                if (poolItemInfo.IsSafeMode)
                {
                    throw new Exception("当前的对象" + poolItemInfo.TargetName + "在池中没有可用物体了,你可以取消安全模式强制复用!!!");
                }
                else
                {
                    index = 0;
                }
            }

            //取出当前模型后从队列位置移除然后重新追加到队尾
            target = LstOffs[index];
            LstOffs.RemoveAt(index);
            LstOffs.Add(target);
        }
        else
        {
            //如果当前对象为空,则从内存中获取当前对象
            if (poolItemInfo.Target == null)
            {
                LoadTargetInMemory();
            }

            //构造当前对象
            target = InstantiateTarget();
        }
        target.SetActive(true);
        return target;
    }

    //预加载
    public void Preload()
    {
        if (poolItemInfo.Target == null)
        {
            LoadTargetInMemory();
        }

        for (int i = 0; i < poolItemInfo.PreloadNums; i++)
        {
            InstantiateTarget().SetActive(false);
        }
    }

    //获得镜像在内存中
    void LoadTargetInMemory()
    {
        //去Resources目录加载
        if (!string.IsNullOrEmpty(poolItemInfo.ResourcesDirectory))
        {
            try
            {
                poolItemInfo.Target = Resources.Load<GameObject>(Path.Combine(poolItemInfo.ResourcesDirectory, poolItemInfo.TargetName));
            }
            catch
            {
                throw new System.Exception(poolItemInfo.TargetName + "在Resources下不存在");
            }
        }

        //去PersistentDataPath下加载物体
        if (!string.IsNullOrEmpty(poolItemInfo.AssetBundlePackageName))
        {
            try
            {                
                AssetBundle assetbundle = AssetBundle.LoadFromFile(ConstantVariable.PersistentDataPathFile + ConstantVariable.buildPathDir + poolItemInfo.AssetBundlePackageName);
                poolItemInfo.Target = assetbundle.LoadAsset<GameObject>(poolItemInfo.TargetName.ToLower());
                assetbundle.Unload(false);
            }
            catch
            {
                throw new System.Exception(poolItemInfo.TargetName + "在AssetBundle下不存在");
            }
        }
    }

    //实例化物体出来
    GameObject InstantiateTarget()
    {
        GameObject target = GameObject.Instantiate<GameObject>(poolItemInfo.Target);

        target.name = poolItemInfo.TargetName;
        target.transform.SetParent(C_Pool.Instance.gameObjectPools.transform);

        LstOffs.Add(target);

        if (poolItemInfo.IsUseGC && IsGCCoroutining == false)
        {
            C_Pool.Instance.StartPoolItemCoroutine(this);
        }
        return target;
    }
}

[Serializable]
public class PoolItemInfo
{
    public string TargetName;                                     //目标名
    //以下三个任选一个赋值
    public GameObject Target;                                     //目标物体
    //Resources路径
    public string ResourcesDirectory;                             //注意注意,不允许放在Resources根目录下,必须新建文件夹,即不允许将该字段设置为空 当要使用此加载方式的时候
    //AssetBundle包名,由于一个包中会包含多个物体,所以这里需要单独指定包名
    public string AssetBundlePackageName;

    public int PreloadNums = 1;                                   //预加载个数
    public int StayNums = 1;                                      //保留个数
    public int LimitNums = 0;                                     //限制个数 0表示无限
    public int OneShotGCNum = 1;                                  //每次回收多少個

    public bool IsSafeMode = false;                               //是否安全模式
                                                                  //安全模式时如果没有可用物体则报错
                                                                  //非安全模式,如果没有可用的物体则强制使用当前第一个物体,取出使用并将其置于队尾
    public bool IsUseGC = false;                                  //是否开启GC回收
    public float IntervalTimeGC = 60f;                            //每隔多长时间加入到回收池 
    WaitForSeconds _ws_IntervalTimeGC;
    public WaitForSeconds ws_IntervalTimeGC                       //每个粒子等待回收时间对象
    {
        get
        {
            if (_ws_IntervalTimeGC == null)
            {
                _ws_IntervalTimeGC = new WaitForSeconds(IntervalTimeGC);
            }
            return _ws_IntervalTimeGC;
        }
    }
}

接下来我们再来看看如何直接通过Resources加载,这样就不用再拖拽到Target了

Resources加载
上一篇下一篇

猜你喜欢

热点阅读