Unity ObjectPool 超轻量高效 对象池 插件

8 篇文章 0 订阅
1 篇文章 0 订阅
class EasyObjectPool 

在这里插入图片描述

Description

A lightweight object pool.

You need to create an object in the scene and then hang it.

Support automatic capacity expansion.

Support recycling detection.

在这里插入图片描述

案例 从对象池中获取一个闲置的对象 指定ID

在这里插入图片描述

案例 回收

EasyObjectPool.GetInstance().Despawn( vfxTrans );

案例 延时回收

粒子效果 播放一段时间后 自动回收
在这里插入图片描述

案例 回收所有激活的对象

一般退出游戏场景时 回收所有对象池元素
在这里插入图片描述

扩展 代码添加一个新的池子

EasyObjectPool.GetInstance().Add( m_ArrowPrefab, 100 ); //添加一个箭的池子 池子预缓存100支箭备用
在这里插入图片描述

源码

最新版 EasyPool插件下载
https://download.csdn.net/download/qq_39162566/87408050

/**
 * 
 * class: EasyObjectPool
 * 
 * A lightweight object pool.
 * 
 * You need to create an object in the scene and then hang it.
 *
 * Support automatic capacity expansion.
 *  
 * Support recycling detection.
 * ————————————————
 * 版权声明:本文为CSDN博主「极客柒」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
 * 原文链接:https://blog.csdn.net/qq_39162566/article/details/128290119
 * 
 */

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

#if UNITY_EDITOR || ENABLE_LOG
using UnityEditor;
#endif

public class EasyObjectPool : MonoBehaviour
{

    public class TransformPool
    {
        /// <summary>
        /// 模板预设
        /// </summary>
        /// <param name="prefab"> 预设 </param>
        /// <param name="parent"> 指定一个父类 </param>
        public TransformPool( GameObject prefab, Transform parent = null )
        {
            this.prefab = prefab;
            this.parent = parent;
        }

        获取预设name(KEY)
        //public string name
        //{
        //    get
        //    {
        //        if ( this.prefab )
        //            return this.prefab.name;
        //        return string.Empty;
        //    }
        //    set
        //    {
        //        if ( this.prefab )
        //            this.prefab.name = value;
        //    }
        //}

        private Queue<Transform> free = new Queue<Transform>();//闲置链表
        private List<Transform> active = new List<Transform>();//激活链表
        private GameObject prefab;//预设模板
        private Transform parent;//父节点
        private float expandTimeSinceStartup = 0f; //扩充时间
        private int expandCount = 10;//扩充基数
        private int tryExpandCount = 0;//尝试扩容的次数
        //动态扩容
        private void AutoExpandImmediately()
        {
            //0.01秒以内发生多次扩充
            if ( Time.realtimeSinceStartup - expandTimeSinceStartup < 1e-2 )
            {
                expandCount = expandCount * 10;
            }
            else
            {
                expandCount = 10;
            }
            //扩充
            Reserve( expandCount );
        }

        /// <summary>
        /// 激活对象
        /// 
        /// 从闲置链表中推出一个闲置对象 并将其加入激活列表的中
        /// 通常情况下 你只需要处理激活列表中的对象即可
        /// 
        /// </summary>
        /// <returns> 你需要为他设置父类 并为它设置 Active为true </returns>
        public Transform Pop()
        {
            if ( free.Count > 0 )
            {
                //申请成功
                tryExpandCount = 0;
                var freeObj = free.Dequeue();
                active.Add( freeObj );
                return freeObj;
            }

            //申请扩充内存失败
            if ( tryExpandCount > 5 )
            {
                //#if UNITY_EDITOR || ENABLE_LOG
                //                //开始暴力GC扩充
                //                System.GC.Collect( 0, System.GCCollectionMode.Forced, true, true );
                //                return Pop();
                //#endif
                return null;
            }

            //扩容
            ++tryExpandCount;
            AutoExpandImmediately();
            return Pop();
        }

        /// <summary>
        /// 限制对象
        /// </summary>
        /// <param name="obj"></param>
        public void Push_Back( Transform obj )
        {
            if ( null != obj )
            {
                obj.transform.SetParent( parent );
                obj.gameObject.SetActive( false );
                if ( active.Contains( obj ) )
                {
                    active.Remove( obj );
                }
                if ( !free.Contains( obj ) )
                {
                    free.Enqueue( obj );
                }
            }
        }


        public enum PoolElementState
        {
            Unknown = 0,//未知 不存在当前对象池中
            Active, //激活状态
            Free, //闲置状态
        }

