Using Multiple Scenes

Project Example Source Code

The source code and example projects for this post are available.  If you'd like to grab it and follow along, just let me know where to send it.

Using Multiple Scenes in Unity

One of the great things about Unity 5 is the ability to load multiple scenes at the same time effectively.
You may have noticed that your scene name now is visible in the top left of the hierarchy.

Multiple Scenes - Empty Room with no other scenes loaded
This is here to show which scene the game objects are in.

If you load multiple scenes, you’ll see them as separate collapsible groups in the list.

Multiple Scenes - Scenes added in Hierarchy

There are a variety of ways you can use additive level loading in your projects.  In this article, we’ll cover some of the most common uses.

  • Splitting scenes for shared editing
  • Randomly generated game content
  • Seamless loading of large worlds
  • Smooth transitions from a menu scene to a game level

Shared editing / splitting the world

Multi-user scene editing in Unity can be painful.  Merging changes isn’t easy, and even when you successfully do a merge, it can be hard to tell if everything is right.

For many games, the new scene management systems in Unity 5 will allow you to split up parts of your world into separate chunks that are in their own scene files.

This means that multiple designer can setup part of the world.

Our Starting State

To demonstrate how this would work, I’ve built two scenes.  There’s a purple scene and a yellow scene.

Multiple Scenes - Yellow and Purple Scenes

With both of them loaded at the same time, you can see that their seams line up and they combine to be a larger scene.

The advantage though is we can have a designer working on the yellow scene while another designer makes changes to the purple one.

This example has simple scenes.  In a real game, just imagine the scenes are different quadrants of a city, chunks of a large castle, or a large scene in one of your previous projects.

Mario Changed the Purple Scene

To show the benefit and how it works, we’ve modified the purple scene.  It now has another sphere and an extra word!

Check out the Hierarchy and notice that only the purple scene has been modified, so when we save, we’re not affecting the yellow scene at all.Multiple Scenes - Yellow and Purple Scenes - Puple Changed

Luigi changed the Yellow Scene

It’s a good thing we didn’t touch the yellow scene too, because another designer has made some changes to it while we were modifying the purple one!  They added a cube and more words!

Multiple Scenes - Yellow Scene Changed

Not a problem

Since we only edited the purple scene, nobody’s overwritten someone else’s work.

Multiple Scenes - Yellow and Purple Scenes - Both Changed

Our end result has changes from two separate designers working in parallel.  Depending on your game, this could be split among any number of people, all in charge of their own area, or at least coordinating who’s editing each area to avoid stepping on each others work.

Generating a level at run-time

The first situation we’ll cover today is loading multiple scenes to build a bigger level dynamically.

For this example, I’ve built two rooms.  One is red and the other is blue.

Multiple Scenes - Blue Room

The Blue Room

Multiple Scenes - Red Room

The Red Room

I’ve also created another scene named ‘EmptyRoom‘.

This scene holds a camera, a light, and a gameobject with a RoomLoadController script.

Multiple Scenes - Empty Room

The RoomLoadController is responsible for loading in our red and blue rooms during the game.

For this sample, our RoomLoadController will watch for the a keypress of the numpad plus and numpad minus keys.  If the user presses either of them, we’ll load add another scene to our game.

using UnityEngine;

public class RoomLoadController : MonoBehaviour
{
    private int zPos = 0;

	private void Update()
	{
		if (Input.GetKeyDown(KeyCode.KeypadMinus))
		{
			AddRoom("RedRoom");
		}

		if (Input.GetKeyDown(KeyCode.KeypadPlus))
		{
			AddRoom("BlueRoom");
		}
	}

	private void AddRoom(string roomName)
	{
		zPos += 7;

		var roomLoader = new GameObject("RoomLoader").AddComponent<RoomLoader>();
		roomLoader.transform.position = new Vector3(0f, 0f, zPos);
		roomLoader.Load(roomName);
	}
}

You may have read the script and wondered, where’s the scene loading part?  Well for this project, I wanted to load a bunch of scenes in and I want them to always be offset by 7 meters.

To keep the code separated and simple, I spawn a new object called RoomLoader to do the work.  We give the RoomLoader is a position and a room name, it will handle the rest.

Let’s take a look at the RoomLoader.

using System.Collections;
using UnityEngine;
using UnityEngine.SceneManagement;

