The good old issue of finding references to other Unity components, or objects. It’s sometimes amazes me how developers reach to awkwardly complex solutions to a really simple problem:
- Assign the reference in the Inspector
- Expose the field as a static property
If I need a reference to my NetcodeState
, I just write this:
var netcodeState = Components.NetcodeState;
Table of Contents
The Components Registry
In the scene, I have an object named Components Registry with a Components
script on it:
Just drag & drop the NetcodeState
component on that Inspector field. That’s it.
Need more component references? Just add more fields.
Components Code
The purpose of Components
is to have all prominent references at a central location.
The Components
code is not the least bit complex. There’s both a private SerializeField
and a public static property for NetcodeState
. The latter merely returns the serialized reference.
Find Or Register References
If in the future there may be a need to actually “find” a reference at runtime, I can extend Components
accordingly. Be it via GetComponentsInChildren
or GameObject.FindWithTag
.
Likewise, if instantiated objects need to be available from a central location, they could simply register themselves with the Components
registry. Or a similar script just for those kinds of objects to avoid blobbing unrelated references in the same place.
And with registering objects you also need to unregister them, during OnDestroy at the latest. Otherwise you will have stale references eventually, at the latest when loading another scene.
Execution Order
By following the rule of thumb to never call out to outside references in Awake
(use them in Start
instead), we won’t have any issues with this setup.
Even if there were, you could always add the Components
script to the Script Execution Order list.
Null Checks On Launch
The ThrowIfComponentIsNull
is simply a validation where each field is supposed to be checked for null. The moment you enter playmode you’ll know there’s an issue – not after minutes of testing!
Pro Tip: Check for potential errors as early as possible!
Singleton In Disguise
Components
has a static instance field, making it a singleton. You could optionally also put it in DontDestroyOnLoad.
Components
is meant specifically to provide access to components where you’d commonly make that component a singleton. But rather than having a dozen singletons, it’s better to have a single one giving access to the global components of your game, be it Netcode, GUI, Game Logic, Player(s), Audio, Services, and so on.
Note that the Components
singleton is not exposed! From the outside it just looks like a static class because the indirection through the instance field is neatly hidden from the user.
Supporting Disabled Domain Reload
To be compatible with Disabled Domain Reload (I prefer: Instant Enter Playmode) we only need to make sure the instance gets reset in OnDestroy
:
There’s no need to use a RuntimeInitializeOnLoadMethod
to support Disabled Domain Reload, as the manual suggests. Not if you clear your statics upon shutdown. Register a static event in Start
and unregister it in OnDestroy
– that’s it! Likewise, reset static fields in OnDestroy
.
I feel like the manual has this totally backwards, and made a report to that effect.
All Singletons Are Singles!
Note that if we ever get a second instance, I throw an exception!
I always do! Never .. any .. other .. way! By definition, a second singleton instance is an ERROR that needs to be fixed!
If you only Destroy()
additional singleton objects like 99% of other developers do, you do run at risk of really weird issues.
Imagine .. it just needs another component on that same object that also runs code in Awake, and already did before the object got destroyed, and then … DooM!
Potential Issues Of A Doubleton
Perhaps that other component’s Awake
cleared the list of all enemies in the game. Or reset the player stats to defaults. Or … ๐ค
Oh and there can be issues from the outside, too!
Say there’s a GetComponent
or FindObject
that runs during Awake
and it does find that second singleton object, not the actual one. And next thing you know, NullReferenceException
!
Even though the singleton object is right there in the scene hierarchy and you’re like ๐.
Oh and … if you do this often enough, you could quite literally call your singletons MEGATONS! ๐
It’s a looming nuclear nightmare with the potential to go off at the worst possible point in time.
It’s Not A Manager!
If you really wanted to, you could also name this script ComponentsManager
.. if tradition is very important to you.
But consider, does this class really manage components? What is management doing anyway, really – do you know?
Most simply never give it any thought. Slam Manager at the end, and it sounds way more meaningful. Right?
But if you struggle to come up with a list of things that every Manager script has in common, then Manager has no meaning to you.
Thus your Manager scripts could as well be called Game or Player or Scene. That hurts, right?
And so should the overuse of ThisManager and ThatManager!
Summary
Components is a real neat solution for global references. And a simple variation of this can be used for registering dynamic references.
The rest of this article was me ranting about seemingly never-ending cargo-cult programming crud and poking a little fun. ๐
Leave a Reply