Sunday, August 3, 2014

Object Pooling

Object pooling is a pretty cool technique developed to reduce the performance cost of instantiating and destroying objects. Its a commonly used technique for simple things like bullets, but you can pool more complex objects like enemies as well.


What's wrong with just using Instantiate() and Destroy()?

Good question. Instantiate is fairly obvious, every time you call instantiate you need to create the GameObject in memory. There is a bunch of components loaded. Materials, textures. It quickly adds up over multiple frames. Destroy is not so expensive to start with, as all it does is remove the references to your object. However destroyed objects hang on to the memory until the garbage collecter runs. Nothing drops the frame rate like frequent calls to the garbage collecter.


Common traps to avoid

There are multiple ways to code a pool, and the above all the code should suit the application. Here are a few potential problems that are easy to avoid when designing a pool.

Set size pools. Unless you are absolutely certain of the size your pool will be its a bad idea to fix the size. Players have a nasty habit of getting into situations where more bullets are fired then you thought possible. You don't want to be firing blanks because your pool ran out of bullets. One way to overcome this is to use a massive pool size, but this is ultimately a waste of resources. For this reason dynamically sized data containers like Lists are better then fixed size Arrays.

Static references. A first glance these seem like a great idea for a pool. Static references, or singletons are great ways to ensure you can access universal managers from anywhere in the game. But most of the code for an object pool is identical across all pools. If you want more then one pool in the game then you can save yourself rewriting all the code again by avoiding static references from the start.


Tricks

There are a few key differences to using pools that are worth noting. The first is obvious, you don't use instantiate or destroy. The pool should take care of any instantiating and destroying that is needed. Slightly less obvious is that Start, Awake and OnDestroy become less useful. Start and Awake can still be used to initialise things, but these methods are only ever called once. For something like a bullets velocity that needs to be initiated every time a bullet is fired then use OnEnable and OnDisable.


My implementation

I built a Pool class. This class has only two exposed members. The first is a GameObject thing used to determine what to pool. The second is a GameObject nextThing used to get the next object out of the pool. Asinging a GameObject to nextThing will return it to the pool.

Inside the class I use a private List<GameObject> to store the inactive GameObjects. I decided only to store references to the inactive GameObjects, rather then keeping track of all of them. This means that I don't have to deal with any lost or destroyed GameObjects. On the other hand it does require some more trickery when it comes to getting the GameObjects back into the pool.

Most of the real action happens inside the get and set method of nextThing. The get method takes care of a couple of key jobs. The first is instantiating new objects. First we check if there are any available inactive GameObjects in the List. If not a new clone is created using instantiate. The parent of the clone is set to the pool. And a component with a reference back to the pool is added. Then the clone is sent off into the world. If it turns out there are inactives available then one of them is activated and returned.

The set method simply takes a GameObject, disables it, and adds it too the list. One possible improvement to the class would be verification that the passed GameObject actually belongs on the list.

The final piece of the puzzle is a PoolMember component that is added to the GameObject after instantiation. This holds a reference to the Pool that created it. It also has an OnDisable method that returns the GameObejct to the pool to be reused. The beauty of this is that you can simply disable any object that is part of the pool without having to worry about where its going.

By now I think you are ready for some actual code. Enjoy:

    using UnityEngine;  
    using System.Collections;  
    using System.Collections.Generic;
  
    public class Pool : MonoBehaviour {  
        public GameObject thing;  
        private List<GameObject> things = new List<GameObject>();  
        public GameObject nextThing {
            get {  
                if (things.Count < 1){  
                    GameObject newClone = (GameObject)Instantiate (thing);  
                    newClone.transform.parent = transform;  
                    newClone.SetActive (false);  
                    things.Add (newClone);  
                    PoolMember poolMember = newClone.AddComponent<PoolMember>();  
                    poolMember.pool = this;  
                }  
            GameObject clone = things[0];  
            things.RemoveAt (0);  
            clone.SetActive (true);  
            return clone;  
            }  
            set {  
                value.SetActive (false);  
                things.Add (value);  
            }  
        }  
    }  

    using UnityEngine;  
    using System.Collections;  
    public class PoolMember : MonoBehaviour {  
    public Pool pool;  
    
        void OnDisable(){  
            pool.nextThing = gameObject;  
        }  
    }  

Edit: If you want to see an example of this check out the git repo on bitbucket. https://bitbucket.org/BoredMormon/object-pooling/src

5 comments:

Richard Gubb said...

Its just been pointed out to me that this code will run faster if you take the last object off the list, instead of the first. Will come back and update the code soon.

Jason said...

Hey Richard! Is it possible to pool an array of GameObject?

Richard Gubb said...

Hi Jason.

Sorry for the delayed response, notification slipped through my email inbox.

The pooling concept can be used for pretty much anything allocated in memory. The idea is to have the objects predefined and then grab an empty one at runtime.

Unknown said...

Could you provide a couple lines of code for actually bringing an object into and out of the game using these scripts?

Richard Gubb said...

Hey Unknown. The easiest way to do this was to set up a git repo. You can check it out here.

https://bitbucket.org/BoredMormon/object-pooling/src

Post a Comment