        /// <summary>
        /// 获取对象在池中的状态
        /// </summary>
        /// <param name="dest"> enum: PoolElementState </param>
        /// <returns></returns>
        public PoolElementState GetElementState( Transform dest )
        {
            if ( free.Contains( dest ) )
            {
                return PoolElementState.Free;
            }

            if ( active.Contains( dest ) )
            {
                return PoolElementState.Active;
            }

            return PoolElementState.Unknown;
        }

        /// <summary>
        /// 是否在对象池
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public bool InSidePool( Transform obj )
        {
            return PoolElementState.Unknown != GetElementState( obj );
        }

        /// <summary>
        /// 回收所有激活的对象
        /// </summary>
        public void Recycle()
        {
            var objs = active.ToArray();
            for ( int i = 0; i < objs.Length; i++ )
            {
                if ( objs[ i ] != null )
                {
                    Push_Back( objs[ i ] );
                }
            }

            objs = free.ToArray();
            for ( int i = 0; i < objs.Length; i++ )
            {
                if ( objs[ i ] != null )
                {
                    Push_Back( objs[ i ] );
                }
            }
        }

        /// <summary>
        /// 预定一定数量的预设
        /// </summary>
        /// <param name="count"></param>
        public void Reserve( int count )
        {
            string key = prefab.name;
            for ( int i = 0; i < count; i++ )
            {
                var inst = GameObject.Instantiate( prefab, parent );
                inst.SetActive( false );
                inst.name = $"{ key } <Clone>";
                free.Enqueue( inst.transform );
                nameofDict.Add( inst.transform, key );
            }
            expandTimeSinceStartup = Time.realtimeSinceStartup;
        }

        /// <summary>
        /// 此操作会完全释放对象的内存占用 是delete哦~
        /// </summary>
        public void Release()
        {
            foreach ( var obj in free )
            {
                Destroy( obj.gameObject, 0.0016f );
            }
            foreach ( var obj in active )
            {
                Destroy( obj.gameObject, 0.0016f );
            }
            free.Clear();
            active.Clear();
        }
    }

    /// <summary>
    /// 预设配置
    /// </summary>
    [System.Serializable]
    public class PreloadConfigs
    {
        [Header( "初始预设数量" )]
        public GameObject prefab;
        public int preloadCount = 100;
        [Header( "预制体路径( 自动生成 )" )]
        [ReadOnly]
        public string url = string.Empty;
    }

    [SerializeField]
    private List<PreloadConfigs> preloadConfigs = new List<PreloadConfigs>();
    private Dictionary<string, TransformPool> poolDict = new Dictionary<string, TransformPool>();
    private static Dictionary<Transform, string> nameofDict = new Dictionary<Transform, string>();
    private static EasyObjectPool instance = null;
    public static EasyObjectPool GetInstance() { return instance; }
    /// <summary>
    /// 首次加载是否完成
    /// </summary>
    public static bool firstPreloadFinish
    {
        get
        {
            return instance != null && instance.preloadConfigs.Count == 0;
        }
    }

#if UNITY_EDITOR || ENABLE_LOG
    private void OnValidate()
    {
        foreach ( var config in preloadConfigs )
        {
            if ( UnityEditor.PrefabUtility.IsPartOfPrefabAsset( config.prefab ) )
            {
                string url = UnityEditor.AssetDatabase.GetAssetPath( config.prefab );
                config.url = url;
            }
        }
    }
#endif

    private void Awake()
    {
        if ( instance != null && instance != this )
        {
            DestroyImmediate( gameObject );
            return;
        }
        instance = this;
        //DontDestroyOnLoad( gameObject );

        //第一次扩充
        foreach ( var config in preloadConfigs )
        {
            Add( config.prefab, config.preloadCount );
        }
        preloadConfigs.Clear();
    }

