Skip to main content
Version: 1.0.0-preview.35

Scene Management

Guide Source Code

Go to the following links for the source code for fully working examples of managed and unmanaged scenes.

When it comes to games, how you manage your scenes can impact many aspects of your game. It can improve performance by only loading what is necessary for that section of your game, keeping your code organized, and making debugging more effortless. This guide will cover how to manage scenes in your game.

Velaptor comes with a scene manager, which performs routine tasks for you.

Here is a list of the tasks the scene manager handles:

  • Life cycle method for loading content
  • Life cycle method for unloading content
  • Life cycle method for updating
  • Life cycle method for rendering
  • Scene transition
  • Texture batching

What does this mean? Regarding the life cycle methods, this means that the standard loading and unloading of content, updating, and rendering are invoked for you by Velaptor. You can use these methods if your scene inherits from the SceneBase class.

Also, SceneBase handles batching for you. What this means is that in your Render() methods, you don't have to worry about calling the IBatcher.Begin() and IBatcher.End() methods.

You can also build a custom scene manager for more control. Velaptor does not stand in your way!

ABOUT THIS GUIDE

This guide will not show you how to manage scenes yourself. That is a more advanced topic. If you are interested, Velaptor is open source! You can see how we built the scene manager and how it works by looking at the source code.

Let's start by creating our first scene.

Step 1: Project setup

Before we start, make sure you have a project set up.

1.1: Setup project

To get started, create a new Velaptor application.

Refer To Guide

Refer to the Project Setup guide for more info.

1.2: Add content

Follow the Adding Content guide to add the content below to your project. You can download the images below by clicking them and the audio player by clicking the three dots.

Click to download kinson digital logo.
Click to download velaptor logo.

Mario Jump


WANT TO USE YOUR CONTENT?

Absolutely! Feel free to use any content you want.

Remember, the content has to be a .png file for images and a .mp3 or .ogg file for audio.

Step 2: Create Scene A

We will be creating two scenes. One will be named SceneA, and the other will be named SceneB. If we want to demonstrate how to navigate between scenes, we need more than one scene. We will create SceneA first.

2.1: Create the class

We can take advantage of scenes with various lifecycle methods, similar to the Window class. These methods are automatically called by the scene manager to load and unload content, update the scene, and render the scene.

Create a new class named SceneA and inherit from the abstract SceneBase class.

public class SceneA : SceneBase
{
}

2.2: Create override methods

Now, we can take advantage of the lifecycle methods by overriding some of them. Create the following methods in the SceneA class.

public class SceneA : SceneBase
{
public override void LoadContent()
{
// You load content in here
base.LoadContent();
}

public override void UnloadContent()
{
// You unload content in here
base.UnloadContent();
}

public override void Update(FrameTime frameTime)
{
// You update the scene here
base.Update(frameTime);
}

public override void Render()
{
// You render the scene here
base.Render();
}
}

2.3: Create loaders and renderers

We must load, use, render, and unload our scene content with our loaders and renderers. We need to create the loaders and renderers as class fields, which will be used throughout the scene's lifetime to load and render the content.

2.3a: Create class fields

public class SceneA: SceneBase
{
private readonly ILoader<ITexture> textureLoader;
private readonly ILoader<IFont> fontLoader; // Used to load the font for rendering the instructions
private readonly ITextureRenderer textureRenderer;
private readonly IFontRenderer fontRenderer; // Used to render the instructions
...
}

2.3b: Set loaders and renderers

Create a constructor and add the following code to set the loaders and renderers.

public SceneA()
{
this.textureLoader = ContentLoaderFactory.CreateTextureLoader();
this.fontLoader = ContentLoaderFactory.CreateFontLoader();

this.textureRenderer = RendererFactory.CreateTextureRenderer();
this.fontRenderer = RendererFactory.CreateFontRenderer();
}

2.4: Load the scene content

Now that we have a way to load content, we can use the loaders to load the content.

2.4a: Texture and font class fields

Add the following class fields to hold the texture and font.

public class SceneA : SceneBase
{
private readonly ILoader<ITexture> textureLoader;
private readonly ILoader<IFont> fontLoader; // Used to load the font for rendering the instructions
private readonly ITextureRenderer textureRenderer;
private readonly IFontRenderer fontRenderer; // Used to render the instructions
private ITexture? logoTexture;
private IFont? font;
...
}

