Singleton Pattern
The Singleton pattern is a widely used design pattern for ensuring that a class has only one instance throughout the lifetime of the application. This pattern provides a way to control access to shared, global resources without the need for multiple instances, which can help avoid unexpected behavior or performance issues due to redundant processing.
When to Implement
In game development a common use case for a singleton is in the creation of a single instance of a manager or controller class (e.g. AudioManager, GameManager, or PlayerControler) where creating multiple instances could lead to conflicts or inefficiencies. By implementing the Singleton pattern, you can ensure that no matter how many times the object is requested or instantiated, only one instance will exist. Use of Singleton Pattern in Unity
Example of how the Singleton pattern can be used inside a class:
public class GameManager : MonoBehaviour
{
//Creates the global access to the instance
public static GameManager Instance { get; private set; }
void Awake()
{
// If no GameManager exists, assign this instance and mark it as persistent across scenes
if (Instance == null)
{
Instance = this; //make this the game manager
DontDestroyOnLoad(gameObject); // Prevents the instance from being destroyed when changing scenes
}
// Else if a GameManager already exists and it's not this one, destroy this instance to maintain the Singleton
else
{
Destroy(gameObject); //if there is a game manager destroy this object
}//end if (Instance == null)
}//end Awake()
//other game manager behaviors goes here
}
Singletons in programming can be likened to the Highlanders from the 1980s cult classic film and lesser-known TV series, Highlander. The story revolves around immortal beings who exist among humans, each with their own unique powers and abilities. Central to the narrative is the belief that "There can be only one." This implies that these immortals must engage in fierce battles, culminating in the beheading of their rivals until only one remains standing. The last immortal standing is said to receive The Prize, a mysterious reward that grants them ultimate power.
In a similar vein, Singletons represent the "immortal" objects in your game. Just as the Highlanders cannot coexist peacefully, your Singleton class ensures that only one instance of the class survives throughout the entire game. Any additional instances created are destroyed, just as an immortal would face beheading if they crossed paths with another.
So, when you're implementing a Singleton, think of it as a programming immortal, living on through various scenes and game states, while ensuring that any additional instances meet their inevitable end! After all, in the realm of coding, there can be only one.
Singleton Base Class
Since there are many instances that require Singleton behavior, rewriting the behavior can be time consuming, while copying and pasting the Singleton code can lead to redundancy and potential bugs. To increase efficiency a base Singleton class that any class can inherit from. This simplifies implementation and makes the codebase more maintainable and scalable.
By using a base class for the Singleton pattern, you:
- Reduce Code Duplication: Avoid rewriting the Singleton logic for each class.
- Improve Maintainability: If you need to change the Singleton logic, you only modify the base class.
- Enhance Readability: By centralizing the Singleton logic, the inherited classes remain clean and focused on their specific responsibilities.
Example Singleton base class with detailed comments:
// Generic Singleton base class that any MonoBehaviour can inherit from
public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
// Static instance that holds the reference to the Singleton
private static T _instance;
// Public property to access the Singleton instance
public static T Instance
{
get
{
return _instance;
}
}
// Unity's Awake method, called when the script instance is being loaded
private void Awake()
{
// Check for singleton duplication
CheckForSingleton();
}//end Awake()
// Ensures that only one instance of the Singleton exists
void CheckForSingleton()
{
// If no instance exists, assign this instance and mark it as persistent across scenes
if (_instance == null)
{
_instance = this as T;
DontDestroyOnLoad(gameObject); // Prevents the instance from being destroyed when changing scenes
}
// If an instance already exists and it's not this one, destroy the new instance to maintain the Singleton
else if (_instance != this)
{
Destroy(gameObject); // Ensure that only the original Singleton instance persists
}//end if (_instance == null)
// Log the current instance for debugging purposes
Debug.Log(_instance);
}//end CheckForSingleton()
}
How It Works:
- Generic Singleton Class: The class is declared as
Singleton<T>
, whereT
is a placeholder for the type that will inherit from this class. This makes the class reusable across different types of MonoBehaviours, like GameManager, AudioManager, etc. - Static Instance: The
_instance
field is static, ensuring that it holds the single instance across all scenes. TheInstance
property provides global access to this instance. - Awake Method: In Unity, the
Awake
method is called when the script is loaded. Here, it's used to trigger theCheckForSingleton
method, which ensures only one instance of the class exists. - CheckForSingleton Method: This method checks whether the
_instance
is already set.- If it's not, it assigns the current object as the Singleton and marks it as
DontDestroyOnLoad
, ensuring it persists across scene changes. - If an instance already exists and it's not the current object, the new instance is destroyed to prevent multiple instances.
- DontDestroyOnLoad: This ensures the Singleton instance is not destroyed when the scene changes, keeping the reference intact for the entire game session.
- If it's not, it assigns the current object as the Singleton and marks it as
- Debug Logging: The
Debug.Log(_instance)
statement logs the current instance, which can be useful for verifying the Singleton behavior during development.
In the example for implementing a singleton pattern to the GameManager class a simple if/else
statement was used, in which if (Instance == null)
then this object became the instance, else
if an instance already exists, the game object is deleted. This approach is simple but there are edge cases (like reloading scenes, object duplication, or special conditions during initialization) where the same instance might be re-registered as the Singleton.
The Singleton<T>
base class, makes use of an else if (_instance != this)
, which doesn't just check if the instance is not is null, but checks specifically if this
object is the not the instance. This method is more robust and better suited for scenarios where Unity might reinstantiate or reload objects unexpectedly.