Get up to 80 % extra points for free! More info:

Lesson 5 - Dividing a MonoGame Project into Components

In the previous lesson, Sounds, Music, Keyboard, and Mouse Input in MonoGame, we showed how to play sounds and process keyboard and mouse input.

Today, we're going to talk about the game architecture, and how to divide it into several separate components. After today's lesson we'll have everything prepared to start programming the Tetris game logic.

I think it's obvious that the game will have multiple scenes. Those will be the menu, the level itself, the credits screen with the game authors, the online high score table and so on. Each scene will then consist of other components, such as the falling blocks, the game area, or the endless clouds we programmed in the previous lessons.

If we put the whole game code into a single class, it'd be very messy. There are several ways to decompose the game code into multiple classes. During the entire course we'll take a look at all of them to see their pros and cons. In the course we'll also create a simple component-object architecture.

Game Components

One of the ways to decompose a game into several parts is called game components. These are already available for us as part of the MonoGame framework. Most tutorials don't talk about them very much, although it's a fairly good way to keep things nice and neat.

The game component is the most independent part of the game with as least dependencies as possible. Its internal structure resembles the game itself, more specifically, the Game class. It has the same methods, such as LoadContent(), Initialize(), Update() and so on. We can think of it as a game in our game. The component should be independent as much as possible. This condition is met by our endless clouds, so let's move it into a component.

Let's remove all the unnecessary experiments we created in the previous lessons from the game code, especially the mouse routines and some of the rendering. Specifically, let's remove mouseState, robotRectangle, and everything associated with it, the text rendering and the Enter key behavior. We'll keep our keyboard and music methods, as well as the moving clouds and the other two backgrounds. We'll keep the entire game Content.

To the Robotris project, add a new component and name it CloudsComponent (right-click the Robotris project in Solution Explorer -> Add -> New Item -> Class):

Visual Studio has generated a class which we'll inherit from Microsoft.Xna.Framework.GameComponent. The instance of our game (more precisely of it's ancestor, the Game class) is passed in the constructor.

The component will look like this:

public class CloudsComponent : Microsoft.Xna.Framework.GameComponent
{
    public CloudsComponent(Game game)
        : base(game)
    {

    }
}

The LoadContent(), Initialize(), and Update() methods are inherited and can be overridden.

Unfortunately, there's no template for creating components in MonoGame, so we have to create the component manually like this.

Renderable Components

A GameComponent cannot be rendered. Although such components may be useful in our game as well, we need the rendering for the clouds. Luckily, MonoGame offers one more component type, namely DrawableGameComponent. We'll change our GameComponent to this type in the class header:

public class CloudsComponent : Microsoft.Xna.Framework.DrawableGameComponent

We need to pass the common game data to the component somehow. We can do this by modifying the constructor parameter type to our game class. We just need to change it from Game to RobotrisGame. Next, we'll add a private robotrisGame field of the RobotrisGame type into the component, and store the instance passed in the constructor to that field. The modified component constructor will look like this:

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

Now let's add the LoadContent(), Initialize(), Update(), and Draw() methods to the component. Just type public override... to make Visual Studio offer you a list of methods that we can override:

Add MonoGame methods to the component - Tetris From Scratch

The component structure is ready. Since we're going to create more similar components in the future, showing it's source code here will become handy:

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

    private RobotrisGame robotrisGame;

    public CloudsComponent(RobotrisGame robotrisGame)
        : base(hra)
    {
        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);
    }
}

When you'll use this code to create another component (which we'll be doing in a moment), be sure to change the class name and the constructor name as well.

The class isn't documented, feel free to add comments in it. Now we'll move all the clouds code from our game class into the component. It should be simple, we just have to access Content in LoadContent() via robotrisGame.Content. In the same way we'll need to access spriteBatch via robotrisGame.spriteBatch inside Draw(). We don't have it public, so we'll modify it in the game class:

public BetterSpriteBatch spriteBatch;

Inside the component's Draw() method, remember to add both the Begin() and End() method calls, just like in the game's Draw() method!

