Object Pooling in Unity

In my previous post, I covered object pooling in CoronaSDK. This article will cover object pooling in Unity, using C#. While the concept of Object Pooling is the same no matter what language, there are obvious syntax and set-up differences, and we’ll cover those Unity specific things here.

I am going to demonstrate a very simple object pooling class to get you started. There are more robust solutions on the Internet and in the Unity Asset Store, and each solution is a bit different, but this is what I have been using and it demonstrates the concept as well.

Step 1 – Initial Set Up

To use this solution, you need a couple of things:

  1. A prefab object that you will use as the object to pool. For this example, we’ll just create a cube, and we’ll call it “EnemyPrefab”.
  2. An empty Game Object. For this example, let’s rename it to “EnemyPool”.

Once you have a prefab and an empty game object, create a new C# script called Pooler. Here is the code for this script:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class Pooler : MonoBehaviour {
	
	public GameObject pooledObject;		// object to pool, drag into Inspector
	public int pooledAmount;			// number of objects, drag into Inspector
	public string objectName;			// name of object, enter into Inspector
	
	private List<GameObject> pooledObjects;
	
	/// <summary>
	/// Inits the object pool.  Renames the pool objects to the objectName
	/// </summary>
	void Awake () {
		pooledObjects = new List<GameObject>();
		for (int i = 0; i < pooledAmount; i++) {
			GameObject obj = (GameObject)Instantiate(pooledObject, Vector3.zero, Quaternion.identity);
			if (objectName != "") obj.name = objectName;
			obj.SetActive(false);
			pooledObjects.Add(obj);
		}
	}

	/// <summary>
	/// Gets the pooled object.
	/// </summary>
	/// <returns>The pooled object or null if all objects are used.</returns>
	public GameObject GetPooledObject() {
		for (int i = 0; i < pooledObjects.Count; i++) {
			if (!pooledObjects[i].activeInHierarchy) {
				return pooledObjects[i];
			}
		}
		
		return null;
	}
}
 Step 2: Pool your Prefab

Pooler PropertiesAttach the Pooler script to the empty game object, and look at the Inspector. You will notice 3 public properties:

  • Pooled Object – drag your prefab here. This is the object you want to pool.
  • Pooled Amount – enter the number of pooled prefabs you want.
  • Object Name – the name of the object. More on that later

Pooler HierarchyWhen your project is run, you will see a number of pooled objects in your Hierarchy view.

See line 20? I like to rename my objects when they are created, but this is up to you how to rename them, if at all. If you enter text in the Object Name property, the object is renamed.

Step 3: Use Objects in Pool

To get an object in the pool:

GameObject obj = GameObject.Find("EnemyPool").GetComponent<Pooler>().GetPooledObject();

or, to break it down:

GameObject gameObj = GameObject.Find("enemyPool");
Pooler enemyPool = gameObj.GetComponent<Pooler>();
GameObject pooledObject = enemyPool.GetPooledObject();

To return an object to the pool, inactivate it:

gameObj.SetActive(false);
That’s it!

There you have a very simple object pooler class. Of course you can expand on this class to do all sorts of things: update other properties, allow the class to grow if needed, etc.

Object Pooling in CoronaSDK

Creating and removing game objects is a very expensive operation. Couple that with enabling and removing physics properties on these objects during game play, and it is no wonder you will notice your game stuttering and lagging during these operations. As more objects are added to the screen, the situation gets worse.

The concept of Object Pooling is something that every new game developer needs to understand. With Object Pooling, you create a set of game objects and configure them as much as you can before the game starts, usually during scene load. During game play, you use and reuse these previously created objects.

Fortunately for Corona developers, creating an object pool is very easy. In this article, we’ll step through this to create a pool of reusable game objects. As an example, I will use one sprite from my game Space Mission: Survival, and I will show you how I create a pool of enemy ships.

Some things to note… for simplicity, I am going to create a pool of static images. In my games, I create my pools using sprite sheets, so I can animate these game objects, but that is outside the scope of this tutorial.

Step 0… Planning

You should determine how you want to pool your game objects. Personally, I like to pool my objects by type, and not mix them.  For example, Space Mission Survival contains an enemy pool and a bullet pool. By keeping them separate, you can manage them easier.  Further, my game contains a bullet pool for the player and a bullet pool that is shared by all enemies, even though the bullet sprite is identical.

Also note that since you are creating a pool of objects, memory will be allocated so try to determine the amount of objects you really need.  For example, if you know you need 50 enemies, create 50 objects, not one hundred.  The extra ones won’t be used, and they will consume memory. In cases where you are not really sure how many you need, you can create extra objects during times where there is little action going on (level starts, info screens, etc).

Step 1… Creation

So, what’s a pool in Corona?  It’s just a table of sprites. Let’s create a pool of enemies:

local maxEnemies = 50
local Enemies = {}

for i = 1, maxEnemies do
    Enemies[i] = display.newImage("enemy.png", 0, 0)
    Enemies[i].isVisible = false
    Enemies[i].isAlive = false

    -- if you need physics on these game objects
    physics.addBody(Enemies[i], "dynamic", params)
    Enemies[i].isBodyActive = false
end

The for loop creates and adds 50 enemies to a table named Enemies, and sets them invisible. Line 7 is a property I added which will keep track of live enemies (enemies currently on the screen).

If you need physics on these game objects, lines 10 and 11 set that up. Line 11 disables any physics checks on this object since it is not alive.

That’s it! This code takes care of the the processor intensive creation of objects that would otherwise slow your game down during game play.

You now have a table of game objects set up with physics, but they are not in the game.  How do you use them?

Step 2 – Use
-- return an available enemy, or nil if there is no enemies left
local function spawnEnemy()
    for i = 1, #Enemies do
        if not Enemies[i].isAlive then
            return Enemies[i]
        end
    end

    -- if we get here, there are no more available enemies in the pool
    return nil
end

-- upon death of enemy, back to the pool
local function killEnemy(enemy)
   enemy.isBodyActive = false
   enemy.isVisible = false
   enemy.isAlive = false
end

-- get enemy from the pool and activate it
local enemy = spawnEnemy()

if enemy ~= nil then
    enemy.x = 100
    enemy.y = 100
    enemy.isVisible = true
    enemy.isAlive = true
    enemy.isBodyActive = true
end

-- do a bunch of stuff in your game and kill enemy when appropriate
killEnemy(enemy)

In the above code, the spawnEnemy function will return a reference to an available enemy in the enemy pool, or nil if one is not available.  The killEnemy function will return the enemy back to the pool.

Step 3 – Clean Up

When done, don’t forget to clean up! Cycle through the pool to remove all event listeners and physics, and don’t forget to nil out the game objects.

Conclusion

Utilizing object pooling is a great way to keep your game responsive and allows you to mange objects by creating and using only what you need. Object reuse keeps your game from having to allocate and release memory by creating the same objects over and over.

This was a basic tutorial on how to get started with object pooling in CoronaSDK. Play around a bit… use sprite sheets to animate pooled objects, or change the sprite of an object. Create a dynamically growing pool in cases where you need more objects than you created. Or, create a generic pooler library to handle creation, deletion, spawning, and removal of all objects in your game.