Summer BF 2
Save up to 80 % on our C and C++ e-learning courses. Only this week!
Get up to 80 % extra points for free! More info

Lesson 12 - Tetris in MonoGame: Game Scene Management

In the previous lesson, Tetris in MonoGame: Level Features, we improved the block rotation and added a ghost block into the level. Now the Tetris game can be considered as complete. You can, of course, add another mechanics to it, such as power-ups, game modes, etc.

Today we're going to focus on game scenes, one of which could be, for example, the game menu.

Game Scenes

We know that MonoGame isn't an engine, but a framework. Therefore, MonoGame itself, apart from components, doesn't provide any way to manage and switch between game scenes. By a game scene is meant some separate part of the game, such as the level, the main menu, the score table, the credits, and so on. The main menu would certainly have a different logic than the Tetris level. We also need to be able to switch between those game scenes and store their state.

There are, of course, many ways to do that. The most silly way would be to write the whole game code into a single file and introduce many states in it (the menu state, the game state, etc.). The final file would probably look very messy. Since we know how to use game components, we'll definitely do so. Sometimes you can see projects that have components for each individual scene. These components are then being switched (e.g. we switch from the Menu component to the Level component). This may be sufficient for small games, but not for larger projects. The problem is that we'd be limited to have just one component per scene. Then we couldn't have, for example, the clouds, level, and other parts of a complex project as separated components.

The solution we're going to show here defines a game scene as a set of components. All components are part of the game project, and after switching the current scene, only those that are used in that particular scene are enabled, while all the other components are disabled.

Let's add a GameScene class to the project. Its instances will represent individual game scenes. We'll make the class public and add two private fields in it. The first one will be a collection of components that the scene consists of, and the second one will be the RobotrisGame instance:

private List<GameComponent> components;
private RobotrisGame robotrisGame;

To use the GameComponent type, we need to add the following using statement:

using Microsoft.Xna.Framework;

We'll add a public AddComponent() method that stores the passed component in the private components collection. The passed component is also stored in the game's Components collection, but it can only be added when it's not already there. Remember that some scenes may use the same components. The method will look like this:

public void AddComponent(GameComponent component)
{
    components.Add(component);
    if (!robotrisGame.Components.Contains(component))
        robotrisGame.Components.Add(component);
}

Because the collection is private, adding components must be done using this method, which ensures that the components will be added into the game's Components as well.

In the class constructor, we'll pass the game instance as a parameter, just as we do in other components and game objects. Also, we'll use the params keyword, which allows us to enter multiple GameComponent instances as other parameters. We'll loop through them and add them using our method.

public GameScreen(RobotrisGame robotrisGame, params GameComponent[] components)
{
    this.robotrisGame = robotrisGame;
    this.components = new List<GameComponent>();
    foreach (GameComponent component in components)
    {
        AddComponent(component);
    }
}

As the last method we'll add ReturnComponents(), which will return the components used by the game scene as an array:

public GameComponent[] ReturnComponents()
{
    return components.ToArray();
}

To make it complete, you can add your own method that'll remove a component. It may be handy in larger projects, but for our needs it's not necessary.

Menu

To have something to test on, we'll add MenuComponent into our game project (add it in the Components/ folder, but keep the namespace only as Robotris). It'll be another DrawableComponent. We've shown how to add one in the Dividing a MonoGame Project into Components lesson. Just to be sure, here's the class code:

public class MenuComponent : Microsoft.Xna.Framework.DrawableGameComponent
{

        private RobotrisGame robotrisGame;

        public MenuComponent(RobotrisGame robotrisGame)
                : base(robotrisGame)
        {
                this.robotrisGame = robotrisGame;
        }

        public override void Initialize()
        {
                base.Initialize();
        }

        protected override void LoadContent()
        {
                base.LoadContent();
        }

        public override void Update(GameTime gameTime)
        {
                base.Update(gameTime);
        }

        public override void Draw(GameTime gameTime)
        {
                base.Draw(gameTime);
        }
}

We'll leave the component blank for now.

Managing Game Scenes

We'll put the game scene management into the RobotrisGame class. It makes sense and is easily accessible from all components. Another option would be to create a separate game scene manager.

Le's go to RobotrisGame.cs where we'll add two public game scenes as class fields. These will be the menu and level scenes:

public GameScene menuScene, levelScene;

We'll create the menu component in Initialize(), next to the other components:

MenuComponent menu = new MenuComponent(this);

We'll completely remove the part adding components to the game's Components collection. That's because the game scene already does that for us. Instead, we'll instantiate the game scenes:

menuScene = new GameScene(this, clouds, menu);
levelScene = new GameScene(this, clouds, level);

We can see that the same Cloud component instance can be used in multiple scenes.

We'll add a private method for enabling and disabling the scenes, taking the component and the enabled state (true/false) as its parameters. MonoGame's GameComponent has the Enabled property that enables/disables its Update() method execution. If we set it to false, the component stops working. If the component is of the DrawableGameComponent type (which is in most cases), we also have to set the Visible property, which specifies whether the Draw() method is being executed and thus whether the component is rendered.

private void ChangeComponentState(GameComponent component, bool enabled)
{
    component.Enabled = enabled;
    if (component is DrawableGameComponent)
        ((DrawableGameComponent)component).Visible = enabled;
}

If we disable the component this way, it won't be rendered nor updated, but it'll still exist and keeps its state until it's enabled again. This may sometimes be very useful (e.g. for switching between different locations, minigames, starting a level from the menu, etc.).

Let's go back to Initialize(). We'll loop through all the game components and disable them, right after creating the scenes instances:

foreach (GameComponent component in Components)
{
    ChangeComponentState(component, false);
}

Finally, we'll add the scene switching method itself. It'll be public and take the scene we want to switch to as its parameter.

public void SwitchScene(GameScene scene)
{
}

First, we'll get the components used by the scene:

GameComponent[] usedComponents = scene.ReturnComponents();

Then we'll check all components in the array to see whether they're used, and change their state accordingly.

foreach (GameComponent component in Components)
{
    bool isUsed = usedComponents.Contains(component);
    ChangeComponentState(component, isUsed);
}

We'll also update the previous keyboard state in the method, because switching the components causes it to skip updating:

previousKeyboardState = keyboardState;

We'll then switch to the current scene at the end of the LoadContent() method (so after everything has been loaded):

SwitchScene(menuScene);

Now when we run the game, we should see the menu scene, which is currently just clouds.

Sample Tetris game in MonoGame

Let's move to the Update() method of MenuComponent, and add a piece of code that'll start the game after pressing the Enter key:

if (robotrisGame.NewKey(Keys.Enter))
                robotrisGame.SwitchScene(robotrisGame.levelScene);

Now let's try our scene. We can see we got the switching functionality.

In the next lesson, Tetris in MonoGame: Game Menu, we'll focus on the game menu :)


 

Download

Downloaded 2x (15.99 MB)
Application includes source codes

 

Previous article
Tetris in MonoGame: Level Features
All articles in this section
Tetris From Scratch
Article has been written for you by David Capka
Avatar
Do you like this article?
No one has rated this quite yet, be the first one!
The author is a programmer, who likes web technologies and being the lead/chief article writer at ICT.social. He shares his knowledge with the community and is always looking to improve. He believes that anyone can do what they set their mind to.
Unicorn university The author learned IT at the Unicorn University - a prestigious college providing education on IT and economics.
Activities (3)

 

 

Comments

To maintain the quality of discussion, we only allow registered members to comment. Sign in. If you're new, Sign up, it's free.

No one has commented yet - be the first!