목록

pooling system

pooling은 어디에나 필요하므로. 프레임워크 내에 기본적으로 가져가려고 내가 편한 형태로 작성


  • 전체 풀링 오브젝트 관리해줄 매니저 스크립트

필요한 기능 생각나면 추가해 사용하자

<PoolingManager.cs>

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using FrameWork.ExtensionMethods;
using System;
using System.Threading.Tasks;

namespace FrameWork
{
    public class PoolingManager : Singleton<PoolingManager>
    {
        #region Internal_Class
        /// <summary>
        /// PoolingObject동작 관리 시스템
        /// </summary>
        public class PoolingSystem
        {
            #region Structs
            public struct Option
            {
                public bool useResize;       // 자동할당사용 여부
                public int resizedCount;     // 자동할당 시 추가 될 수
                public int totalLmit;        // 자동할당 시 전체 수 제한 -1 is Unlimited
                public int initialCount;     // 초기 확보 수

                public Option(bool useResize, int resizedCount, int totalLmit, int initialCount)
                {
                    this.useResize = useResize;
                    this.resizedCount = resizedCount;
                    this.totalLmit = totalLmit;
                    this.initialCount = initialCount;
                }
                /// <summary>
                /// Pooling옵션.
                /// </summary>
                /// <param name="useResize">자동할당기능</param>
                /// <param name="resizedCount">자동할당 시 생성 될 수</param>
                /// <param name="totalLmit">전체 수 제한(-1이면 제한없음)</param>
                /// <param name="initialCount">초기 할당 수</param>
                /// <returns></returns>
                public static Option Create(bool useResize, int resizedCount, int totalLmit, int initialCount)
                {
                    return new Option(useResize, resizedCount, totalLmit, initialCount);
                }
                public static Option Defalut()
                {
                    return new Option(true, 10, -1, 20);
                }
            }
            #endregion

            #region Fields
            Queue<PoolingObject> m_ReadyForUseObjs;     // 사용 준비중인 Object큐
            Option m_Option;
            GameObject m_OriginPrefab;                  // 원본 Prefab
            int
                m_TotalPoolingObjCnt,
                m_UsedPoolingObjCnt;
            Action<GameObject>
                m_AcPoolingObjRevive,                   // 풀링객체 사용될 떄 호출될 콜백
                m_AcPoolingObjReturn;                   // 풀링객체 회수될 때 호출될 콜백
            Transform m_PoolHolder;                     // Pooling객체 잡아둘 홀더
            #endregion

            #region Properties
            public int ReadyForUseCount { get => m_ReadyForUseObjs.Count;  }    // 사용가능 대기중인 수
            public int TotalPoolingObjCnt { get => m_TotalPoolingObjCnt; }      // 전체 풀링객체 수
            public int UsedPoolingObjCnt { get => m_UsedPoolingObjCnt; }        // 사용되고 있는 수
            #endregion

