美文网首页
[个人框架 C_Framework] C_Pool快速对象池

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

作者: hh5460 | 来源:发表于2018-11-05 14:25 被阅读0次

    对象池 作为游戏开发过程中最常用的手段之一,在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加载

    相关文章

      网友评论

          本文标题:[个人框架 C_Framework] C_Pool快速对象池

          本文链接:https://www.haomeiwen.com/subject/nrgcxqtx.html