Check the original blog post at The Gamedev Guru: Object Pooling in Unity 2021+
While developing your game, you noticed that instantiating 100 bullets per second is suffocating your mobile CPU performance. You then...
- A: you reduce the number of bullets to 20
- B: you implement your own pooling system
- C: you pay 50 bucks for a pooling system in the Asset Store
- D: you use the new Unity Pooling API introduced in 2021
In this blog post, we are going to cover this last option.
Today, you will learn how to use the new Pooling API introduced in 2021.
Table of Content
- When Do You Actually Need Pooling?
- So, What is (Object) Pooling in Unity After All?
- When Should You Avoid Pooling?
- Object Pooling in Unity 2021: Your Options
- How to Use The New Object Pooling API in Unity 2021
- The Types of Pooling in Unity 2021+
- The Problems with Pooling (Why You Shouldn't Abuse Them)
- So... What Else?
Since Unity 2021, you have access to a wealthy set of pooling features that will help you develop high-performing Unity projects.
Ready to get to know them?
When Do You Actually Need Pooling?
Let’s start with the most important question: when do you need pooling?
I ask that because pooling shouldn’t be your go-to solution 24/7.
Object pooling in Unity has definitely some important drawbacks that do more harm than good, so you have to be careful there.
We will look into those later.
To keep it short, consider pooling when:
- You instantiate and destroy gameobjects in rapid succession, e.g. weapon bullets.
- You frequently allocate and deallocate objects stored in the heap (instead of reusing them). That includes C# collections.
These operations cause lots of allocations and therefore:
- A waste of CPU cycles for instantiation and destroy operations (or new/dispose).
- Premature garbage collections that end in these gameplay freezes your players hate
- Memory fragmentation that make it hard to find free contiguous memory regions
Do you feel like those problems can pose a threat for you?
(If not now, they might later)
Let’s keep going.
So, What is (Object) Pooling in Unity After All?
Now that you know if you are in trouble (or still safe), let me quickly explain what pooling is.
Pooling is a performance optimization technique that is all about reusing C# entities instead of creating and destroying them every time you need them.
An entity can be anything: a game object, an instanced prefab, a C# dictionary, etc..
Let me put the concept of pooling in the context of a real life example.
Let’s say you have to go grocery shopping tomorrow morning.
What do you usually take with you apart from your wallet and keys?
Well, you might take reusable bags. After all, you need some sort of container to bring your rations back home.
So you take your empty reusable bags, fill them with groceries and come back home.
Once you’re home, you empty your bags and put them back into your drawer.
So that’s pool.
Reusable bags are a better alternative than buying (allocating) plastic bags and trashing (deallocating) them every time you go shopping.
You need a bag?
Ok, you go to your pool of bags (e.g. a drawer in the kitchen), you take a few, you use them, you empty them and finally you return them back to the pool.
See what we did there?
Here are the main components of a pooling use case:
- The elements you want to pool, e.g. a reusable bag, an instantiated bullet, etc...
- The goal you have for these elements, e.g. holding groceries, shooting a bullet, etc..
- The functions you perform on the pool and its elements: Take, Return, Reset.
In the case of a shooter game, you can create and destroy bullets every single time... or you can create a bunch beforehand and then reuse them like this:
- You spawn a thousand bullets and put them in a pool.
- Whenever you fire your weapon, you a bullet from that pool.
- When the bullet hits something and disappears, you put it back into the pool.
This way, you save the CPU cycles it takes to instantiate and destroy these prefabs. Plus, you alleviate pressure on the garbage collector.
Now, before you jump right into pooling, be aware of a few points...
When Should You Avoid Pooling?
The pooling technique has a few (potential) issues:
- Your items might be dirty.
Since they were used in the past, you might have left them in an undesirable state, such as bullets with some red juice on them.
That means, you need to spend some CPU cycles to clean your items before using them: the reset operation.
- You reserve memory that you might actually not need.
If you pool thousands of bullets but all your player wanted to do is to look at the landscape, then you wasted memory.
- It adds complexity to your codebase.
You need to manage the lifecycle of your pools.
This not only adds CPU cycles but brain cycles due to processing a larger codebase.
All you need to do is to avoid pooling in cases where you would not really profit from it.
Like, there’s no need to pool the final boss. It’s just one, after all.
Remember: what matters most is the frequency of your instantiate and destroy operations.
If you do them often, consider pooling. Otherwise, skip it.
We will see more pooling issues in detail later on.
Now, let’s have a look at your options for pooling.
Object Pooling in Unity 2021: Your Options
If you want to pool your objects in your Unity project, you have three options:
- Make your own system
- Buy a third-party pooling system
- Import UnityEngine.Pool
Let’s check them out.
A) Create Your Own Pooling System
One option is to put your crafting skills into practice.
Implementing your own pooling system doesn’t sound too complex, as you only need to implement a few operations:
- Create & dispose pool
- Take from the pool
- Return into the pool
- Reset operations
But it often gets more complicated than that when you start thinking of:
- Type safety
- Memory management and data structures
- Custom object allocation/deallocation
Does that sound like a headache already?
I can already see your face a bit paler than before...
My suggestion is to not reinvent the wheel (unless it’s a learning exercise).
Since it’s an already-solved problem, use something that works so you can focus on your project.
Focus on bringing the fun to your players.
That’s what they’ll pay you for anyway.
Let’s check the second option.
B) 3rd Party Object Pooling
Here, you choose a 3rd party provider from sources like:
- The Unity Asset Store
- A friend or family member
Let’s see a few examples:
But before you click the buy button... keep reading.
3rd party tools can work wonders and have tons of features.
But that comes with drawbacks:
- You rely on their support to fix issues and upgrade the packages to newer editor versions.
- If you don’t have source code, you can’t fix issues yourself.
- More features = complex code. It will take you time to understand and maintain their system.
- They can get expensive (in money and time).
You probably knew all of that, but it’s always a neat reminder :-)
And nowadays there are even fewer reasons to go for a 3rd party asset, since Unity has quietly released a new pooling API in Unity 2021.
And that’s the main topic of this post.
C) The New Unity Pooling API
With version 2021 onwards, Unity released a few C# pooling mechanisms that will help you in a myriad of use cases.
These pools of objects are directly integrated in the Unity engine. No extra downloads required and kept up to date on each Unity update.
As a huge plus, you have access to their source code.
And I must say that the implementations are quite straight forward. It’s a nice evening read.
Let’s see how you can start using the Unity Pooling API today so that you can reduce the performance cost of these operations you and I know about.
How to Use The New Object Pooling API in Unity 2021
The first step is to make sure you are on Unity 2021+.
(I mean, you can just copy & paste the code into any of your older projects... but hey, I never said this)
Then, it’s just a matter of knowing the Unity Pooling API:
- The pooling operations.
- The different pooling containers.
I already gave you a few spoilers on the pooling operations. But let's dig deeper into them.
1. Constructing Your Pool
The first operation you need to do is to construct the pooling container of your choice.
That’s usually done in a single line of code, so don’t worry here.
The constructor parameters depend on the specific container you want to use, but they are quite similar
Here are the usual Unity pooling constructor parameters:
|createFunc||Called to create a new instance of your object, e.g. () => new GameObject(“Bullet”) or () => new Vector3(0,0,0)|
|actionOnGet||Called when you take an instance the pool, e.g. to activate the game object|
|actionOnRelease||Called when you return an instance to the pool, e.g. to clean up and deactivate the instance.|
|actionOnDestroy||Called when the pool destroys this item, i.e. when it doesn’t fit (exceeds maximum size) or the pool is destroyed|
|collectionCheck||True if you want Unity to check that this item was not already in the pool when you try to return it (only in editor)|
|defaultCapacity||Default pool size: initial size of the stack/list that will contain your elements|
|maxSize||Pool size: maximum number of free items that are in the pool at any given time. If you return an item to a pool that is full, this item will be destroyed instead.|
Here’s how you can create an object pool of GameObjects:
_pool = new ObjectPool<GameObject>(createFunc: () => new GameObject("PooledObject"), actionOnGet: (obj) => obj.SetActive(true), actionOnRelease: (obj) => obj.SetActive(false), actionOnDestroy: (obj) => Destroy(obj), collectionChecks: false, defaultCapacity: 10, maxPoolSize: 10);
I left the parameter names for clarity; feel free to skip them in production code :-).
And sure enough, this is just an example with a GameObject. You can use it with any type you want.
Okay, now you have a pool of _GameObject_s.
How do you use it?
2. Creating Your Pool Elements
The first thing Unity needs to know how to create more of your _GameObject_s whenever you request more than are available.
We already specified that in the constructor, as we passed the createFunc function as the first parameter to the pool constructor.
Every time that you want to take a GameObject from an empty pool, Unity will create one for you and give it to you.
And to create it, it will use the createFunc function you passed.
And how do we take a GameObject from the pool?
3. Taking an Element From Your Pool
Now that you have your pool reference stored in _pool, you can call its Get function:
GameObject myGameObject = _pool.Get();
Now you can use the object as you wish (within certain limits).
When you are done with it, you need to return it back to your pool so you can use it later on.
4. Returning an Element From Your Pool
So you have been using your element for a few minutes and now you’re done with it.
Here is what you do not do now: you do not destroy/dispose it yourself.
Instead, you return it to the pool so that the pool can manage its lifecycle correctly according to the functions you provided.