Search code examples
c#unity-game-engineeventssingletonobject-pooling

Unity3D: How to do object pooling without a spawner singleton


Usually, if you use object pooling, you make a singleton like in this video. After seeing this video, I discovered how messy singleton can be. Is there any other way to do object pooling without using singletons? I wanna instead use Events.


Solution

  • You would need to hold the pool in a class which is not a singleton, and handle your gameobject pool according to your events. Regarding to call them with events, "I want to use events" is not a very concrete question. You need to set your events to listen (method subscribe) and to call them in the code wherever they're supposed to occur, this is invoke the method. I suggest that if you are not clear about this, try to use the unity events (OnTriggerEnter, if(Input.GetMouseButtonDown(0)) in the Update etc) until you dig in the topic enough to understand them and make ones of you own with c# events or UnityEvents when needed.

    Find two template scripts, a pool and and event handler to handle your objects in the scene. You can check those out in an empty scene with your respective two gameObject to attach, and the object you want in the pool, pressing 'space' and 'A' to create from pool and return to pool respectively.

    Pool manager:

    using System.Collections.Generic;
    using UnityEngine;
    
    public class PoolManager : MonoBehaviour
    {
        private Queue<GameObject> objPool;
        private Queue<GameObject> activeObj;
        private int poolSize = 10;
        public GameObject objPrefab;
    
        void Start()
        {
            //queues init
            objPool = new Queue<GameObject>();  
            activeObj = new Queue<GameObject>();
            //pool init
            for (int i = 0; i < poolSize; i++) 
            {
                GameObject newObj = Instantiate(objPrefab);
                objPool.Enqueue(newObj);   
                newObj.SetActive(false);    
            }
        }
    
        public GameObject GetRandomActiveGO() {
            GameObject lastActive = default;
            if (activeObj.Count > 0)
                lastActive = activeObj.Dequeue();
            else {
                Debug.LogError("Active object queue is empty");
            }
            return lastActive;
        }
    
        //get from pool
        public GameObject GetObjFromPool(Vector3 newPosition, Quaternion newRotation)
        {
            GameObject newObject = objPool.Dequeue();
            newObject.SetActive(true);
            newObject.transform.SetPositionAndRotation(newPosition, newRotation);
    
            //keep actives to be retrieved
            activeObj.Enqueue(newObject);
            return newObject;
        }
    
        //return to pool
        public void ReturnObjToPool(GameObject go)
        {
            go.SetActive(false);
            objPool.Enqueue(go);
        }
    }
    

    Event handler:

    using UnityEngine;
    
    public class EventHandler : MonoBehaviour
    {
        public delegate GameObject OnSpacePressed(Vector3 newPosition, Quaternion newRotation);
        public OnSpacePressed onSpacePressed;
    
        public delegate void OnAKeyPressed(GameObject go);
        public OnAKeyPressed onAKeyPressed;
    
        public PoolManager poolManager;
    
        void Start()
        {
            onSpacePressed = poolManager.GetObjFromPool;
            onAKeyPressed = poolManager.ReturnObjToPool;
        }
    
        void Update()
        {
            if (Input.GetKeyDown(KeyCode.Space))
            {
                onSpacePressed?.Invoke(new Vector3(0, 0, 0), Quaternion.identity);
            }
    
            //here I get a random active, however this would be called in the specific objects remove circumstances, 
            //so you should have a reference to that specific gameobje when rerunrning it to the pool.
            if (Input.GetKeyDown(KeyCode.A))
            { 
                GameObject go = poolManager.GetRandomActiveGO();
                onAKeyPressed?.Invoke(go);
            }
        }
    }
    

    Edit: Singleton pattern

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
    {
        protected static T _instance;
        public static T instance
        {
            get
            {
                if (_instance == null)
                {
                    _instance = GameObject.FindObjectOfType<T>();
                    if (_instance == null)
                    {
                        _instance = new GameObject(typeof(T).Name).AddComponent<T>();
                    }
    
                }
                return _instance;
            }
        }
    }