Introduction to MonoGame

One Framework to Rule them All

Subsections of Introduction to MonoGame

Introduction

In this class we are using the MonoGame framework to build our game projects. MonoGame is an open-source, cross-platform framework built on C# and .NET. I like to use it for this course because it is truly a framework, not a game engine. Rather, it supplies tools that provides abstractions for some of the more technically challenging details of developing game software in a non-opinionated manner.

From the developer standpoint, there are several clear benefits:

  • You write code in familiar C#
  • You have access to all the .NET libraries you are familiar with
  • Memory is managed (i.e. you don’t need to write allocation/de-allocation code as you would in C/C++)
  • Access and configuration of the graphics hardware is simplified (if you’ve ever written raw DirectX initializers, you’ll appreciate this)

XNA Roots

MonoGame is the open-source descendant of Microsoft’s XNA. In fact, the first builds of MonoGame were direct ports of XNA, and MonoGame still uses the Microsoft.Xna namespaces. XNA was created by Microsoft to encourage indie and community game development for the Xbox 360, Windows PCs, and the Windows Phone. From the developer perspective, it was an extremely successful program; many classic games were developed using XNA, and the XBox 360 had a thriving marketplace for independent games. Moreover, if you owned an XBox 360, you could deploy your XNA game directly to it using only a network cable; effectively any XBox 360 could be used as a dev kit!

However, the Windows phone was not a market success, and as the XBox One neared development, Microsoft chose not to spend the resources necessary to adapt XNA to it, instead encouraging the indie developer community to adopt the Unity Game Engine. Eventually, Microsoft announced the official retirement of XNA related technologies on April 1, 2014.

MonoGame was one of several attempts to re-implement the XNA 4 API and provide a successor to the XNA platform. Thus it has most of the XNA functionality, plus a few additions. Moreover, it can be targeted at a wide range of platforms, though for this class we’ll stick with Windows.

Website and Documentation

You can find the documentation for MonoGame at https://docs.monogame.net/. This includes Articles discussing MonoGame and the published API.

See the Getting Started section for details on installing MonoGame and starting your first project.

Warning

MonoGame’s libraries are now loaded as a Nuget package, which means the first time you create a MonoGame app on your computer, it will need to download these packages. This happens automatically, but takes a moment. Until they finish downloading, your game will report that items in the Microsoft.XNA.Framework namespace cannot be found.

Additionally, MonoGame uses the dotnet mgcb command-line tool to build content. As Nuget downloads its packages under your user account, and Visual Studio places projects in your user account, this means your user account will be in the path to both. If your user folder has spaces in the name, i.e. “C:/Users/Bob Test”, the space will cause an error when the build process attempts to build the content. The only fix I am aware of for this is to create another user account that does not contain spaces, and run your builds from there.

The Game Class

At the heart of an XNA project is a class that inherits from Game. This class handles initializing the graphics device, manages components, and most importantly, implements the game loop.

The MonoGame Game Loop

As you saw in the Game Loop Chapter of Game Programming Patterns:

A game loop runs continuously during gameplay. Each turn of the loop, it processes user input without blocking, updates the game state, and renders the game. It tracks the passage of time to control the rate of gameplay.

This is precisely what the Game class implements for us - a loop that 1) processes user input, 2) updates the game state, and 3) renders the game.

As a MonoGame developer, you create a new class that inherits from Game (if you use one of the MonoGame templates, this class will probably be named Game1, but feel free to rename it). Then, you can write code to execute during steps 2 and 3 of the loop by overriding the virtual methods: Update(GameTime gameTime) and Draw(GameTime gameTime). These methods are invoked by Game each time it executes the game loop. In software engineering parlance, we call this kind of method a “hook,” as we can use it to pull new functionality into the existing class.

Time and the Game Loop

Time in the MonoGame framework is typically measured using System.TimeSpan struct. While this struct has many general uses, for games we almost totally rely on the TimeSpan.TotalSeconds property, which is a double representing the full length of time the TimeSpan represents as a double measured in seconds.

You probably noticed that both methods have a GameTime object as a parameter. This is a class used to store measurements of time in the game. It is basically a data object with three properties:

  • GameTime.ElapsedGameTime is a TimeSpan measuring the time that elapsed between this and the previous call to Update(GameTime). In other words, it is the time between passes in the game loop.
  • GameTime.TotalGameTime is a TimeSpan measuring the total time since the game was started.
  • IsRunningSlowly is a Boolean indicating that the game is lagging (more on this shortly)

