Simple Pooling

PoolingSplash

Everything we do in our games requires some form of resource – memory, cpu time, storage space – and anything we can do to help reduce the drain on our resource budget can be good.

Pooling can help us do this.

In Unity, it’s easy to add new GameObjects to our scene –  Missiles, Bullets, Enemies, Asteroids… We simply need a reference to the Asset we want to instantiate and we can check in Update() to see if the user pulls the trigger to fire a weapon, or in our code if the Game Controller needs to add more enemies or hazards.

using UnityEngine;
using System.Collections;

public class ShootingExample : MonoBehaviour {

  public GameObject shot;
  public Transform shotSpawn;
  public float fireRate;

  private float nextFire;

  void Awake () {
    nextFire = 0.0f;
  }

  void Update () {
    if (Input.GetButton("Fire1") && Time.time > nextFire) {
      nextFire = Time.time + fireRate;
      Instantiate(shot, shotSpawn.position, shotSpawn.rotation);
    }
  }
}

This is essentially the sample code from Input GetButton.

Later, we could Destroy our shot when it either hits its target or leaves the game area.

There is, however, a certain amount of overhead to Instantiate a new GameObject in a scene. There is even more overhead to destroy that GameObject and clean up after it has been removed from the scene (see Garbage Collection).

To reduce the resources needed to add and remove GameObjects from our scene, we can do something called Object Pooling.

Object pooling is about creating, maintaining and recycling objects rather than creating and destroying them. In its simplest form, we create a container to hold our objects, put objects into that container and pull them out when we need them. We then put them back when we are done with them to be used again. Think of this as a drawer in our kitchens where we keep the spoons. In a disposable world, we would open a package containing one plastic spoon, use it and throw it away. We can instinctively understand the waste of resources in doing this – from the manufacturing process required to make and package the spoons to the money we need to spend on buying them. Normally, we’d grab a spoon from the cutlery drawer, use our spoon, wash it and put it back in the drawer to be used again. That being said, there are times when we’d want to use or need to have a disposable plastic spoon, but it’s not something we do every day. When we make that decision, we understand the drain on our resources.

There are several tools for pooling on the Asset Store.

In general, however, pooling can be very simple in its base form.

It goes like this:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class PoolingExample : MonoBehaviour {
  public GameObject objectPrefab;
  public Transform poolSpawnPoint;

  // optionally: the minimum number of starting objects in the pool
  public int minimumNumber;

  private List <GameObject> objectPool;

  void Start () {
    objectPool = new List<GameObject>();

    // optionally one can instantiate any number of objects
    for (int i = 0; i < minimumNumber; i++) {
      GameObject newObject = 
        Instantiate (
          objectPrefab, 
          poolSpawnPoint.position, 
          Quaternion.identity
        ) as GameObect;
      newObject.SetActive (false);
      objectPool.Add (newObject);

       // for organizational purposes only
      newObject.transform.parent = poolSpawnPoint;
    }
  }

  void Update () {}
    if (Input.GetButton ("Fire1")) {
      GameObject myObject = GetObject ();
      DoSomethingWith (myObject);
    }
  }

  GameObject GetObject () {
    foreach (GameObject thisObject in objectPool) {
      if (thisObject.activeSelf == false) {
        thisObject.SetActive (true);
        return thisObject;
      }
    }
    GameObject newObject = 
      Instantiate (
        objectPrefab, 
        poolSpawnPoint.position, 
        Quaternion.identity
      ) as GameObject;
    objectPool.Add (newObject);

    // for organizational purposes only
    newObject.transform.parent = poolSpawnPoint;

    return newObject;
  }
}

First, we create a list to hold our GameObject pool. In this case we are using a Generic List rather than an Array, as it’s easier to push things into one.

When we want a new GameObject, we check the object pool with the function “GetObject” for an inactive GameObject. We use the first inactive object we find in the pool. If there are no inactive GameObjects in the pool, then we create a new object and add it to the pool. When we are done with the GameObject, we don’t destroy it. We simply set back to being inactive.

