Friday, 12 December 2014

Object Pooling–Unity3D

I know, I know, there are already lots of solutions out there for this in Unity3D, and you have probably already got your own mechanism in place to handle this, but I thought I would post how I have object pooling in my current game project.
Ill start with an apology, I have anew laptop, have not blogged in a while and don’t seem to be able to find the Windows Live Plugin I use to use for pasting code from Visual Studio.
So, what is object pooling, well the usual application of this is for things like bullets, I am also using it for bombs, missiles as well as blast fragments.
As you can imagine you could end up having to create a lot of these sorts of items on the fly using the Unity3D Intrinsic “Instantiate” which will create a totally new object when called. So, a way around this is to have a group of pre instantiated objects, a pool of objects :)

Object Pooler

Lest look at my pool class, we will start with the variables we are going to use. It’s derived from MonoBehavior so we can attach it to an empty game object in the editor.
    public int StartingPoolSize = 25;

    public GameObject ObjectType;

    List<GameObject> Queue = new List<GameObject>();
    List<GameObject> Live = new List<GameObject>();

    public int TotalObjects = 0;
    public int QueuedObjects = 0;
    public int LiveObjects = 0;

So, we have a starting pool size, I have a default of 25 for this, but in the editor you can set this to be what ever you like.

We then have an ObjectType, this is the GameObject that is going to be pooled.

I then have two lists, one is the list of items that are not yet in use, the Queue list and a list of those in use, the Live list.

I then have a few counters, these are just so I can see what’s going on while I am debugging the pool.




void Start()


Now, the Start method looks like this:

    void Start()
    {
        for (int o = 0; o < StartingPoolSize; o++)
            AddObject();
    }

So, all we are doing here is initializing the Queue list to the size of our default Queue size, so lets look at what the AddObject method is doing.




void AddObject()


    public void AddObject()
    {
        Queue.Add((GameObject)Instantiate(ObjectType));
        Queue[Queue.Count - 1].transform.parent = transform;
        Queue[Queue.Count - 1].gameObject.SetActive(false);
        TotalObjects++;
    }

All that this method is doing is instantiating an object and adding it to the Queue list, for now making it a child of this game object, and ensuring it is not active. I then increment the total objects count.




GameObject InstanciateObject()


So, now we need a method to get a new object from the Queue and put it in the game world, this is where this method comes in.

    public GameObject InstanciateObject()
    {
        if (Queue.Count == 0)
        {
            // Create 3 more...
            for (int i = 0; i < 3; i++)
                AddObject();
        }

        AddLive(Queue[Queue.Count - 1]);

        Queue.RemoveAt(Queue.Count - 1);

        return Live[Live.Count - 1];
    }

The first thing I do is see if there are any object left in the Queue list, the list is reduced each time we take an object and put it in the game world, so if they are all used, we need to pad the list out a little, so if it is empty I create another 3 objects. I guess I could add another public property for growth, so rather than a hard coded 3 the list will grow by this property as it’s set in the editor.

I then call my AddLive method, this moved the object over to the Live list, makes sure it’s ready for the world and then is activated. I then remove this object from the Queue and return the new live object.




void AddLive(GameObject o)


    void AddLive(GameObject o)
    {
        Live.Add(o);

        if (Live[Live.Count - 1].GetComponent<GameObjectDeath>() != null)
        {
            Live[Live.Count - 1].GetComponent<GameObjectDeath>().ReSet();
            Live[Live.Count - 1].GetComponent<GameObjectDeath>().IsPooled = true;
        }        

        Live[Live.Count - 1].gameObject.SetActive(true);
    }

As you can see, it adds the object to the Live list, then there is a bit of code that makes sure that if this object is being used for the nth time, by that I mean, it’s not the first time it’s been used, that I reset my object. I have a GameObjectDeath script, this script will “Destroy” an object if it collides with another object, or it can be given a life span, this bit of code just makes sure that this gets set.

Finally it makes the object active.




void DestroyObject(GameObject o)


    public void DestroyObject(GameObject o)
    {
        o.SetActive(false);
        Queue.Add(o);
        Live.Remove(o);
    }

And so, when an object  is destroyed, rather than calling the intrinsic Unity3D method, we use this method. It deactivates the object and moves it from the Live list back into the Queue.

To use this now, we can create an empty game object in the editor and attach the script, we can then set these variables in the editor like this:

image

As you can see this pool is managing bullets. The bullet object is a prefabricated game object I have already created.








OK, so now we can pool an object, but wouldn't it be great if we could do this for lots of types of object?




ObjectPoolManager


And that’s where this bit of code comes in :D

All we need in here are two variables

    public static ObjectPoolManager thisObjectPoolManager;

    List<ObjectPooler> pools;

I have a static so I can get at the class and it’s methods from any other script and a list of ObjectPoller objects. These will be child game objects held in this game object.




void Start()


 void Start () 
    {
        thisObjectPoolManager = this;

        pools = GetComponentsInChildren<ObjectPooler>().ToList();
    }


In the Start method we set the static to this, then get all the child ObjectPooler game objects.




GameObject InstanciateObject(GameObject gameObj)


    public GameObject InstanciateObject(GameObject gameObj)
    {
        ObjectPooler pl = pools.First(p => p.ObjectType.name == gameObj.name);
        if (pl != null)
            return pl.InstanciateObject();
        else
        {
            print("Can't find pool for " + gameObj.name);
            return null;
        }        
    }

So, now when another script want’s to instantiate a new game object, it will call the manager, the manger in turn then gets the pool related to the object they want to instantiate, by comparing the pool’s ObjectType.name property with the name property of the object to be instantiated, if found it then calls the pools InstanciateObject method and returns the world ready object to the caller.

If it can’t find it (and this will be because you have not set up a pool for the required object) then it will return null.




GameObject InstanciateObject(GameObject gameObj, Vector3 Position, Quaternion rotation)


Sometimes you want p instantiate an object at a position, with a rotation, and this overloaded method will do just that.

    public GameObject InstanciateObject(GameObject gameObj, Vector3 Position, Quaternion rotation)
    {
        GameObject go = InstanciateObject(gameObj);
        go.transform.position = Position;
        go.transform.rotation = rotation;

        return go;
    }





void DestroyObject(GameObject gameObj)


    public void DestroyObject(GameObject gameObj)
    {
        pools.First(p => p.ObjectType.name+"(Clone)" == gameObj.name).DestroyObject(gameObj);
    }

Again, when we want to destroy an object, we call this method, like the InstanciateObject method(s) it finds the required pool, this time it post fixes the ObjectType.name property with “(Clone)” as when you instance an object it has this at the end. I guess you could replace this with a Contains, rather than getting an exact match. Upon finding it, it calls the pool sDestroyObject method passing in the object to be destroyed.





So, in the editor, you can create an empty game object add the ObjectPoolManager script to it, then put all your pool game objects in it like this.

image

Well, that’s my current approach to object pooling, and for the limited way I am using it it’s working out quite well. If you have any questions or suggestions, then please feel free to post here.

I am also a member of the Unity Indie Devs on Facebook, if you are not already a member, join up, join the chatter :)