Goal: Create a high-performance drone spawner that reuses
objects instead of constantly creating/destroying them.
Developer's Note: "Never Panic Early"
β οΈ What to Expect During Implementation
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.
How to Handle Development Errors
Don't Ignore: Note the errors - they're telling you something
Don't Panic: These are expected until all files are created
Stay Calm: Follow the implementation order and errors will resolve
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.
The Problem: Instantiate/Destroy Overhead
β Without Object Pooling
voidUpdate()
{
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;
}
}
β οΈ 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!
Initialization: Pre-create a collection of inactive objects
Get: Activate an object from the pool (or create if pool empty)
Use: Object performs its function (bullet flies, drone patrols)
Release: Deactivate object and return to pool
Repeat: Same object gets reused many times!
Building a Simple Object Pool
Understanding the Mechanics (We'll delete this next!)
π File Structure Note - INSTRUCTIONAL EXAMPLE
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.
using UnityEngine;
using System.Collections.Generic;
public classSimpleObjectPool : MonoBehaviour
{
public GameObject prefab;
public int poolSize = 10;
private List<GameObject> pool = new List<GameObject>();
voidStart()
{
// Pre-create all pool objectsfor (int i = 0; i < poolSize; i++)
{
GameObject obj = Instantiate(prefab);
obj.SetActive(false); // Start inactive
pool.Add(obj);
}
}
public GameObject Get()
{
// Find first inactive objectforeach (GameObject obj in pool)
{
if (!obj.activeInHierarchy)
{
obj.SetActive(true); // Activate itreturn obj;
}
}
return null; // Pool exhausted!
}
public voidRelease(GameObject obj)
{
obj.SetActive(false); // Deactivate and return to pool
}
}
Key Concepts: Pre-create objects, use SetActive for get/release, reuse instead of Destroy().
This is the basic pattern - but Unity's version is much better!
Note: This is a learning exercise only. You'll delete this code and use Unity's
ObjectPool<T> in the next slide, which handles edge cases and is production-ready!
Unity's Native ObjectPool<T>
Built-In Pool System
Unity includes UnityEngine.Pool.ObjectPool<T>
- a production-ready pooling system!
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.
using UnityEngine;
public classDrone : MonoBehaviour
{
public float speed = 5f;
public float maxHeight = 10f;
public float wobbleAmount = 2f;
// Called when drone is spawned from poolpublic voidOnSpawn()
{
gameObject.SetActive(true);
Debug.Log("[Drone] Spawned and activated");
}
// Called when drone is returned to poolpublic voidOnDespawn()
{
gameObject.SetActive(false);
Debug.Log("[Drone] Despawned and deactivated");
}
voidUpdate()
{
// Simple upward movement with wobblefloat 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 heightif (transform.position.y > maxHeight)
{
// Note: DroneSpawner doesn't exist yet - you'll create it in Step 2!// Your editor will show errors until then - this is expected (Never Panic Early!)
DroneSpawner.Instance.ReleaseDrone(this);
}
}
}
Implementation Step 2: Spawner Setup
π File Structure Note - PRODUCTION CODE
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;
using UnityEngine.Pool;
public classDroneSpawner : MonoBehaviour
{
// Singleton accesspublic static DroneSpawner Instance { get; private set; }
// Pool configurationpublic Drone dronePrefab;
public int defaultCapacity = 10;
public int maxPoolSize = 50;
public bool collectionCheck = true;
// The object poolprivate ObjectPool<Drone> _pool;
voidAwake()
{
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 droneprivate Drone CreateDrone()
{
Drone drone = Instantiate(dronePrefab);
drone.gameObject.SetActive(false);
Debug.Log("[Pool] Created new drone");
return drone;
}
// Called when drone is taken from poolprivate voidOnGetDrone(Drone drone)
{
drone.OnSpawn();
Debug.Log("[Pool] Drone retrieved from pool");
}
// Called when drone is returned to poolprivate voidOnReleaseDrone(Drone drone)
{
drone.OnDespawn();
Debug.Log("[Pool] Drone returned to pool");
}
// Called when pool is cleared (optional cleanup)private voidOnDestroyDrone(Drone drone)
{
Destroy(drone.gameObject);
Debug.Log("[Pool] Drone destroyed");
}
Quick Note: GameObject vs gameObject
Understanding Unity's Naming Patterns
Notice the Capitalization?
In the previous slide, you saw: drone.gameObject.SetActive(false)
Why gameObject (lowercase g) instead of GameObject (capital G)?
GameObject (Capital G)
What it is: A class type
Used for: Declaring variables, type references
Examples:
GameObject obj;
Instantiate(prefab) returns GameObject
new GameObject("MyObject")
gameObject (lowercase g)
What it is: A property on MonoBehaviour
Used for: Accessing the GameObject this script is attached to
Examples:
gameObject.SetActive(false)
gameObject.name = "Drone"
drone.gameObject.transform
The Pattern in Unity
Unity follows this pattern for built-in types:
Transform (class) vs transform (property on Component)
Rigidbody (class) vs rigidbody (cached component reference)
Renderer (class) vs renderer (cached component reference)
Rule of Thumb: Capital = Type/Class, lowercase = instance property.
When you see drone.gameObject, you're accessing the GameObject that the Drone component is attached to!
Implementation Step 4: Public API
// Continuing DroneSpawner class...// Spawn a drone at specified positionpublic Drone SpawnDrone(Vector3 position)
{
Drone drone = _pool.Get();
drone.transform.position = position;
return drone;
}
// Release drone back to poolpublic voidReleaseDrone(Drone drone)
{
_pool.Release(drone);
}
// Get pool statisticspublic intGetActiveCount()
{
return _pool.CountActive;
}
public intGetInactiveCount()
{
return _pool.CountInactive;
}
voidOnDestroy()
{
// Clean up pool when spawner is destroyed
_pool.Clear();
}
Implementation Step 5: Test Client
π File Structure Note - TESTING CODE
Create a new file: Assets/Scripts/Testing/TestPool.cs
Test client that spawns drones and displays pool statistics. β οΈ This is temporary testing code - you can remove it after testing your implementation.
using UnityEngine;
public classTestPool : MonoBehaviour
{
public float spawnInterval = 0.5f;
public float spawnRange = 5f;
private float _nextSpawnTime;
voidUpdate()
{
// Spawn drones periodicallyif (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;
}
}
voidOnGUI()
{
// Display pool statistics
GUILayout.Label($"Active Drones: {DroneSpawner.Instance.GetActiveCount()}");
GUILayout.Label($"Pooled Drones: {DroneSpawner.Instance.GetInactiveCount()}");
}
}
π‘ Alternative: When you have multiple test scripts with overlapping GUI buttons,
consider using TestPanel.cs to combine all test controls into one draggable window.
See the Event Bus lecture for the unified TestPanel implementation.
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!
π File Structure Note - PRODUCTION CODE
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.
public voidOnSpawn()
{
gameObject.SetActive(true);
// CRITICAL: Reset all state!
transform.rotation = Quaternion.identity; // Reset rotation// Reset velocity if Rigidbody exists
Rigidbody rb = GetComponent<Rigidbody>();
if (rb != null)
{
rb.velocity = Vector3.zero;
rb.angularVelocity = Vector3.zero;
}
// Reset any other state variables your drone might have// Example: health = maxHealth;// Example: currentState = DroneState.Idle;
}
Why Check for Null?
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!
Common Bug: Forgetting to reset velocity on Rigidbody objects,
causing "ghost momentum" on respawn!
Testing the Object Pool
Test Setup Steps
Create new Unity scene
Create Drone prefab (Cube with Drone script)
Create empty GameObject "DroneSpawner"
Attach DroneSpawner script, assign drone prefab
Create empty GameObject "TestPool"
Attach TestPool script
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:
Create Drone MonoBehaviour with OnSpawn/OnDespawn
Create DroneSpawner with ObjectPool<Drone>
Implement all four pool callbacks
Create TestPool spawning script
Add GUI to display pool statistics
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!
Gaming History Moment πΉοΈ
Golden Age Arcades: Space Invaders (1978)
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.
Connection to Object Pool Pattern
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!
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
π File Structure Note - OPTIONAL ENHANCEMENT
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.
// Pre-populate pool at startup for zero initial lagvoidStart()
{
// Create pool
_pool = new ObjectPool<Drone>(
createFunc: CreateDrone,
actionOnGet: OnGetDrone,
actionOnRelease: OnReleaseDrone,
actionOnDestroy: OnDestroyDrone,
defaultCapacity: 20
);
// WARM-UP: Pre-create objectsvar tempList = new List<Drone>();
for (int i = 0; i < 20; i++)
{
tempList.Add(_pool.Get());
}
// Return all to pool immediatelyforeach (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)
Implement Drone class with OnSpawn/OnDespawn + custom reset logic
Implement DroneSpawner with Unity ObjectPool<Drone>
Add all four pool callbacks with debug logs
Create TestPool that spawns 2 drones/second
Add GUI showing active/inactive pool counts
Homework Assignment (Continued)
Part 2: Performance Comparison (50 points)
Create Two Scenes:
Scene A: WITHOUT pooling (Instantiate/Destroy)
Scene B: WITH pooling (ObjectPool)
Both scenes spawn 100 drones over 10 seconds
Record performance metrics:
Open Unity Profiler (Window β Analysis β Profiler)
Record FPS average
Record GC.Alloc count
Take screenshots of Profiler CPU usage
Document findings: Which scene performs better? By how much?
Optional Enhancement:
Implement pool warm-up that pre-creates 20 drones at Start() - great for eliminating first-spawn lag!
Video Submission Guidelines
Recording Checklist (3-5 minutes)
Show Code: Briefly show Drone and DroneSpawner classes
Show Scene Setup: Hierarchy and Inspector settings