    /// <summary>
    /// 从对象池中拿一个闲置的对象
    /// </summary>
    /// <param name="key"></param>
    /// <returns>当返回null时 说明不存在这个预设的池子 你可以使用 GeneratePool 来添加一个新的池子 </returns>
    public Transform Spawn( string key )
    {

        TransformPool res = null;
        if ( poolDict.TryGetValue( key, out res ) )
        {
            Transform trans = res.Pop();
            trans.gameObject.SetActive( true );
            return trans;
        }
        return null;
    }
    /// <summary>
    /// 回收一个对象到对象池中
    /// </summary>
    /// <param name="obj"></param>
    /// <returns></returns>
    public bool Despawn( Transform obj )
    {
        if ( null == obj )
        {
#if UNITY_EDITOR || ENABLE_LOG
            throw new System.Exception( "Despawn obj is null" );
#else
            return false;
#endif
        }

        if ( nameofDict.TryGetValue( obj, out string name ) && poolDict.TryGetValue( name, out TransformPool res ) )
        {
            res.Push_Back( obj );
            return true;
        }
        else
        {
            //容错处理
#if UNITY_EDITOR || ENABLE_LOG
            obj.gameObject.SetActive( false );
            Debug.LogError( $"current object is not objectPool element: {obj.name}", obj.gameObject );
#else
            Destroy( obj.gameObject );
#endif
        }
        return false;
    }


    /// <summary>
    /// 将指定key的缓存池内所有的对象全部回收
    /// </summary>
    /// <param name="pool"></param>
    public void Despawn( string pool )
    {
        if ( poolDict.TryGetValue( pool, out TransformPool res ) )
        {
            res.Recycle();
        }
#if UNITY_EDITOR || ENABLE_LOG
        else
        {
            Debug.LogError( $"exclusive pool: {pool}" );
        }
#endif
    }

    /// <summary>
    /// 延迟回收
    /// </summary>
    /// <param name="obj"></param>
    /// <param name="delay"></param>
    public void Despawn( Transform obj, float delay )
    {
        Timer.SetTimeout( delay, () => Despawn( obj ) );
    }

    /// <summary>
    /// 是否是对象池元素
    /// </summary>
    /// <param name="target"></param>
    /// <returns></returns>
    public bool Contains( Transform element )
    {
        TransformPool pool;
        if ( null != element && nameofDict.TryGetValue( element, out string name ) && poolDict.TryGetValue( name, out pool ) && pool.InSidePool( element ) )
        {
            return true;
        }
        return false;
    }

    /// <summary>
    /// 回收自身所有的对象池元素 ( 包含自身 )
    /// </summary>
    /// <param name="root"></param>
    public void DespawnSelfAny<T>( Transform root ) where T : Component
    {
        T[] suspectObjects = root.GetComponentsInChildren<T>();
        foreach ( var obj in suspectObjects )
        {
            if ( Contains( obj.transform ) )
            {
                Despawn( obj.transform );
            }
        }
    }


    /// <summary>
    /// 回收自己的子节点 如果子节点是对象池元素的话
    /// </summary>
    /// <param name="root"> 父节点 </param>
    /// <param name="includeSelf"> 本次回收是否包含父节点 </param>
    /// <param name="force"> true: 遍历所有的孩子节点  false: 仅遍历一层 </param>
    public void DespawnChildren( Transform root, bool includeSelf = false, bool force = false )
    {
        List<Transform> children = null;
        
        if ( force )
        {
            Transform[] suspectObjects = root.GetComponentsInChildren<Transform>();
            children = new List<Transform>( suspectObjects );
            if ( !includeSelf ) children.Remove( root );
           
        }
        else
        {
            children = new List<Transform>();
            if ( includeSelf )
            {
                children.Add( root );
            }
            foreach ( Transform child in root )
            {
                children.Add( child );
            }
        }

        foreach ( var child in children )
        {
            Despawn( child );
        }
    }

    /// <summary>
    /// 新增要给对象池
    /// </summary>
    /// <param name="prefab"></param>
    /// <param name="firstExpandCount"></param>
    public void Add( GameObject prefab, int firstExpandCount = 100 )
    {
        if ( prefab != null )
        {
            var key = prefab.name;
            if ( poolDict.ContainsKey( key ) )
            {
                Debug.LogError( $"Add Pool Error: pool name <{key}> already exist!" );
#if UNITY_EDITOR || ENABLE_LOG
                Selection.activeGameObject = prefab;
#endif
                return;
            }
            var pool = new TransformPool( prefab, transform );
            poolDict.Add( key, pool );
            pool.Reserve( firstExpandCount );
#if UNITY_EDITOR || ENABLE_LOG
            Debug.Log( $"<color=#00ff44>[EasyObjectPool]\t对象池创建成功: {key}\t当前闲置数量: {firstExpandCount}</color>" );
#endif
        }
        else
        {
            Debug.LogError( $"Add Pool Error: prefab is null" );
        }
    }


    /// <summary>
    /// 回收所有激活对象
    /// </summary>
    public void Recycle()
    {
        foreach ( var pool in poolDict )
        {
            pool.Value.Recycle();
        }
    }

}


