Lesson 1 - MonoGame Project Structure
In the introduction to the MonoGame, we introduced and installed the framework. Today, we're going to take a look at the structure of a MonoGame project and explain the basics of working with this framework.
I'd like to point out once again that this course is teaching the MonoGame framework, not the C# language itself. If you aren't familiar with the C# language, go through at least the first two C# courses and also the Files and I/O course.
We already have the MonoGame framework installed from the last time, so let's
start Visual Studio and create a new MonoGame Cross Platform Desktop Project,
which we'll name Robotris
:
If the name sounds similar to Tetris to you, you're right. The whole course will be focused on this particular game. We'll create the game itself and also the game menu, online score table, and the screen with the authors. You'll learn the basics of the framework and become capable of making any game
Project Structure
MonoGame projects have specific structure. In the new solution you'll find
the x64/
and x86/
folders, which, together with the
*.dylib
files, ensure the project to work cross-platform. For our
tutorial, the most important thing is the Game1.cs
file, which
contains the main game logic, and the Content/
folder, which
contains all the assets (images, sounds and music). We add the assets into this
folder using the MonoGame Pipeline Tool.
First, let's look at the Game1.cs
class that the Visual Studio
generated for us, and describe its code:
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; namespace Robotris { /// <summary> /// This is the main type for your game. /// </summary> public class Game1 : Game { GraphicsDeviceManager graphics; SpriteBatch spriteBatch; public Game1() { graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; } /// <summary> /// Allows the game to perform any initialization it needs to before starting to run. /// This is where it can query for any required services and load any non-graphic /// related content. Calling base.Initialize will enumerate through any components /// and initialize them as well. /// </summary> protected override void Initialize() { // TODO: Add your initialization logic here base.Initialize(); } /// <summary> /// LoadContent will be called once per game and is the place to load /// all of your content. /// </summary> protected override void LoadContent() { // Create a new SpriteBatch, which can be used to draw textures. spriteBatch = new SpriteBatch(GraphicsDevice); // TODO: use this.Content to load your game content here } /// <summary> /// UnloadContent will be called once per game and is the place to unload /// game-specific content. /// </summary> protected override void UnloadContent() { // TODO: Unload any non ContentManager content here } /// <summary> /// Allows the game to run logic such as updating the world, /// checking for collisions, gathering input, and playing audio. /// </summary> /// <param name="gameTime">Provides a snapshot of timing values.</param> protected override void Update(GameTime gameTime) { if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape)) Exit(); // TODO: Add your update logic here base.Update(gameTime); } /// <summary> /// This is called when the game should draw itself. /// </summary> /// <param name="gameTime">Provides a snapshot of timing values.</param> protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.CornflowerBlue); // TODO: Add your drawing code here base.Draw(gameTime); } } }
Namespaces
First there are several namespaces declared. Here we can see that there's no
MonoGame namespace and that everything is provided by the
Microsoft.Xna.Framework
namespace. This is because MonoGame is
based on the XNA framework, and the developers decided to use the original
namespace. That's all about the namespaces, and now let's move on to the
Game1
class.
The Game
Class
We'll rename the class to RobotrisGame
by moving the cursor on
Game1
, pressing F2, and confirming the following dialog.
We can see that the class inherits from the
Microsoft.Xna.Framework.Game
class.
We have 2 fields declared - graphics
and
spriteBatch
.
graphics
is aGraphicsDeviceManager
instance. It provides methods to resize the game window, switch to the fullscreen mode, and so on.spriteBatch
is aSpriteBatch
instance. It provides the sprite functionality. We have a new term here - sprite. Sprite is simply an image. It's a key element of all 2D games, since they're used for basically everything, like the game background, the game characters, or fonts (each letter is a single sprite). Sprites can also be animated.
The Constructor and
Initialize()
Method
Let's move on to the constructor. Here the graphics
field is
initialized, and the root folder for the Content
is also set. We
already know that it's the folder in which our game data will be stored.
In addition to the constructor, we also have the Initialize()
method. This may be confusing, especially since MonoGame doesn't tell us when to
use the constructor to initialize and when to use the Initialize()
method.
The difference is that the constructor is called when the game instance (or a game component instance, which we'll explain later) is created, and it should set up the game or the component. We won't add anything new here for the game. Another purpose of the constructor is to pass dependencies (we'll need them for the components that the game will be made from).
The Initialize()
method is then used to load the non-content
data (that means, no sprites, sounds, or music), such as map files. We'll also
do all the game initialization here, create all the necessary objects, set up
properties and so on.
The Initialize()
method already has an embedded line of
code:
base.Initialize();
It takes care of calling the Initialize()
method of all the game
components. We'll say more about the components later. Similar line can be also
found in other methods of the class.
LoadContent()
The class also have the LoadContent()
method which loads the
game content such as sprites, sounds, and music. We can see that the
spriteBatch
is also created here. At the end of the method we can
write custom behavior, which we need to run after the content is loaded. For
example, we can't get the height of a font that hasn't been loaded yet, so the
code would go to the LoadContent()
method, instead of the
Initialize()
method.
The UnloadContent()
method is called when the game is about to
quit. But because we'll always use Content
(which is a game
property with a ContentManager
instance) which releases the
resources automatically, the method isn't important to us.
The following two methods are the most important ones.
Update()
The Update()
method contains the real-time logic, that is,
everything that is processed in real time. This is usually the keyboard, mouse
or other controls input, collisions, and objects movement. Generally, we
implement reaction to events, objects movement, or even animations here. The
method is called 60 times per second. If we move a game character in this method
by 1, he'd walk with the same speed even on a faster computer. We can set a
different interval as a decimal number less than 1 using the
TargetElapsedTime
property, but we won't need that. It's important
to know that MonoGame will optimize automatically, so if the computer isn't fast
enough, the game will compensate it by omitting some frames.
In the method parameter we get a GameTime
instance that contains
the game time. It has two useful properties:
ElapsedGameTime
- ATimeSpan
of the elapsed time since the last update.TotalGameTime
- ATimeSpan
of the total elapsed time since the game start.
Thanks to these properties, we can work with the real time inside the
Update()
method, do something every second, minute and so on. We'll
show everything later in this course.
Another property of the gameTime
is
IsRunningSlowly
, which is true
whenever the game is
lagging and MonoGame omits to render some of the frames. That gives us the
opportunity to react to such situations, but we won't create such advanced games
that would need it yet
The method already contains behavior that quits the game when the Back button
on the gamepad is pressed. This is very important code that makes it able to
quit the game on XBox if we run it there. Among other things, we can see that
the game is quit using the Exit()
method.
Draw()
The Draw()
method takes care of the game rendering (as the name
suggests). Rendering is done by calling methods of a SpriteBatch
instance. The graphics device must be cleared before rendering each frame, to
avoid having there objects rendered in the last frame. This is achieved using
the Clear()
method and the blue color (the color actually doesn't
matter if the game has a custom background). Here we meet the Color
structure, which MonoGame uses to represent colors. It has several static
methods that return its instance set to specific colors.
The Game Loop
It's important to know how the game works under the hood. The game methods are called in a loop called game loop in the following order:
Initialize()
LoadContent()
Update()
Draw()
UnloadContent()
The Update()
and Draw()
methods are called
repeatedly until the game is quit. The loop explained in a flowchart would look
like this:
In the next lesson, Adding Content in MonoGame, we'll add content into our project, such as sprites, fonts, sounds and music
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 27x (141.93 kB)
Application includes source codes