Welcome back!
This is post number two of my introduction to programming patterns for games. This time I'm going to focus on a pattern known as singleton.
Introduction to the Singleton Pattern
The singleton pattern is used to create a public script in such a way that there is only ever one instance of it. This allows any other script to reference it, but ensures that it is always the same instance, as there can never be more than one in a game's scene. But be warned- there are very specific situations where using singletons is a good idea, but it can do more harm than good when used incorrectly; often, another solution should be used instead. I will cover some of the good and the bad of singletons in this post.
Example of a Singleton in Unity
To explain the usefulness of a singleton, let's get ourselves in the shoes of a developer. Imagine you're trying to have different characters play different sounds when triggered. Let's say this game has a player character that makes a sound when it jumps, there's a cat that meows, and enemies that go "grrr" normally, but when they're killed, go "eurgh". In order to play these different audio clips, we could have a script on each separate character that controls when it's own sound is played. This is valid, and games sometimes use this solution, but it does have drawbacks. For one, if each enemy script controls its own sound, and there are hundreds or thousands of enemies, this could lead to duplicate code and performance issues. It can also be trickier to debug any problems with the sounds being played if the control of audio is spread out across multiple scripts. Instead, let's see if we can have one manager script which contains our sound effects and is responsible for playing them when asked. One of the easiest ways to do this is to use a singleton. A basic singleton in the Unity game engine, using our audio example, looks something like this:
class AudioManager
{
public static AudioManager instance;
//Awake is called at the start of the game loop, see my post
//about the Game Loop pattern for more info!
void Awake() {
if (instance != null && instance !== this)
{
Destroy(gameObject);
}
else
{
instance = this;
//the line below isn't necessary for singletons, but I
//added it as it is often the case that we want our
//singletons to persist throughout all game scenes!
DontDestroyOnLoad(gameObject);
}
}
}
I'll break it down. We have a class called AudioManager. The first line in the code block creates a public static object of class AudioManager that I called instance, which is a common name you'll see for singletons, though not the only one. Being public and static means that other scripts can easily access our instance
through the Audio Manager class. Then, in Awake()
, we check if instance is equal to null. If you're not sure what Awake()
is, here is my post explaining it and the rest of Unity's game loop. If our instance isn't null and it isn't equal to the instance that we're in, that means that there is already an instance of our AudioManager out there. Since the point of a singleton is to only ever have one, we destroy the current one to avoid having a duplicate. If, however, instance is equal to null, then we create this script as our one instance of the Audio Manager. DontDestroyOnLoad
is a useful addition if we would like our script to persist across all scenes in the game, such as with a GameManager script. However, not all cases of the singleton pattern will need this part of the script.
An even better implementation of our singleton class would be to make our instance variable a property, like this:
public static AudioManager Instance {get; private set;}
This makes our Instance
a public property, so it's still accessible to other scripts, but having a private set ensures that only the AudioManager script can set itself as Instance, and no other script can change it on us.
By ensuring there is only ever one of its class in our game's scene, we avoid unpredictable behavior and bugs that come from scripts accessing different instances of our AudioManager script. And instead of hundreds or thousands of references to the same audio clips on each enemy script, we're now down to one single reference that can be used from any script by referencing AudioManager.instance
. In this way, any methods or variables available in our AudioManager
script become available through AudioManager.instance
. This way, we can store a reference to our 'eurgh' sound in AudioManager, and then when one of the enemies dies, we can simply do AudioManager.instance.PlaySound(AudioManager.instance.eurgh)
. Easy as ...eurgh! (Let's just assume that makes sense).
As you can see, a singleton is essentially a global variable of a class that there is only one instance of at any time. This is extremely useful for accessing scripts that you know you only ever want one of and a great way to solve a common problem that regularly comes up in game development: our scripts often need to interact with each other. Think about it- when your player uses a weapon to attack an enemy, you need to have a player script that tells a projectile script to go in the direction the player script aimed it, and if that projectile hits an enemy, you need that enemy script to know to take damage to its health and display some kind of visual effect or animation in response, or play a sound, sometimes using other scripts to do so. What a given object needs to do at any given time heavily depends on its interactions with other objects and their scripts. The problem is determining how to efficiently communicate with the correct script in order to update its behavior, and that is why singletons can be very useful.
This may make it tempting to use the singleton pattern for any such problem. But you must resist that temptation! Singletons should only be used in relatively rare circumstances, if at all. In fact, if you were to Google the singleton pattern, a lot of the results will be people telling you to not use them ever. I err on the side of use-with-caution rather than not at all. For example, I think the singleton pattern is a great choice for Manager scripts where you know you only ever want one of and that control a specific area of your game, such as AudioManagers, SaveManagers, and the like, but there are plenty out there who would disagree with even that use case.
The Problems With Using the Singleton Pattern
A good example of a case where the singleton pattern would not likely be a good choice is with a player's script. Often in games, many scripts need to interact with the player script in some way. With our handy-dandy singleton pattern, we could make that easy! However, if we were to make our player script into a singleton, our game becomes far less flexible. Once we've decided to make a class a singleton, it becomes very difficult and tedious to change that should we want to. If, down the road, we decided to implement a multiplayer feature, we would run into problems with our singleton class only allowing one instance of a player at a time. We would have to completely change how our player script interacts with all other scripts to even begin implementing multiplayer functionality, which would be a time-consuming task that could have been avoided. It takes many months or years to make a game- imagine having to redo how the main game object gets and shares its information with all other scripts. Yikes! This is why it's important to be sure there are no features you might like to add down the road that would require you to re-do your singletons before committing to using the pattern. It's worth noting that having multiple Player scripts would not be an ideal solution, either, and implementing some sort of player or multiplayer manager, which could itself then be a singleton, would be better in the case of multiplayer games.
Another more obvious example would be using a singleton to make any sort of projectile or enemy script. Sure, those scripts need to communicate with other scripts and a singleton would make that easy, but then you'd be limited to only ever having one projectile or one enemy script at a time! However, a ProjectileManager or EnemyManager script would potentially be suitable scripts for a singleton pattern, so long as you carefully consider your options and recognize the potential consequences first. Whether it's a good fit will be different for every game.
There are other reasons why you might not want to use a singleton pattern for a class, such as its globally accessible nature. While this may seem convenient and be a part of what attracts people to using singletons in the first place, in the long term it can make for some tedious debugging sessions. Global variables can be accessed by any script- if you are running into problems with your singleton instance not behaving correctly, you have to go through and find all references to that singleton throughout your code to figure out where the problem lies; there are more opportunities for things to go wrong when anyone can use it. This global availability also makes it difficult to use with games that take advantage of multi-threading capabilities. If scripts are accessing your singleton asynchronously, there may be issues where you are getting incorrect information or unexpected behavior, known as a race condition.
Another feature of singletons that seems great at first, that there is only ever one instance, can also be a problem for unit testing, when you would usually need to create multiple instances.
For more information about the disadvantages of singletons and ways to cope with them, see methodpoet's article about the subject, or this article that very much discourages the use of singletons.
To Sum it All Up...
The singleton pattern is best used only in circumstances where you are sure you will not need to change the functionality of that class at any point later on, and even then, it doesn't hurt to evaluate your needs and see if there's a better solution. As I said before, the best use cases and the ones you would most likely see the singleton pattern used with are for various manager scripts, such as our AudioManager. Another common one used is the GameManager, which often holds the state of the game, like whether it is playing or paused, or possibly stores information that must be used across different game scenes. Speaking of scenes, another very common singleton use-case is a SceneManager, which controls transitioning between scenes. However, every game is different, and the singleton pattern may not be appropriate for even these common uses, as it can have problems that stem from its global accessibility and single-instance nature. I highly recommend doing some further research on the singleton pattern to ensure you understand the pros and cons fully to determine if it is the right choice for you and the script you're working on. In the right circumstances, the singleton pattern can be a huge help- but when used incorrectly, it's more of a huge headache!
Key-Takeaways
- Singleton pattern is best used when you are certain that a class will not need to change its functionality later on.
- Common use cases for the singleton pattern include manager scripts like AudioManager, GameManager, or SceneManager.
- The global accessibility and single-instance nature of singletons can create problems if used incorrectly.
- Research the pros and cons of the singleton pattern before implementing it in your game development project.
Reach Out!
Let me know in the comments if you agree or disagree, and why! I'd love to hear your experiences of when the singleton pattern was more of a nightmare than helpful. Also, feedback on the structure of this article and how I can improve is always appreciated!
More Resources on the Singleton Pattern
If you're interested in reading more about the pattern, I recommend this resource. For more examples of the variety of implementations of the singleton pattern, check out C-Sharp Corner. You may also be interesting in this overview of the pattern as well. And for more info about using singletons in Unity, check out GameDevBeginner.
Top comments (0)