How to Setup a Multi-Scene Unity Project for Startup

Scene loading played an important role in this, and I believe my approach is pretty sound and proven highly useful. Let me know what you think, and what ideas you tend to apply for handling this.

When developing in a multi scene project in Unity, one of the most annoying things I've worked with in the past, is the need to boot into the right place. I've worked with and developed dozens of different approaches, most of which falling into custom menu options, or resetting the default loading scene. I want to share a solution I had that is simple, small, and has worked great.

General Overview

Codavore is broken into several scenes BootStrap, Ui and Level/Scene/Room. Boot Strap guarantees the order of several things like my service locator and a few others. Next there is the Ui, which contains the menus and such. And finally, we have the scene with the content for the game I'm on. There is a fourth, "DontDestroyOnLoad" where Unity puts all its don't destroy objects to keep them alive between scene loads.

So, because the UI has requirements on the locator then I have bootstrap load first and then load the other two. UI and then the specific scene needed. BootStrap is actually pretty simple. It mainly wraps around this function: 

Problem 1 - Guaranty proper loading order at boot.

This is pretty straight forward. Selecting the starting scene to be boot strap, so runtime will always start with that. Then a script inside (BootStrap.cs) loads the other. In the start function I simply tell it 

        SceneManager.LoadScene("Ui", LoadSceneMode.Additive);

Followed by another request for what scene it should start them with in the game. This can just be two calls to do this. This is also not any real difficulty. To make this easy to change at design time, I made a field to use manually enter and set what the default scene is.

        public string TargetSceneName = "";

This posed a little more challenge. I created a script called BootValidator which starts by asking if BootStrap has already been loaded. If it has, then the scene loading order has already been set, and it exits out. To test whether or not bootstrap has been loaded, I keep a singleton reference to it, like so: 

    public class BootStrap : MonoBehaviour
    {
        public static BootStrap Singleton;

        public BootStrap()
        {
            Singleton = this;
        }

    public class BootValidator : MonoBehaviour
    {
        public void OnEnable()
        {
            if (BootStrap.Singleton != null) return;

But if BootValidator has not been loaded, then it means we are in the editor, and someone pressed play in this scene. When that happens, we need to change BootStrap to load the scene we want. But that raises its own difficulty. Since we can't load in instance of bootstrap on our own (though we could have created a manager to mitigate the behaviour's quirks) I solved it by adding another static variable to bootstrap and setting it with my current scene name.

    public class BootStrap : MonoBehaviour
    {
        public static string TargetStartScene = "";

    public class BootValidator : MonoBehaviour
    {
        public void OnEnable()
        {
            var scene = SceneManager.GetActiveScene();
            BootStrap.TargetStartScene = scene.name;

Finally, when BootStrap is starting, we need to tell it to check if the default target should be replaced.

        public void OnEnable()
        {
            if (!string.IsNullOrEmpty(BootStrap.TargetStartScene))
            {
                this.TargetSceneName = BootStrap.TargetStartScene;
            }

This is all it takes to get the scene to automatically switch to the scene I'm editing, having had all the prereqs met now.

Of course there are a few addon's, which I may cover in a later post (and hopefully remember to update this one with a link when I do). But as a rough idea, this include the following concepts:

  • DoNotUseAsScene
    • This is a boolean on the validator class that says not to load the class we are in, but instead use the default.
    • An example of its use is when I am editing the UI and want to test it. The UI is managed in its own scene, but still needs bootstrap. I don't want to have to jump into another scene to test this, so instead, I let it go to default.
  • UseDifferentTargetScene & DifferentTargetScene
    • These tell the validator to force a different scene to load. 
    • An example would be the UI. I might want to test a feature so I tell it to load a particular scene that happens to use or expose that feature right away.
  • OnSceneFirstFrame Event
    • This is a Unity Event that can call all sort of other actions to be triggered on scene load. 
    • This class in particular has been given a high priority, to load before anything else, to help prevent errored dependencies from showing up in the logs. But OnSceneFirstFrame happens at the first frame.
    • An example is to force load another item when the scene begins. We want to make sure that it does not trigger normally if the scene loads because we are in the editor and hit play, but rather once everything is properly loaded. This is a safe guard against subtle changes to scene load / start / OnEnable etc alterations between versions and OS's, which I've seen change too many times to trust.
  • TestEvents
    • This is a list of child classes that contain a name, description, event action and boolean "Active", and is used to set temporary options while editing. 
    • If the game loads from a build, none of these events execute.
    • An example of is when we need a particular dialogue sequence to start while developing a feature or debugging a problem. I structure the UnityEvents to setup data, and call the particular events to immediately load the proper setup. This allows for very simple testing of particular setups. 
    • Since it is an array of these, it allows me to enter different test states, and then have more than one active, or deactivate them and use them again later as needed. 

Comments

Popular posts from this blog

C# Parser and Code View

A Momentary Lapse of Passion