Recently, I saw some rather horrifying things on Stackoverflow: accepted answers with high scores that told people about some “best” practices about game initialization in Unity.
Here’s a short article about how things could be done, some explanations why it shouldn’t be done like that, followed by better solutions.
Example: The music player
Consider the idea of a music player. A GameObject
that persists through scene loads because the AudioSource
component should keep playing music even while the scene changes. This means that the GameObject
should be unique (existing only once at any time) and has to exist at all times, since other objects may want to use it to change the song that is played.
This context yields multiple obvious solutions. First would be the Pseudo-Singleton pattern, which basically means that a script makes sure that if the music player object is instantiated a second time, the new instance destroys itself so there is always only one. This is used in combination with a DontDestroyOnLoad
call so the first object that is being instantiated will stick around forever. Next, you usually throw a music player into your scenes, perhaps by dragging a prefab into them.
This solution isn’t very good as it requires you to do some mandatory setup for each scene for it to get basic functionality. Wouldn’t it be better if things that should be present in every scene just worked, even in new scenes?
Alternatively, and this is something I’ve seen lately, you could have an “initialization scene” – a scene where all your “important objects” are located. The scene is at the top of the build list, so when a player starts the game, it runs. A script on some GameObject
immediately starts the next scene in the build list, but the important objects stick around with the help of a DontDestroyOnLoad
call on each.
This solution is not very good either, and – contrary to statements I’ve read on Stackoverflow lately – there’s better alternatives. The reason I wouldn’t recommend this is that when testing something you’ve built in a scene, you somehow have to load the initialization scene once. I’ve seen people build some editor extensions to automate this to some degree. But in the end, your workflow has to adapt to the fact that some parts of your project simply don’t work unless you – somewhat manually – make sure that some preconditions are met. I wouldn’t like my systems to depend on some editor scripts during play mode testing.
Example: The savegame loader
A slightly different case would be that of a savegame loader. We have a procedure we want to happen to initialize some values. In comparison to the music player example, we don’t need an object that persists through the entire game. But we want some values to be loaded before other systems depend on them.
The Pseudo-Singleton pattern doesn’t make too much sense at this point, but it wasn’t recommended anyway. The initialization scene makes more sense: Add a GameObject
that runs some savegame loading script and that’s all. However, the same criticism as above applies.
Example: Custom Input
As a final example, think of a custom input system that does something in every Update
call. It’s not just used by other systems, it proactively does things by itself. Again, the same ideas as above would work, and again, they’d come with the same disadvantages.
What to do instead: Lazy loading
One possible solution that I would recommend is lazy loading. The term “lazy” here is easily explained: Remember when you had an essay to write for school, and you never did it until the last day, when it was absolutely necessary? That’s exactly what happens here. Instead of making sure that your system has already done its work by the time the results are needed, just let the system do it the first time someone asks for them.
[RequireComponent(typeof(AudioSource))] public class MusicPlayer : MonoBehaviour { private static MusicPlayer _singleton; public static MusicPlayer singleton { get { if (!_singleton) { CreateSingleton(); } return _singleton; } } private static void CreateSingleton() { var gameObject = new GameObject("Music Player"); gameObject.hideFlags = HideFlags.HideAndDontSave; _singleton = gameObject.AddComponent<MusicPlayer>(); } }
You might notice that this still matches the Pseudo-Singleton pattern. It’s simply extended by lazy loading, which means that instead of blindly getting the object reference, the class checks whether the object even exists when being asked for it. If not, an instance is hastily created and then returned.
You can still work with Awake
here and, for example, cache the reference to the AudioSource
component that was automatically created through the [RequireComponent]
attribute. (For more information on this attribute, please see this article.) Awake
will be called during the AddComponent
call, and with that, before the new object is returned.
The line that sets the hideFlags
hides the object this code creates so you can’t accidentally destroy or manipulate it in the editor; it’s completely under the control of this class.
For the classes that use the MusicPlayer
class, nothing changes here.
Generally, I’d like to add that the Pseudo-Singleton pattern isn’t free of issues in itself. Please read this article for more information. In the context of lazy loading, it’s not too bad a solution.
However, maybe you don’t want your savegame loading to happen right at the start, and perhaps your input system can’t wait until some system uses it – it has to start working by itself. This is why the following section contains a solution that always works. And when in doubt, use that instead of lazy loading.
What to do instead: RuntimeInitializeOnLoad
A small Unity attribute that is very often overlooked by developers is the RuntimeInitializeOnLoadMethodAttribute. Its gigantic name suggest some complex concept, but it’s really simple: Slap it on top of a static method, and the method will run when the game starts. That’s it.
You can use this to initialize any system you want. For example, in the same way the CreateSingleton
method would work in the above example. To make this even more elegant, let’s put the component class into a static class.
public static class MusicPlayer { [RequireComponent(typeof(AudioSource))] private class MusicPlayerComponent : MonoBehaviour { // ... } [RuntimeInitializeOnLoadMethod] private static void Initialize() { var gameObject = new GameObject("Music Player"); gameObject.hideFlags = HideFlags.HideAndDontSave; instance = gameObject.AddComponent<MusicPlayerComponent>(); } private MusicPlayerComponent instance; // TODO Add Static methods }
Note how the MonoBehaviour
is a nested private class. This means that you can’t manually create an instance of the component; neither through the editor nor through AddComponent
from another class. Of course it needs to be noted that this little nesting trick works just fine to enhance the lazy loading example from above – as long as you don’t create your objects through code and not in the editor, you can use this pattern to have a cleaner interface.
What you do with the RuntimeInitializeOnLoadMethodAttribute
is basically the same as in an initialization scene. But instead of listing all the scripts that need to run at the start of the game in the scene’s hierarchy, you just add an initialization method to the class itself, so the fact that a piece of code runs at game start is not defined in some completely different place, but directly next to that code, in the same class.
Issues with RuntimeInitializeOnLoad
Let’s have a look at an issue with this approach that somehow needs to be solved.
When you set up a scene (or prefab, or ScriptableObject) in Unity, Unity will write anything you assign in the inspector into some file. When you open your scene/prefab/ScriptableObject at a later point, that file is read and everything you saved is loaded again – from any light source’s color to the numbers you entered in your script component through the inspector (more on “Serialization” in this article).
This means that when you make a scene or a prefab, you can set values for any serialized field (more or less any field you see in the inspector), and when the game runs, these values will be used. That includes objects you dragged into the inspector, like AudioClip
s, textures or… language text files.
When you use the [RuntimeInitializeOnLoadMethod]
approach, you seem to lose that advantage, as you cannot drag a texture asset into your static initialization method. What if your initialization code is supposed to load a text file for whatever reason?
I avoid Resources.Load
almost entirely. If you want to know why, here’s an article for you! However, in this situation, it’s one of the simpler solutions to just load the very few assets you will need for global game initialization through this method. Combine it with the mentioned initialization attribute and you can load any data you want with it at game start.
Note: In an earlier version of this article, there was a section here about default values in MonoBehaviour and ScriptableObject classes. This section was removed as this approach doesn’t work in builds.
Conclusion
If you want something done at game start, know that you are not forced to adapt your workflow to that system. You don’t need to start populating all your scenes with the same prefab, and you definitely don’t need an “initialization scene”.
Systems should work in themselves as much as possible, and a system that requires you to set up scenes in a specific way in order to actually work is not good design in my eyes. Thus, consider using the [RuntimeInitializeOnLoadMethod]
attribute (or lazy loading) in the future – to create systems that don’t yield a mandatory team workshop on how to make them work.