These are not mere pedestrian ideas. These are...
Zydeas

Why do unity devs use singletons so much?

C.L. Kerl,   June 20, 2020

One useful mechanism unity developers tend to use are what I call statically available controllers. However, most people call them Singletons. If you’re just starting out and reading lots of Unity tutorials, you’ll see many people using them. But why exactly? And why do some other developers say they’re bad to use?

To answer this, we first need to define what Singletons actually are in the context of programming in general. While they may seem like an advanced concept when you first hear about them, in reality singletons are just another way for programmers to trick their brains into thinking they aren’t using global variables. The idea is that you store your data inside a class, and then create some form of mechanism to ensure there’s only ever one instance of that class existing. This can lead to something like this c++ example from TutorialsPoint:

class Singleton {
   static Singleton *instance;
   int data;
 
   // Private constructor so that no objects can be created.
   Singleton() {
      data = 0;
   }

   public:
   static Singleton *getInstance() {
      if (!instance)
      instance = new Singleton;
      return instance;
   }

   int getData() {
      return this -> data;
   }

   void setData(int data) {
      this -> data = data;
   }
};

And then, the supposed usage

//Initialize pointer to zero so that it can be initialized in first call to getInstance
Singleton *Singleton::instance = 0;

int main(){
   Singleton *s = s->getInstance();
   cout << s->getData() << endl;
   s->setData(100);
   cout << s->getData() << endl;
   return 0;
}

You could technically do away with the getters & setters to make this a lot simpler while still being considered a singleton, as they’re pointless encapsulation.

If you’re unfamiliar with c++ or programming in general, the above example may seem complex, but all it’s really doing is defining a class with a static field of its own type (instance). Then it has a method (getInstance()) that returns the instance variable, creating a new instance if none exists. This way, there will only ever be one (single) instance of the class at a time (hence the name singleton).

This is just an example of the concept itself of course. Let’s find a better use-case, like, for instance, a simple class that stores the savable data in a game:

class GameData {
   // Boilerplate
   static GameData* Instance;
   GameData() {}
public:
   static GameData* get() {
      if(!Instance) Instance = new GameData;
      return Instance;
   }
   
   // Actual relevant code
   int plr_score = 0;
   int plr_health = 100;
   int plr_kills = 0, plr_deaths = 0; // Maybe tracking these is important
   
   bool        enemies_killed[100] = {}; // Maybe enemies stay dead in our game 
   LevelState  level_states[20] = {};    // Maybe we need to track the states of our levels in some way.
   
   // And of course, our singleton should have procedures related to the game's save data, right?
   void OnStartGame() {
      // ...
   }
   
   void SaveGame() {
      // ...
   }
   
   void LoadGame() {
      // ...
   }
}

Then, when using it in the actual game, you’d be able to access the variables in a similar fashion:

GameData* data = data->get();
data->plr_score += 50;
data->plr_kills++;
data->enemies_killed[38] = true;
data->SaveGame();

This all seems well and good. It’s understandable why so many are drawn to the pattern. It uses OO-style encapsulation, and it’s not that hard to teach to inexperienced programmers. The benefit however, is illusory. This bit of code would have the same mechanical effect if you’d just typed all of it at global scope:


int plr_score = 0, plr_health = 100, plr_kills = 0, plr_deaths = 0;

bool        enemies_killed[100];
LevelState  level_states[20];

void OnStartGame()   {/* ... */}
void SaveGame()      {/* ... */}
void LoadGame()      {/* ... */}

This and the singleton are functionally equivalent. Both the singleton’s members and the global variables can only have one instance, and you can access the variables from anywhere in the program. The layout of the data in memory will still be the same, and the way you access the data would be fairly straightforward:

plr_score += 50;
plr_kills++;
enemies_killed[38] = true;
SaveGame();

Am I suggesting you do this exact thing in your codebase? I don’t know, probably not. That’s really up to you though. I’ll be the first to admit that having all the variables just sit there at global scope global variable isn’t the best in terms of organization. It’d probably be a good idea to wrap that data in a struct:

struct GameData {
   int plr_score = 0, plr_health = 100, plr_kills = 0, plr_deaths = 0;

   bool        enemies_killed[100];
   LevelState  level_states[20];
};

GameData Data = {};

The point here isn’t “You should use X pattern instead of Y pattern because of Z reasons”. Rather that programmers (seemingly) don’t use singletons because they provide an actual tangible benefit, but because they want to avoid using global variables, even when they’re warranted. In most games, there is only ever one copy of the data that needs to be tracked for saving. Any other data (player position, for example) is usually gathered at the moment the game gets saved. So why wrap that data inside a class with an entire extra mechanism that ensures only one instance of that class, while making working with the data itself harder? There really isn’t much of an actual rational for it.

What was this article supposed to be about? Oh, right, Unity.

Unity presents a couple fun problems when we try to work with global data. See, in a class-based language like Java or C#, you’d need to wrap everything in a static class:

public static class GameData {
   public static int plr_score   = 0;
   public static int plr_health  = 100;
   public static int plr_kills   = 0;
   public static int plr_deaths  = 0;
   
   public static bool[]       enemies_killed = new bool[100];
   public static LevelState[] level_states = new LevelState[20];
   
   public static void OnStartGame()   {/* ... */}
   public static void SaveGame()      {/* ... */}
   public static void LoadGame()      {/* ... */}
}

// Usage:
GameData.plr_score += 50;
GameData.plr_kills++;
GameData.enemies_killed[38] = true;
GameData.SaveGame();

Okay, a bit more verbose than just the global version, but still totally manageable. If you try to do this in Unity however, you’ll quickly run into the following:

  • There’s no inspector support whatsoever for this. This is one of the more powerful features of Unity, and is a great aid with debugging. At the bare minimum, you’d need to make a custom editor window that let you edit all the values of that class. Ideally you’d build that editor using reflection, so you wouldn’t need to manually draw the widgets for each field.
  • How do you actually work with GameObjects with this thing? Yes, you could instantiate stuff just fine, but how do you know when to do so? I guess you could probably hook into some editor callback somewhere that tells you when the game starts, or when a scene is loaded… And you miss out on just being able to manually set up GameObject references in the inspector, which is really useful in some cases. If you had something just sitting in a scene when the game starts, and you needed to get a reference to it, how would you easily do that?

I think these are the two biggest reasons Singletons are so common in the Unity community. If you set up a generic class like this:

public abstract class Controller<T> : MonoBehaviour
        where T : MonoBehaviour
{
    public static T Live;
    public static bool Exists => Live != null;

    protected virtual void OnEnable() => Live = GetComponent<T>();
    protected virtual void OnDisable() => Live = null;
}

And implement it like this:

public class GameData : Controller<Game> { 
   public int plr_score   = 0;
   public int plr_health  = 100;
   public int plr_kills   = 0;
   public int plr_deaths  = 0;
   
   public bool[]       enemies_killed = new bool[100];
   public LevelState[] level_states = new LevelState[20];
   
   public void OnStartGame()   {/* ... */}
   public void SaveGame()      {/* ... */}
   public void LoadGame()      {/* ... */}
}

// Usage:
GameData.Live.plr_score += 50;
GameData.Live.plr_kills++;
GameData.Live.enemies_killed[38] = true;
GameData.Live.SaveGame();

It solves both issues fairly cleanly. You can easily have it stuck on a GameObject and inspect it at will. And you could set up any GameObject references you needed right there in the inspector, and you have a root transform right there to spawn new ones.

However, there is one issue I haven’t mentioned yet. How do you make sure everything is initialized properly? That’ll have to wait till the next post