Wednesday, November 6, 2013

Objects Pool Manager in Unity3d

Standard
Hello All:

Sometimes in our games we require game objects of same type to be instantiated constantly like bullets from a gun or coins on the map. This generation of game objects incur huge amount of overhead on CPU. Here we can use the technique of pooling the objects and make the objects enable/ disable from this pool.


Object pooling means to instantiate specific amount of re-usable objects well before they are used and then give the objects from this pool when ever there is need of any.

Demo for this tutorial can be seen here.

This post will have following parts:

  1. ObjectPool: Class which will depict our pool of objects
  2. ObjectPoolManager: This class will manage all the pools
  3. ObjectPoolsInitializers: This class will initialize the object pools
  4. ObjectGenerator: This class will randomly generate game objects to test the object pools
  5. Some helper classes
Fireup unity and create a project. Let us call it ObjectPoolDemo.Create following scripts in the unity project
  1. ObjectPool.cs
                            using UnityEngine;
    using System.Collections;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    public class ObjectPool
    {
        GameObject poolGameObject;
        Stack<GameObject> availableGameObjects;
        List<GameObject> allGameObjects;
        Action<GameObject> initAction;
        public ObjectPool(GameObject gameObject, int initialCapacity, Action<GameObject> initAction)
        {
            this.poolGameObject = gameObject;
            this.initAction = initAction;
            availableGameObjects = new Stack<GameObject>();
            allGameObjects = new List<GameObject>();
            int index = 0;
            for (index = 0; index < initialCapacity; index++)
            {
                GameObject localGameObject = GetGameObject();
                allGameObjects.Add(localGameObject);
                availableGameObjects.Push(localGameObject);
            }
        }
        private GameObject GetGameObject()
        {
            GameObject result = GameObject.Instantiate(this.poolGameObject) as GameObject;
            result.name = this.poolGameObject.name + "_" + (allGameObjects.Count + 1).ToString();
            if (this.initAction != null)
            {
                this.initAction(result);
            }
            result.SetActive(false);
            return result;
        }
        #region Methods
        public GameObject Spawn(Vector3 position, Quaternion rotation)
        {
            GameObject result =null;
            if (!availableGameObjects.Any())
            {
                result = GetGameObject();
                allGameObjects.Add(result);
                availableGameObjects.Push(result);
            }
            result = availableGameObjects.Pop();
            Transform resultTrans = result.transform;
            resultTrans.position = position;
            resultTrans.rotation = rotation;
            SetActive(result, true);
            result.SendMessage("Start");
            return result;
        }
        public bool Destroy(GameObject target)
        {
            availableGameObjects.Push(target);
            SetActive(target, false);
            return true;
        }
        public void Clear()
        {
            foreach (var item in availableGameObjects)
            {
                GameObject.Destroy(item);
            }
            foreach (var item in allGameObjects)
            {
                GameObject.Destroy(item);
            }
            availableGameObjects.Clear();
            allGameObjects.Clear();
        }
        public void Reset()
        {
            availableGameObjects.Clear();
            foreach (var item in allGameObjects)
            {
                item.SetActive(false);
                availableGameObjects.Push(item);
            }
        }
        protected void SetActive(GameObject target, bool value)
        {
            target.SetActive(value);
        }
        #endregion
    }
    
  2. ObjectPoolManager.cs
                            using UnityEngine;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    public class ObjectPoolManager
    {
        public Dictionary<PoolType, ObjectPool> objectsPool;
        static ObjectPoolManager instance = null;
        public static ObjectPoolManager Instance
        {
            get
            {
                if (instance == null)
                {
                    instance = new ObjectPoolManager();
                    instance.objectsPool = new Dictionary<PoolType, ObjectPool>();
                }
                return instance;
            }
        }
        public void CreatePool(PoolType type, ObjectPool pool)
        {
            if (!objectsPool.ContainsKey(type))
            {
                objectsPool.Add(type, pool);
            }
            else
            {
                objectsPool[type].Clear();
            }
        }
        public void Reset()
        {
            foreach (var item in objectsPool)
            {
                item.Value.Reset();
            }
        }
        public ObjectPool GetPool(PoolType type)
        {
            try
            {
                return objectsPool[type];
            }
            catch
            {
                Debug.Log(type);
            }
            return null;
        }
        public void Spawn(PoolType type, Vector3 instantiateVector)
        {
            if (Instance.objectsPool.Any())
            {
                ObjectPoolManager.Instance.GetPool(type).Spawn(instantiateVector, Quaternion.identity);
            }
        }
        public void Destroy(PoolType type, GameObject target)
        {
            if (ObjectPoolManager.Instance.objectsPool.Any())
            {
                ObjectPoolManager.Instance.GetPool(type).Destroy(target);
            }
        }
    }
    
  3. ObjectPoolsInitializers.cs
                            using UnityEngine;
    using System.Collections;
    public class ObjectPoolsInitializers : MonoBehaviour
    {
        public GameObject cube;
        public GameObject cylinder;
        public int cubePoolSize = 1;
        public int cylinderPoolSize = 1;
        public static ObjectPoolsInitializers Instance;
        void Awake()
        {
            //InitializeObjectPools();
            Instance = this;
            InitializeObjectPools();
        }
        private void InitializeObjectPools()
        {
            ObjectPoolManager.Instance.CreatePool(PoolType.Cube, GetPool(cube, cubePoolSize));
            ObjectPoolManager.Instance.CreatePool(PoolType.Cylinder, GetPool(cylinder, cylinderPoolSize));
        }
        public ObjectPool GetPool(GameObject poolGameObject, int poolSize)
        {
            ObjectPool pool = new ObjectPool(poolGameObject, poolSize, (go) =>
            {
            });
            return pool;
        }
    }
    public enum PoolType
    {
        Cube,
        Cylinder
    }
    
  4. ObjectGenerator.cs
                            using UnityEngine;
    using System.Collections;
    using System.Collections.Generic;
    using System;
    using System.Linq;
    using Rand = UnityEngine.Random;
    public class ObjectGenerator : MonoBehaviour
    {
        #region Variables
        float rateOfSpawn = 1.0f;
        float nextSpawn = 2.0f;
        #endregion
        #region Methods
        // Use this for initialization
        void Start()
        {
        }
        // Update is called once per frame
        void Update()
        {
            PoolType type = PoolType.Cube;
            string name = string.Empty;
            if (Time.time > nextSpawn)
            {
                switch (Rand.Range(0, 2))
                {
                    case 0: name = "Cube";
                        type = PoolType.Cube;
                        break;
                    case 1: name = "Cylinder";
                        type = PoolType.Cylinder;
                        break;
                    default: break;
                }
                ObjectPoolManager.Instance.Spawn(type, new Vector3(Rand.Range(0, 5), Rand.Range(0, 5), Rand.Range(0, 5)));
                rateOfSpawn = Rand.Range(.1f,1f);
                nextSpawn = Time.time + rateOfSpawn;
            }
        }
        #endregion
    }
    
  5. Cube.cs
                            
    using UnityEngine;
    using System.Collections;
    using System.Collections.Generic;
    using System;
    using System.Linq;
    using Rand = UnityEngine.Random;
    public class Cube : MonoBehaviour
    {
        #region Variables
        float startTime;
        #endregion
        #region Methods
        void Start()
        {
            startTime = Time.time;
            gameObject.transform.renderer.material.color = Rand.Range(1, 10) % 2 == 0 ? Color.red : Color.blue;
        }
        // Update is called once per frame
        void Update()
        {
            if ((startTime + 3.0f) <Time.time)
            {
                ObjectPoolManager.Instance.Destroy(PoolType.Cube, gameObject);
            }
        }
        #endregion
    }
    
  6. Cylinder.cs
                            
    using UnityEngine;
    using System.Collections;
    using System.Collections.Generic;
    using System;
    using System.Linq;
    using Rand = UnityEngine.Random;
    public class Cylinder : MonoBehaviour
    {
        #region Variables
        float startTime;
        #endregion
        #region Methods
        // Use this for initialization
        void Start()
        {
            startTime = Time.time;
            gameObject.transform.renderer.material.color = Rand.Range(1, 10) % 2 == 0 ? Color.red : Color.blue;
        }
        // Update is called once per frame
        void Update()
        {
            if ((startTime + 4.0f) < Time.time)
            {
                ObjectPoolManager.Instance.Destroy(PoolType.Cylinder, gameObject);
            }
        }
        #endregion
    }
    


You can get the sample project here.

Hope this helps.

Thanks for printing this post. Hope you liked it.
Keep visiting and sharing.
Thanks,
Ashwani.

2 comments :

  1. Hi,

    thank you for the great tutorial. I discovered a small error, at least on my end.
    Because some of the objects are not Unity Objects they wont get destroyed at the end of a scene. So if I restart my level the ObjectPool wont be destroyed, but all objects inside it.
    So as a result of this I have a lot of pointers to destroyed objects. A quick fix on my end was to extend the method CreatePool:

    public void CreatePool(PoolType type, ScriptObjectPool pool)
    {

    if (!objectsPool.ContainsKey (type)) {
    objectsPool.Add (type, pool);
    } else {
    objectsPool[type].Clear();
    }
    }
    You might want to include this in your example ;)
    But anyway great work and thanks a lot for sharing.

    Cheers Pascal

    ReplyDelete
    Replies
    1. Thanks for pointing it out. I have update the code.

      Happy coding :)

      Delete