How I Initialize Scenes In Unity

When creating anything larger than the simplest game, you will probably need some way to segment the information your player sees at once into discrete "screens". Unity calls these "Scenes". Scenes are stored in your project as .unity files and the GameObject hierarchy for anything that needs to be displayed or run at one time. In your build settings, you elect a single screen to be an entry point, and you can programmatically load other screens.

Let's say we have a main menu scene with two buttons labeled "Options" and "New Game". When you click on each of them, they load the options and the main game scenes respectively. A simple script you might have to attach to the click handlers for these buttons may look like this:

public class MainMenuManager : MonoBehaviour {
    public void LoadOptionsMenu() {
        Application.LoadLevel("options");
    }

    public void LoadGame() {
        Application.LoadLevel("main_game");
    }
}

Assuming you've attached the functions to these scripts to the click handlers for the buttons, when the user clicks, they will be immediately presented with the main menu scene or the options scene. Unity will destruct and discard all GameObjects from the main menu scene, then populate the runtime with all GameObjects configured in your options scene or game scene. The instant transition works, but ideally you'd want something a little nicer to make it less jarring for the user.

Let's assume for a moment that we have an object that implements this interface:

public interface SceneTransitioner {
    IEnumerator TransitionOut();
    IEnumerator TransitionIn();
}

These methods are coroutines that asynchronously do whatever scene transition you decide to implement. It could be a simple fade to black, or a star wipe, or a big explosion. What matters is there is a notion of transitioning into and out of a scene.

In order to make our scene transitions feel nice and consistent, when the user initiates a scene transition, somehow SceneTransitioner.TransitionOut() should be called. Once that transition is complete, the next level should be loaded. (This step becomes harder if your levels become long enough to warrant loading screens.) After the level is loaded, SceneTransitioner.TransitionOut() should be called. Let's centralize that behavior into a SceneManager script.

public class SceneManager : MonoBehavior {
    // How this is initialized is up to you. Dependency-injection would be ideal! (http://strangeioc.github.io/strangeioc/)
    private SceneTransitioner _transitioner;

    public void Start() {
        /**
         * This ensures Unity allows this object to persist between scenes
         * Find out more at: http://docs.unity3d.com/ScriptReference/Object.DontDestroyOnLoad.html
         */
        DontDestroyOnLoad(this);
    }

    public void TransitionToScene(string sceneName) {
        StartCoroutine(PerformTransitionSequence(sceneName));
    }

    private IEnumerator PerformTransitionSequence(string sceneName) {
        yield return StartCoroutine(_transitioner.TransitionOut());
        Application.LoadLevel(sceneName);
        yield return StartCoroutine(_transitioner.TransitionIn());
    }
}

Let's take stock of the moving parts here:

  • MainMenuManager - The scene-specific script that handles triggering scene loads from button clicks
  • SceneTransitioner - An (unimplemented) object that will use coroutines to perform some aesthetically-pleasing transition between levels
  • SceneManager - The singleton-ish object that mediates between Unity's Application class and our SceneTransitioner.

In order to wire these things together, MainMenuManager should now look like this:

public class MainMenuManager : MonoBehaviour {
    // Once again, how this is initialized is totally up to you.
    private SceneManager _sceneManager;

    public void LoadOptionsMenu() {
        _sceneManager.TransitionToScene("options");
    }

    public void LoadGame() {
        _sceneManager.TransitionToScene("main_game");
    }
}

Great! Now we've got our main menu waiting for a nice transition out before loading the level, and performing a transition in when the next level is loaded. However, there is one additional feature I believe is very important.

Given how we've structured things, when the game scene loads, all GameObjects immediately begin doing what they are scripted to do. The user probably would appreciate some sort of warning or countdown that the game is about to begin. However, by default that will appear and begin counting down while your SceneTransitioner is still performing TransitionIn(), depending on the length of your transition. 

To solve this problem, I decided to decouple when a scene actually initializes from the logic needed to kick things off. At a high level,. the idea is that the SceneManager issues a global event on a shared channel to inform anyone who may be interested in the scene we just loaded that things are starting. Any script that requires synchronizing with the true 'start point' of a scene extends a class and implements an abstract method that will be called when our scene is truly ready. That abstract class is quite small:

public abstract class SceneEntryPoint {
    /**
     * StrangeIoC (http://strangeioc.github.io/strangeioc/) provides a very robust publish-subscribe system. 
     * This is a pub/sub channel with zero event data.
     */
    public static readonly Signal SceneStart = new Signal();
    abstract void OnSceneStart();

    public void OnEnable() {
        SceneStart.AddListener(OnSceneStart);
    }

    public void OnDisable() {
        SceneStart.RemoveListener(OnSceneStart);
    }
}

Also, we must make a minor addition to our SceneTransitioner to ensure the pub/sub channel is dispatched upon when the transition is done.

/// SceneManager.cs
private IEnumerator PerformTransitionSequence(string sceneName) {
  yield return StartCoroutine(_transitioner.TransitionOut());
  Application.LoadLevel(sceneName);
  yield return StartCoroutine(_transitioner.TransitionIn());
  SceneEntryPoint.SceneStart.Dispatch();
}

Now when we would like to have our main game scene greet the user or alert them that the game is about to begin, we can do the following.

public class MyGame : SceneEntryPoint {
    override void OnSceneStart() {
        // Do whatever you want in here!
        // This will be callled only after the transition is complete.
        stateMachine.trigger("game_started");
    }
}

The primary omission from this walkthrough was the use of Singletons to make these objects a bit easier to use in a larger system. I would recommend making the SceneManager a singleton using the base class provided on the wiki. As always, be careful not to overuse singletons, and to limit access to them as much as possible, but always having the SceneManager available decreases the number of things you have to remember when creating a new scene!