Today we write a lot of code. Your code should be similar to something like this:

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

    private int change;
    private int direction;
    private Texture2D clouds;
    private Vector2 position;
    private RobotrisGame robotrisGame;

    public CloudsComponent(RobotrixGame robotrisGame)
        : base(robotrisGame)
    {
        this.robotrisGame = robotrisGame;
    }

    public override void Initialize()
    {
        position = new Vector2(0, 0);
        change = 0;
        direction = 1;

        base.Initialize();
    }

    protected override void LoadContent()
    {
        clouds = robotrisGame.Content.Load<Texture2D>(@"Sprites\spr_clouds");

        base.LoadContent();
    }

    public override void Update(GameTime gameTime)
    {
        // moving clouds to the left
        position.X--;
        // return to the start position when out of screen
        if (position.X < -(clouds.Width))
            position.X = 0;

        // clouds color change according to the direction
        change += direction;
        if (change >= 96)
            direction = -1;
        if (change <= 0)
            direction = 1;

        base.Update(gameTime);
    }

    public override void Draw(GameTime gameTime)
    {
        robotrisGame.spriteBatch.Begin();
        Color color = new Color(128 + change, 255 - change, 128 + change);
        for (int i = 0; i < 6; i++)
            robotrisGame.spriteBatch.Draw(clouds, new Vector2(position.X + i * clouds.Width, 0), color * 0.8f);
        robotrisGame.spriteBatch.End();

        base.Draw(gameTime);
    }

}

We'll finish it later. Now we'll add another renderable component to our game. We'll name it LevelComponent, and it'll contain the gameplay logic itself. We haven't implemented that yet, but we'll at least render the robot background. Surely you can do it. The steps are similar as for the clouds - move the background rendering from the game class into this component.

The result will look like this:

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

    private RobotrisGame robotrisGame;
    private Texture2D background;

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

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

    protected override void LoadContent()
    {
        background = robotrisGame.Content.Load<Texture2D>(@"Sprites\background_level");
        base.LoadContent();
    }

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

    public override void Draw(GameTime gameTime)
    {
        robotrisGame.spriteBatch.Begin();
        robotrisGame.spriteBatch.Draw(background, new Vector2(0, 0), Color.White);
        robotrisGame.spriteBatch.End();

        base.Draw(gameTime);
    }

}

Using Components in the Game

What's left in the game class is now only the rendering of the block background in Draw() and the keyboard state processing in Update(). Now let's move to Initialize() and create instances of our components, to which we pass the game class instance in their constructors:

CloudsComponent clouds = new CloudsComponent(this);
LevelComponent level = new LevelComponent(this);

Now comes the magic! The game has the Components property, which is the collection (List, to be more precise) of the components the game is made of. We'll add our components into this collection in the order we want them to be updated and rendered:

Components.Add(clouds);
Components.Add(level);

Let's run it. See that the game runs as well as before. The components are updated all by themselves and we don't have to do any extra work. The game code is clear and very short. The components contain only what is relevant to them. Everything works because of calling base.Initialize(), base.Update(), and other in the game methods which results in calling these methods in all the active game components as well.

Just a few final aesthetic changes - let's rename the Game1.cs file to RobotrisGame.cs. Also, since there will be more components, we'll create a folder for them (right-click on Robotris -> Add -> New Folder) and name it Components/. Now move both of our components in it.

Solution explorer of MonoGame game Robotris - Tetris From Scratch

We've reached the milestone from which we can finally start to implement the Tetris logic :P

We'll take a look at it in the next lesson, Tetris in MonoGame: Block.


 

Did you have a problem with anything? Download the sample application below and compare it with your project, you will find the error easily.

Download

By downloading the following file, you agree to the license terms

Downloaded 51x (15.06 MB)
Application includes source codes

 

Previous article
Sounds, Music, Keyboard, and Mouse Input in MonoGame
All articles in this section
Tetris From Scratch
Skip article
(not recommended)
Tetris in MonoGame: Block
Article has been written for you by David Capka Hartinger
Avatar
User rating:
4 votes
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 David learned IT at the Unicorn University - a prestigious college providing education on IT and economics.
Activities