Object Pool

https://github.com/GandhiGames/unity_game_foundations

Why should you use it?

Creating and destroying objects is an expensive process. An Object Pool helps to improve performance by retrieving objects from a fixed pool that was previously instantiated at a suitable time (i.e. during initial run/during a loading screen). It also aids memory fragmentation by instantiating the objects together in memory in one go.

Memory fragmentation occurs when the heap is broken into unnecessarily small chunks, which can prevent you from instantiating any new objects even if there is enough free space (just not contiguously).

How heap fragmentation can cause you to have a bad day. Image found here.

Performance and memory usage concerns are especially important on mobile platforms.

When should you use it?

I use an Object Pool when I know I will need to spawn or destroy a large number of items. Most commonly used when spawning tile based environments (retrieving tiles from the pool) or when shooting projectiles. However, you can use an object pool whenever you would normally be creating and destroying objects frequently.

You can imagine how object pools are used extensively in any game in the “Bullet Hell” genre. Image found here.

How do you use it?

Once you have imported the Github project, setting up and using the object pool is straightforward. For an example on how to setup a pool see the example scene included with the project.

Pooling Objects

When pooling an object (if the pool is responsible for the type), the object is:

  • Disabled
  • Set as a child of the object pool
  • Added to the pool
///<summary>
/// Pools the object specified. Will not be pooled if there is no prefab of that type.
/// </summary>
/// <param name="obj"> Object to be pooled.</param>
public void PoolObject(GameObject obj)
{
for (int i = 0; i < objectPoolItems.Length; i++)
{
if (objectPoolItems[i].prefab.name == obj.name)
{
obj.SetActive(false);
obj.transform.SetParent(container.transform);
pooledObjects[i].Add(obj);
return;
}
}

throw new PrefabNotFoundException(obj.name + ": not setup to use object pool");
}

If the pool is not responsible for the type provided, a PrefabNotFoundException is raised. It is important to deal with the exception promptly as the object you attempted to pool will continue to live in the current scene.

try
{
ObjectPool.instance.PoolObject(gameObject);
}
catch (PrefabNotFoundException ex)
{
Debug.Log(ex.Message);
Destroy(gameObject);
}

You may have noticed how the Object Pool is referenced, by first obtaining a static instance (ObjectPool.instance..). This is not generally recommended and may be something you would want to remove in your own project. For more information, see the my post on the Singleton Pattern.

Retrieving Objects

When the pool is initialised, it creates the entire collection of objects up front and adds them to its internal pool. To retrieve an object, you request it from the pool. When you receive the object it will be disabled so must be manually activated.

try
{
GameObject cube = ObjectPool.instance.GetObject(this.name);
cube.SetActive(true);
GameObject cubeTwo = ObjectPool.instance.GetAvailableObject(this.name);
cubeTwo.SetActive(true);
}
catch (PrefabNotFoundException e)
{
// Object not contained in pool.
}

GetObject will return an object even if no objects are currently pooled (one will be instantiated and returned). GetAvailableObject will only return an object if there is one currently pooled and available. Both will raise PrefabNotFoundException if the pool is not responsible for maintaining the objects you are trying to retrieve.

Limitations

A couple of things you may want to consider when implementing your own object pool:

  • Tuning is required otherwise resources may be wasted on instantiation of unnecessary objects.
  • Values are not automatically reset. For example, you shoot an enemy, which results in its health being set to zero, being removed from the game and added to the pool. Later in the level, the game spawns the same enemy from the pool; however, you forgot to reset the health, so instead of attacking the player it dies. The enemy never had a chance. While this is a simple example, it is important to remember to reset any necessary values when pooling or retrieving an object.

As always, thank you for reading 🙂