Unity Game Programming: Bad Singletons
If ( you only make short games || you don't care about clean code || you don't intend to make your code extensible)
return;
One of the biggest questions while programming in Unity is "How do I communicate between classes?" This is a question that has many answers but which answer should we follow, or which answer is the better one?
When I first started with Unity I didn't have any specific way to handle the communication. I used whatever method I felt like using. I didn't worry about performance. As a beginner, getting the code running was a challenge. But at a professional level, we can't use anything we want. We need to know the best way to do something in terms of performance, code readability and code organization.
This article is not about "Performance". Rather, its about code cleanliness and discipline. If you are looking for a quick solution just to get the code running and not worrying about extensibility or cleanliness, then this article is not for you. I always try to write clean and extensible code, and try not to make a mess. Sometimes I can accomplish that throughout the project, Sometimes due to a deadline or frequently changing requirements I fail to maintain clean code throughout the project. That's why I was looking for something that not only helps me to write clean code, but also enforces it. We'll talk about that later. But first a let's get into Singletons.
I used to love singletons. Most of us does. It gives a direct access to a script without any hassle to get the script. It makes communication simpler. But I'll tell you what the biggest problem that I face with Singleton. It's "TOO RIGID". Let me give you an example.
You are making a game where you need to make a player who will move around a map and collect coins. You make 2 singletons
- PlayerController.Instance : Controls the player
- ScoreManager.Instance: Controls the player score and notifies the UI
You are halfway through development when there's a requirement change and you need to make this a io game where there will be many cars and it's a competition of who can collect most coins.
What do you do? Go.
You see what I mean? Too Rigid. You can't move any further unless you break the Singleton and use direct public references of the scripts
Let's see another example where you want to take your player to a separate scene and test something out. Maybe you want to use the player as base and work on something different, or you want to test the player or some other thing. But when you press Play you will see errors popping up because of other singletons references you had in the PlayerController like
ScoreManager.Instance.IncreaseScore();
Now you bring in ScoreManager into the scene and press play. You see some other Singleton references that are absent and you start to bring in the other references too. If you are too dependent on Singletons, you will see that a few minutes later, you have rebuilt almost the entire previous scene just to avoid errors. This means your code has no modularity. You can't separate one module without bringing in others. That's really not a clean approach. It rejects Unit Testing among many other things.
Now, Image this scenario. You are creating a network of objects that are connected to each other using ropes. So naturally create a RopeConnectionManager who takes 2 positions as input and connects them by a rope. You make it a singleton for easy access. Now, requirements change as they always do. You need to create connections using a laser instead of rope. If this wasn't singleton, you could create an interface named IConnectionManager with a method
void Connect(Vector3 point1, Vector3 point2);
and then inherited this interface in 2 different systems RopeConnectionManager and LaserConnectionManager. You could reference IConnectionManager instead, and could swap out any of the inherited connection manager when you wanted. But instead you have Singleton making things TOO RIGID. RIP clean code.
These are a few reasons why I avoid Singletons, avoid rigidity and make way for extension.
Seasoned Game Producer | Hybrid Casual / Casual F2P games expert
5 å¹´Great findings Rakib. Another handy way I use to avoid missing component references is Dependency Injection pattern. All controller level components assign their instances to the only ONE singleton GameManager at their time of instantiation. This also satisfy Separation of Concerns (SoC) pattern. ????