Simplifying Singleton Patterns in Unity with a Reusable Base Class
Singleton patterns are a fundamental part of many Unity projects, ensuring that a class has only one instance throughout the game. However, writing Singleton code repeatedly for different classes can be tedious and error-prone. In this blog post, we'll explore a streamlined approach to implementing Singletons in Unity using a reusable base class. This class not only manages the Singleton behavior but also allows for persistent Singletons across scenes, making your development process more efficient.
What is a Singleton?
A Singleton is a design pattern that ensures a class has only one instance and provides a global point of access to that instance. In Unity, this pattern is commonly used for managers like Game Managers, Audio Managers, and Input Managers—systems that should only have a single instance controlling their respective functionalities.
The Problem with Traditional Singleton Implementation
In Unity, a traditional Singleton might look like this:
public class GameManager : MonoBehaviour
{
public static GameManager Instance { get; private set; }
public int score;
private void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(gameObject);
}
public void AddScore(int points)
{
score += points;
Debug.Log("Score: " + score);
}
// Other game management code can be added here
}
While effective, this approach requires repeating the same code in every class where you want to implement a Singleton. This redundancy increases the risk of bugs and makes your code harder to maintain, especially in larger projects.
A Reusable Singleton Base Class
To solve this problem, we can create a reusable base class that handles the Singleton logic for us. The following code provides a robust and reusable Singleton implementation that can be easily extended for different classes.
using UnityEngine;
public abstract class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
private static T _instance;
private static object _lock = new object();
private static bool _isApplicationQuitting = false;
public static T Instance
{
get
{
if (_isApplicationQuitting)
{
Debug.LogWarning($"[Singleton] Instance '{typeof(T)}' already destroyed on application quit. Returning null.");
return null;
}
lock (_lock)
{
if (_instance == null)
{
_instance = (T)FindObjectOfType(typeof(T));
if (FindObjectsOfType(typeof(T)).Length > 1)
{
Debug.LogError($"[Singleton] Something went really wrong - there should never be more than 1 singleton! Reopening the scene might fix it.");
return _instance;
}
if (_instance == null)
{
GameObject singleton = new GameObject();
_instance = singleton.AddComponent<T>();
singleton.name = typeof(T).ToString() + " (Singleton)";
Debug.Log($"[Singleton] An instance of {typeof(T)} is needed in the scene, so '{singleton}' was created.");
}
else
{
Debug.Log($"[Singleton] Using instance already created: {_instance.gameObject.name}");
}
}
return _instance;
}
}
}
protected virtual void Awake()
{
if (_instance == null)
{
_instance = this as T;
if (this is IPersistentSingleton)
{
DontDestroyOnLoad(gameObject);
}
}
else if (_instance != this)
{
Destroy(gameObject);
}
}
private void OnEnable()
{
_isApplicationQuitting = false;
}
private void OnApplicationQuit()
{
_isApplicationQuitting = true;
}
private void OnDestroy()
{
_isApplicationQuitting = true;
}
}
public interface IPersistentSingleton
{
// Marker interface for singletons that should persist across scenes.
}
Breaking Down the Code
Let's go through the code step by step to understand how it works and why it's beneficial.
Singleton Class
The Singleton<T>
class is a generic abstract class where T
is the type of the Singleton. This allows any class that inherits from Singleton<T>
to become a Singleton automatically.
Private Static Fields:
_instance
: Holds the Singleton instance._lock
: Ensures thread safety when accessing the Singleton instance._isApplicationQuitting
: Tracks if the application is quitting to prevent creating new instances during shutdown.
Public Static Property (Instance):
This property manages the creation and access to the Singleton instance. If the application is quitting, it returns
null
to avoid issues.If the instance doesn't exist, it searches for an existing instance in the scene. If none is found, it creates a new GameObject with the required component.
Awake Method:
- This method is called when the script instance is being loaded. It sets up the Singleton instance or destroys any duplicates. If the class implements
IPersistentSingleton
, it makes the Singleton persistent across scenes usingDontDestroyOnLoad
.
- This method is called when the script instance is being loaded. It sets up the Singleton instance or destroys any duplicates. If the class implements
OnEnable and OnDestroy Methods:
OnEnable
: Resets_isApplicationQuitting
when the script is enabled.OnApplicationQuit
andOnDestroy
: Set_isApplicationQuitting
totrue
to prevent creating new instances during application quit.
IPersistentSingleton Interface
This is a marker interface used to indicate that a Singleton should persist across scene loads. It doesn't contain any methods but is used in the Awake
method to determine whether to call DontDestroyOnLoad
.
Making a Singleton with Persistence
To create a persistent Singleton, simply inherit from the Singleton<T>
class and implement the IPersistentSingleton
interface:
public class GameManager : Singleton<GameManager>, IPersistentSingleton
{
// Game management logic here
}
This ensures that the GameManager is a Singleton and persists across scene loads.
Making a Non-Persistent Singleton
If you don't need persistence across scenes, just inherit from Singleton<T>
without the IPersistentSingleton
interface:
public class UIManager : Singleton<UIManager>
{
// UI management logic here
}
This allows you to create Singletons that are unique within a scene but will be destroyed when the scene changes.
Use Cases for Singletons in Unity
Audio Manager
An Audio Manager controls all audio aspects, such as playing background music or sound effects. This is another common use case for a persistent Singleton:
public class AudioManager : Singleton<AudioManager>, IPersistentSingleton
{
public void PlaySound(string soundName)
{
// Code to play sound
}
// Other audio management code
}
Input Manager
An Input Manager centralizes all input handling, making it easier to manage player controls. This can be a non-persistent Singleton:
public class InputManager : Singleton<InputManager>
{
public bool IsJumpPressed()
{
return Input.GetButtonDown("Jump");
}
// Other input handling code
}
Game Manager
A Game Manager is responsible for the overall game state, such as handling game start, pause, and game-over states. It often needs to be persistent across scenes:
public class GameManager : Singleton<GameManager>, IPersistentSingleton
{
public int score;
public void AddScore(int points)
{
score += points;
Debug.Log("Score: " + score);
}
// Other game management code
}
Using the GameManager
Singleton
Accessing the GameManager
Singleton from any other script is straightforward. Here's an example of how you might use it in a PlayerController
script:
using UnityEngine;
public class PlayerController : MonoBehaviour
{
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
GameManager.Instance.AddScore(10); // Adds 10 points to the score
}
}
}
In this example, pressing the spacebar increases the score by 10 points, and the updated score is logged to the console.
Advantages of Using the Singleton Base Class
Reduces Boilerplate Code: By inheriting from the
Singleton<T>
class, you avoid writing the same Singleton code in multiple classes.Improves Maintainability: Centralizing the Singleton logic makes it easier to maintain and update.
Thread Safety: The
_lock
object ensures that the Singleton instance is accessed safely, even in multithreaded environments.Prevents Issues During Application Quit: The
_isApplicationQuitting
flag prevents the creation of new Singleton instances when the application is closing, avoiding potential issues.
Conclusion
The Singleton pattern is a powerful tool in Unity development, but implementing it repeatedly can lead to redundant code. By using a reusable base class, you can streamline your Singleton implementations, making your code cleaner and easier to maintain. Whether you need persistent or non-persistent Singletons, this approach provides a flexible and efficient solution.
What’s Next?
Try implementing this Singleton base class in your next Unity project. It not only saves time but also helps in writing more maintainable and less error-prone code. Happy coding!
Subscribe to my newsletter
Read articles from Srawan Paudel directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by