CSCI 3213 - Game Programming
Reuse, don't recreate - the key to smooth performance!
Improving Performance with the Object Pool Pattern
From: Game Development Patterns with Unity 2021 (2nd Edition)
By David Baron
Goal: Create a high-performance drone spawner that reuses objects instead of constantly creating/destroying them.
As we implement this pattern, we'll be creating multiple files in a specific order. You will see errors in Unity and your IDE until all files are complete.
As Apollo 13's Fred Haise said: "Never Panic Early"
This is a critical skill for software development. Note problems, but stay focused on the implementation path.
Problems: GC SPIKES, memory allocation overhead, framerate stutters, poor performance
β’ Allocates memory on the heap
β’ Creates GameObject, components, and scripts
β’ Registers in Unity's scene hierarchy
β’ Cost: Expensive!
β’ Marks object for deletion
β’ Leaves memory as "garbage"
β’ Doesn't immediately free memory
β’ Cost: Creates garbage!
β’ Unity pauses your game periodically
β’ Scans for unused memory
β’ Frees garbage back to system
β’ Cost: FRAME RATE STUTTER!
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.
Recycle instead of recreate! Create objects once, disable them when not in use, re-enable when needed again.
Create a new file: Assets/Scripts/Testing/SimpleObjectPool.cs
This is a learning exercise to understand pool mechanics - we'll delete this later!
β οΈ Temporary code for understanding only - not for production use.
Start() - Pre-creates all objects at startup. Each one is Instantiated once, then immediately deactivated and stored in the list.
SetActive(false) - Unity's way to "hide" an object. It stops rendering, physics, and Update() calls but the object stays in memory ready to reuse.
Get() - Scans the list for the first inactive object. Linear search (O(n)) - simple but slow for large pools.
!obj.activeInHierarchy - Checks if the object is currently inactive (available). The ! means "not active" = available for reuse.
Release() - Simply deactivates the object. No Destroy() call - the object returns to the pool for reuse.
This works but has limitations: fixed size, no callbacks, no type safety. Unity's ObjectPool<T> solves all of these!
Unity includes UnityEngine.Pool.ObjectPool<T>
- a production-ready pooling system!
Our Approach: Use Unity's built-in system - no need to reinvent the wheel!
βββββββββββββββββββββββββββ
β 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()
βββββββββββββββββββββββββββ
Create a new file: Assets/Scripts/Enemies/Drone.cs
Enemy drone object that works with Unity's ObjectPool.
β οΈ This code goes into your Unity project for Blade Racer.
Public fields - speed, maxHeight, wobbleAmount are editable in the Unity Inspector. Tweak without changing code!
OnSpawn() - Called by the pool's OnGet callback. Activates the GameObject so it becomes visible and starts running Update().
OnDespawn() - Called by the pool's OnRelease callback. Deactivates the GameObject - it stops rendering and updating but stays in memory.
ReleaseDrone(this) - The drone releases itself back to the pool when it reaches max height. this refers to the current Drone instance.
Notice: no Destroy() call anywhere! The drone is deactivated and recycled, never destroyed.
Create a new file: Assets/Scripts/Spawners/DroneSpawner.cs
Spawner that manages drone pooling using Unity's ObjectPool.
β οΈ This code goes into your Unity project for Blade Racer.
using UnityEngine.Pool - Required import to access Unity's built-in ObjectPool<T> class
Singleton - Instance lets any script access the spawner via DroneSpawner.Instance without needing a reference.
ObjectPool<Drone> - Generic pool typed to Drone. Ensures type safety - you can only pool/retrieve Drone objects.
Constructor params:
Pool Empty + Get() called
β
βΌ
ββββββββββββββββββββ
β CreateDrone() β β Instantiate new
ββββββββββββββββββββ
_pool.Get()
β
βΌ
ββββββββββββββββββββ
β OnGetDrone() β β Activate
ββββββββββββββββββββ
_pool.Release()
β
βΌ
ββββββββββββββββββββ
β OnReleaseDrone() β β Deactivate
ββββββββββββββββββββ
_pool.Clear() or maxSize exceeded
β
βΌ
ββββββββββββββββββββ
β OnDestroyDrone() β β Destroy
ββββββββββββββββββββ
Debug.Log in each callback lets you trace pool behavior in the Console during testing.
In the previous slide, you saw: drone.gameObject.SetActive(false)
Why gameObject (lowercase g) instead of GameObject (capital G)?
GameObject obj;Instantiate(prefab) returns GameObjectnew GameObject("MyObject")gameObject.SetActive(false)gameObject.name = "Drone"drone.gameObject.transformUnity follows this pattern for built-in types:
drone.gameObject, you're accessing the GameObject that the Drone component is attached to!
SpawnDrone() - Wraps _pool.Get() and sets the spawn position. Callers never touch the pool directly.
ReleaseDrone() - Wraps _pool.Release(). The Drone calls this on itself when it reaches max height.
Statistics - CountActive and CountInactive are built-in ObjectPool properties. Useful for debugging and GUI.
OnDestroy() - Unity lifecycle method called when the GameObject is destroyed. _pool.Clear() triggers OnDestroyDrone for each pooled object.
Encapsulation: other scripts only call SpawnDrone/ReleaseDrone, never interact with ObjectPool<T> directly.
Add these fields and keyboard shortcuts to your existing TestPanel.cs.
New Fields: Store spawner reference, auto-spawn state, and timing config. _poolExpanded controls the collapsible GUI section.
FindFirstObjectByType<DroneSpawner>() - Finds the spawner in the scene at startup. Safer than using the Singleton in Start() since execution order isn't guaranteed.
G key: Spawns one drone at a random position. T key: Toggles continuous auto-spawning on/off (every 0.5s).
Auto-spawn timer: The Time.time > _nextSpawnTime cooldown pattern spawns a drone every 0.5 seconds while auto-spawn is toggled on.
_spawner prevents errors if DroneSpawner isn't in the scene.
Add DrawObjectPoolSection() and call it from DrawWindow(). Update the keymap.
Collapsible header: Cyan toggle button expands/collapses the section. Same pattern as the green Command Pattern header.
Spawn + Auto-Spawn buttons: Mirror the keyboard shortcuts (G and T). Button label changes based on auto-spawn state.
Live pool stats: Active and Pooled counts update every frame - watch objects recycle in real-time!
DrawObjectPoolSection() in DrawWindow()!
When objects are reused, they carry over their previous state. You MUST reset them to clean initial conditions!
Update the OnSpawn() method in: Assets/Scripts/Enemies/Drone.cs
Add this expanded reset logic to ensure proper object state when reused.
β οΈ This code goes into your Unity project for Blade Racer.
GetComponent<Rigidbody>() might return null!
If your drone doesn't have a Rigidbody component attached, trying to access
rb.velocity would crash the game.
Unsafe approach:
rb.velocity = Vector3.zero;
β Crashes if rb is null!
Safe approach:
if (rb != null) { rb.velocity = Vector3.zero; }
β
Safely skips if no Rigidbody
Best practice: Always check for null when using GetComponent!
GC spike every 2 seconds
Frame drops visible
No GC spikes
Smooth 60 FPS
Implement the Object Pool Pattern:
In 1978, Tomohiro Nishikado created Space Invaders for Taito, and it became the first blockbuster arcade game. Running on extremely limited hardware, the game had to manage dozens of aliens and player bullets on screen simultaneously. With only 8KB of ROM and 1KB of RAM, creating and destroying objects was expensive.
Nishikado's solution? Reuse everything. Bullets weren't created and destroyed - they were recycled! When you fired, the game grabbed an inactive bullet from a fixed pool, activated it, and sent it upward. When it hit an alien or reached the top, it returned to the pool. The game pre-allocated a maximum of 3 player bullets and dozens of alien projectiles at startup, then reused them constantly.
Space Invaders pioneered object pooling out of necessity! With such limited memory, the game couldn't afford to allocate and deallocate bullets every frame. Pre-allocating a pool and recycling objects - exactly what we're implementing today - was essential to making the game run smoothly on 1978 hardware. This pattern became fundamental to game development and remains critical for performance even on modern systems!
Example: 10 bullets/sec Γ 3 second lifetime Γ 1.2 buffer = 36 bullets
Example: 36 default Γ 2 safety = 72 max bullets
Bullets, arrows, missiles, lasers
Why: Spawned frequently, short-lived
Minions, drones, zombies
Why: Wave-based spawning
Explosions, impacts, trails
Why: Particle systems are expensive
Sound effect players
Why: Many sounds play simultaneously
Coins, health, power-ups
Why: Spawn/despawn continuously
Damage numbers, notifications
Why: Instantiate/Destroy is slow
Update the Start() method in: Assets/Scripts/Spawners/DroneSpawner.cs
Add warm-up logic to pre-populate the pool at startup.
β οΈ This is an optional optimization - implement if needed for your game.
Why warm up? - The first _pool.Get() triggers CreateDrone() which calls Instantiate(). Warming up moves all that cost to the loading screen instead of mid-gameplay.
_pool.Get() - Triggers CreateDrone callback, creating a real drone instance
_pool.Release(drone) - Immediately returns each drone to the pool as inactive
The pattern - Get all β store in temp list β release all back. After this, the pool has 20 pre-created drones ready to go with zero allocation cost.
Essential for games where the first seconds of gameplay must be perfectly smooth (e.g., wave-based shooters).
To prove that object pooling actually improves performance, you'll create two identical scenes β one with pooling, one without β and compare them using Unity's Profiler.
Destroy()Instantiate()ReleaseDrone()ObjectPool<T>
Create a new file: Assets/Scripts/Testing/NaiveDrone.cs
A simplified drone that self-destructs β the "old" way, without pooling.
β οΈ This is for your comparison scene only β not for production use.
Same movement - Identical speed, height, and wobble logic. The only difference is what happens when the drone reaches max height.
Destroy(gameObject) - Marks the object for deletion. Unity will free its memory later during garbage collection β causing the GC spikes we want to measure.
No pool reference - NaiveDrone doesn't know about DroneSpawner or ObjectPool. It's completely standalone β simpler code, but worse performance.
Pooled Drone: β pool.Release() β deactivated NaiveDrone: β Destroy() β garbage collected
This contrast is exactly what the Profiler will reveal!
Create a new file: Assets/Scripts/Testing/NaiveSpawner.cs
A simple spawner that creates drones with Instantiate β no pool, no reuse.
β οΈ This is for your comparison scene only β not for production use.
Instantiate() - Every call allocates new memory: GameObject, Transform, MonoBehaviour, and all component data. At 10/sec, that's 10 heap allocations per second.
No callbacks - Unlike DroneSpawner, there's no OnGet/OnRelease/OnDestroy. No control over the object lifecycle at all.
spawnInterval = 0.1f - Spawns 10 drones/sec. Over 10 seconds that's 100 Instantiate() calls + 100 Destroy() calls = 200 expensive operations.
DroneSpawner: pool.Get() β reuse existing object NaiveSpawner: Instantiate() β allocate new memory
The Profiler will show the cumulative cost of all these allocations.
0.1 (10 drones/sec)
You already have most of this from Part 1! Just verify everything is in place.
0.1 (match Scene A!)
Window β Analysis β Profiler (or press Ctrl+7 / Cmd+7)
Look for: Irregular spikes in the timeline, high GC.Alloc values, inconsistent frame times
Look for: Flat, consistent timeline, near-zero GC.Alloc, stable frame times
Due: Next class
Submission: Unity project + video to D2L
Implement pool warm-up that pre-creates 20 drones at Start() - great for eliminating first-spawn lag!
Homework Due Next Class:
Drone Pool + Performance Comparison + Profiler Data
Video submission to D2L
Next: Observer Pattern for decoupled component communication! π