Object Pooling


Overview

This technique is a performance optimization technique that reuses created objects to avoid frequent memory allocation and deallocation. Similar to memory pool, it focuses on recycling fully initialized object instances. It is ideal for objects that repeatedly appear and disappear.

⚠️ Note: This technique improves performance by reducing CPU overhead through reuse, though it may increase memory usage due to pre-allocation.


Structure Diagram

  • White border: process flow
  • Yellow border: objects container

Practical Application Examples

1. Project: Infinite Runners

Infinite Runners is one of my old project, used here to demonstrate object pooling.

In Infinite Runners, the MapGenerator class uses an array-based pool structure to randomly reuse maps through Object Pooling, allowing for varied map sequences.

MapGenerator Class - Execution Flow

MapGenerator Class - Code Breakdown

private void CreateMapPool()
{
	m_MapPool = new GameObject[m_MapPoolSize];
	
	for (int i = 0; i < m_MapPoolSize; i++)
	{
		GameObject obj = Instantiate(m_MapPrefabs[i % m_MapPrefabs.Length], Vector3.zero, Quaternion.identity, transform);
		obj.SetActive(false);
	
		m_MapPool[i] = obj;
	
		MapBehaviour mapBehaviour = obj.GetComponent<MapBehaviour>();
		mapBehaviour.SetPoolIdx(i);
	}
}
All map objects are instantiated once and stored in an array. They are set inactive by default, and each map is assigned a unique pool index via its MapBehaviour component for future tracking.


private void InitializeFreeIndices()
{
	m_FreeIndices = new List<int>(m_MapPoolSize);
	
	for (int i = 0; i < m_MapPoolSize; i++)
	{
		m_FreeIndices.Add(i);
	}
}
This function fills a list with all the indices of the pre-instantiated map objects. It acts as a tracker for which maps are currently available for reuse.


private void SpawnMapsAtStart()
{
	for (int i = 0; i < m_SpawnAmount; i++)
	{
		int randListIdx = GetRandomFreeIdx();
		int poolIdx = m_FreeIndices[randListIdx];
		m_FreeIndices.RemoveAt(randListIdx);
 
		m_NextSpawnPoint += new Vector3(0.0f, 0.0f, m_GroundLength);
		m_MapPool[poolIdx].transform.position = m_NextSpawnPoint;
 
		m_MapPool[poolIdx].SetActive(true);
	}
}
A set number of maps (m_SpawnAmount) are randomly taken from the pool, positioned sequentially, and activated to create the starting environment.


private void OnTriggerEnter(Collider other)
{
	if (other.CompareTag("Map"))
	{
		MapBehaviour mB = other.gameObject.GetComponent<MapBehaviour>();
		if (mB != null)
		{
			ReplaceMap(mB.GetPoolIdx());
		}
		else
		{
			Destroy(other.gameObject);
		}
	}
}
This function is called when a collider enters the trigger zone. If the collider belongs to a map (tagged "Map") and has a MapBehaviour component, its pool index is passed to ReplaceMap(). Otherwise, the object is destroyed.


private void ReplaceMap(int oldIdx)
{
	int randListIdx = GetRandomFreeIdx();
	int newIdx = m_FreeIndices[randListIdx];
	m_FreeIndices[randListIdx] = oldIdx;
 
	m_NextSpawnPoint = m_MapPool[oldIdx].transform.position + new Vector3(0.0f, 0.0f, m_GroundLength * m_SpawnAmount);
	m_MapPool[newIdx].transform.position = m_NextSpawnPoint;
 
	m_MapPool[newIdx].SetActive(true);
	m_MapPool[oldIdx].SetActive(false);
}
This function recycles maps by selecting a free index, repositioning and activating a new map, and deactivating the old one while returning its index to the pool for future reuse. It ensures the pool maintains reusable maps without frequent instantiation.


2. Project: Spaceship Battle

Spaceship Battle is one of my old project, used here to demonstrate object pooling. View Repository

In SpaceShip Battle, the BulletManager class uses a queue-based pool structure to reuse bullets, as there’s no need for random selection like in Infinite Runners.

No Pool vs. Pool - Performance Comparison

As seen in the images above, the FPS difference shows the left side without pooling performs worse, while the right side with pooling shows a significant performance boost.


Conclusion

Object Pooling is a powerful technique for managing frequently created and destroyed objects at runtime. However, pre-creating too many objects can lead to significant memory waste. It’s essential to estimate how many objects will be needed during runtime. Based on that estimate, an appropriate pool size should be selected to avoid over-allocation. Finally, as seen in my two projects demonstrating object pooling, selecting the right data structure for the pool is crucial, depending on the specific use case.