Singletons & more

The Limitations of Static Variables

To start with the basics, a static variable looks like this:

public class SomeClassObject : MonoBehaviour {
    public static bool someStaticBoolFlag = false;
    public bool someLocalBool = true;
}

With this code, you could use SomeClassObject.someStaticBoolFlag to access that variable from anywhere. Moreover, this points to a single, shared bool that exists exactly once in your entire game. On the other hand, every copy of SomeClassObject will have its own someLocalBool, and someLocalBool is visible in the Inspector.

What if you want a variable that is visible and editable in the inspector, but you only want there to be one of them, and you want it accessible from everywhere? You might be looking for a singleton. To put it simply, a singleton is a static reference to an object of that same type, and generally, there is only one of them in the scene. In its simplest form, it looks something like this:

public class SomeClassObject : MonoBehaviour {
    public static SomeClassObject instance;
    public bool someLocalBool = true;

    void Awake() {
        instance = this;
    }
}

Now, in any script file, you can use the code SomeClassObject.instance.someLocalBool = false; to access that local bool on the object, and you can modify this bool in the object's inspector, as well. Success!

Words of Warning

"That seems amazing and really simple," I head you saying through the screen. "Why would I not want to use these everywhere?" Well, you know what, you might want to. But the truth is that you shouldn't. Many people are hesitant to teach newbies about singletons because they're like code candy - they're simple, they're quick, they're easy, but in the long run, overuse of singletons can be bad for your project.

The critical weakness of a singleton is that there can be only one. And the trap of the singleton is that, occasionally, design changes in the game may lead to there suddenly needing to be more than one of something. Probably the most common example of this is the player character. There's only going to be one of those, right? So you make the PlayerCharacter into a singleton, and happily access it as PlayerCharacter.instance everywhere in your code. And it works great! ...until you decide 6 months down the line that you want to add multiplayer, and then... whoops. Suddenly your singleton isn't quite so single anymore, and now you have to spend hours or days untangling your singleton references so that they access the right PlayerCharacter.

Since this trap is largely psychological in nature, perhaps a psychological solution is called for. When I create a singleton, I generally attempt to name it appropriately. For example, rather than PlayerCharacter.instance, perhaps PlayerCharacter.userControlled, or PlayerCharacter.focused. Then in another function - maybe an OnCollisionEnter of your enemy projectile - when you might be tempted to use PlayerCharacter.instance, seeing that the name is actually PlayerCharacter.focused should remind you that the focused PlayerCharacter is not fundamentally the same as the PlayerCharacter that'd be receiving damage, and maybe you should go ahead and use collision.gameObject.GetComponent<PlayerCharacter>() just to be safe.

You can also use what you might call a "weak singleton" - a class where there is a main copy of it, but this is not necessarily the only copy. A perfect example of this is Unity's own Camera.main - where it finds the Camera object that is tagged with the MainCamera tag. You can have other cameras rendering stuff, but generally Camera.main refers to the one that renders the majority of the game view. I like to use a bool/checkbox to indicate which one is the "main" one rather than tags, but the concept is the same.

Improving on the Code

Now that the warnings and caveats are out of the way, let's revisit our original singleton code. That code is quick and easy and efficient, but it's not very durable.

For one, you should avoid public members unless you actually want its value to be modifiable anywhere. In our case, it makes no sense to allow other scripts to change the value of .instance - there's only one object it can possibly point to! Let's use a property to control access, without sacrificing any of our convenience:

public class SomeClassObject : MonoBehaviour {
    public static SomeClassObject instance {
        get {
            return _instance;
        }
    }
    private static SomeClassObject _instance;

    void Awake() {
        _instance = this;
    }
}

Not bad. But there's still more we can improve. One thing about Unity's execution order of scripts is that, if another script were to try to access SomeClassObject.instance in its own Awake(), that might execute before our Awake() does. Additionally, what if we forget to put one of these objects in the scene? We could demand that every script do its own null check before accessing .instance, but that's pretty tedious. If there's a version of this with some sensible default values that lives in a Resources folder as a prefab, we can Instantiate that object on demand when it's accessed for the first time!

public class SomeClassObject : MonoBehaviour {
    public static SomeClassObject instance {
        get {
            if (_instance == null) _instance = FindObjectOfType<SomeClassObject>();
            if (_instance == null) {
                GameObject spawned = (GameObject)Instantiate(Resources.Load("DefaultSomeClassObject") );
                //the spawned object's Awake() will run at this point, setting _instance to itself
            }
            return _instance;
        }
    }
    private static SomeClassObject _instance;

    void Awake() {
        _instance = this;
    }
}

And there you have it - a robust, reliable singleton that you can customize as you see fit.

Multiton (maintained static list)

What if you want global access to something, but it's not the only one of its kind? What if you need a list of all the enemies in your scene? Or all the powerups? You could use FindObjectsOfType<Enemy>(), but doing this every frame is really slow (possibly even slower than GameObject.Find). A better option is to maintain a list of the type that you know you're gonna need, and have it ready on demand.

public class Character : MonoBehaviour {
    public static Character[] all {
        get {
            if (allArrayCached == null) allArrayCached = _all.ToArray();
            return allArrayCached;
        }
    }
    private static Character[] allArrayCached = null;
    private static List<Character> _all = new List<Character>();
    void OnEnable() {
        all.Add(this);
        allArrayCached = null;
    }
    void OnDisable() {
        all.Remove(this);
        allArrayCached = null;
    }
}

//in any other script file, anywhere
foreach (Character c in Character.all) {
    Debug.Log("Character named "+c.gameObject.name+" is at "+c.transform.position);
}

This is a little more complex than the singleton, but still pretty straightforward. In addition to the protections as provided for on the singleton implementation, this code returns the list as a builtin array, and also provides some basic caching.

results for ""

    No results matching ""