2.4b: Load the content

Now, we can load the content using the LoadContent() method. The loaders load the texture and font.

public override void LoadContent()
{
this.logoTexture = this.textureLoader.Load("kd-logo");
this.font = this.fontLoader.Load("TimesNewRoman-Regular", 12);
base.LoadContent();
}
Content Reminder

Remember, if you have decided to use your content, make sure that the name of the content loaded matches the name of your content file without the extension.

2.5: Unloading the content

Depending on your game, you may or may not need to unload the content. In most cases, if you move to another scene, you no longer need the content. To free up resources, you can unload the content using the UnloadContent() method.

To do this, use the loaders to unload your content using the UnloadContent() method, as shown below.

public override void UnloadContent()
{
this.textureLoader.Unload(this.logoTexture);
this.fontLoader.Unload(this.font);
base.UnloadContent();
}
PREVENT MULTIPLE UNLOADS

2.6: Add something interesting

To add something interesting in the scene to see the Update() and Render() methods in action, we can use the mouse input to have the texture follow the mouse as you move it around the screen.

Create class fields to get the mouse's state and remember its position. We will use the IAppInput<MouseState> mouse field to get the state of the mouse on every frame.

public class SceneA : SceneBase
{
private readonly ILoader<ITexture> textureLoader;
private readonly ILoader<IFont> fontLoader; // Used to load the font for rendering the instructions
private readonly ITextureRenderer textureRenderer;
private readonly IFontRenderer fontRenderer; // Used to render the instructions
private readonly IAppInput<MouseState> mouse;
private ITexture? logoTexture;
private PointF logoPosition;
private IFont? font;
}

2.6a: Create mouse object

Go into the constructor and use the HardwareFactory to set the mouse input class field.

public SceneA()
{
this.textureLoader = ContentLoaderFactory.CreateTextureLoader();
this.fontLoader = ContentLoaderFactory.CreateFontLoader();

this.textureRenderer = RendererFactory.CreateTextureRenderer();
this.fontRenderer = RendererFactory.CreateFontRenderer();

this.mouse = HardwareFactory.GetMouse();
}

2.6b: Setting mouse position

Awesome! Now that we have a way to get the state of the mouse. We can now go into the Update() method and add some code to get the current position of the mouse.

public override void Update(FrameTime frameTime)
{
var currentMouseState = this.mouse.GetState();
this.logoPosition = mouseState.GetPosition();

base.Update(frameTime);
}

Almost there!

The final piece is to render the texture at the mouse's position on every frame. When you move the mouse, the position of the mouse is remembered and then used to render the texture at that position.

INVOKE ORDER REMINDER

Remember, in most game frameworks and engines, the Update() is called first, and then the Render() is called. Velaptor is no different . The Update() method will be called in the same frame as the Render() method.

2.6c: Render the texture

Add the following code to the Render() method.

public override void Render()
{
// Convert the `PointF` to a `Vector2`
var logoPos = new Vector2(this.logoPosition.X, this.logoPosition.Y);

// Render the image
this.textureRenderer.Render(this.logoTexture, logoPos);

// Render the text
this.fontRenderer.Render(this.font, Instructions, new Vector2(WindowWidth / 2f, 20));

base.Render();
}

2.7: A little extra safety

Before rendering the texture, check if the content is null before using it to add some extra safety. Not only does this satisfy any warnings for null references, but it will also verify that we remembered to load the content. If we did not load the content, we would get a null reference exception, and then we could fix the problem.

Add the following null check code to the beginning of the Render() method.

public override void Render()
{
ArgumentNullException.ThrowIfNull(this.logoTexture);
ArgumentNullException.ThrowIfNull(this.font);

// Convert the `PointF` to a `Vector2`
var logoPos = new Vector2(this.logoPosition.X, this.logoPosition.Y);

// Render the image
this.textureRenderer.Render(this.logoTexture, logoPos);

base.Render();
}

2.8: Add some instructions

Next, we will add some simple instructions to the scene and render the instruction text at the top of the window to let the user know what to do.

2.8a: Add constants

Add some class field constants to hold the window's width and the instruction text.

