Game Design Tips #12 – Object Pooling

I’ve given up all hope of this being a daily series, but fret not! I’ll still be posting tips whenever I get the chance. Obviously being back at uni will probably mean I’m a little less frequent with updates, but I’ll try and keep up my usual blog regime of posting whenever I’ve done any interesting game development. On that note, the first Warwick Game Design Society two-week challenge just finished, and the theme was ‘Retro’. I opted for a 2D kind-of-platformer starring an edgy 90’s hipster kid. Obviously. You can read about it in my previous post. Enough of that though, it’s time for today’s tip!

What is Object Pooling?

Object pooling is a common technique used by developers to make their games a bit more efficient. On desktop and console platforms, it’s not exactly necessary but it’s still a good habit to get into; mobile platforms will be much slower without it. The technique isn’t bound to any one platform, engine or language though, so you non-Unity people out there can breathe a sigh of relief. I’ll still be using good ‘ol Unity and C# for my examples though.

tip_12-03

These are not the pools you’re looking for.

Instantiation and destruction of objects can be expensive processes, since they involve a lot of memory allocation and garbage collection respectively. A system where we just had a bunch of one item already instantiated at start (enemies, projectiles, you name it) and we only make them active when needed would alleviate this problem; object pools do just this. By setting up a list of an item and populating the list with instantiated, inactive objects, we’ve taken all of the instantiation overhead at level load and never destroy anything until the level ends. As we all know, destruction is bad because it rouses the garbage collector.

Why use it?

By removing the overhead of instantiating and destroying objects and instead activating and deactivating them, our game is now much faster at stuff like spawning and removing things. It’s significant on mobile platforms when we can’t really be affording to waste CPU cycles. There is of course a RAM overhead as all the objects in the pool (and the list storing the pool itself) need to stay live in memory, so on platforms especially restricted by memory, another approach may work better for you.

Another reason to use pooling is that it gives more control to the game programmer. Who the hell knows what instantiation and destruction might be doing in the background? If you, the programmer, create your own object pooling system, then you more fully know the finer details of how the pool operates. You can also tweak it to your exact, bespoke needs rather than relying on a general-purpose method that probably carries redundant overhead with it.

How do I implement one?

Simple! I’ll be creating an ObjectPool class that will constitute the pool itself, and a PoolController that handles putting items into the pool and taking items out of the pool. Note, the ObjectPool class doesn’t inherit MonoBehaviour, so we can’t just stick this as a Component onto a GameObject – we’ll have to create ObjectPool objects inside a PoolController (which does inherit MonoBehaviour, and should be a component on some GameObject). I’ll explain the theory behind various types of pool, but I’ll only show an example of the last type.

Bounded-size non-generic ObjectPool

The concept behind a fixed-size pool is simple – we construct a list and fill it with a specified number of objects at level load – this number does not change during the program’s lifespan. The advantage is that we always know exactly how many objects are floating around. However, we may run out of objects when we try and get one from the pool. Most implementations of this will define what type they’re holding hard-coded into the pool (for example, in Unity, I’d make my pool hold GameObjects, e.g. List<GameObject>, and all methods will take GameObjects as parameters or return GameObjects). The problem with this is that we can’t guarantee type safety – at level start, we might be able to add an enemy game object, then add a particle game object, then a player game object, and so on all to the same pool. That means we can’t guarantee the items we receive from the pool are of any one type, because we might have added in any random type of object.

It’s also worth noting that this version actually works better if implemented with an array holding the GameObjects rather than a list. I won’t delve too far into why, but it’s to do with asymptotic running times of certain operations (specifically, you can either add and remove items with an array quicker than with a list, or you can implement an array in less space than a list – it’s hard to explain here). Look up Big O notation for more information. The next example will assume you’ve used a list though, as the next two versions work better with lists than arrays.

Bounded-size generic ObjectPool

We can modify this approach to use generic types.  Using generics prohibits adding anything that does not match the original type definition –  we’d define our list internally in the ObjectPool class as a List<T> instead of a List<GameObject>, and define our class as public class ObjectPool<T> where T : MonoBehaviour, then we can only add one type of object at runtime to any one pool in the PoolController class. This means:

  • public class ObjectPool<T> where T : MonoBehaviour means this pool can hold any kind of GameObject, but can only hold one type of GameObject at a time. We instantiate the ObjectPool with new ObjectPool<YourObjectType>().
  • List<T> means the object pool’s internal list holds that same type of object.

It’s hard to explain, but you should see what I mean in the examples below.

If you’ve never touched on generic programming before and you’re confused or would like to know more, check out the C# reference if you’re using Unity, or the Java reference for other uses. Those are the languages I’ve used with generics, but your choice of language or engine may also support generic programming. Generics can turn nasty runtime bugs into compile-time hiccups, so I’d recommend using them where it’s sensible to.

Unbounded generic ObjectPool

I’ll show an example of one of these below. The other examples of ObjectPools have had the problem of possibly running out of objects. To avoid this, we can make our object pool have a variable length; by allowing us to add another object to the pool if we’ve run out, we suffer the overhead of instantiation, but we still avoid garbage collection since it’s not destroyed. This too has drawbacks – if we have a spike in the middle of the game where we need, say, 500 enemies, we’ll end up creating more enemies dynamically and once used and deactivated, they’ll stay in the pool forever. For platforms bounded by RAM size, it’s not exactly ideal, and you’d be best off sticking with the previous version. We could improve this solution by creating some kind of way of destroying objects when the game is idle, and possible framerate drops are acceptable (say, when a menu is open), however that can get very complicated so I won’t go into it here.

ObjectPool.cs

tip_12_01

PoolController.cs

tip_12_02

This example assumes you have some sort of Enemy type already defined. We set up an ObjectPool in PoolController for every type of object we want to pool – in this example, enemies and particles. We have also assigned which objects we’ll be adding to the pool (the enemy and particle variables). At start, we instantiate a given number of objects for each pool (I’ve only shown this for the enemy pool though; you’d have a for-loop for every pool) – this number is defined in Unity’s Inspector. When we want to retrieve an object from a given pool from some other script (I’ve provided the poolController variable for this purpose, so we can type PoolController.poolController to reference the singular PoolController we’ll have in the scene), we use the GetEnemy() method (or GetParticle(), but I’ve not written it since it’s mostly the same as GetEnemy()). When we’ve finished using an object and would usually destroy it, we call DeactivateObject() instead, causing it to stay in the pool but be inactive in the scene.

I hope this has given you a deeper understanding of object pools. If you have any questions, feel free to leave a comment below.

Advertisements