public class RoomLoader : MonoBehaviour
{
    public void Load(string roomName)
	{
		SceneManager.sceneLoaded += SceneManager_sceneLoaded;
		SceneManager.LoadSceneAsync(roomName, LoadSceneMode.Additive);
	}

	private void SceneManager_sceneLoaded(Scene scene, LoadSceneMode mode)
	{
		SceneManager.sceneLoaded -= SceneManager_sceneLoaded;
		StartCoroutine(MoveAfterLoad(scene));
	}

	private IEnumerator MoveAfterLoad(Scene scene)
	{
		while (scene.isLoaded == false)
		{
			yield return new WaitForEndOfFrame();
		}

		Debug.Log("Moving Scene " + transform.position.x);

		var rootGameObjects = scene.GetRootGameObjects();
		foreach (var rootGameObject in rootGameObjects)
			rootGameObject.transform.position += transform.position;
	}
}

Check out the load method.  This is what’s being called from the RoomLoadController. It does two things.

  1. Registers a callback for the SceneManager.sceneLoaded event.
  2. Calls SceneManager.LoadSceneAsync, using the LoadSceneMode.Additive option.

SceneManager.sceneLoaded Add a delegate to this to get notifications when a scene has loaded

After line 10 executes, the scene specified in roomName will start loading. Because of the LoadSceneMode.Additive option, we will keep our current scene open as well, including our camera, light, and RoomLoadController.

Once the scene finishes loading, our SceneManager_sceneLoaded method will be called by the delegate (registered on line 9).  The first thing we do is deregister from the event, so we don’t get called by every other scene that loads.  Then we kick off a coroutine to wait for the scene to be completely ready.  Lines 21-24 do the waiting…. and waiting…. until the scene.IsLoaded.

I’m not sure why the scene isn’t completely loaded when the sceneLoaded event fires.  I’m sure there’s a reason for it, but I haven’t found the explanation yet.  If you happen to know, please comment.

On line 28, we get the root gameobjects of the newly loaded scene.  We then move those objects over to be offset by the amount this RoomLoader is.  This is why the RoomLoadController is moving the RoomLoader.

Blue Room Root Objects

Blue Room Root Objects

Let’s check out the end result.

Multiple Scenes - Loading Red and Blue Rooms

Again, for this example, we’re controlling the loading of scenes, but there’s no reason we couldn’t randomly pick some.

This same technique can be used to randomly generate a dungeon out of pre-built scenes or load new scene parts as a player explores the world.

Part Two

Scene management is a huge subject, and while we've covered some important basics, there's a lot more to learn.

If you're interested in this subject, you can get part two delivered directly to you as soon as it's ready.

Part Two of this post will cover:

  • Seamless loading of large worlds
  • Smooth transitions from a menu scene to a game level

 

  • Ricardo

    Would love to see the continuation of the dependency injection tutorial i though i saw the second part at one point
    but cant seem to find now and definitely the second part of the scene loading.

    Love all the topics . I dont’ use VR so i cant appreciate those at the moment.
    Thank you for doing this site and please keep it up.

  • chris

    Hello! I’m a bit confused as to the purpose of the delegate and of the deregistering process in this execution. Could you elaborate on those elements and why they are used here?

    • JW

      Sure 🙂

      What’s happening is we register for the Scenemanager’s static sceneLoaded event before we start loading the scene.

      When the scene finishes loading, it invokes that event, which tells our script that the scene is ready and we can start the move operation.

      But we’ll also be loading more scenes, calling that Load() method, so we need to make sure we’re not registering for the event more than once. If we didn’t unregister, every time any scene was loaded, we’d re-call the move operation on all of our scenes.

      Scene 1 would Load, register, and move on sceneLoaded
      Scene 2 would Load, register, and move on sceneLoaded , but Scene 1 would also get that sceneLoaded event again and re-run the move.

      We could of course structure this differently and have the event registration at a different level, but then we’d need to keep track of a bit more data to know the state of our loaded scenes (but not have to worry about reregistration)

      It’s also just good practice to deregister for events when you’re done listening for them. If you don’t, the garbage collector will hold on to those objects until the application closes, because it can’t kill off something that’s hooked into with an event. In my experience with non-game development, this is the #1 cause of memory leaks for c# apps.