            #region Methods_Private
            private void Init()
            {
                m_ReadyForUseObjs.Clear();
                for (int i = 0; i < m_Option.initialCount; ++i)
                {
                    CreatePoolingObject(m_PoolHolder, m_AcPoolingObjRevive, m_AcPoolingObjReturn);
                }
            }
            private void CreatePoolingObject(Transform parent_, Action<GameObject> whenRevive_, Action<GameObject> whenReturn_)
            {
                GameObject _go = UnityEngine.Object.Instantiate(m_OriginPrefab);
                _go.name = m_OriginPrefab.name;
                _go.SetActive(false);

                PoolingObject _poolingObj = _go.GetComponent<PoolingObject>();
                if (whenRevive_ != null) _poolingObj.AddEventWhenRevive(whenRevive_);
                if (whenReturn_ != null) _poolingObj.AddEventWhenReturn(whenReturn_);

                _poolingObj.transform.SetParent(parent_);
                m_ReadyForUseObjs.Enqueue(_poolingObj);
                ++m_TotalPoolingObjCnt;
            }
            private bool Resize()
            {
                // 사이즈제한 있는 경우
                if (m_Option.totalLmit != -1)
                {
                    // 사이즈제한 넘으면 false
                    if (m_TotalPoolingObjCnt > m_Option.totalLmit)
                        return false;

                    // 사이즈제한 고려해 오브젝트 생성
                    int _permitCount = m_Option.totalLmit - m_TotalPoolingObjCnt;
                    if (_permitCount > m_Option.resizedCount)
                    {
                        for (int i = 0; i < m_Option.resizedCount; ++i)
                        {
                            CreatePoolingObject(m_PoolHolder, m_AcPoolingObjRevive, m_AcPoolingObjReturn);
                        }
                    }
                    else
                    {
                        for (int i = 0; i < _permitCount; ++i)
                        {
                            CreatePoolingObject(m_PoolHolder, m_AcPoolingObjRevive, m_AcPoolingObjReturn);
                        }
                    }
                    return true;
                }
                else
                {
                    for (int i = 0; i < m_Option.resizedCount; ++i)
                    {
                        CreatePoolingObject(m_PoolHolder, m_AcPoolingObjRevive, m_AcPoolingObjReturn);
                    }
                    return true;
                }
            }
            #endregion

            #region Methods_Public
            public PoolingSystem(PoolingObject poolingObj_, Option option_, Transform parent_, Action<GameObject> whenObjRevive_ = null, Action<GameObject> whenObjReturn_ = null)
            {
                m_OriginPrefab = poolingObj_.gameObject;
                m_Option = option_;
                m_ReadyForUseObjs = new Queue<PoolingObject>(m_Option.initialCount);
                m_AcPoolingObjRevive = whenObjRevive_;
                m_AcPoolingObjReturn = whenObjReturn_;
                m_PoolHolder = parent_;
                Init();
            }
            public static PoolingSystem Create(PoolingObject poolingObj_, Option option_, Transform parent_, Action<GameObject> whenObjRevive_ = null, Action<GameObject> whenObjReturn_ = null)
            {
                return new PoolingSystem(poolingObj_, option_, parent_, whenObjRevive_, whenObjReturn_);
            }
            /// <summary>
            /// 풀링객체 반환. 만약 resize가 안되서 반환할게 없다면 null반환한다.
            /// </summary>
            /// <returns></returns>
            public PoolingObject GetPoolingObject()
            {
                PoolingObject _poolingObj = null;

                if (m_ReadyForUseObjs.Count == 0)
                {
                    if (m_Option.useResize)
                    {
                        if (Resize())
                        {
                            _poolingObj = m_ReadyForUseObjs.Dequeue();
                        }
                    }
                }
                else
                {
                    _poolingObj = m_ReadyForUseObjs.Dequeue();
                }

                if (_poolingObj != null) ++m_UsedPoolingObjCnt;
                return _poolingObj;
            }
            public void ReturnPoolingObject(PoolingObject po_)
            {
                --m_UsedPoolingObjCnt;
                m_ReadyForUseObjs.Enqueue(po_);
            }
            #endregion
        }
        #endregion

        #region Fields
        [SerializeField] Dictionary<string, PoolingSystem> m_DicPooling;
        #endregion

        #region Properties
        public Dictionary<string, PoolingSystem> DicPooling { get => m_DicPooling; }
        #endregion

        #region Methods_Protected
        protected override void Init()
        {
            m_DicPooling = new Dictionary<string, PoolingSystem>();
        }
        #endregion