As you saw in the Game Programming Patterns, the game loop can be clamped (fixed), or run as fast as possible. MonoGame allows you to choose either strategy. You can specify the strategy you want by changing the Game.IsFixedTimeStep boolean property. When using a fixed time step, you can specify the desired time step (the time between game loop passes) by setting the Game.TargetElapsedTime property to a TimeSpan of the desired duration.

By default, MonoGame adopts the fixed update time step, variable rendering strategy from Game Programming Patterns. If a pass through the game loop takes too long, it skips rendering (the Game.Draw() is not invoked), and the TimeSpan provided to the Game.Update() method has its IsRunningSlowly property set to true. The game will continue to drop rendering frames until the Game.MaximumElapsedTime value is reached, at which point it will invoke Game.Draw(). 1

Setting the Game.IsFixedTimeStep property to false instead runs the game loop as fast as possible.

Info

You might be wondering what timestep you should use. It’s a tricky question, but there are some easy parameters you can use to frame it:

Fast enough to provide the illusion of motion The human brain begins to translate quickly changing images to motion around 16 frames per second. That’s a timestep of $ 0.0625 $

At a multiple of 30 frames per second At least, in the Americas and parts of Asia televisions and monitors refresh at a multiple of 30, as AC power is delivered at 60 hertz cycles (other parts of the world use 50 hertz). Cheaper monitors run at 30 frames per second (a timestep of $ 0.03\overline{3333} $), while most modern monitors and televisions run at 60 frames per second (a timestep of $ 0.01\overline{6666} $) and high-end devices might run at 120 frames per second (a timestep of $ 0.008\overline{3333} $).

Slow enough your game doesn’t lag This speed will vary depending on the hardware in question. But if your game is consistently slow, you need to either increase the timestep or optimize your code.

By default, the Game.TargetElapsedTime is set to the refresh rate of your monitor - which in most cases will be the ideal rate (as drawing frames more often gives no benefit).

The Game Window

While MonoGame does support 3D rendering, we’re going to start with 2D games. When working in 2D, MonoGame uses a coordinate system similar to the screen coordinates you’ve seen in your earlier classes. The origin of the coordinate system $ (0, 0) $, is the upper-left corner of the game window’s client area, and the X-axis increases to the right and the Y-axis increases downward.

The part of the game world that appears on-screen is determined by the active viewport, represented by a Viewport struct - basically a rectangle plus a minimum and maximum depth. From the game class, the active viewport is normally reached with GraphicsDevice.Viewport. It defines the portion of the game world drawn on-screen with four measurements:

  • Viewport.X the farthest left range of the viewport along the X-axis
  • Viewport.Y the upper range of the viewport along the Y-axis
  • Viewport.Width the farthest right range of the viewport along the X-Axis
  • Viewport.Height the lower range of the viewport along the Y-axis
Info

You can set the viewport to a subsection of the screen to render into only a portion of the screen - useful for split-screen games, radar systems, missile cameras etc. We’ll explore this technique in a later chapter.

Aspect Ratios and the TitleSafe Region

In addition to these measurements, the Viewport class has a AspectRatio property which returns the aspect ratio (the width/height) of the window (or full screen). XNA was originally developed during the transition from the old 3:1 television standard to the newer 16:9 widescreen television resolution, so aspect ratio was an important consideration.

Along with this is the idea of a title safe region - a part of the screen that you could expect to be visible on any device (where titles and credits should be displayed, hence the name). Televisions often have a bit of overscan, where the edges of the displayed image are cut off. Further, if a 3:1 aspect ratio video is displayed on a 16:9 screen, and the player doesn’t want to have black bars on the left and right edges of the screen, one of the possible settings will scale the image to fill the space, pushing the top and bottom of the scene into the overscan regions. Filling a 3:1 screen with a 16:9 image works similarly, except the sides are pushed into the overscan area.

Thus, the Viewport also has a TitleSafeArea which is a Rectangle defining the area that should always be shown on a television. It is a good idea to make sure that any UI elements the player needs to see fall within this region.

The Game Window

The window itself is exposed through the GameWindow class. There should ever only be one instance of the GameWindow class for a given game. It is created by the Game and assigned to the Game.Window property during initialization. It exposes properties for working with the window. For example, you can set your game title with:

