Introductino
Performance optimization is critical in game development. Object pooling provides an efficient solution for managing frequetn instantiation and detsruction of game objects, reducing memory allocation and garbage collection overhead.
Object Pool Fundamentals
An object pool maintains a collection of reusable game objects. Instead of creating new instances, objects are retrieved from the pool when needed and returned after use. This approach minimizes:
- Memory fragmentation from frequent allocations
- Garbage collection spikes
- Object initialization costs
Core Pool Operations
Object pools implement five key operations:
- Acquire: Retrieve an object from the pool
- Release: Return an object to the pool
- Preload: Initialize objects during startup
- Shrink: Reduce pool size when excess capacity exists
- Reset: Clear object state before reuse
Unity Implementation
Pool Manager
using UnityEngine;
using System.Collections.Generic;
public class PoolManager : MonoBehaviour
{
private Dictionary<GameObject, Pool> pools = new Dictionary<GameObject, Pool>();
private Transform poolContainer;
public void Initialize(GameObject prefab, int count)
{
Pool targetPool = FindPool(prefab);
targetPool.Preload(count);
}
public GameObject GetObject(GameObject prefab, Vector3 position, Quaternion rotation, Transform parent = null)
{
Pool targetPool = FindPool(prefab);
GameObject instance = targetPool.Get(position, rotation, parent);
return instance;
}
public void ReturnObject(GameObject obj, float delay = 0f)
{
StartCoroutine(DelayedReturn(obj, delay));
}
private IEnumerator DelayedReturn(GameObject obj, float delay)
{
yield return new WaitForSeconds(delay);
Pool targetPool = FindPoolForInstance(obj);
targetPool?.Return(obj);
}
private Pool FindPool(GameObject prefab)
{
if (!pools.ContainsKey(prefab))
{
GameObject poolObj = new GameObject($"{prefab.name}Pool");
poolObj.transform.SetParent(poolContainer);
Pool newPool = poolObj.AddComponent<Pool>();
newPool.Initialize(prefab);
pools.Add(prefab, newPool);
}
return pools[prefab];
}
}
Pool Component
public class Pool : MonoBehaviour
{
public GameObject template;
public int maxSize = -1;
private Stack<GameObject> available = new Stack<GameObject>();
private List<GameObject> active = new List<GameObject>();
public void Initialize(GameObject prefab)
{
template = prefab;
}
public void Preload(int quantity)
{
for (int i = 0; i < quantity; i++)
{
GameObject obj = Instantiate(template, Vector3.zero, Quaternion.identity, transform);
obj.SetActive(false);
available.Push(obj);
}
}
public GameObject Get(Vector3 position, Quaternion rotation, Transform parent)
{
GameObject obj = available.Count > 0 ? available.Pop() : Instantiate(template);
obj.transform.SetPositionAndRotation(position, rotation);
obj.transform.SetParent(parent);
obj.SetActive(true);
active.Add(obj);
obj.SendMessage("OnAcquire", SendMessageOptions.DontRequireReceiver);
return obj;
}
public void Return(GameObject obj)
{
obj.SendMessage("OnRelease", SendMessageOptions.DontRequireReceiver);
obj.SetActive(false);
obj.transform.SetParent(transform);
active.Remove(obj);
available.Push(obj);
}
}
Practical Example
Weapon System
public class Weapon : MonoBehaviour
{
[SerializeField] private GameObject projectilePrefab;
[SerializeField] private Transform launchPoint;
[SerializeField] private float launchForce = 1000f;
private void Start()
{
PoolManager.Instance.Initialize(projectilePrefab, 10);
}
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
GameObject projectile = PoolManager.Instance.GetObject(projectilePrefab,
launchPoint.position,
launchPoint.rotation);
projectile.GetComponent<Rigidbody>().AddForce(launchPoint.forward * launchForce);
PoolManager.Instance.ReturnObject(projectile, 1f);
}
}
}
Projectile Behavior
public class Projectile : MonoBehaviour
{
private void OnAcquire()
{
Debug.Log("Projectile launched");
}
private void OnRelease()
{
Debug.Log("Projectile returned");
GetComponent<Rigidbody>().velocity = Vector3.zero;
}
}