        #region Methods_Public
        /// <summary>
        /// 풀링할 오브젝트들을 미리 등록한다.
        /// </summary>
        /// <param name="prefab_">등록할 Prefab</param>
        /// <param name="option_">PoolingSystem에 사용될 Option값</param>
        /// <param name="whenObjRevive_">풀링될객체가 사용될 때 호출될 콜백함수 - POInstantiate로 생성될 떄 호출</param>
        /// <param name="whenObjReturn_">풀링될객체가 회수될 때 호출될 콜백함수 - PODestroy로 회수될 댸 호출</param>
        public static void RegisterPrefab(GameObject prefab_, PoolingSystem.Option option_, Action<GameObject> whenObjRevive_ = null, Action<GameObject> whenObjReturn_ = null)
        {
            if (PoolingManager.Instance.m_DicPooling.ContainsKey(prefab_.name))
            {
                Debug.LogError("aleady Register" + prefab_.name);
                return;
            }

            // PoolingObject컴포넌트 설정
            PoolingObject _poolingObj = prefab_.GetComponent<PoolingObject>();
            if (_poolingObj == null) _poolingObj = prefab_.AddComponent<PoolingObject>();

            GameObject _poolingObHandler = new GameObject("Pool_" + prefab_.name);
            _poolingObHandler.transform.SetParent(PoolingManager.Instance.transform);
            // Dic에 등록
            PoolingManager.Instance.m_DicPooling.Add(prefab_.name, PoolingSystem.Create(_poolingObj, option_, _poolingObHandler .transform, whenObjRevive_, whenObjReturn_));
        }
        /// <summary>
        /// 사용하기에 앞서 RegisterPrefab에 등록된 프리팹인지 확인.
        /// <para>PODestroy로 풀로 돌려줄 떄는 PoolingObject인자로 호출하는게 더 최적화된다. => 미리 PoolingObject캐싱해 놓자.</para>
        /// </summary>
        /// <param name="prefab_"></param>
        /// <returns></returns>
        public static PoolingObject POInstantiate(GameObject prefab_)
        {
            if (!PoolingManager.Instance.m_DicPooling.ContainsKey(prefab_.name))
            {
                Debug.LogError("Please RegisterPrefab before Call POInstantiate");
                return null;
            }

            PoolingObject _po = PoolingManager.Instance.m_DicPooling[prefab_.name].GetPoolingObject();
            _po.gameObject.SetActive(true);
            return _po;
        }
        public static PoolingObject POInstantiate(GameObject prefab_, Transform parent_)
        {
            if (!PoolingManager.Instance.m_DicPooling.ContainsKey(prefab_.name))
            {
                Debug.LogError("Please RegisterPrefab before Call POInstantiate");
                return null;
            }

            PoolingObject _po = PoolingManager.Instance.m_DicPooling[prefab_.name].GetPoolingObject();
            _po.gameObject.SetActive(true);
            _po.transform.SetParent(parent_);
            return _po;
        }
        public static PoolingObject POInstantiate(GameObject prefab_, Transform parent_, bool worldPositionStays)
        {
            if (!PoolingManager.Instance.m_DicPooling.ContainsKey(prefab_.name))
            {
                Debug.LogError("Please RegisterPrefab before Call POInstantiate");
                return null;
            }

            PoolingObject _po = PoolingManager.Instance.m_DicPooling[prefab_.name].GetPoolingObject();
            _po.gameObject.SetActive(true);
            _po.transform.SetParent(parent_, worldPositionStays);
            return _po;
        }
        public static PoolingObject POInstantiate(GameObject prefab_, Vector3 position_, Quaternion rotation)
        {
            if (!PoolingManager.Instance.m_DicPooling.ContainsKey(prefab_.name))
            {
                Debug.LogError("Please RegisterPrefab before Call POInstantiate");
                return null;
            }

            PoolingObject _po = PoolingManager.Instance.m_DicPooling[prefab_.name].GetPoolingObject();
            _po.gameObject.SetActive(true);
            _po.transform.SetPositionAndRotation(position_, rotation);
            return _po;
        }
        public static PoolingObject POInstantiate(GameObject prefab_, Vector3 position_, Quaternion rotation, Transform parent_)
        {
            if (!PoolingManager.Instance.m_DicPooling.ContainsKey(prefab_.name))
            {
                Debug.LogError("Please RegisterPrefab before Call POInstantiate");
                return null;
            }

            PoolingObject _po = PoolingManager.Instance.m_DicPooling[prefab_.name].GetPoolingObject();
            _po.gameObject.SetActive(true);
            _po.transform.SetPositionAndRotation(position_, rotation);
            _po.transform.SetParent(parent_);
            return _po;
        }
        public static void PODestroy(GameObject prefab_)
        {
            if (!PoolingManager.Instance.m_DicPooling.ContainsKey(prefab_.name))
            {
                Debug.LogError(prefab_.name + "is not poolingObject");
            }

            prefab_.SetActive(false);
            PoolingManager.Instance.m_DicPooling[prefab_.name].ReturnPoolingObject(prefab_.GetComponent<PoolingObject>());
        }
        public static void PODestroy(PoolingObject po_)
        {
            if (!PoolingManager.Instance.m_DicPooling.ContainsKey(po_.name))
            {
                Debug.LogError(po_.name + "is not poolingObject");
            }

            po_.gameObject.SetActive(false);
            PoolingManager.Instance.m_DicPooling[po_.name].ReturnPoolingObject(po_);
        }
        public static void PODestroy(GameObject prefab_, float t_)
        {
            AppTools.WaitThenCallback(PoolingManager.Instance, t_, () => { PoolingManager.PODestroy(prefab_); });
        }
        public static void PODestroy(PoolingObject po_, float t_)
        {
            AppTools.WaitThenCallback(PoolingManager.Instance, t_, () => { PoolingManager.PODestroy(po_); });
        }
        #endregion
    }
}

