Object Pooling
/***New Object Pooling Post - Click Here or Check the Navigation to the Right***
If you’ve been around game design for very long you’ve likely seen a video or a post on “object pooling.” Despite the amazing performance of the modern PC a game can still start to slow down if objects are constantly being created or destroyed. By constantly, I don’t mean one object per frame. I mean 10 objects, or 100 objects per frame OR 1000 objects once every 5 seconds. Those are scenarios where object pooling can save the player from frustrating lag spikes and help an older machine squeeze out a few more FPS.
The idea of object pooling is clever, but prtettysimple. Rather than constantly creating and destroying objects the idea is to recycle or reuse objects. This is most often done by turning the object on/off or disabling it in some way. In Unity there is still some cost for turning objects on and off, but the cost is far lower than creating and destroying that object.
Before we get into the code there are lots of different “flavors” of object pooling. People have different preferences on how to keep references to objects. Personally, I like to keep a list (or queue) of objects that are not in use and that’s how I’ll be doing it here. Other ways to do this is to keep a list of all objects and search through the list for an object that can be used (often an object that is not active). While for most small game the performance difference isn’t much, but I still like the tidiness of a single collection for objects that are ready to go.
I’m also going to show two ways to create an object pool. The first will be the most usual way or at least the way that I’ve seen done in the past. The second will be more of my own take on the pattern and add more generic functionality. I can imagine there may be some performance hit to the way I’m doing it, but again the tidiness of the solution and ease of use, in my case, more than makes up for it.
For this tutorial, I’ll be using the same project as for the “State Pattern.” Check out the GIF to the right or the earlier post.
Simple Object Pooling
In the “simple” object pooling I’ll be using a queue to store references to game objects. These objects can then be retrieved from the queue when needed and returned to the queue when they are no longer needed.
If you haven’t heard of or used a “queue” it’s very much like a list in that it will contain multiple references or values. The biggest difference is a queue is a “first in first out” collection - just like a queue in real life. The first object to be “enqueued” will be the first to be “dequeued.” This makes it easy to get an object from the collection. Whereas with the more standard list we would need to get a reference to the object and then remove the object from the list. A queue does that work for us. If this is new to you make sure to check out “stacks” too which are “first in last out.”
The object pool class has three fields.
The first is the prefab that will be pooled. In my case, since I’m using the same project as for the “State Pattern” I’ll be pooling the “critter” objects.
The second is the queue that will hold references to all the gameObjects and the third is an optional parameter that will “preload” the object pool.
Preloading the pool (in the start function) may or may not be necessary and very much depends on your projects needs. If there will be a sudden and large demand for an object, think bullets in a bullet hell style game, then it might be smart to use the first frame (or several frames) of the game to create a large pool of object. This creates a lag spike at the beginning but avoids it during the game which is when the player is more likely to notice. In the case above, all I’m doing is instaniating copies of the prefab and adding them to the queue.
The object pool class also needs a function to “get” an object as well as “return” an object - both of which will be public. There can be a good argument to make these functions static as well, but I choose to keep things simple.
The “Get Critter” function will first check if there are any critters in the pool and if there are it’ll “dequeue” a critter, set it to active and then return that critter object. Instead, if the pool is empty the function will instantiate a new instance and return that object. Creating a new object is not always necessary or might not even be a good idea depending on your design constraints. In my case I’m happy to create as many critters as necessary. Another option would be to create a limit on the number that can be created - this could help control an unforeseen edge case.
The “Return Critter” function will be called by the critter itself when it has been disabled. This simply add the critter object back to the queue and then turns the object off. In some cases you may not need to or even want to not turn the object off - it just depends on the use case and the type of object. For my project turning the critter off is easy and I’m not worried about the minor performance hit of turning objects on and off.
Spawning
For object pooling to work we need a “spawner” object. To serve this purpose I’ve added a small cube with a “spawner class” on it. The class contains a value that controls how frequently an object is spawned, a values that tracks how long since the last object was spawned and a reference to the object pool.
In the start function a reference to the object pool is cached.
Then in the update fucntion the time since spawn is updated with the time of the last frame. This creates a basic timer.
Then if the time since the last spawn is greater than the spawn time - we get a new critter from the object pool. It’s important when using object pooling to be able to “initalize” the objects. In this case that means moving it to the correct position, but for more complicated objects you may need to create a dedicated initialize function. Lastly, we set the time since spawn to zero so the timer restarts - without this the spawner would create a new critter every frame!
The last piece of any object pooling system is returning the object to the pool. There are many ways to do this, but for simplicity I’ve created a new class that needs to be added to the critter. This class when disabled will return the critter to the object pool. This works, because when the NPC attacks the critter it calls a function that turns off the object.
Now this is all pretty good and not too complicated to implement.
BUT!
There is a glaring problem, at least in my eyes. When a project gets bigger and more complex their will be a need for more and more object pools.
Each. And. Every. Object will need it’s own pool. Yikes. That gets to be a mess in a hurry. And that’s where my next approach comes in.
“Advanced” Object Pooling
My personal project is a game that will be 100% okay if it runs at 30 fps or even 20 fps - it hopefully won’t but it would be okay. It also has a lot of objects that need to be created and destroyed. This includes spaceship modules and UI components. There will (hopefully) be 10’s or maybe 100’s of different ship modules. So it’s impractical to create a object pool for each type of object…
It could be possible to just create one big object pool and then search through it trying to find the object that is currently needed, but I don’t like that approach either.
So my approach is to essentially create a pool of pools AND create pools dynamically at runtime when needed. How’s that sound?
The basic functionality is pretty similar to the earlier example in that we are getting and returning objects, but in this case I’ve created a dictionary that uses a string as a key and a queue of game objects as the values.
For each prefab the name of the prefab will be used as the key and a queue of those objects will be created. The object pool class is no longer specific to a given object and therefore we only need one instance per scene.
In the “Get Object” function we have an incoming prefab that is being requested - this is an important distinction to our previous verison of object pooling. We then check to see if we can find a value in the disctionary that matches the name of the requested prefab. If a value can be found we then check to see if the queue has any entries. If it doesn’t, then we create a new object and return that object. If the queue does contain a game object we “dequeue” it, set it to active and return the game object. Very much like what was done before.
There is one additional case, that of not being able to find a value in the dictionary. If this is true we also create and return a instance of the requested object.
NOTE: In the “create new object” function we are naming the new object the same as the incoming prefab. This is crucial since we are using the name as the key in the dictionary and if we don’t rename the object Unity will add a numerical suffix to the object’s name and will break our system. This is a major point of brittleness in this approach. You could also encode the key in another way such as an enum on a script.
The real magic of this system comes in the return function. Here once again we check to see if we can find a matching value in the dictionary. If we can, we simply “enqueue” the object. If we can’t find a value then that means this is the first time this type of object or prefab has been added to the pool. So we need to create a new queue, enqueue the returned object and then add the queue to our dictionary along with the corresponding key.
It’s a bit more abstract, but quite a bit more useful!
Spawner
The spawner in this case is nearly identical to the previous case but with two important differences.
We’ve added a “gameObject” field that contains the prefab that will be spawned. The second important difference is that we are now calling a different function to get an object but more importantly we are also passing in the desired prefab.
Returning the Object
Just like with the earlier example we need to return the object to the pool. And this code is functionally identical to the earlier return class.
Conclusion
This second approach allows us to pool any prefab. All we need to do is give the spawning object a reference to the prefab. In my project this means I can create a “Green Critter” prefab and drop it into a different spawner object and it just works… In my opinion that’s pretty darn slick!