What Is It?
A Singleton restricts a class to one instance and exposes it through a static access point, usually a property called Instance. Whoever asks first causes it to exist; everyone after that receives the very same object.
In Unity the twist is that most singletons are MonoBehaviours living on a GameObject. That means the "one instance" rule has to survive scene loads (DontDestroyOnLoad) and has to cope with a designer accidentally dropping a second copy into another scene — the newcomer must destroy itself.
A typical example is a GameManager that tracks score, pause state and the current level. The player, the HUD and every enemy can call GameManager.Instance.AddScore(10) without anyone wiring references in the Inspector.
When Is It Used?
Use it for genuinely global services where a second copy would be a bug: an audio mixer controller, a save system, a platform/achievements bridge, or a top-level game state manager.
It is handy in prototypes and game jams where speed of wiring matters more than testability — one static property beats dragging references across twenty prefabs.
Avoid it as a default way to share data. If many systems reach into Instance for everything, you get hidden dependencies and hard-to-test code; consider dependency injection or ScriptableObject services once the project grows.
Interactive Demo
Click the controls and watch the objects collaborate. The console mirrors what the C# code below would log.
Code
using UnityEngine;
namespace Patterns.Creational.Singleton
{
/// <summary>
/// A client that needs the GameManager. No Inspector reference:
/// it simply asks for GameManager.Instance.
/// </summary>
public class ScoreClient : MonoBehaviour
{
[SerializeField] private int pointsPerPickup = 10;
private void OnEnable()
{
GameManager.Instance.ScoreChanged += OnScoreChanged;
}
private void OnDisable()
{
// Instance may be null while the application is quitting.
var gm = GameManager.Instance;
if (gm != null) gm.ScoreChanged -= OnScoreChanged;
}
private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Pickup"))
{
GameManager.Instance.AddScore(pointsPerPickup);
Destroy(other.gameObject);
}
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.Escape))
GameManager.Instance.SetPaused(!GameManager.Instance.IsPaused);
if (Input.GetKeyDown(KeyCode.N))
GameManager.Instance.LoadLevel("Level2");
}
private void OnScoreChanged(int score) => Debug.Log($"Score: {score}");
}
}using UnityEngine;
namespace Patterns.Creational.Singleton
{
/// <summary>
/// Generic MonoBehaviour singleton. The first access creates the instance
/// lazily; any duplicate found later destroys itself in Awake.
/// </summary>
public abstract class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
private static T instance;
private static bool quitting;
public static T Instance
{
get
{
if (quitting) return null;
if (instance == null)
{
instance = FindFirstObjectByType<T>();
if (instance == null)
{
var go = new GameObject($"[{typeof(T).Name}]");
instance = go.AddComponent<T>();
}
}
return instance;
}
}
[SerializeField] private bool persistAcrossScenes = true;
protected virtual void Awake()
{
if (instance != null && instance != this)
{
Debug.Log($"Duplicate {typeof(T).Name} destroyed.");
Destroy(gameObject);
return;
}
instance = this as T;
if (persistAcrossScenes)
DontDestroyOnLoad(gameObject);
}
protected virtual void OnApplicationQuit() => quitting = true;
protected virtual void OnDestroy()
{
if (instance == this) instance = null;
}
}
}using System;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace Patterns.Creational.Singleton
{
/// <summary>
/// The one and only game manager. Holds state that must survive
/// scene loads, such as the score and the pause flag.
/// </summary>
public class GameManager : Singleton<GameManager>
{
public int Score { get; private set; }
public bool IsPaused { get; private set; }
public event Action<int> ScoreChanged;
protected override void Awake()
{
base.Awake();
Debug.Log($"GameManager ready (id {GetInstanceID()})");
}
public void AddScore(int amount)
{
Score += amount;
ScoreChanged?.Invoke(Score);
}
public void SetPaused(bool paused)
{
IsPaused = paused;
Time.timeScale = paused ? 0f : 1f;
}
public void LoadLevel(string sceneName)
{
// This object is marked DontDestroyOnLoad, so Score is kept.
SceneManager.LoadScene(sceneName);
}
}
}Advantages & Disadvantages
+ Advantages
- Single access point — no Inspector wiring, any script can find the service.
- Lazy creation means the object only exists once something actually needs it.
- With
DontDestroyOnLoadit naturally carries state (score, settings) across scenes.
− Disadvantages
- Creates hidden global dependencies; it is hard to see which classes rely on it.
- Unit testing is awkward because the static instance leaks between tests.
- Initialization order issues: calling
Instancefrom anotherAwakecan hit a half-initialised object.
Tips
- 01Use a generic
Singleton<T>base class so every manager gets the same duplicate-handling and persistence logic for free. - 02In
Awake, ifInstancealready exists and is notthis, callDestroy(gameObject)immediately — this handles the "manager placed in every scene" case. - 03Unity APIs are main-thread only, so a MonoBehaviour singleton rarely needs locking. For plain C# singletons accessed from worker threads, use
Lazy<T>for thread-safe lazy initialisation. - 04Clear the static reference in
OnDestroyand, with Enter Play Mode Options (no domain reload), reset statics via[RuntimeInitializeOnLoadMethod]. - 05Guard against
OnApplicationQuitordering: other objects destroyed after the manager may callInstanceand accidentally spawn a fresh one.