or: why Unity Magic Methods are evil and should be avoided at all costs
Have you ever been working on a game and needed to access your UI system from another script? Or a central game manager? The typical way to do this in Unity is you simply make a public variable and drag / drop the reference to the component into the field, like so:
public MyComponent component;
Easy right? Unity serializes this and makes it visible in the inspector.
There are 2 main problems with this approach:
is requires that you have ALL of the elements you are referencing in a single scene
it requires manual configuration / customization in order to work (the dreaded ‘null reference exception’
In order to prevent these two issues, and just as a general best practice, there are a couple of general rules that I try to follow for my Unity development:
Components should auto-configure themselves unless absolutely necessary
We control the initialization order of everything. Do not trust Unity’s built-in magic methods unless completely unavoidable.
The first is fairly self-explanatory. If you have a component that requires a reference to something else in the scene, it should automatically find it when needed (or initialized).
The second (control the initialization process) is something that becomes absolutely critical with larger projects.
Most Unity projects / code simply use the magic methods provided, for example Awake() or Start() to initialize their code, and do whatever they need to do.
However, what if you have 2 scripts that use a Start() method, but one requires information from the other?
For example:
public class MyComponentA : MonoBehaviour
{
public GameObject myPrefab;
public GameObject instance;
private void Start()
{
instance = Instantiate(myPrefab);
}
}
and
public class ComponentB : MonoBehaviour
{
public GameObject target;
private void Start()
{
var go = GameObject.FindObjectOfType<ComponentA>();
target = go.instance;
// potential null exception
target.GetComponent<SomethingElse>();
}
}
In this case we have 2 scripts, the second references a game object that gets instantiated in the Start() method of the first and then attempts to ‘do something’ with that instance.
This ‘might’ work – the scripts ‘may’ get executed in this order:
Scenario A
MyComponentA.Start()
MyComponentB.Start()
HOWEVER, there is also a chance that the scripts might get executed in the opposite order:
Scenario B
MyComponentB.Start()
MyComponentA.Start()
If this happens, then Component B breaks immediately and throws the dreaded Null Reference Exception and even worse – it’s not immediately clear why.
The hacky way is to make one use Awake and the other Start() – since Start() runs after Awake() the second script will work fine, however that is just bad practice and honestly should be avoided, since there will eventually come a time when you need another script and and other, and this just cascades into a nightmare.
Unity Script Execution Order
Before I get to how I solve this, let’s take a very simplistic look at what Unity does under the hood in a situation like this. The Unity scripting team will probably look on in horror at this description, but here we go ;}
If we look at the Unity Manual’s Execution Order flowchart, we can see that Unity has a specific order or operations that a script goes through:
What that page does NOT explain is that every Component on every single GameObject in your scene is running through those same steps, one by one.
The internal processing that Unity does is that it finds ALL of the components that have an Awake() method and puts them in a list. Then at runtime, it goes through each of those components, one by one, and calls the Awake() method. The same thing for Start(), Update() and so on. Predicting the execution order of 2 different components can quickly become tricky.
So in Scenario A above Unity happened to put the 2 components that we created in the order that we were hoping and the scripts work. In Scenario B, the components were placed in a different order and it broke the scripts.
You can start to see where this might become problematic at scale. A large Unity game typically has hundreds, if not thousands of components, many of which are dependent on each other in some way, shape or form, and trusting that Unity ‘might’ get things right is frankly, dangerous and how you end up with dreaded bugs that happen in certain cases but not others and can be tricky to troubleshoot and reproduce.
A simple real world example of the above are a player script that might need to access the UI system to show info on the Hud. You have a ‘Hud.cs’ script and a ‘PlayerController.cs’ script that need to know about each other – on the start of the scene you might want to set a value in the Hud to the player’s current health, but maybe the health is stored in a sub-component if the player called ‘HealthSystem’ which in turn needs to load data from a save game, etc.
You can start to see how things can get complicated quickly.
Custom Execution Order & Why you should not use it
Side note: if you’ve used Unity for a while, you might be familiar with the ‘custom execution order’ system that Unity provides. Unity ‘does’ provide a system that allows you to specify specific scripts that need to run in specific order. We ‘could’ use it to make ComponentA from our example, execute at a higher priority than other scripts.
I’ll be perfectly honest: Unless you have a VERY GOOD REASON to do this, and know exactly why you are doing it – this is a terrible idea for several reasons:
it’s just hacky
It means that in order for your script to work, you need to remember to customize the script order, and remember WHY you did it in the future.
I really dislike auto-magic things like the custom execution order system that Unity provides. It has what some would call ‘code smell’ – basically just feels like a hack that was added because someone needed it and couldn’t figure out another way of getting the same thing done.
If you need your scripts to execute in the correct order – then write the code so that it does that, don’t rely on hacky workarounds.
Service Locators & Singletons
That long-winded side quest was to explain some of the reasoning behind using a pattern like the Service Locator and Singletons.
Singletons are nice, because you can reference them from anywhere in your code, typically via an ‘Instance’ property, like so:
MyComponent.Instance.DoSomething();
If it’s a proper Singleton implementation, if ‘MyComponent’ does not exist, it will automatically instantiate the Singleton and set up the instance reference properly.
Note: I’ve published the Singleton implementation that I use here https://github.com/PixelWizards/com.pixelwizards.singleton – it’s also available at OpenUPM here: https://openupm.com/packages/com.pixelwizards.singleton/
Depending upon who you talk to, Singletons are either super useful or evil and to be avoided. I’ve always been of the opinion that ‘whatever works for you’ when it comes to software approaches. Any technique can be abused, yes, but particularly in game development – if it works, it’s good enough.
Prior to discovering the Service Locator pattern I tended to use Singletons for things like the UI system, Game Managers, high-level State systems and the like. Basically anything that you might need to reference from ‘anywhere’ at any time, it makes sense to use a pattern like Singletons for this.
However, like a wise Jedi master once said, there is another way to approach this kind of global state / behaviour systems, and that is the Service Locator.
Service Locator usage
If you were following along during my last post you might have seen that I spawn a prefab simply called the ‘Service Manager’. What this prefab does is register all of the services that I use in the game so they’re ready to go.
It also has controls the actual order of the startup operations, making sure that everything is not only initialized in the proper order, but also does things like check that the game is online (has a valid network connection), and logs into Steam, Playfab and Photon so the game is ready to go before the main menu displays.
I’ve shared the Service Locator implementation that I’m using on github as well: https://github.com/PixelWizards/com.pixelwizards.servicelocator and it’s also available on OpenUPM: https://openupm.com/packages/com.pixelwizards.servicelocator/
How does the Service Locator work? You can turn any script into a service simply by implementing an Instance method, like so:
public class MyService: MonoBehaviour
{
public static MyService Instance
{
get { return ServiceLocator.Get<MyService>(); }
}
}
In a similar manner to the Singleton, you can then reference the service from anywhere else with this Instance property, like so:
MyService.Instance.DoSomething();
As you can see in the instance method, it will check the ServiceLocator and return the active instance of the service.
That’s it really. It’s pretty straightforward.
Now you might be asking why I go this approach instead of simply making a bunch of Singletons?
The primary reason is that by registering the services up front like this, I control when they are created, initialized and ready to go. Normally you’d just have Singletons everywhere and they literally get created the first time that you use them. I much prefer to have a very explicit startup order for everything in the code.
Registering versus ‘Initializing’ the Service
Since all of these services are ‘just’ standard MonoBehaviours, technically they have the same script lifecycle as any other, just like I described above. However, instead of using the standard Awake(), Start() methods to initialize the services, I always implement an explicit Init() method for just about every script that I create. This lets me control WHEN and HOW the script was initialized, AND also lets me ‘reinitialize’ them if I need to for some reason.
Back to the Service Manager I mentioned above. In essence it runs through all of the services that I have created and adds them to the service locator one by one.
Here’s a snippet from the Service Manager where I register a number of different services:
// generic UI system, initialize first so that we can display stuff on the screen
var uiService = go.GetOrAddComponent<UIManager>();
ServiceLocator.Add(uiService);
var ai = go.GetOrAddComponent<ApplicationInfoService>();
ServiceLocator.Add(ai);
Even though the services are registered, the startup routine then iterates through various stages of the startup and initializes each in the desired order, using a call like:
// initialize the application
ApplicationInfoService.Instance.Init();
The specifics of what this services does isn’t important for this discussion, but you can see that the service is referenced via the Instance property, and then calls the appropriate Init() method.
That’s about it for how the service locator works. I use it for most of the key global systems in the game, the UI system, Steam, Lobby systems etc. Basically anything that is important enough that it will be likely referenced from other areas and also that exists outside of the context of a single scene.
Other Helpful Utils
There are a couple of other simple utility components that I use in my bootstrap process:
DestroyOnPlay
These are fairly self-explanatory I think. They are single-purpose scripts that do literally what they claim. I’ve linked to Github Gists for each.
Don’t Destroy on Load
This script simply marks the GameObject that it is attached so that it won’t be removed when Unity loads a new scene.
You can find out more about this property in the Unity docs:
https://docs.unity3d.com/ScriptReference/Object.DontDestroyOnLoad.html
Github Gist:
Only One can Survive
This script is a bad play on the Highlander movies, but does basically what it says – it enforces that only a single instance of a component can exist at one point in time.
For example, the Unity UI System requires an ‘EventSystem’ component in order for it to work properly. However, it also breaks if you have MORE than one of them in a scene. You can use this script to get around this issue.
Github Gist:
Destroy on Play
This script might sound strange, but it basically deletes the game object it is attached to automatically when the scene is loaded in play mode. I use this to include remove debug elements in scenes, basically anything that I might have in a scene temporarily that should not be present in the actual game.
For example, I have a global camera that is loaded when the game first starts. The individual game levels do NOT have a camera in them, but in order to work on lighting / etc I have a temporary setup of a camera, etc that I use while working on the scene that is automatically removed in the game via this script:
Github Gist:
Ok that’s probably enough for this week. Next time I’ll go into a bit more detail about the actual game flow in DystopiaPunk, how things are progressing, and (if all goes according to plan) have a working game lobby that shows things from startup to in-game!
Until next time!