Window.Title = "My Cool Game Title";

This will update what Windows displays in the title bar of the window, as well as when hovering over the icon in the start bar, in the task manager, etc.

The GameWindow class handles much of the work of embedding the game within the host operating system. For example, when the game looses focus, the Window.Active property is false, and the game loop stops updating (effectively pausing the game).

You shouldn’t need to use most of its properties.

Setting the Game Window Size

If you want to specify the size of the window for your game, you can do so by setting the BackBufferWidth and BackBufferHeight properties of the Graphics object. For example to set the window to 760 x 480, you would add the following code to the Game.Initialize() method (assuming you used the latest MonoGame project template):

    _graphics.PreferredBackBufferWidth = 760;
    _graphics.PreferredBackBufferHeight = 480;
    _graphics.ApplyChanges();

You can make the window any size you like - but if it is larger than your screen resolution, you won’t be able to see all of it. To make your game fullscreen and exactly the size of your monitor, use:

    _graphics.PreferredBackBufferWidth = GraphicsDevice.DisplayMode.Width;
    _graphics.PreferredBackBufferHeight = GraphicsDevice.DisplayMode.Height;
    _graphics.IsFullScreen = true;
    _graphics.ApplyChanges();
Warning

Be sure that you have a means to exit full screen before you run a game in debug mode! Otherwise, you may not be able to reach VisualStudio’s window to stop the debugger. The default template includes code to close the game window when ESC is pressed. Also, the default GameWindow configuration uses ALT+ F4 to close the window.

Game Initialization

Before we actually move into the game loop, we need to initialize the game - load all of its needed parts and set all initial values. The MonoGame Game class provides two virtual hook methods for doing this: Game.Initialize() and Game.LoadContent().

You might be wondering why we have two methods, or asking why the constructor is not included in this count. These are all good questions. First, in the documentation we see that Initialize():

Initializes attached GameComponent instances and calls LoadContent().

And, if we look at the template-generated Game.Initialize() method:

protected override void Initialize()
{
    // TODO: Add your initialization logic here
 
    base.Initialize();
}

We can see that the Game.Initialize() is only invoked after our own initialization logic. Thus, it is largely a matter of controlling timing. We only want content (i.e. sounds, textures, etc.) to be loaded once the game is fully initialized.

This is largely because we are using the 3D hardware, which has its own RAM (video memory). Ideally, textures should be stored there for the fastest and most efficient rendering. So we want to delay loading our graphics until we have finished configuring the graphics device. Thus, we do any graphics card configuration in the Initialize() method, before invoking base.Initialize().

And why not the constructor? What if we want the player to be able to, upon loosing, immediately restart the game? If our initialization logic is in Initialize(), we can simply re-invoke that method. We can’t re-construct the Game class though, as it is tied to the life of our application.

Finally, the Game.LoadContent() is invoked after both our Initialze() and the base.Initialize() methods have finished. This means the graphics card is fully initialized, and we can now transfer graphics assets into its memory.

A Simple Example

Let’s look at a super-simple example demonstrating the game loop. We’ll have a ball that moves a bit each frame and bounces off the sides of the window.

Variable Declarations

First, we need to know the ball’s position and velocity. In a two-dimensional game, we would probably use a Vector2 Struct to represent these. We also need a Texture2D to be the image of the ball:

// The ball's information
private Vector2 _ballPosition;
private Vector2 _ballVelocity;
private Texture2D _ballTexture;

Add these lines to the top of the Game1 class definition, along with the other field declarations.

Initialize() Additions

Then, in our Initialize() method, let’s center the ball on the screen:

    // Position the ball in the center of the screen
    _ballPosition.X = GraphicsDevice.Viewport.Width / 2;
    _ballPosition.Y = GraphicsDevice.Viewport.Height / 2;

We’ll also give it a random velocity:

    // Give the ball a random velocity
    System.Random rand = new System.Random();
    _ballVelocity.X = (float)rand.NextDouble();
    _ballVelocity.Y = (float)rand.NextDouble();
    _ballVelocity.Normalize();
    _ballVelocity *= 100;

For now we’ll use the System.Random class you are used to. For some game purposes, it is sufficient, though its randomness is not as random as we’ll need for some kinds of games. Also, note that because Random.NextDouble() returns a double, and Vector2 uses floats, we need to implicitly cast the result. Finally, Vector2.Normalize() will shorten our velocity vector to be of length $ 1 $, which the _ballVelocity *= 100; line scales up to a length of $ 100 $. Eventually this will mean our ball will be traveling 100 pixels per second.

