【Unity】Implementing the Singleton Pattern Correctly in Unity: Best Practices and Pitfalls

Created: 2025-12-10

Learn the correct implementation of the Singleton pattern in Unity and important caveats. Includes how to create a MonoSingleton base class.

Overview

When developing games in Unity, you inevitably encounter situations where you need "a single manager accessible from anywhere." For example, a class managing overall game score, a class controlling BGM and sound effects, or a class holding game state (paused, game over, etc.).

Finding these manager classes with GameObject.Find() every time or manually setting references from the Inspector is very tedious. Also, accidentally placing multiple instances of the same manager class across scenes can cause unexpected bugs.

This article explains the basic concepts of the Singleton pattern and how to implement it safely in Unity.

Basic Concepts of the Singleton Pattern

The Singleton pattern is a mechanism that guarantees a class's instance is unique throughout the application and provides a global access point to that instance.

To achieve this in a regular C# class, the following elements are typically needed:

  1. Static instance variable: A static variable to hold the class itself.
  2. Private constructor: To prevent external instantiation via new.
  3. Static access property: A static property to return the unique instance.

Implementing MonoBehaviour Singleton in Unity

When implementing the Singleton pattern in Unity, many manager classes need to inherit from MonoBehaviour. This is to use Unity's lifecycle methods like Awake and Update, or to configure from the Inspector.

Here we introduce a method to create a versatile base class using Generics that can make any class a Singleton. This method is considered one of the best practices for Singleton implementation in Unity.

Generic MonoSingleton Class

Save the following code as MonoSingleton.cs.

using UnityEngine;

// Constraint that T must be a class inheriting MonoBehaviour
public abstract class MonoSingleton<T> : MonoBehaviour where T : MonoSingleton<T>
{
    // Static variable holding the unique instance
    private static T instance;

    // Static property for external access
    public static T Instance
    {
        get
        {
            // If instance doesn't exist yet
            if (instance == null)
            {
                // Find T type object in scene
                instance = (T)FindObjectOfType(typeof(T));

                // If still not found, create new GameObject and attach
                if (instance == null)
                {
                    GameObject singletonObject = new GameObject();
                    instance = singletonObject.AddComponent<T>();
                    singletonObject.name = typeof(T).ToString() + " (Singleton)";
                }
            }
            return instance;
        }
    }

    // Called immediately after instance is created
    protected virtual void Awake()
    {
        // If instance already exists (duplicate prevention)
        if (instance != null && instance != this)
        {
            // Destroy self to prevent duplicates
            Debug.LogWarning($"[Singleton] Instance already exists, destroying {typeof(T).Name}.");
            Destroy(gameObject);
            return;
        }

        // Set self as the unique instance
        instance = (T)this;

        // Persist object across scene transitions
        // Only apply at runtime due to editor behavior considerations
        if (Application.isPlaying)
        {
            DontDestroyOnLoad(gameObject);
        }

        // Method to delegate initialization to child classes
        OnInitialize();
    }

    // Clear static reference on destruction
    protected virtual void OnDestroy()
    {
        if (instance == this)
        {
            instance = null;
        }
    }

    // Method for child classes to override for initialization
    protected virtual void OnInitialize() { }
}

Usage (Example: Sound Manager)

Next, create an actual manager class by inheriting this base class.

using UnityEngine;

// Inherit MonoSingleton<SoundManager>
public class SoundManager : MonoSingleton<SoundManager>
{
    // Public methods for external access
    public void PlayBGM(AudioClip clip)
    {
        Debug.Log($"Playing BGM: {clip.name}");
        // Actual BGM playback logic...
    }

    public void PlaySE(AudioClip clip)
    {
        Debug.Log($"Playing sound effect: {clip.name}");
        // Actual SE playback logic...
    }

    // Override initialization process
    protected override void OnInitialize()
    {
        // Processes to execute only once at initialization, like loading sound settings
        Debug.Log("SoundManager initialization complete.");
    }
}

Access Method

Now you can easily access it from anywhere in the game, regardless of scene.

// From some other script
public class PlayerController : MonoBehaviour
{
    public AudioClip jumpSound;

    void Jump()
    {
        // Access the unique instance and call method
        SoundManager.Instance.PlaySE(jumpSound);
    }
}

Key Point: The MonoSingleton<T> class's Instance property automatically searches the scene if the instance doesn't exist, and creates a new GameObject to attach if still not found. This prevents errors even if you forget to manually place it in the scene, increasing safety.

3 Common Pitfalls for Beginners and Solutions

While the Singleton pattern is convenient, Unity-specific lifecycle issues can trap beginners.

1. Duplicate Instance Creation During Scene Transitions

The most common problem is that a Singleton class using DontDestroyOnLoad gets duplicated when moving between scenes.

  • Mistake Example: SoundManager is created in Scene A and persisted with DontDestroyOnLoad. When Scene B loads, if Scene B also has a SoundManager prefab placed, a second instance is created.
  • Solution: As in the MonoSingleton<T> implementation above, check if instance already exists in the Awake method, and if so, immediately destroy self with Destroy(gameObject);. This guarantees only one instance always exists.

2. Reference Order Issues (Script Execution Order)

In Unity, script execution order (when Awake and Start are called) is basically undefined. When trying to access a Singleton's Instance in some script's Awake, that Singleton's Awake may not have executed yet, and instance may be null.

  • Solutions:
    1. By adopting logic that creates instance within the Instance property (like the code above), the instance is guaranteed to exist when accessed.
    2. In Script Execution Order settings (Edit -> Project Settings -> Script Execution Order), set Singleton classes to execute earlier than other scripts. This guarantees initialization completes before other scripts access them.

3. Forgetting to Clear Static Reference in OnDestroy

During scene transitions or game exit, Singleton objects may be destroyed. If you forget to clear the static instance variable at this time, a reference to an already destroyed object remains, potentially causing unexpected errors (MissingReferenceException etc.) on next access.

  • Solution: Implement OnDestroy method in the MonoSingleton<T> class to clear the static reference with instance = null; when the object is destroyed.
    // Clear static reference on destruction
    protected virtual void OnDestroy()
    {
        if (instance == this)
        {
            instance = null;
        }
    }

Singleton Pattern Caveats (Disadvantages)

While the Singleton pattern is very powerful, overuse can harm project health. Even small-scale developers should know these two main disadvantages:

  1. Tight Coupling (Strong Dependencies): Since Singletons provide global access points, any class can easily access them. As a result, many classes become dependent on specific Singleton classes, increasing code coupling. Tight coupling increases the risk of unexpected bugs when modifying parts of the code.

  2. Testing Difficulty: When performing unit tests, Singleton classes hold global state, making it difficult to maintain test independence as state is shared between tests. Also, replacing Singleton classes with mocks (fake objects) is difficult, reducing test flexibility.

Summary

This article explained the correct implementation and caveats of the Singleton pattern in Unity development. Proper use of this pattern keeps your game's management structure simple.

Key takeaways:

  • The Singleton pattern is a design pattern that guarantees a single instance and provides global access.
  • In Unity, creating a generic base class inheriting MonoBehaviour is the best practice for safe and versatile Singleton implementation.
  • Performing duplicate instance check and destruction in the Awake method prevents bugs during scene transitions.
  • Applying DontDestroyOnLoad(gameObject); persists objects across scenes.
  • Singleton overuse causes tight coupling, harming code maintainability and testability, so limiting use to global managers is important.

Use this knowledge to make your Unity projects more efficient and robust.