As we need objects, our pool will quickly grow to the stable size we need it to be. If we ever run out of objects, the system will simply create another one, so our pool never runs dry.

Optionally, and this is probably a good idea, just after initializing the pool list in Awake, we could create an arbitrary number of objects and add these to the pool when we start our game so our performance at the start of the game isn’t slowed by a large amount of Instantiating as we fill up our pool “on demand”.

As an example, here is a rather dated video where I was testing a prototype:

There are several pools in this prototype. The weapons, both the laser bolts and torpedoes, are pooled as I reuse them all the time. I also pool the explosion prefabs.

By pooling these assets, this not only means I’m avoiding the constant Instantiating new GameObjects, it means I’m preventing the excessive use of the garbage collector to remove the destroyed objects after they have been destroyed. Every time I need a new laser bolt, I simply grab one from the pool. Reduce, reuse, recycle.

When pooling the explosions, some additional care was needed to make sure that not only was the GameObject deactivated, but that the particle system component was reset, or there would have been issues when re-activating the particle system. The particle system would have tried to continue where it had left off before being deactivated.

We must also be careful not to destroy our pooled GameObjects by accident. In this prototype, I ran into trouble with the pooled explosion prefabs. I was parenting an instance of the explosion to the impact point on the asteroids – and the asteroids were being destroyed. This messed up my references to the explosions. The explosions were being tracked by the pool. By destroying the explosion prefabs along with asteroids while they were still present in the pool list created some rather vile and unpleasant errors.

One solution to this is to hand off the GameObject to a new owner when it is being used, removing it from the pool list.

  GameObject GetObject () {
    foreach (GameObject thisObject in objectPool) {
      if (thisObject.activeSelf = false) {
        thisObject.SetActive (true);
        objectPool.remove (thisObject);
        return thisObject;
      }
    }
    GameObject newObject = 
      Instantiate (objectPrefab, poolSpawnPoint.position, Quaternion.identity) as GameObject;
    objectPool.Add (newObject);
    return newObject;

    // for organizational purposes only
    newObject.transform.parent = poolSpawnPoint;
  }

This way, if the GameObjects have been removed from the pool list and they do get destroyed, we don’t leave dangling references to destroyed objects behind. When we are done with the object, we must explicitly return the object to the pool by putting it back or re-adding it to the list. Like our spoons, we take one, use it, and we put it back when we’re done.

As a simple example, you could have as part of your pooling code something like this to return your objects:

public void ReturnObject (GameObject thisObject) {
  // deactivate the GameObject
  thisObject.setActive (false);

  // if the object was removed from the holding parent, return it:
  thisObject.transform.parent = poolSpawnPoint;

  // if the object was removed from the object pool, put it back:
  objectPool.Add (thisObject);
}

Then, instead of “Destroy (myGameObject);” you could use “ReturnObject(myGameObject);”

In general, instantiate/destroy is simple and this is the pattern supplied by Unity as the “typical” solution to adding and removing GameObjects from our scene. However, coming from Unity mobile gaming, I love pools and I am so used to them. I don’t see any real additional amount of work in creating, maintaining and using them over instantiate/destroy.

Jump in – the water’s fine.

3 Comments

Adam

about 10 years ago Reply

I bumped into this the other day... A more complicated and more robust pooling option. For those code-savvy types, it's worth a read of the code. For those not, but whom want a more robust solution, worth a try.

Adam

about 10 years ago Reply

There are two additional points worth noting: The first is, if we were to remove the objects from the pool, and then return them when done, this will add to the overhead of the system. It's best that the pool remain intact, as the act of adding and removing items from the list will use resources. Removing items from the list should only be done if there is a real need, as in the situation above, where the pooled item was being destroyed. The second is, there may be some value in leaving the pooled objects inactive when returned, and leave it up to the function requesting the object to activate it. This is a minor note, and probably only important to a particular game's architecture... but it is worth noting.

Leave a Comment

Leave a Reply to Adam Cancel Reply

Please be polite. We appreciate that. Your email address will not be published and the required fields are marked.