In Unity3D Object Pooling is a technique to pre-instantiate GameObjects before they’re needed, and minimize instantiation and destroy calls. We use object pooling to minimize slow frames or studders, but it’s also a key technique to remove un-necessary garbage collection. If you search for Unity Object Pooling, there are dozens of samples all with their own opinions and nuances.. and most of them are perfectly good options. Today though, I wanted to share one of my recent pooling systems.
What’s different about this Unity3D Object Pooling system?
The main thing I wanted to do here was keep the pooling system very light and low on ceremony while still enforcing some basic functionality. The way this pooling system works, my objects get a reference to the pool or pools they need at startup and do it by passing in the poolable prefab that’s assigned. Of course, keeping it simple does mean that it’s not ultra configurable and feature rich, it does basic clean pooling, nothing more.
How do I use it?
Before I go into the details of how the system works internally, let’s get a quick look at how you use it.
Take a look at this sample usage from my ProjectileLauncher class. [ LINE 21 ]
You can see in the Start method that I get a reference to the pool that’s needed by simply passing in the prefab.
Then in the Fire method, I request a projectile from my pool and launch it.
How does it work?
One important thing to note is that for this system to work, the prefabs need to implement a very small interface IPoolable
The IPoolable interface only has a single Action to track when the object is ‘destroyed’.
Now since we want our gameobjects to be re-used, not destroyed and re-created, this event is actually called in the OnDisable call of the prefabs.
When the poolable objects get disabled, the pool automatically re-adds them to a collection of available objects, then when a pooled object is requested, it pulls the next one out of the collection, moves it to the desired location and re-enables it.
For example, take a look at the OnDisable method in this projectile class.
You can view the entire Projectile.cs class here: https://gist.github.com/unity3dcollege/a241c5677dbf8cd8ba772c7c70224f0a
The guts of the Object Pooling System
The majority of the pooling system is really handled in a single small class, appropriately named “Pool”.
You can download or view the entire Pool.cs class here: https://gist.github.com/unity3dcollege/21c082ca4caf94fddc75fc188441e0ee
Pool.cs Class Overview
The class starts out with some static methods for getting or pre-warming a pool. If you look back that the ProjectileLauncher, you can see that it’s calling the GetPool method to get a pool for the desired projectile.
GetPool / Prewarm
GetPool & Prewarm both do very similar stuff (and should get refactored).
- First, we check the static dictionary of pools to see if a pool already exists for the specified prefab.
- We then return the pool if already exists in the dictionary and isn’t null (from being destroyed by a scene load or something else).
- If it doesn’t exist, or was destroyed and went null, we create a new pool and initialize it.
- If the initialization happens via the Get() call, we just use a standardized default pool size.
- When we need tighter control over the pool size, the Prewarm method allows that size to be passed in as a parameter.
Initialize
The Initialize method is responsible for actually creating the gameobjects we need pooled.
On line 55 they’re instantiated.
Next the OnDestroyEvent is registered to call AddObjectToAvailable()
And on line 60, we set the instantiated object to inactive, triggering the OnDestroyEvent and adding the object to the available pool.
Get
The get method grabs the next available pooled object from the queue and returns it. If no more objects are available, it will grow the pool size by 10% (or 1 if 10% is equal to none).
The public Get method also takes in a position and rotation so that the pooled object can be placed before it’s activated.
Update
The last thing we do is move any recently deactivated pooled objects to become children of the pool.
We do this because when the pooled object is active, we want freedom to control it’s parenting. This allows us to do things like make it’s position to be relative to the thing that spawned it. But when it’s ready to go back into the pool, I really prefer to have it as a child of the pool so that the scene is clean and it’s easy to avoid mistakes.
But how do I pre-warm it??
You may have noticed that the pooling system’s Prewarm wasn’t called by any of the code so far.
While having a pooling system that lazy initializes is better than no pooling system, pre-warming is almost always a good practice.
For that, I use the PoolPreparer class on a GameObject in my scene.
The pool preparer script gets added to a gameobject in my scene that needs the pools.
In it, I assign the prefabs I want pre-warmed, and it does the work during awake.
Most of the Awake method is just validation to prevent pre-warming multiple times, the actual initialization is on line 31.
The OnValidate() method is there just to make sure the system isn’t used incorrectly by accident.
Limitations, Thoughts, and Conclusions
Like I mentioned before, this object pool system is very basic, and is meant to be very lightweight. In the past, I’ve used and even built my own pooling systems that had all kinds of different requirements from a BasePoolable class to editor heavy tooling. For this one though, I wanted to be able to accomplish similar results with almost no special code, just implementing a single small interface.
That said, the system is not extremely configurable out the gate, if you wanted to initialize different pool sizes, you’d have to have multiple pool preparer components (which you could do all on a single gameobject), or you’d have to modify something.
On top of that, the pool growing setup is super simplistic and just increases the pool size by 10% (all in one frame as well). So if you aren’t initializing the pool with the size you actually need, you could still run into a hiccup on occasion.
And finally, the setup is not intended to work across scenes. If you need to pool objects across scenes, the Pool class would need to be updated with some DontDestroyOnLoad calls for itself and the pooled objects it creates, though that wouldn’t be much work.
But it does do the job it’s intended to do, and does it with very little ceremony, and it just works..
If you have another favorite pooling system that you really like though, please share below. And if there’s enough interest, I may do an update where I show a couple of the previous pooling systems I’ve used and built over the years.