public class SceneA : SceneBase
{
private const int WindowWidth = 1000;
private const string Instructions = "Left & right arrow keys to navigate to scenes.";
private readonly ILoader<ITexture> textureLoader;
private readonly ILoader<IFont> fontLoader; // Used to load the font for rendering the instructions
private readonly ITextureRenderer textureRenderer;
private readonly IFontRenderer fontRenderer; // Used to render the instructions
private readonly IAppInput<MouseState> mouse;
private ITexture? logoTexture;
private PointF logoPosition;
private IFont? font;
}

2.8b: Render the instructions

Add some code to render the instructions at the scene's top.

public override void Render()
{
ArgumentNullException.ThrowIfNull(this.logoTexture);
ArgumentNullException.ThrowIfNull(this.font);

// Convert the `PointF` to a `Vector2`
var logoPos = new Vector2(this.logoPosition.X, this.logoPosition.Y);

// Render the image
this.textureRenderer.Render(this.logoTexture, logoPos);

// Render the text
this.fontRenderer.Render(this.font, Instructions, new Vector2(WindowWidth / 2f, 20));

base.Render();
}

That is it for the first scene!!. We have created our first scene! Now we can move on to creating the second scene so we can demonstrate how to navigate from scene to scene.

Step 3: Create Scene B

The second scene is almost identical to the first. The only differences will be the image, the instruction text, and the mouse behavior. For the behavior, instead of following the mouse, we will simply set the image to the position where the mouse is clicked and then play a sound.

3.1: Copy the other scene

To make things easier, since SceneB will be almost identical to SceneA, copy the SceneA class and rename it to SceneB. Also, rename the constructor from SceneA to SceneB. After this is complete, we will make the necessary changes. Do not worry; we will show you what should be removed and added.

Perform the rename as shown below.

public class SceneA : SceneBase
public class SceneB : SceneBase
{
...
public SceneA()
public SceneB()
{
...
}
}

3.2: Add class fields

Here, we will add some additional class fields. You can use the class fields to hold the audio, which makes a sound every time the user clicks the mouse button and updates the instructions.

public class SceneB : SceneBase
{
private const int WindowWidth = 1000;
private const int WindowHeight = 1000;
private const string Instructions = "Left & right arrow keys to navigate to scenes.\nClick anywhere in the window.";
private readonly ILoader<ITexture> textureLoader;
private readonly ILoader<IFont> fontLoader;
private readonly ILoader<IAudio> audioLoader;
private readonly ITextureRenderer textureRenderer;
private readonly IFontRenderer fontRenderer;
private readonly IAppInput<MouseState> mouse;
private ITexture? logoTexture;
private PointF logoPosition;
private IFont? font;
private IAudio? audio;
private MouseState prevMouseState;
...
}

3.3: Create audio loader

SceneB uses audio as part of the behavior, which means we must create an audio loader.

public SceneB()
{
this.textureLoader = ContentLoaderFactory.CreateTextureLoader();
this.fontLoader = ContentLoaderFactory.CreateFontLoader();
this.audioLoader = ContentLoaderFactory.CreateAudioLoader();

this.textureRenderer = RendererFactory.CreateTextureRenderer();
this.fontRenderer = RendererFactory.CreateFontRenderer();

this.mouse = HardwareFactory.GetMouse();
}

3.4: Update load content

Now that we have the additional audio loader, we can update our LoadContent() method.

public override void LoadContent()
{
this.logoTexture = this.textureLoader.Load("kd-logo");
this.logoTexture = this.textureLoader.Load("velaptor-mascot");
this.font = this.fontLoader.Load("TimesNewRoman-Regular", 12);
this.audio = this.audioLoader.Load("mario-jump", AudioBuffer.Full);

// Set the default location of the texture to the center of the window
this.logoPosition = new Point(WindowWidth / 2, WindowHeight / 2);

base.LoadContent();
}

3.5: Update unload content

This scene has an additional type of content. Update the UnloadContent() method to unload the audio content.

public override void UnloadContent()
{
this.textureLoader.Unload(this.logoTexture);
this.fontLoader.Unload(this.font);
this.audioLoader.Unload(this.audio);

base.UnloadContent();
}

3.6: Change the behavior

The most significant change in SceneB is the behavior. The texture's position will be set, and a sound will be played wherever the user clicks the mouse.

