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.
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.
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.
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.
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!
Not a problem
Since we only edited the purple scene, nobody’s overwritten someone else’s work.
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.
I’ve also created another scene named ‘EmptyRoom‘.
This scene holds a camera, a light, and a gameobject with a RoomLoadController script.
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.
- Registers a callback for the SceneManager.sceneLoaded event.
- 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.
Let’s check out the end result.
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