Let’s create a mobile game with Xamarin and OpenGL – Part 5 (Scenes)

In this part we are going to implement a scene. Scenes are helpful when you need to display different views in your game. Also scenes will make it possible to separate specific game code from the engine code. The first scene your game will show is usually a menu where the user can choose to start the game or enter the settings. Another scene might be the actual game where you render sprites that are controlled by user interaction.

Adding scenes

First we are going to create an interface that will be used to implement different scenes. Create a new interface “IScene.cs” in “Engine.Shared/Abstractions”:

using OpenTK; using System; namespace Engine.Shared.Abstractions { public interface IScene : IDisposable { void Load(); void Render(Matrix4 viewProjection); } }

Next we need to create a class that “SceneManager.cs” in “Engine.Shared/Managers” that will handle our scenes, and determine which scene i currently active:

using Engine.Shared.Abstractions; namespace Engine.Shared.Managers { public class SceneManager { public IScene CurrentScene { get; private set; } public void LoadScene(IScene scene) { if (CurrentScene != null) { CurrentScene.Dispose(); } CurrentScene = scene; CurrentScene.Load(); } } }

Decapsule game specific code

In the last parts we were drawing sprites and handling user input. This was all done in the “GameController.cs” class which is part of the engine. Since we want our engine to be reusable so we can create many different games, we should decapsule some code from there. Now that we have the possiblity to create scenes it will be pretty simple. Create a new class “GameScene.cs” in “MyGame.Shared”:

using Engine.Shared; using Engine.Shared.Abstractions; using Engine.Shared.Drawables; using Engine.Shared.Managers; using Engine.Shared.Renderers; using OpenTK; using System.Reflection; namespace MyGame.Shared { public class GameScene : IScene { private readonly GameController gameController; private readonly InputManager inputManager; private readonly RectangleRenderer rectangleRenderer; private readonly SpriteRenderer spriteRenderer; private readonly TextureManager textureManager; private readonly BufferManager bufferManager; private DrawableRectangle rectangle1; private DrawableRectangle rectangle2; private DrawableSprite sprite1; public bool Show { get; set; } = true; public GameScene(GameController gameController, InputManager inputManager, RectangleRenderer rectangleRenderer, SpriteRenderer spriteRenderer, TextureManager textureManager, BufferManager bufferManager) { this.gameController = gameController; this.inputManager = inputManager; this.rectangleRenderer = rectangleRenderer; this.spriteRenderer = spriteRenderer; this.textureManager = textureManager; this.bufferManager = bufferManager; } public void Load() { var gameAssembly = Assembly.GetExecutingAssembly(); rectangle1 = rectangleRenderer.Create(512f, 512f); rectangle2 = rectangleRenderer.Create(256f, 256f); sprite1 = spriteRenderer.Create(textureManager.Load(gameAssembly, "Resources.Images.Cross.png")); inputManager.Listeners.Add(new SelectionInputListener(this)); } public void Render(Matrix4 viewProjection) { rectangleRenderer.BeforeRender(); rectangleRenderer.Render(rectangle1, new Vector4(1f, 0f, 0f, 1f), viewProjection); rectangleRenderer.Render(rectangle2, new Vector4(0f, 1f, 0f, 1f), viewProjection); rectangleRenderer.AfterRender(); if (Show) { spriteRenderer.BeforeRender(); spriteRenderer.Render(sprite1, new Vector4(0.5f, 0.5f, 1f, 1f), viewProjection); spriteRenderer.AfterRender(); } } public void Select(float x, float y) { Show = !Show; } public void Dispose() { textureManager.Dispose(); bufferManager.Dispose(); inputManager.Listeners.Clear(); } } }

As you can see a loot of code was moved into this new class. Also some dependencies need to be passed in the constructor so we are able to access the engine features also from here. Conversely this means we also need to make a few changes to the “GameController.cs” class in in “Engine.Shared”:

namespace Engine.Shared { public class GameController : IDisposable { ... public bool IsReady { get; set; } = false; public event Action Ready; public int Width { get; private set; } public int Height { get; private set; } public delegate IScene LoadSceneArguments(GameController gameController, InputManager inputManager, RectangleRenderer rectangleRenderer, SpriteRenderer spriteRenderer, TextureManager textureManager, BufferManager bufferManager); public GameController(FileManager fileManager) { ... this.sceneManager = new SceneManager(); } ... public void LoadScene(LoadSceneArguments create) { sceneManager.LoadScene(create(this, inputManager, rectangleRenderer, spriteRenderer, textureManager, bufferManager)); } public void Render() { GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); sceneManager.CurrentScene?.Render(this.viewProjection); } public void Resize(int width, int height) { if(width > 0 && height > 0) { ... if(!IsReady) { IsReady = true; Ready?.Invoke(); } } } ... } }

A new event “Ready” was added. We will need this event in the next step to be able to load a scene after the engine is loaded and the screen dimensions are available. A delegate “LoadSceneArguments” was defined that will be used like a factory to create a new instance of a scene and pass the dependencies. The LoadScene methods on the controller basically just calls the “LoadScene” method of “SceneManager” which receives a new instance of the scene. Inside the render method now the current scene, if applied, will be rendered.

In “MainActivity.cs” in project “MyGame.Android” a few things needed to be changed as well:

protected override void OnCreate(Bundle savedInstanceState) { ... var gameController = new GameController(new AndroidFileManager()); gameController.Ready += () => { gameController.LoadScene((gameController, inputManager, rectangleRenderer, spriteRenderer, textureManager, bufferManager) => new GameScene(gameController, inputManager, rectangleRenderer, spriteRenderer, textureManager, bufferManager)); }; ... }

A callback is registered for the “Ready” event of “GameController”. It will make sure that the first and only scene will be rendered, as soon as everything is initialized. Since the “GameScene” class is not part of the engine, its instance needs to be created in a common project.

The last change is that the input listener was moved into its own class file:

using Engine.Shared.Abstractions; namespace MyGame.Shared { public class SelectionInputListener : IInputListener { private readonly GameScene scene; public SelectionInputListener(GameScene scene) { this.scene = scene; } public bool Down(float x, float y) { scene.Select(x, y); return true; } public bool Move(float x, float y) { return false; } public bool Up(float x, float y) { return false; } } }

If you run the application you will notice no difference visually. But with scenes we are ready to start implementing a real game!

You can download the source code of this article here: