There’s a trend in some of the modern programming languages (Kotlin is the best example) of what’s called a “Lazy” implementation of a value. The way I could describe this is that lazy is a feature where the value of a given thing isn’t known until runtime, but you don’t want to calculate that value until it’s needed… when it is calculated, it’s then cached, so it’s only calculated once…
Why is this important? Ben mentioned in one of the videos about a Singleton without an Instance, and his method is acutally quite elegant when you want to determine if there are other instances of your class, and then commit hari kari if there’s another class… but sometimes, an actual static instance variable is needed, even if the implementation of same is quite messy…
public Class myClass: MonoBehavior {
public static myClass instance; // THIS IS PUBLIC, ANYBODY CAN MODIFY IT!!!!
This leaves you with a public instance that any other class can muck about with… and I have an explicit
hatred of any feature that leaves open the possibility of bypassing encapsulation. (Sometimes you have no choice, but generally the actual underlying backing data of any property should as limiited in scope as possible. So… I’ve generally tended to avoid the instance method of Singletons because of this scope violation.
In many of my projects, I often find myself putting code like this in my projects:
public Class myClass: MonoBehavior {
OtherClass otherClass;
void Start{
otherClass=GameObject.FindObjectOfType<OtherClass>();
}
This, of course, grabs an explicit reference to the first object found that is of OtherClass, and caches it… which is fine in most cases… then I ran into a special case and it made me rethink things…
I have a ship travels from level to level, and it wants to talk to the scoreboard and healthbar on each of those levels as it goes along… well… in the Start class, I cached the value of the scoreboard and healthbar, but they don’t exist anymore when I go to the next level. The next level has its own scoreboard and healthbar…
So… I devised a caching test:
Scoreboard _scoreboard;
Scoreboard scoreboard{
get{
if(!_scoreboard){
_scoreboard=GameObject.FindObjectOfType<ScoreBoard>();
}
return _scoreboard;
}
This is a form of Lazy initialization. Instead of grabbing the link to the ScoreBoard in the Start() function, it’s now not initialized until it’s actually needed… (in this case, my score function). It’s called automatically the first time it’s needed… Thanks to C#'s memory management, if the scoreboard is destroyed (because I changed levels and my ship didn’t), when you access scoreboard again, it fails the test for a valid scoreboard, and the player finds the new scoreboard.
I took this a step further, as I started implementing other features that could modify the score… it turned out that an Instance variable was needed after all, or I would need to place the above code in every class that used a Scoreboard (and every instance of that class would have to run it, and cache it…).
So… I realized it would be better to use the .instance, but I also applied the same principle as above to caching the instance…
public class MyClass: MonoBehavior {
static MyClass _instance;
public static MyClass instance {
get{ //This is a read only property... No SETTER... meaning no other class can touch it.
if(!_instance){
_instance=GameObject.FindObjectOfType<MyClass>();
}
return_instance;
}
}
So once again, the first time something calls MyClass.instance, the _instance backing field is cached, but not UNTIL this happens. (That’s the “lazy initialization”). Garbage collection should change _instance to null when the actual instance is destroyed, but there’s nothing wrong (I’d say more likely, it’s a best practice) to explicitly clear _instance on destruction of the object…
OnDestroy(){
if(_instance==this){ //don't clear instance if it's not me
_instance==null;
}
This still doesn’t manage the “destroy myself if another exists” issue. This code is for distinctive cases where by design there will always be exactly one of a given item… i.e. objects that are part of each scene (UI elements, for example) where they might be used, but aren’t given DontDestroyOnLoad status.