<PoolingObject.cs>

  • 풀링 프리팹이 등록되면 자동적으로 붙을 컴포넌트
  • 각 개별적으로 풀링 호출 밑 회수 때 이벤트 부여하기 위해 만듬
    더 추가할 이벤트 있으면 추가로 작성하면 된다
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using System;

public class PoolingObject : MonoBehaviour
{
    #region Fields
    Action<GameObject> m_AcRevive;
    Action<GameObject> m_AcReturn;
    #endregion

    #region Properties
    #endregion

    #region UnityEvents
    private void Awake() { }
    private void OnEnable() => m_AcRevive?.Invoke(gameObject);
    private void OnDisable() => m_AcReturn?.Invoke(gameObject);
    #endregion

    #region Methods_Public
    public void AddEventWhenRevive(Action<GameObject> ac_) => m_AcRevive += ac_;
    public void AddEventWhenReturn(Action<GameObject> ac_) => m_AcReturn += ac_;
    #endregion
}

—사용법

  • 등록할 프리팹 변수 선언
    pbulic Gameobject _prefab;

  • PoolingManager에 pooling할 오브젝트 등록

    // 사용할 프리팹 등록
    PoolingManager.RegisterPrefab(_prefab, PoolingManager.PoolingSystem.Option.Create(true, 5, -1, 5),
    (_go) => {
      // 생성 될 때 호출
      Debug.Log(_prefab.name + "is Revive.");
      // PoolingManager.PODestroy(PoolingObject, 2f); 인자 값으로 미리 캐싱해놓은 PoolingObject사용하는게 더 최적화된다.
      // 여기서는 간단하게 확인상 생성과 동시에 제거하느라 캐싱을 못했지만 POInstantiate사용시 PoolingObject컴포넌트 반환하므로
      // 미리 캐싱해놓고 사용하자.(Gameobject로 넘길시 내부에서 GetComponent<PoolingObject>호출함)
      PoolingManager.PODestroy(_go, 2f); //2초뒤에 회수되도록
    },
    (_go) => {
      // 회수 될 때 호출
      Debug.Log(_prefab.name + "is Return to Pooling.");
    });
  • 생성하고 싶은 곳에서 생성

    // 사용할 곳에 Instantiate와 동일하게 호출
    PoolingObject _po = PoolingManager.POInstantiate(_prefab, Vector3.zero, Quaternion.identity);