public override void Update(FrameTime frameTime)
{
var currentMouseState = this.mouse.GetState();
this.logoPosition = currentMouseState.GetPosition();

ArgumentNullException.ThrowIfNull(this.audio);

var currentMouseState = this.mouse.GetState();

// If the left mouse button was fully clicked
if (currentMouseState.IsLeftButtonUp() && this.prevMouseState.IsLeftButtonDown())
{
this.audio.Play();
this.logoPosition = currentMouseState.GetPosition();
}

this.prevMouseState = currentMouseState;
base.Update(frameTime);
}

3.7: Adjust text position

We will update the Render() method to render the instructions. Since SceneB has larger instruction text, we need to position the text farther down so it is not rendered off the screen.

public override void Render()
{
ArgumentNullException.ThrowIfNull(this.logoTexture);
ArgumentNullException.ThrowIfNull(this.font);

// Convert the `PointF` to a `Vector2`
var logoPos = new Vector2(this.logoPosition.X, this.logoPosition.Y);

// Render the image
this.textureRenderer.Render(this.logoTexture, logoPos);

// Render the text
this.fontRenderer.Render(this.font, Instructions, new Vector2(WindowWidth / 2f, 20));
this.fontRenderer.Render(this.font, Instructions, new Vector2(WindowWidth / 2f, 30));

base.Render();
}

Step 4: Managed Scenes

Let's get into it!

There are two ways to manage scenes. You can either use the built-in scene manager or manage the scenes yourself. Why would you want to manage the scenes yourself? You may want more control over how you manage scenes, or the built-in scene manager does not have a particular feature you need.

Flexibility is important in many kinds of software, but it is even more important in games.

4.1: Update the game class

In the Game constructor, you will instantiate the scenes and add them to the manager.

REMINDER

You should have created the Game class in step Step 1.1.

Set the title and the window size in the constructor.

public Game()
{
Title = "Managed Scenes";
Width = 1000;
Height = 1000;
}

4.2: Keyboard scene navigation

We will be using the arrow keyboard keys to navigate between the scenes. Keyboard input is something you should be familiar with.

4.2a: Keyboard input fields

Add the 2 class fields and create a keyboard object.

public class Game : Window
{
private readonly IAppInput<KeyboardState> keyboard;
private KeyboardState prevKeyState;
...
}

4.2b: Create keyboard input object

In the constructor, create the keyboard object.

public Game()
{
Title = "Managed Scenes";
Width = 1000;
Height = 1000;

this.keyboard = HardwareFactory.GetKeyboard();
}

4.3: Create the scenes

Now, we can create instances of the scenes we made earlier in the guide and add them to the scene manager.

4.3a: Create scene instances

public Game()
{
Title = "Managed Scenes";
Width = 1000;
Height = 1000;

this.keyboard = HardwareFactory.GetKeyboard();

var sceneA = new SceneA { Name = "Scene A", };
var sceneB = new SceneB { Name = "Scene B", };
}

4.3b: Add scenes to the manager

public Game()
{
Title = "Managed Scenes";
Width = 1000;
Height = 1000;

this.keyboard = HardwareFactory.GetKeyboard();

var sceneA = new SceneA { Name = "Scene A", };
var sceneB = new SceneB { Name = "Scene B", };

SceneManager.AddScene(sceneA, setToActive: true);
SceneManager.AddScene(sceneB);
}

4.4: Scene navigation behavior

Now, add some logic to the Update() method. It will allow the user to navigate between the scenes if the left or right arrow keys are pressed.

protected override void OnUpdate(FrameTime frameTime)
{
var currentKeyState = this.keyboard.GetState();

// If the user has pressed the left or right arrow keys
if (currentKeyState.IsKeyDown(KeyCode.Right) && this.prevKeyState.IsKeyUp(KeyCode.Right))
{
SceneManager.NextScene();
}
else if (currentKeyState.IsKeyDown(KeyCode.Left) && this.prevKeyState.IsKeyUp(KeyCode.Left))
{
SceneManager.PreviousScene();
}

this.prevKeyState = currentKeyState;
base.OnUpdate(frameTime);
}

That is it! You have created a game with two scenes and can navigate between them using the arrow keys. As you can see, the code for managing the scenes is straightforward and minimal. The scene manager handles all the content loading, unloading, updating, and rendering.

Step 5: Run it!

Run the application! You should see the two scenes shown below.