Singleton Pattern

Learn how to create a globally accessible single instance using the singleton pattern in Unity.

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

  1. Global Access: Accessible from anywhere via Instance
  2. Single Instance: Ensures only one instance exists
  3. Memory Efficient: Prevents unnecessary duplicate instances

Singleton Pattern Disadvantages

  1. Global State: Global state makes testing and debugging difficult
  2. Dependency: Can create strong coupling with other classes
  3. 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

  1. Initialize in Awake(): Initialize before other objects
  2. Null Check: Always check null before use
  3. Prevent Duplicates: Immediately destroy duplicate instances
  4. Use Appropriately: Only use when necessary

Practice Problems

  1. Create a SoundManager singleton that can play sounds from anywhere.

  2. Add a score variable to the GameManager singleton and allow other scripts to increase the score.

  3. Create a settings manager using the singleton pattern that persists across scene changes.