public class ReadOnlyAttribute : PropertyAttribute
{

}

#if UNITY_EDITOR || ENABLE_LOG
[CustomPropertyDrawer( typeof( ReadOnlyAttribute ) )]
public class ReadOnlyDrawer : PropertyDrawer
{
    public override float GetPropertyHeight( SerializedProperty property, GUIContent label )
    {
        return EditorGUI.GetPropertyHeight( property, label, true );
    }

    public override void OnGUI( Rect position, SerializedProperty property, GUIContent label )
    {
        GUI.enabled = false;
        EditorGUI.PropertyField( position, property, label, true );
        GUI.enabled = true;
    }
}
#endif

最新定时器脚本同步更新中:

https://blog.csdn.net/qq_39162566/article/details/113105351

定时器源码:

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

/// <summary>
///
/// Timer
/// 当前定时器程序为游戏运行真实时间 不受Time.timeScale的影响 切记
/// 
/// 案例1: 1秒后调用一次
/// Timer.SetTimeout(1.0f, () =>
/// {
///     Debug.Log("after 1 second.");
/// });
/// 
/// 
/// 案例2: 每1秒更新一次 不限制次数 直到程序退出后停止
/// int i = 0;
/// Timer.SetInterval(1.0f, () =>
/// {
///     Debug.Log(i++);
/// });
/// 
/// 案例3: 每1秒更新一次 更新10次后停止
/// int i = 0;
/// Timer.SetInterval(1.0f, () =>
/// {
///     Debug.Log(i++);
/// }, 10);
/// 
/// 
/// 案例4: 当前帧多次调用仅在结束帧执行一次 
/// for ( int i = 0; i < 1000000; i++ )
/// {
///     Timer.CallerLate( () =>
///     {
///         Debug.Log( "######### hello world" );
///     } );
/// }
///  
/// Anchor: ChenJC
/// Time: 2022/10/09
/// 原文: https://blog.csdn.net/qq_39162566/article/details/113105351
/// </summary>
public class Timer : MonoBehaviour
{

    //定时器数据类
    public class TimerTask
    {
        public int tag;
        public float tm;
        public float life;
        public long count;
        public Action func;
        public TimerTask Clone()
        {
            var timerTask = new TimerTask();
            timerTask.tag = tag;
            timerTask.tm = tm;
            timerTask.life = life;
            timerTask.count = count;
            timerTask.func = func;
            return timerTask;
        }
        public void Destory()
        {
            m_freeTaskCls.Enqueue(this);
        }
    }


    #region Member property

    protected static List<TimerTask> m_activeTaskCls = new List<TimerTask>();//激活中的TimerTask对象
    protected static Queue<TimerTask> m_freeTaskCls = new Queue<TimerTask>();//闲置TimerTask对象
    protected static HashSet<Action> lateChannel = new HashSet<Action>();//确保callLate调用的唯一性
    protected static int m_tagCount = 1000; //timer的唯一标识
    protected static bool m_inited = false; //初始化
    protected bool m_isBackground = false;//是否可以后台运行 false:退到后台时定时器停止运行 

    #endregion


    #region public methods

    //每帧结束时执行回调 : 当前帧内的多次调用仅在当前帧结束的时候执行一次
    public static void CallerLate(Action func)
    {
        if (!lateChannel.Contains(func))
        {
            lateChannel.Add(func);
            SetTimeout(0f, func);
        }
    }


    //delay秒后 执行一次回调
    public static int SetTimeout(float delay, Action func)
    {
        return SetInterval(delay, func, false, 1);
    }

    /// <summary>
    /// 周期性定时器 间隔一段时间调用一次
    /// </summary>
    /// <param name="interval"> 间隔时长: 秒</param>
    /// <param name="func"> 调用的方法回调 </param>
    /// <param name="immediate"> 是否立即执行一次 </param>
    /// <param name="times"> 调用的次数: 默认永久循环 当值<=0时会一直更新调用 当值>0时 循环指定次数后 停止调用 </param>
    /// <returns></returns>
    public static int SetInterval(float interval, Action func, bool immediate = false, int times = 0)
    {
        //从free池中 获取一个闲置的TimerTask对象
        var timer = GetFreeTimerTask();
        timer.tm = 0;
        timer.life = interval;
        timer.func = func;
        timer.count = times;
        timer.tag = ++m_tagCount;

        //尝试初始化
        Init();

        //立即执行一次
        if (immediate)
        {
            --timer.count;
            func?.Invoke();
            if (timer.count == 0)
            {

                timer.Destory();
            }
            else
            {
                //添加到激活池中
                m_activeTaskCls.Add(timer);
            }
        }
        else
        {
            //添加到激活池中
            m_activeTaskCls.Add(timer);
        }

        return m_tagCount;
    }

