Unity Software Design – Singletons


First in a series of posts on Software Design for Unity. Read the introduction here.

A recent debate on Twitter (on #UnityTips Tuesday) got me thinking. The vast majority of coding articles and tutorials for Unity (both official and third-party) are pretty specific. You get plenty of “How to Make a Space Shooter” or “How to Make Hitscan Weapons”, and almost none of “Minimising Coupling Between Classes” or “Writing Maintainable Code”.[1]Also, most are videos, and that drives me crazy.[2]Also also, most third party ones feel the need to spend the first five minutes explaining how to open Unity, create a new gameobject, create a new script, open MonoDevelop, attach the script to the gameobject…

In other words, they’re teaching people how to use Unity, not how to design software[3]To be clear – I’m counting “How to write C#” under “How to use Unity”; the syntax of a language is entirely separate from how code is designed. Knowing how to write English doesn’t imply the ability to write a great novel!. There’s nothing wrong with that, but it can leave a lot of gaps.

On the other hand, there’s plenty of abstract software design stuff floating around the internet, but little on how to relate it back to Unity development. The result is that, for those without an existing background in programming, the “natural” way to learn Unity is somewhat like a jigsaw puzzle, putting together pieces. But it’s harder to learn general software design, with Unity as a framework.

Get to the point?

So it occurred to me that it could be useful to discuss such things as they apply to development in Unity. Emphasis on “discuss” – I don’t claim to be an expert!

I’m ad-libbing here – I don’t have much of a plan, but I hope to semi-regularly post about design concepts. Subjects might end up broad or narrow, or somewhat off the stated topic of “Software Design for Unity” [4]I’ll probably do a post on version control, and it’s debatable at best whether that’s “software design”…. Or this might just be the first and last post I write. Who knows.

I was going to start with the subject of the Twitter debate – factoring – but my opinions on it are a little controversial, so probably best to start off on a (somewhat) less contentious note[5]Although I’ll try to do a post on factoring later!. So without further ado, let’s talk Singletons.

Singletons

Admittedly, most Unity users have probably already heard of singletons. They’re a pretty common design pattern, as they’re easily implemented and powerful. But discussion is often more about “how” than “why”.

The power, and danger, of singletons comes from the fact that they’re essentially an implementation of global variables – that is, objects that can be accessed from anywhere in your code without being passed as an argument somewhere. The attraction of this is obvious – if you need something across your codebase, it’s nice to just have it there by default. The downsides are less obvious, but I’ll get to that.

Here’s an example of what a singleton might look like in C#.

public class MySingleton {
    private static MySingleton _instance;
    public static MySingleton instance {
        get {
            if (_instance == null){
                _instance = new MySingleton();
            }
            return _instance;
        }
    }
    // Other methods, fields, properties...
}

Note that this isn’t the only way to implement a singleton, and should your class derive from MonoBehaviour, there’ll be substantial differences. But that’s easily found elsewhere.

The primary feature here is public static access to an instance of the class, given by simply typing MySingleton.instance anywhere in your codebase. The secondary feature is lazy initialisation. No instance of the class exists to begin with; it’s automatically initialised the first time it’s accessed. This means it will always be there when you need it, and you don’t have to fit it into any startup/bootstrap procedure. Nice and atemporal.

And that’s basically it. Now anything you put in that class (and expose publicly) can be poked at from anywhere in your code. For example, your player character class could be a singleton, with a method DoDamage(), called by enemies when they hurt it. A really simple way of gluing your game together, without having to worry about passing data around in OnCollisionEnter and such like. Great!

Now here’s a bunch of reasons you probably shouldn’t do that.

With great power…

As I hinted at, the number one problem with singletons is power. While they look powerful, it’s actually a bit more like this – the entirety of your game has power over the singleton. The ability to easily modify global state is a really, really good source of bugs. If loads of bits of your code arbitrarily modify a value in a global object, and there’s a bug caused by the state of that object – how do you narrow down which particular modification, or combination of modifications, resulted in that state?

The ability to easily modify global state is a really, really good source of bugs

It’s also easy to get static state hanging over when you don’t expect it. Particularly in the example above, where the class doesn’t inherit from MonoBehaviour, if you’re not careful you might unintentionally have data that’s not cleared from a previous scene. Static data persists across scene loads!

Don’t underestimate these phenomena. These sort of bugs are really easy to hit, and amongst the hardest to diagnose and solve. Everyone’s great at writing bugs whether they think so or not, and to use singletons incautiously is to pour petrol on that fire. Because metaphors.

Single?

The other major problem is inherent in the concept. The singleton pattern is designed to ensure that there is only ever one of this object. So what happens, for instance, when you decide you want to implement local multiplayer? Suddenly, the fact that you reference Player.instance.DoDamage() everywhere in your code becomes your biggest nightmare. You’ve got to rethink everything – not only do you need to redesign Player to support multiple instances, you’ve got rewrite every time you’ve called DoDamage() to work out which player you’ve interacted with, get access to it and do damage again.

This is a specific case of the more general problem of tight coupling. Depending on how heavily it’s used, and how much other code depends on the specifics of the singleton’s implementation, refactoring said singleton can break your whole project. It’s something to keep in mind whether or not you end up using singletons, but it’s an ever-present problem in software engineering and practically impossible to fully solve, even with advanced techniques such as Dependency Injection[6]which I’ll hopefully cover in another post..

When are they useful?

While I’ve just listed a bunch of negatives, that’s not to say singletons are “bad”. As with any pattern, the key to using singletons is knowing when they’re appropriate and when they’re not. In small projects, particularly prototypes, singletons are fantastic; they’re often the easiest way to quickly try out functionality.

In a larger project, the cons above lead to a couple of conclusions – the best uses for singletons are when you want to read often, but actually interact seldom.

A great example is settings. If you know that only your settings menu is going to write data, you can fairly safely read that data anywhere. On the other hand, allowing enemies to arbitrarily affect the state of your Player instance is likely to cause problems.

If only your settings menu is going to write settings data, you can fairly safely read that data anywhere

Just as singletons aren’t inherently bad, that’s not to say they’re always the best way to implement settings! It’s just an example, and there are many other ways to achieve such things. The idea is just to consider what’s going to work best in your specific case. In general, my advice is to err on the side of action – do a bit of planning, but try something out sooner rather than later, and expect to rewrite and refactor. You’ll never foresee all the cases you’ll face over the course of a project. No one technique is a silver bullet, and it’s imperative to always stay open to different solutions.

Alternatives

So what if you’ve decided singletons aren’t going to work for you? Well… that’s a really broad question, and it’s difficult to answer on a blog post of finite length. Singletons are such a powerful concept that they can be used in almost any situation. The “settings” and “player” examples are entirely different – one is just passing around commonly-accessed data, while the other is used in lieu of a more robust (but more complex) system for the world interacting with the user’s behaviour.

For settings, one could use Service Locators, Dependency Injection or Inversion of Control, while for the player, concepts such as Observers and Events might be useful. All of these are complex topics that need their own posts! The intention for this first post is to take an already-commonly-used pattern, and discuss the pros and cons that some might be unaware of.

Conclusion

I’ll leave it there for today. There’s a wealth of information out there, and this is intended as a brief overview of things to consider when choosing techniques to use. Hopefully it’s useful to someone! Comments on that (or any other aspects) are very much welcomed – I’d love to hear some feedback on how to proceed with this series! Many thanks for reading.

Footnotes   [ + ]

1. Also, most are videos, and that drives me crazy.
2. Also also, most third party ones feel the need to spend the first five minutes explaining how to open Unity, create a new gameobject, create a new script, open MonoDevelop, attach the script to the gameobject…
3. To be clear – I’m counting “How to write C#” under “How to use Unity”; the syntax of a language is entirely separate from how code is designed. Knowing how to write English doesn’t imply the ability to write a great novel!
4. I’ll probably do a post on version control, and it’s debatable at best whether that’s “software design”…
5. Although I’ll try to do a post on factoring later!
6. which I’ll hopefully cover in another post.


4 Comments

  1. This article is interesting. You are right, tutorials on unity does not deal with this kind of concept. I would be happy to read more about that... Thank you. :)
  2. Great reading. Big Up for "how to use Unity, not how to design software" motto. Could replace Unity by anything else. I remember my XNA first steps. Samples were awful (single master class with 1000+ lines and so on...). A little bit embarrassed when I discovered Unity too. The Unity way is not really the standard one. Luckily we can leverage package like Zenject (IoC) to fit to SOLID principles and ensure low coupling and enforce good design rules.
  3. Pingback: http://www.sigtrapgames.com/unity-software-design-factoring/

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>