Don't worry about memorizing! We'll learn each pattern in context
when we need it for Blade Racer.
Part 2: The Singleton Pattern
What is a Singleton?
A class that ensures only one instance exists
and provides global access to that instance.
Real-World Analogy
Like a country having only one president at a time. Everyone knows who the
president is, and there can only be one person in that role.
The Problem Singleton Solves
❌ Without Singleton
// Player needs game manager
GameManager manager = GameObject.Find("GameManager")
.GetComponent<GameManager>();
// Enemy also needs game manager
GameManager manager = GameObject.Find("GameManager")
.GetComponent<GameManager>();
// UI also needs game manager... 😫
Problems: Repeated code, slow GameObject.Find(), no guarantee of single instance
✅ With Singleton
// Anywhere in your code:
GameManager.Instance.StartGame();
GameManager.Instance.PauseGame();
Benefits: Clean, fast, guaranteed single instance, globally accessible
When to Use the Singleton Pattern
✅ Perfect For
Game Manager
Audio Manager
Input Manager
Save/Load Manager
Network Manager
Pool Manager
❌ Not Suitable For
Multiple instances needed
Player characters
Enemies
Projectiles
UI elements (usually)
Temporary objects
Rule of Thumb: If you need global access and only ONE instance should exist, use Singleton.
Singleton: Benefits & Drawbacks
✅ Benefits
Single Instance: Guaranteed only one exists
Global Access: Available from anywhere
Lazy Instantiation: Created when needed
Clean Code: No repeated GameObject.Find()
Performance: Fast direct access
⚠️ Drawbacks
Global State: Can create hidden dependencies
Testing: Harder to unit test
Coupling: Code becomes dependent on singleton
Overuse: Can become anti-pattern if abused
Threading: Not thread-safe by default
⚠️ Important: Don't make everything a Singleton! Use it judiciously for true manager classes.
Singleton Pattern Structure
Core Components
1. Private Static Instance
Stores the single instance
2. Public Static Accessor
Provides global access point (Instance property)
3. Private Constructor (optional)
Prevents external instantiation
4. Instance Check
Creates instance if it doesn't exist
Basic Singleton Implementation
using UnityEngine;
public classGameManager : MonoBehaviour
{
// 1. Private static instance variableprivate static GameManager _instance;
// 2. Public static accessor propertypublic static GameManager Instance
{
get
{
if (_instance == null)
{
// Find existing instance in scene
_instance = FindObjectOfType<GameManager>();
if (_instance == null)
{
// Create new GameObject with GameManager
GameObject go = new GameObject("GameManager");
_instance = go.AddComponent<GameManager>();
}
}
return _instance;
}
}
voidAwake()
{
// Ensure only one instance existsif (_instance != null && _instance != this)
{
Destroy(this.gameObject);
}
else
{
_instance = this;
}
}
}
Persistent Singleton (Don't Destroy)
voidAwake()
{
if (_instance != null && _instance != this)
{
// Destroy duplicate
Destroy(this.gameObject);
}
else
{
_instance = this;
// Persist across scene loads!
DontDestroyOnLoad(this.gameObject);
}
}
DontDestroyOnLoad()
Keeps the GameObject alive when loading new scenes. Perfect for managers
that need to persist throughout the game!
Testing Note: You'll test this with 5 scenes to see it persist!
Generic Singleton (Reusable Template)
using UnityEngine;
public class Singleton<T> : MonoBehaviour where T : Component
{
private static T _instance;
public static T Instance
{
get
{
if (_instance == null)
{
_instance = FindObjectOfType<T>();
if (_instance == null)
{
GameObject obj = new GameObject();
obj.name = typeof(T).Name;
_instance = obj.AddComponent<T>();
}
}
return _instance;
}
}
public virtual voidAwake()
{
if (_instance == null)
{
_instance = this as T;
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
}
}
}
Generics Power! Remember from our crash course? Now any class can become a Singleton!
Using the Generic Singleton
// Simply inherit from Singleton<T>public classGameManager : Singleton<GameManager>
{
public voidStartGame()
{
Debug.Log("Game Started!");
}
}
public classAudioManager : Singleton<AudioManager>
{
public voidPlaySound(string soundName)
{
Debug.Log("Playing: " + soundName);
}
}
// Use them anywhere!public classPlayer : MonoBehaviour
{
voidStart()
{
GameManager.Instance.StartGame();
AudioManager.Instance.PlaySound("PlayerSpawn");
}
}
Part 3: Designing a Game Manager
What Does a Game Manager Do?
🎮 Initialize game systems
⏸️ Handle game state (Start, Pause, Resume, End)
📊 Track game-wide data (score, time, level)
🔄 Coordinate between systems
💾 Manage scene transitions
For Blade Racer: Our Game Manager will initialize the race,
track time, handle pause/resume, and manage race completion.
Game Manager Implementation
using UnityEngine;
public classGameManager : Singleton<GameManager>
{
// Game stateprivate bool _isGameActive;
private float _gameTime;
public override voidAwake()
{
base.Awake(); // Call Singleton Awake
}
voidStart()
{
InitializeGame();
}
private voidInitializeGame()
{
Debug.Log("[GameManager] Initializing game...");
_isGameActive = false;
_gameTime = 0f;
}
public voidStartGame()
{
Debug.Log("[GameManager] Game Started!");
_isGameActive = true;
}
public voidPauseGame()
{
Debug.Log("[GameManager] Game Paused");
_isGameActive = false;
Time.timeScale = 0f;
}
public voidResumeGame()
{
Debug.Log("[GameManager] Game Resumed");
_isGameActive = true;
Time.timeScale = 1f;
}
voidUpdate()
{
if (_isGameActive)
{
_gameTime += Time.deltaTime;
}
}
}
Part 4: Testing the Singleton
Testing Strategy
Create Test Script: TestGameManager.cs
Call Instance: Verify global access works
Test Methods: Call StartGame(), PauseGame(), etc.
Multiple Scenes: Load 5 different scenes
Verify Persistence: Confirm same instance across scenes
Your Homework: Create 5 test scenes and verify the Game Manager
persists across all of them!
Test Script Example
using UnityEngine;
using UnityEngine.SceneManagement;
public classTestGameManager : MonoBehaviour
{
voidStart()
{
// Test global access
Debug.Log("Testing GameManager Singleton...");
// Start the game
GameManager.Instance.StartGame();
}
voidUpdate()
{
// Test pause/resumeif (Input.GetKeyDown(KeyCode.P))
{
GameManager.Instance.PauseGame();
}
if (Input.GetKeyDown(KeyCode.R))
{
GameManager.Instance.ResumeGame();
}
// Load next scene to test persistenceif (Input.GetKeyDown(KeyCode.N))
{
int nextScene = SceneManager.GetActiveScene().buildIndex + 1;
if (nextScene < SceneManager.sceneCountInBuildSettings)
{
SceneManager.LoadScene(nextScene);
}
}
}
}
Setting Up Test Scenes
Step-by-Step Setup
Create 5 new scenes: Scene1, Scene2, Scene3, Scene4, Scene5
Add all scenes to Build Settings (File → Build Settings)
In Scene1: Add GameObject with GameManager script
In each scene: Add different colored background (visual verification)
In each scene: Add GameObject with TestGameManager script
Add UI Text showing scene name (optional but helpful)
Visual Test: Different background colors help you see when scenes change!
Testing Checklist
✅ Verify These Behaviors
✓ GameManager.Instance is accessible from any script
✓ Only ONE instance exists (check Hierarchy)
✓ Instance persists across scene loads
✓ StartGame() logs correctly
✓ PauseGame() freezes time (press P)
✓ ResumeGame() restores time (press R)
✓ Scene loads work (press N for next scene)
Common Singleton Issues
❌ Common Problems
Multiple instances: Check Awake() logic
Null reference: Instance accessed before creation
Destroyed on load: Missing DontDestroyOnLoad()
Wrong instance: Not calling Destroy() on duplicates
✅ Solutions
Destroy duplicates: if (_instance != this)
Lazy instantiation: Create in getter if null
Persist properly: DontDestroyOnLoad(gameObject)
Debug logs: Verify Awake() calls
Debug Tip: Add Debug.Log() in Awake() to see when instances are created/destroyed!