    #endregion


    #region Get Timer methods

    /// <summary>
    /// 通过Tag获取定时器对象
    /// </summary>
    /// <param name="tag"></param>
    /// <returns></returns>
    public static TimerTask GetTimer(int tag)
    {
        return m_activeTaskCls.Find((TimerTask t) =>
        {
            return t.tag == tag;
        })?.Clone();
    }

    /// <summary>
    /// 通过Tag获取定时器对象
    /// </summary>
    /// <param name="tag"></param>
    /// <returns></returns>
    public static TimerTask GetTimer(Action func)
    {
        return m_activeTaskCls.Find((TimerTask t) =>
        {
            return t.func == func;
        })?.Clone();
    }
    #endregion


    #region Clean Timer methods

    /// <summary>
    /// 通过ID 清理定时器
    /// </summary>
    /// <param name="tag">定时器标签</param>
    /// <returns></returns>
    public static void ClearTimer(int tag)
    {
        int index = m_activeTaskCls.FindIndex((TimerTask t) =>
        {
            return t.tag == tag;
        });

        if (index != -1)
        {
            var t = m_activeTaskCls[index];
            if (lateChannel.Count != 0 && lateChannel.Contains(t.func))
            {
                lateChannel.Remove(t.func);
            }
            m_activeTaskCls.RemoveAt(index);
            m_freeTaskCls.Enqueue(t);
        }
    }

    /// <summary>
    /// 通过方法 清理定时器
    /// </summary>
    /// <param name="func">处理方法</param>
    /// <returns></returns>
    public static void ClearTimer(Action func)
    {
        int index = m_activeTaskCls.FindIndex((TimerTask t) =>
        {
            return t.func == func;
        });

        if (index != -1)
        {
            var t = m_activeTaskCls[index];
            if (lateChannel.Count != 0 && lateChannel.Contains(t.func))
            {
                lateChannel.Remove(t.func);
            }
            m_activeTaskCls.RemoveAt(index);
            m_freeTaskCls.Enqueue(t);
        }
    }

    /// <summary>
    /// 清理所有定时器
    /// </summary>
    public static void ClearTimers()
    {
        lateChannel.Clear();
        m_activeTaskCls.ForEach(timer => m_freeTaskCls.Enqueue(timer));
        m_activeTaskCls.Clear();
    }

    #endregion


    #region System methods

    //Update更新之前
    private void Start()
    {
        DontDestroyOnLoad(gameObject);
        StopAllCoroutines();
        StartCoroutine(TimerElapse());
    }

    //程序切换到后台
    private void OnApplicationPause(bool pause)
    {
        if (!m_isBackground)
        {
            if (pause)
            {
                StopAllCoroutines();
            }
            else
            {

                StopAllCoroutines();
                StartCoroutine(TimerElapse());
            }
        }
    }

    //定时器调度
    private IEnumerator TimerElapse()
    {

        TimerTask t = null;

        while (true)
        {
            if (m_activeTaskCls.Count > 0)
            {
                float dt = Time.unscaledDeltaTime;
                for (int i = 0; i < m_activeTaskCls.Count; ++i)
                {
                    t = m_activeTaskCls[i];
                    t.tm += Time.unscaledDeltaTime;
                    if (t.tm >= t.life)
                    {
                        t.tm -= t.life;
                        if (t.count == 1)
                        {
                            m_activeTaskCls.RemoveAt(i--);
                            if (lateChannel.Count != 0 && lateChannel.Contains(t.func))
                            {
                                lateChannel.Remove(t.func);
                            }
                            t.Destory();
                        }
                        --t.count;
                        t.func();
                    }
                }
            }
            yield return 0;
        }
    }

    //初始化
    protected static void Init()
    {
        if (!m_inited)
        {
            m_inited = true;
            var inst = new GameObject("TimerNode");
            inst.AddComponent<Timer>();
        }
    }

    //获取闲置定时器
    protected static TimerTask GetFreeTimerTask()
    {
        if (m_freeTaskCls.Count > 0)
        {
            return m_freeTaskCls.Dequeue();
        }
        return new TimerTask();
    }

    #endregion

}





  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

极客柒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值