Adding the Image to the Project

As we said above, LoadContent() is where we load our assets. For now, we just need an image of a ball. However, getting this image into our game takes a bit of doing.

First, we need to find an image to use - a .jpeg, .gif, or .png will work fine. Feel free to use this one a golden ball a golden ball.

Look in the Content folder of your solution explorer in Visual Studio. You should also notice a file, Content.mgcb in the same folder. This is a listing of all content to bring into the game. Go ahead and open it; it will look something like:

The MGCB Editor The MGCB Editor

Tip

If instead of the editor program a text file is loaded in your VisualStudio instance, try right-click the file and choose “open with”. From the dialog, choose the mgcb-editor-wpf. If it is not listed, you may need to install it. From the command line:

> dotnet tool install --global dotnet-mgcb-editor 
> mgcb-editor --register

Click the “Add Existing Item” toolbar button:

Add Existing Item toolbar button Add Existing Item toolbar button

In the Open dialog that appears, select the ball image and click “Open”. Then in the next dialog, choose “Copy the file to the directory”:

Add File Dialog Add File Dialog

Finally, save the .mgcb file:

Save the .mgcb file Save the .mgcb file

Now the image will be built into a game-specific binary format as part of the build process. We’ll delve deeper into how this works in the chapter on the Content Pipeline.

LoadContent() Additions

To bring the ball texture into the game, we need to load it with a ContentManager class by invoking the ContentManager.Load<T>() method with the name of our content. The Game class has one already created and ready in the Game.Content property; we’ll use it:

_ballTexture = Content.Load<Texture2D>("ball");

Add this line just below the #TODO: use this.Content to load your game content here line in the LoadContent() method.

Note that we use the filename (sans extension) to identify the content file to load, and that we also specify the type of object it should be loaded as (in this case, Texture2D).

At this point, if we were to run the game everything would be initialized. Now we need to handle updating and rendering the game world.

The Update Method

As we mentioned before, the virtual Game.Update(GameTime gameTime) method is a hook for adding your game’s logic. By overriding this method, and adding your own game logic code, you fulfill the update step of the game loop.

This is where you place the simulation code for your game - where the world the game is representing is updated. Here, all your actors (the parts of the game world that move and interact) are updated.

Note the GameTime parameter - it provides us with both the total time the game has been running, and the time that has elapsed between this and the previous step through the game loop (the frame). We can use this in our physics calculations.

Our Simple Example

So in our example, we want the ball to move around the screen, according to its velocity. If you remember your physics, the velocity is the change in position over time, i.e.:

