Have you ever faced performance issues when developing games that need to spawn a lot of enemies? Or frame drops when a boss enemy summons thousands of skeletons every time you damage him? If you have ever worked with something that requires a large amount of creation and destruction, you must have heard of a pattern called Object Pooling in Unity.
What is Object Pooling?
Object Pooling is creating a pool of objects that are initialized once during their creation, which then get reused every time the object is requested – instead of destroying and creating the object every time it needs to appear. Creating objects and continuously destroying them can cause performance issues due to the garbage collected in the background.
Here's an example: imagine we have a gameplay scenario where you have a gun that can shoot 100 bullets per round fired. When you fire the gun, you are creating/instantiating each bullet on every frame, so if you keep shooting your gun for 60 frames, you will shoot around 500-600 bullets every second.
After shooting, you need to dispose of (destroy) those bullets because you don't want your player to see the bullets he already shot a while ago. This means that you need to create up to 600 GameObjects every second and destroy all those 600 GameObjects in the next second.
Overall you would have to create and destroy 1200 GameObjects every 2 seconds, which averages 600 GameObjects created and destroyed per second. This process will cause a gigantic spike in performance due to the garbage collection and the collection delay. You can assume that your game will get a maximum of 10-20 fps with a mid-level CPU with those calculations above.
The object pooling pattern will save our life in such cases. Using object pooling, we can create a pool of GameObjects that we can spawn at an initial point. When we need an object, we can simply get it from our pool. Once it has done its work, we can return it to the pool by releasing it.
Unity's object pooling system provides many pooling possibilities, from a general object pooling system to a linked pool system or a HashSet pool system.
In this blog, we will be using Unity's new IObjectPool interface to create a recycling system for a cube generation project.
Cube Spawning Using IObjectPool
To get started, we will set up a basic project in Unity with a camera and terrain where we will be creating a new GameObject called
spawner that will be responsible for spawning our objects in the environment.
We can now create a cube with a box collider and rigidbody. This is the cube we will spawn through the spawner. We can also create a C# script called
Object.cs that will be attached to our cube object.
Then, we create a spawner GameObject with a C# script called
Spawner.cs that will instantiate a bunch of cubes every frame in the update method, which should look like this.
In the following gif, we have used a normal system that instantiates cubes with rigidbody every frame and destroys each object after 5 secs. However, you can see the frame drop once it generates a lot of cubes in the scenes, making the scene very laggy and unplayable. This is how a swarm of objects would be spawned without using IObjectPool.
To optimize spawning, we will change our
Object.cs to implement our object pooling pattern.
Spawner.cs should look like this.
In the script above, we have created an IObjectPool with our object as its type. In
Start we have initialized an ObjectPool that takes in
OnRealeased as constructor params.
To get deep into these terms, we can look at the official unity documentation for IObjecPool's constructor params and their usage.
As you can see, the first function we pass into is
createFunc, which is used to create a new instance of the pool with the objects we initialize at the start. Then,
OnObject is called when we take an object from the pool.
OnRelease is called once the object is returned to the pool once again.
If you now look again at my code, you can see that we generate a new cube in the
InstantiateObject function and return it to the pool so that the pool can contain the objects we want to reuse later. In the
Update function, we get the prefab we need from the pool instead of instantiating it every single time. And in the
OnObject function, we have set the object's active state to true so that it can be seen as we get the object from the pool and when it's time to release it.
We have disabled the GameObject in
OnRelease so that it is no longer visible once it is gone. Also, in the
InstantiateObject function, we have referenced our pool to the object's pool field because our
Object script is the one handling the disposal/release of our already used objects.
Object.cs, we reference the pool from
Spawner.cs in the
SetPool function and assign it as our active pool. This makes it so that we release the GameObject to the pool instead of destroying it every time the cube hits the ground.
Learning how to use IObjectPool and object pooling, in general, will help your game achieve better results and smoother performance. The Object Pooling pattern is very useful in many other cases like level generation, enemy generation, creating recycler views, paginations etc.
Thank you for reading and learning with me. Please leave comments if you have any regarding this article.