Skip to content
tbriley edited this page Mar 23, 2015 · 6 revisions

Watch on YouTube

Introduction

Scene Managers act like bootstrapping objects. Every time you load a scene with a Scene Manager uFrame ensures that everything is loaded and ready to work correctly. Additionally they allow you to use scene transitions. You can define what scenes to load and how to setup your Scene Manager. Finally they allow you to execute any logic during the loading. It’s a good place to generate procedural stuff or load a saved state of the scene.

Defining a Scene Manager

To define a Scene Manager you should be in the general graph where you define sub systems. Here you can double-click, select "Add Scene Manager", and change its name. However, to make it work you have to tell the Scene Manager what part of your game logic should be loaded with the scene, and that’s why you have to link a sub system to the Scene Manager. The main point here is that the Scene Manager only allows you to have one sub system connected. So for each Scene Manager it’s a good practice to define a single sub system which will incorporate all the other needed sub systems and also expose some scene specific elements.

Creating Scenes

In this example, I have MainMenuSystem connected to MainMenuSceneManager and LevelSystem connected LevelSceneManager. This basically means that I’m going to have one scene which will display MainMenu, and this scene will probably be unique. However, you may want to have several level scenes using the same LevelSceneManager. To make some scene use the specific SceneManager, the scene must be properly configured. Let’s save and compile.

We get a bunch of files generated for us, and now if you right-click on the MainMenuSceneManager and select "Create Scene", a new scene will be created for you, with a _GameManager and a _SceneManager automatically added. Scene Managers are in fact MonoBehaviours, but they’re not Views. And the Game Manager in this scene is told to work with this Scene Manager, which is MainMenuSceneManager.

Alternatively you can create a new scene and create two game objects. One of those is going to be _GameManager, and the other one is going to be _SceneManager. On the Game Manager you drop the GameManager script. On the Scene Manager you drop the Scene Manager script you want to be used in this scene. In my case it’s LevelSceneManager.

Finally, I tell the Game Manager in this scene to work with this Scene Manager. I can save this scene in my project folder in the scenes.

Finally I can duplicate those, so I’ve got three levels which use the same Scene Manager.

Defining Transitions

Now it’s time to define some scene transitions. I'll drill into MainMenuSystem, create an element called MainMenuRoot, and give it a command called ToLevel that takes a string argument.

On the other side in the LevelSystem, I'll create a LevelRoot Element with a ToMainMenu command with no argument.

Finally, I'll expand my sub system and register my MainMenuRoot as a globally registered instance. And I do the same for my LevelRoot.

Now if you expand your Scene Managers, you have an Add Transition button here, and you can select the exposed commands that come from your globally registered elements. ToLevel will go to LevelSceneManager and ToMainMenu will go back to MainMenuSceneManager.

So whenever MainMenuRoot executes ToLevel command, it’s going to transition to some scene which uses LevelSceneManager. But how do we know what scene to go to? Well, we can define it in the code. Let’s Save and Compile.

Now Ctrl-Click on MainMenuSceneManager and your IDE should pop up. Here you see the implementation of MainMenuSceneManager which was automatically generated by uFrame. There are a lot of comments here, but the code does nothing more than the base invocations. Right here, we can override a special method. It’s called GetToLevelScenes. If you remember, my ToLevel command brings a string argument, and that’s why you have a string here.

I have to return some sort of Enumerable which will have all the scenes I should load additively, in case you provide it with one scene, only that scene will be loaded. In my case I’m going to transition to the scene which corresponds to the name which was passed.

Now we need to prepare a few things. I’m going to create a new empty object and call it MainMenuRoot. And I’ll drop in MainMenuRootView (Creating Views and Views) and tell it to use the globally registered MainMenuRoot instance.

Now I’ll go to my build settings and place all my scenes in the list. Also, there is a special scene shipped with uFrame called Loading that I'll include here.

Now I can hit play and as you can see now my Scene Manager is MainMenuSceneManager. On my MainMenuRoot I have a commands foldout where I can execute commands with a given parameter. I’ll type in the name of a scene which has the LevelSceneManager. Let’s say it’s Level 2.

And as you can see now, the Scene Manager is LevelSceneManager and we’re in the scene called Level 2. So now we can transition from MainMenuScene to any level. But how do we get back? Well, using code for this is overkill because we always know the name of the scene where we want to go to, the MainMenuScene here. So in Level 1 scene I'll create a new object called LevelRoot. I’ll drop in LevelRootView here and tell it to use the registered LevelRoot instance. (Elements and Views)

In the Scene Manager I have ToMainMenu transition settings. Here I can specify the scenes, and I’ll just type in MainMenuScene.

And now if I hit play, select my LevelRoot and hit "ToMenu", I’ll be in the MainMenu again and now I can get back to the other Levels.

Scene Manager Settings

At this point we know how to transition between the scenes. Let’s see we can transfer some data around the scenes and adjust our scene during the runtime. One way to do it is using the Scene Manager Settings. Let’s open this up.

It’s a public sealed partial class, which means you can’t inherit from this, but you can add your own properties here. Let’s say when loading your scene you want to generate a big world and you want the size of the world to come from the other scene which you transitioned from. So let’s create two public fields here.

Now if we go back to the scene and take a look at our LevelSceneManager, you can see that under LevelSceneManager Settings, we got a new setting for Width and for Height.

If you specify some values here, those are going to be default. But I want those to come from MainMenuSceneManager while we transition. So let’s go to MainMenuSceneManager code and override a method which is related to ToLevel transition and is called ToLevelTransitionComplete. I’ll remove the base invocation and as you can see I get the LevelSceneManager here. I can adjust its settings to something else. I can set Height to 100 and I can set Width to something else.

Now in my MainMenuScene if I transition to Level 1 I can take a look at my Scene Manager and see that Width is set to 400 and Height is set to 100. Amazing!

You should probably know that this method which is invoked every time the transition is complete can serve for a lot of purposes. You’re not bound to set the properties here, but you can modify the existing Scene Manager in any way you want.

Adjusting Loading Procedure

So now we know how to setup Scene Managers, how to define Transitions, and how to setup Scenes and Settings. Now it’s time to learn a bit about the loading procedure. In every generated Scene Manager code you have plenty of methods which are heavily documented. You can use each entry point to do some stuff which you want, but it’s very important to know what kind of things are accessible from here. First of all you get access to all the defined controllers. You also get access to all the globally registered instances defined in all of the connected subsystems. So in my case I have only LevelRoot, and I can pretty much do everything with it.

The loading starts with this OnLoading method which is invoked right before the Load coroutine is started. And this is a coroutine so you can use yield statements here. Like for example, you can use yield return new WaitForSeconds(). Also in this coroutine you have a progress delegate which you can update with some values. For example, I can send a message and some float progress.

Right after this coroutine is finished, OnLoaded() is invoked. And when the scene dies, Unload() is invoked. The Setup() method is only invoked when the scene first loads. So now, if we go back to MainMenuScene and try to transition, you can see that we’re loading controllers for 3 seconds, and then we’re in the LevelScene manager.