$ \overrightarrow{p'} = \overrightarrow{p} + \overrightarrow{v} * t $

We can express this in C# easily:

_ballPosition += _ballVelocity * (float)gameTime.ElapsedGameTime.TotalSeconds;

Add this code to the Update() method, just below the // TODO statement. Note that MonoGame provides operator overrides for performing algebraic operations on Vector2 structs, which makes writing expressions involving vectors very much like the mathematical notation. Also, note again that we have to cast the double TotalSeconds into a float as we are loosing some precision in the operation.

Also, note that because we multiply the velocity by the elapsed time, it does not matter what our timestep is - the ball will always move at the same speed. Had we simply added the velocity to the position, a game running with a 60fps timestep would be twice as fast as one running at 30fps.

Info

You may encounter advocates of using a hard-coded fixed time step to avoid calculations with elapsed time. While it is true this approach makes those calculations unnecessary (and thus, makes your code more efficient), you are trading off the ability of your game to adjust to different monitor refresh rates. In cases where your hardware is constant (i.e. the Nintendo Entertainment System), this was an easy choice. But with computer games, I would advocate for always calculating with the elapsed time.

Keeping the Ball on-screen

We need to handle when the ball moves off-screen. We said we wanted to make it bounce off the edges, which is pretty straightforward. First, we need to determine if the ball is moving off-screen. To know when this would happen, we need to know two things:

  1. The coordinates of the ball
  2. The coordinates of the edges of the screen

For 1, we have _ballPosition. Let’s assume this is the upper-right corner of the ball image. We’ll also need to factor in the size of the ball. The image linked above is 64 pixels, so I’ll assume that is the size of the ball we’re using. Feel free to change it to match your asset.

For 2, we can use GraphicsDevice.Viewport to get a rectangle defining the screen.

It can be very helpful to draw a diagram of this kind of setup before you try to derive the necessary calculations, i.e.:

A diagram of the game A diagram of the game

To check if the ball is moving off the left of the screen, we could use an if statement:

if(_ballPosition.X < GraphicsDevice.Viewport.X) {
    // TODO: Bounce ball
}

We could then reverse the direction of the ball by multiplying its velocity in the horizontal plane by $ -1 $:

    _ballVelocity.X *= -1;

Moving off-screen to the right would be almost identical, so we could actually combine the two into a single if-statement:

    // Moving offscreen horizontally
    if (_ballPosition.X < GraphicsDevice.Viewport.X || _ballPosition.X > GraphicsDevice.Viewport.Width - 64)
    {
        _ballVelocity.X *= -1;    
    }

Note that we need to shorten the width by 64 pixels to keep the ball on-screen.

The vertical bounce is almost identical:

    // Moving offscreen vertically
    if (_ballPosition.Y < GraphicsDevice.Viewport.Y || _ballPosition.Y > GraphicsDevice.Viewport.Height - 64)
    {
        _ballVelocity.Y *= -1;
    }
Info

Our bounce here is not quite accurate, as the ball may have moved some pixels off-screen before we reverse the direction.
In the worst case, it will actually so far off screen that with floating point error, it might be off-screen next frame as well (which will result in it getting stuck). But as long as our ball is traveling less than its dimensions each frame, we should be okay.

Now we just need to draw our bouncy ball.

The Draw Method

The Game.Draw(Game.Update(GameTime gameTime) method is a another hook, this one for adding your game’s rendering code. By overriding this method, and adding your own rendering code, you fulfill the draw step of the game loop.

MonoGame uses the graphics hardware to render the scene, along with double buffering. Thus, when we render, we are drawing into a back buffer, and once that drawing is complete, we flip the buffers so that the one we just finished is what ends up being rendered on-screen, and we now can start drawing into the next back buffer.

This is why we request a certain window size by setting Game.PreferredBackBufferWidth and Game.PreferredBackBufferHeight. It is an acknowledgement that we are working with the back buffer (all buffers end up this size). If our window’s client area is a different size, then the texture the back buffer contains is scaled to fit the client dimensions. If this is not the same aspect ratio, our game will appear squished in one dimension and stretched in the other.

This is why resizing the window is disabled by default in MonoGame. If you let the user resize the window, you’ll want to also adjust your buffers to compensate.

Our Simple Example

Our game is two-dimensional. Since MonoGame uses the 3D rendering hardware, this means we’re really pretending a 3D scene is two-dimensional. You might think of it as a bunch of cardboard cut-outs all facing the audience. To make life easier for us, MonoGame provides the SpriteBatch class to manage all of those cut-outs.

We’ll dig deeper into how it works in a later chapter. But for now, know that we can render any number of images on-screen by invoking SpriteBatch.Draw() between a SpriteBatch.Begin() and a SpriteBatch.End() invocation.

For our simple ball, this breaks down to:

    _spriteBatch.Begin();            
    _spriteBatch.Draw(_ballTexture, _ballPosition, Color.White);
    _spriteBatch.End();

Effectively, we’re saying we want to draw our texture _ballTexture at _ballPosition, and apply a white color to the image (i.e. leave it the color it already is).

This should be placed after the // TODO in the Base We're going to be rendering with the SpriteBatch` class.

That wraps up our simple exercise. You should be able to run the game now, and see the ball bounce around the screen.

Summary

In this chapter we looked at how MonoGame implements the Game Loop pattern within its Game class. We also saw how the Game class interacts with the GameWindow class, which provides an abstraction of the operating system’s window representation. We saw how we can add our own custom code into the MonoGame game loop by overriding the Game.Update() and Game.Draw() methods, as well as the overriding Game.Initialize() and Game.LoadContent() to set up the game world.

We briefly explored ideas about performing physics calculations within that game world, as well as representing position and velocity of game actors with Vector2 objects. We also touched on how MonoGame renders 2D games with 3D hardware, and used a SpriteBatch instance to render a Texture2D to the screen. Finally, we animated a bouncing ball using all of these ideas. The one aspect of the game loop we did not cover though, is input, which we’ll take a look at in the next chapter.