Object Pool Pattern - Performance Optimization

Performance Optimization

The Object Pool Pattern ♻️

CSCI 3213 - Game Programming

Spring '26 - Week 6, Class 1

Reuse, don't recreate - the key to smooth performance!

Today's Content

πŸ“š Based on Chapter 8

Improving Performance with the Object Pool Pattern

From: Game Development Patterns with Unity 2021 (2nd Edition)
By David Baron

Note: Unity 2021 introduced native ObjectPool<T> support! We'll learn both the pattern theory and Unity's built-in implementation.

Today's Learning Objectives

What We'll Master

  • 🎯 Understand garbage collection performance issues
  • 🎯 Learn Object Pool Pattern structure
  • 🎯 Implement Unity's ObjectPool<T> system
  • 🎯 Build a drone spawning pool
  • 🎯 Measure performance improvements

Goal: Create a high-performance drone spawner that reuses objects instead of constantly creating/destroying them.

The Problem: Instantiate/Destroy Overhead

❌ Without Object Pooling

void Update() { if (Time.time > nextSpawn) { // Create new drone - EXPENSIVE! GameObject drone = Instantiate(dronePrefab); // After 5 seconds, destroy it - TRIGGERS GARBAGE COLLECTION! Destroy(drone, 5f); nextSpawn = Time.time + spawnInterval; } }

Problems: GC SPIKES, memory allocation overhead, framerate stutters, poor performance

⚠️ Real Impact: Spawning 100 bullets per second = 100 Instantiate() calls + 100 Destroy() calls = major performance hit!

Understanding Garbage Collection

What Happens When You Instantiate/Destroy

1. Instantiate()

β€’ Allocates memory on the heap

β€’ Creates GameObject, components, and scripts

β€’ Registers in Unity's scene hierarchy

β€’ Cost: Expensive!

2. Destroy()

β€’ Marks object for deletion

β€’ Leaves memory as "garbage"

β€’ Doesn't immediately free memory

β€’ Cost: Creates garbage!

3. Garbage Collection (GC)

β€’ Unity pauses your game periodically

β€’ Scans for unused memory

β€’ Frees garbage back to system

β€’ Cost: FRAME RATE STUTTER!

What is the Object Pool Pattern?

Object Pool Pattern

A creational pattern that manages a set of reusable objects, handing them out when needed and reclaiming them when done, instead of creating and destroying objects repeatedly.

βœ… Key Concept

Recycle instead of recreate! Create objects once, disable them when not in use, re-enable when needed again.

In Simple Terms: Like a library - books (objects) are checked out, used, returned, and checked out again by someone else!

Object Pool: Benefits & Drawbacks

βœ… Benefits

  • Performance: Eliminates GC spikes
  • Memory: Predictable memory usage
  • Framerate: Consistent, smooth performance
  • Mobile-Friendly: Critical for mobile games
  • Control: Set max pool size

⚠️ Drawbacks

  • Complexity: More code to manage
  • Memory Trade-off: Holds objects in memory
  • Reset Logic: Must clean object state
  • Over-pooling: Can waste memory if oversized
  • Debugging: Objects never fully destroyed
Best For: Frequently spawned objects - bullets, enemies, particles, VFX, pickups

Object Pool Lifecycle

CREATE POOL
β†’
GET OBJECT
β†’
USE OBJECT
β†’
RELEASE BACK
β†Ί

Step-by-Step Process

  1. Initialization: Pre-create a collection of inactive objects
  2. Get: Activate an object from the pool (or create if pool empty)
  3. Use: Object performs its function (bullet flies, drone patrols)
  4. Release: Deactivate object and return to pool
  5. Repeat: Same object gets reused many times!

Unity 2021 ObjectPool<T>

Built-In Pool System

Unity 2021 introduced UnityEngine.Pool.ObjectPool<T> - a production-ready pooling system!

  • βœ“ Type-Safe: Generic implementation
  • βœ“ Callbacks: OnCreate, OnGet, OnRelease, OnDestroy
  • βœ“ Flexible: Works with any object type
  • βœ“ Tested: Battle-tested by Unity engineers
  • βœ“ Thread-Safe Options: For advanced scenarios

Our Approach: Use Unity's built-in system - no need to reinvent the wheel!

Object Pool Pattern Structure

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   DroneSpawner          β”‚  ← Pool Manager
│─────────────────────────│
β”‚ - pool: ObjectPool<T>  β”‚
β”‚ - dronePrefab           β”‚
β”‚ + SpawnDrone()          β”‚
β”‚ + ReleaseDrone()        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚ manages
         β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  ObjectPool<Drone>      │────────▢│      Drone       β”‚  ← Pooled Object
│─────────────────────────│         │──────────────────│
β”‚ + Get()                 β”‚         β”‚ + OnSpawn()      β”‚
β”‚ + Release(drone)        β”‚         β”‚ + OnDespawn()    β”‚
β”‚ + Clear()               β”‚         β”‚ + Patrol()       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚
         β”‚ callbacks
         β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Pool Callbacks        β”‚
│─────────────────────────│
β”‚ + OnCreateDrone()       β”‚  ← Create new instance
β”‚ + OnGetDrone()          β”‚  ← Activate from pool
β”‚ + OnReleaseDrone()      β”‚  ← Deactivate to pool
β”‚ + OnDestroyDrone()      β”‚  ← Clean up on Clear()
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                

Implementation Step 1: Drone Object

using UnityEngine; namespace Chapter.ObjectPool { public class Drone : MonoBehaviour { public float speed = 5f; public float maxHeight = 10f; public float wobbleAmount = 2f; // Called when drone is spawned from pool public void OnSpawn() { gameObject.SetActive(true); Debug.Log("[Drone] Spawned and activated"); } // Called when drone is returned to pool public void OnDespawn() { gameObject.SetActive(false); Debug.Log("[Drone] Despawned and deactivated"); } void Update() { // Simple upward movement with wobble float wobble = Mathf.Sin(Time.time * wobbleAmount); transform.Translate(Vector3.up * speed * Time.deltaTime); transform.Translate(Vector3.right * wobble * Time.deltaTime); // Release back to pool when reaching max height if (transform.position.y > maxHeight) { DroneSpawner.Instance.ReleaseDrone(this); } } } }

Implementation Step 2: Spawner Setup

using UnityEngine; using UnityEngine.Pool; namespace Chapter.ObjectPool { public class DroneSpawner : MonoBehaviour { // Singleton access public static DroneSpawner Instance { get; private set; } // Pool configuration public Drone dronePrefab; public int defaultCapacity = 10; public int maxPoolSize = 50; public bool collectionCheck = true; // The object pool private ObjectPool<Drone> _pool; void Awake() { Instance = this; // Create the pool with callbacks _pool = new ObjectPool<Drone>( createFunc: CreateDrone, actionOnGet: OnGetDrone, actionOnRelease: OnReleaseDrone, actionOnDestroy: OnDestroyDrone, collectionCheck: collectionCheck, defaultCapacity: defaultCapacity, maxSize: maxPoolSize ); } } }

Implementation Step 3: Pool Callbacks

// Continuing DroneSpawner class... // Called when pool needs to create a new drone private Drone CreateDrone() { Drone drone = Instantiate(dronePrefab); drone.gameObject.SetActive(false); Debug.Log("[Pool] Created new drone"); return drone; } // Called when drone is taken from pool private void OnGetDrone(Drone drone) { drone.OnSpawn(); Debug.Log("[Pool] Drone retrieved from pool"); } // Called when drone is returned to pool private void OnReleaseDrone(Drone drone) { drone.OnDespawn(); Debug.Log("[Pool] Drone returned to pool"); } // Called when pool is cleared (optional cleanup) private void OnDestroyDrone(Drone drone) { Destroy(drone.gameObject); Debug.Log("[Pool] Drone destroyed"); }

Implementation Step 4: Public API

// Continuing DroneSpawner class... // Spawn a drone at specified position public Drone SpawnDrone(Vector3 position) { Drone drone = _pool.Get(); drone.transform.position = position; return drone; } // Release drone back to pool public void ReleaseDrone(Drone drone) { _pool.Release(drone); } // Get pool statistics public int GetActiveCount() { return _pool.CountActive; } public int GetInactiveCount() { return _pool.CountInactive; } void OnDestroy() { // Clean up pool when spawner is destroyed _pool.Clear(); }

Implementation Step 5: Test Client

using UnityEngine; namespace Chapter.ObjectPool { public class TestPool : MonoBehaviour { public float spawnInterval = 0.5f; public float spawnRange = 5f; private float _nextSpawnTime; void Update() { // Spawn drones periodically if (Time.time > _nextSpawnTime) { // Random spawn position Vector3 randomPos = new Vector3( Random.Range(-spawnRange, spawnRange), 0f, Random.Range(-spawnRange, spawnRange) ); DroneSpawner.Instance.SpawnDrone(randomPos); _nextSpawnTime = Time.time + spawnInterval; } } void OnGUI() { // Display pool statistics GUILayout.Label($"Active Drones: {DroneSpawner.Instance.GetActiveCount()}"); GUILayout.Label($"Pooled Drones: {DroneSpawner.Instance.GetInactiveCount()}"); } } }

Critical: Object Reset Logic

⚠️ Always Reset Object State!

When objects are reused, they carry over their previous state. You MUST reset them to clean initial conditions!

public void OnSpawn() { gameObject.SetActive(true); // CRITICAL: Reset all state! transform.rotation = Quaternion.identity; // Reset rotation GetComponent<Rigidbody>()?.ResetVelocity(); // Stop movement health = maxHealth; // Reset health currentState = DroneState.Idle; // Reset state machine // Reset any timers, counters, flags, etc. elapsed = 0f; isAggro = false; }
Common Bug: Forgetting to reset velocity on Rigidbody objects, causing "ghost momentum" on respawn!

Testing the Object Pool

Test Setup Steps

  1. Create new Unity scene
  2. Create Drone prefab (Cube with Drone script)
  3. Create empty GameObject "DroneSpawner"
  4. Attach DroneSpawner script, assign drone prefab
  5. Create empty GameObject "TestPool"
  6. Attach TestPool script
  7. Run and watch drones spawn/recycle!

What to Observe

  • Drones spawn every 0.5 seconds
  • Drones rise to max height, then disappear
  • Watch console logs: "Created" only appears once per unique drone!
  • GUI shows active vs pooled count

Performance Comparison

Without Pooling

~15ms

GC spike every 2 seconds

Frame drops visible

With Pooling

~0.1ms

No GC spikes

Smooth 60 FPS

Performance Metrics (100 drones/sec)

Memory Allocations: 150x reduction
GC Frequency: 95% reduction
Frame Time Consistency: 10x improvement
Mobile Battery Life: ~20% longer

Hands-On Implementation πŸ’»

30-Minute Implementation Challenge

Implement the Object Pool Pattern:

  1. Create Drone MonoBehaviour with OnSpawn/OnDespawn
  2. Create DroneSpawner with ObjectPool<Drone>
  3. Implement all four pool callbacks
  4. Create TestPool spawning script
  5. Add GUI to display pool statistics
  6. Test and verify objects are recycled (check console logs)
Success Metric: Console should show "Created new drone" only 10-15 times even after spawning 100+ drones!

Determining Pool Size

Key Questions

  • What's the maximum concurrent objects?
  • What's the typical spawn rate?
  • How long does each object "live"?
  • What's acceptable memory overhead?

Formula: defaultCapacity = spawnRate Γ— avgLifetime Γ— 1.2

Example: 10 bullets/sec Γ— 3 second lifetime Γ— 1.2 buffer = 36 bullets

Formula: maxSize = defaultCapacity Γ— 2 (or peak concurrent)

Example: 36 default Γ— 2 safety = 72 max bullets

Pro Tip: Use Unity Profiler to measure actual peak concurrent objects in gameplay!

What to Pool in Games

πŸ”« Projectiles

Bullets, arrows, missiles, lasers

Why: Spawned frequently, short-lived

πŸ‘Ύ Enemies

Minions, drones, zombies

Why: Wave-based spawning

πŸ’₯ VFX

Explosions, impacts, trails

Why: Particle systems are expensive

🎡 Audio Sources

Sound effect players

Why: Many sounds play simultaneously

πŸ’Ž Pickups

Coins, health, power-ups

Why: Spawn/despawn continuously

πŸ“„ UI Elements

Damage numbers, notifications

Why: Instantiate/Destroy is slow

Advanced: Pool Warm-Up

// Pre-populate pool at startup for zero initial lag void Start() { // Create pool _pool = new ObjectPool<Drone>( createFunc: CreateDrone, actionOnGet: OnGetDrone, actionOnRelease: OnReleaseDrone, actionOnDestroy: OnDestroyDrone, defaultCapacity: 20 ); // WARM-UP: Pre-create objects var tempList = new List<Drone>(); for (int i = 0; i < 20; i++) { tempList.Add(_pool.Get()); } // Return all to pool immediately foreach (var drone in tempList) { _pool.Release(drone); } Debug.Log("[Pool] Warmed up with 20 drones"); }
Benefit: Eliminates first-spawn lag. Crucial for tight gameplay!

Homework Assignment πŸ“

Assignment: Drone Pool with Performance Testing

Due: Next class
Submission: Unity project + video to D2L

Part 1: Core Implementation (50 points)

  1. Implement Drone class with OnSpawn/OnDespawn + custom reset logic
  2. Implement DroneSpawner with Unity ObjectPool<Drone>
  3. Add all four pool callbacks with debug logs
  4. Create TestPool that spawns 2 drones/second
  5. Add GUI showing active/inactive pool counts

Homework Assignment (Continued)

Part 2: Performance Comparison (50 points)

  1. Create Two Scenes:
    • Scene A: WITHOUT pooling (Instantiate/Destroy)
    • Scene B: WITH pooling (ObjectPool)
  2. Both scenes spawn 100 drones over 10 seconds
  3. Record performance metrics:
    • Open Unity Profiler (Window β†’ Analysis β†’ Profiler)
    • Record FPS average
    • Record GC.Alloc count
    • Take screenshots of Profiler CPU usage
  4. Document findings: Which scene performs better? By how much?

Bonus (+10 points):

Implement pool warm-up that pre-creates 20 drones at Start()

Video Submission Guidelines

Recording Checklist (3-5 minutes)

  1. Show Code: Briefly show Drone and DroneSpawner classes
  2. Show Scene Setup: Hierarchy and Inspector settings
  3. Test Scene A (No Pool):
    • Show Profiler recording
    • Run for 30 seconds
    • Point out GC spikes in Profiler
  4. Test Scene B (With Pool):
    • Show Profiler recording
    • Run for 30 seconds
    • Show smooth performance, no GC spikes
    • Show GUI with pool statistics
  5. Compare Results: Side-by-side Profiler screenshots
  6. Narration: Explain the performance difference

Grading Rubric (100 points)

Point Breakdown

15 pts: Drone class with proper OnSpawn/OnDespawn and reset logic
20 pts: DroneSpawner correctly implements ObjectPool<T>
15 pts: All four pool callbacks implemented and logged
15 pts: Two comparison scenes (with/without pooling)
20 pts: Profiler data showing performance improvement
15 pts: Video demonstrates all features with clear narration
+10 pts BONUS: Pool warm-up implementation

Additional Resources

πŸ“š Further Reading

πŸ”§ Tools

  • Unity Profiler: Window β†’ Analysis β†’ Profiler
  • Memory Profiler: Package Manager β†’ Memory Profiler
  • Frame Debugger: Window β†’ Analysis β†’ Frame Debugger
Office Hours: Questions about Profiler or performance testing? Come see me!

Questions & Discussion πŸ’¬

Open Floor

  • How does garbage collection impact performance?
  • When should I use object pooling?
  • How do I determine optimal pool size?
  • What's the difference between defaultCapacity and maxSize?
  • How do I use the Unity Profiler?
  • Homework clarifications?

Performance Optimized! ♻️

Today's Achievements:

Homework Due Next Class:

Drone Pool + Performance Comparison + Profiler Data

Video submission to D2L

Next: Observer Pattern for decoupled component communication! πŸ‘€