What is the Singleton Pattern?
The singleton pattern is a design pattern that ensures only one instance of a class exists. It is used for objects that need to be accessed globally, such as game managers and sound managers.
Basic Concepts
- Single Instance: Only one instance of the class exists
- Global Access: Accessible from anywhere via
Instance - Auto Creation: Automatically creates instance if it doesn't exist
- Scene Persistence: Maintains across scene changes with
DontDestroyOnLoad()
1. Basic Singleton Structure
Basic Singleton Implementation
using UnityEngine;
public class GameManager : MonoBehaviour
{
// Singleton instance (static property)
public static GameManager Instance { get; private set; }
void Awake()
{
// Initialize singleton
InitializeSingleton();
}
private void InitializeSingleton()
{
if (Instance == null)
{
// Set as first instance
Instance = this;
// Don't destroy on scene change
DontDestroyOnLoad(gameObject);
}
else
{
// Destroy duplicate instance
Destroy(gameObject);
}
}
}
Usage Example
using UnityEngine;
public class FallingFireball : MonoBehaviour
{
void OnTriggerEnter2D(Collider2D other)
{
if (other.CompareTag("Player"))
{
// Access GameManager instance
if (GameManager.Instance != null)
{
GameManager.Instance.GameOver();
}
}
}
}
2. DontDestroyOnLoad() - Persist Across Scenes
DontDestroyOnLoad() prevents an object from being destroyed when the scene changes.
Usage
using UnityEngine;
public class GameManager : MonoBehaviour
{
public static GameManager Instance { get; private set; }
void Awake()
{
if (Instance == null)
{
Instance = this;
// Don't destroy on scene change
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
}
}
}
Precautions
- Prevent Duplicates: If multiple exist in the same scene, keep one and destroy the rest
- Persist Across Scenes: Used for game managers, sound managers, etc.
- Memory Management: Must manually destroy when no longer needed
3. Practical Examples
Example 1: GameManager Singleton
using UnityEngine;
public class GameManager : MonoBehaviour
{
#region Singleton
public static GameManager Instance { get; private set; }
#endregion
#region Events
public System.Action OnGameOverEvent;
public System.Action OnGameRestartEvent;
#endregion
#region Fields
[Header("Game State")]
[SerializeField] private bool isGameOver = false;
#endregion
#region Properties
public bool IsGameOver => isGameOver;
#endregion
#region Unity Lifecycle
void Awake()
{
InitializeSingleton();
}
#endregion
#region Initialization
private void InitializeSingleton()
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
}
}
#endregion
#region Game State Management
public void GameOver()
{
// Prevent duplicate execution
if (isGameOver)
{
return;
}
isGameOver = true;
Debug.Log("Game Over!");
// Handle game over
HandleGameOver();
}
private void HandleGameOver()
{
// Stop time
Time.timeScale = 0f;
// Trigger game over event
OnGameOverEvent?.Invoke();
}
public void RestartGame()
{
isGameOver = false;
Time.timeScale = 1f;
OnGameRestartEvent?.Invoke();
ReloadScene();
}
private void ReloadScene()
{
UnityEngine.SceneManagement.SceneManager.LoadScene(
UnityEngine.SceneManagement.SceneManager.GetActiveScene().name
);
}
#endregion
}
Example 2: Using from Other Scripts
using UnityEngine;
public class FallingFireball : MonoBehaviour
{
void OnTriggerEnter2D(Collider2D other)
{
if (other.CompareTag("Player"))
{
// Access singleton instance
if (GameManager.Instance != null)
{
GameManager.Instance.GameOver();
}
}
}
}
using UnityEngine;
public class SheepController : MonoBehaviour
{
void Update()
{
// Ignore input if game over
if (GameManager.Instance != null && GameManager.Instance.IsGameOver)
{
return;
}
// Handle input...
}
}
4. Access via Properties
Read-Only Property
using UnityEngine;
public class GameManager : MonoBehaviour
{
private static GameManager instance;
// Read-only property
public static GameManager Instance
{
get
{
// Auto-find if instance is null
if (instance == null)
{
instance = FindObjectOfType<GameManager>();
}
return instance;
}
private set
{
instance = value;
}
}
void Awake()
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
}
else if (Instance != this)
{
Destroy(gameObject);
}
}
}
5. Auto-Create Singleton
A singleton that automatically creates an instance if it doesn't exist.
Implementation
using UnityEngine;
public class GameManager : MonoBehaviour
{
private static GameManager instance;
public static GameManager Instance
{
get
{
if (instance == null)
{
// Find in scene
instance = FindObjectOfType<GameManager>();
// Create if not found
if (instance == null)
{
GameObject go = new GameObject("GameManager");
instance = go.AddComponent<GameManager>();
DontDestroyOnLoad(go);
}
}
return instance;
}
}
void Awake()
{
if (instance == null)
{
instance = this;
DontDestroyOnLoad(gameObject);
}
else if (instance != this)
{
Destroy(gameObject);
}
}
}
6. Precautions
1. Prevent Duplicate Instances
void Awake()
{
if (Instance == null)
{
Instance = this;
}
else if (Instance != this)
{
// Destroy duplicate instance
Destroy(gameObject);
}
}
2. Null Check
// ✅ Safe way
if (GameManager.Instance != null)
{
GameManager.Instance.GameOver();
}
// ❌ Dangerous way (may cause null reference error)
GameManager.Instance.GameOver();
3. Scene Transition Caution
// Using DontDestroyOnLoad persists across scene changes
// Must manually destroy when no longer needed
void OnDestroy()
{
if (Instance == this)
{
Instance = null;
}
}
4. Initialization Order
// Safe to initialize in Awake()
// Start() may not be called before other objects' Start()
void Awake()
{
InitializeSingleton();
}
7. Summary
Singleton Pattern Advantages
- Global Access: Accessible from anywhere via
Instance - Single Instance: Ensures only one instance exists
- Memory Efficient: Prevents unnecessary duplicate instances
Singleton Pattern Disadvantages
- Global State: Global state makes testing and debugging difficult
- Dependency: Can create strong coupling with other classes
- Multithreading: Requires caution in multithreaded environments
When to Use
- Game Manager: Game state management
- Sound Manager: Sound playback management
- Scene Manager: Scene transition management
- Settings Manager: Game settings management
Best Practices
- Initialize in Awake(): Initialize before other objects
- Null Check: Always check null before use
- Prevent Duplicates: Immediately destroy duplicate instances
- Use Appropriately: Only use when necessary
Practice Problems
-
Create a SoundManager singleton that can play sounds from anywhere.
-
Add a score variable to the GameManager singleton and allow other scripts to increase the score.
-
Create a settings manager using the singleton pattern that persists across scene changes.