Subscribe to GameFromScratch on YouTube Support GameFromScratch on Patreon


15. June 2015

 

In this chapter we are going to look closely at the structure of a typical XNA game.  By the end you should have a better idea of the life cycle of a typical MonoGame application, from program creation, loading content, the game loop, unloading content and exiting.

 

If you prefer videos to text, you can watch this content in HD video right here.

 

Let’s start by looking at the code for an automatically generated application, stripped of comments.  There are two code files in the generated project, Program.cs and [YourProjectName].cs.  Let’s start with Program.cs

using System;

namespace Example1
{
#if WINDOWS || LINUX
    public static class Program
    {
        [STAThread]
        static void Main()
        {
            using (var game = new Game1())
                game.Run();
        }
    }
#endif
}

The heart of this code is the creation of our Game object, then calling it’s Run() method, which starts our game executing, kicking off the game loop until the Game execution finishes.  We will talk about the game loop in a bit more detail later on.  Otherwise this is a standard C# style application, with Main() being the applications entry point.  This is true for  Windows and Linux applications at least, which is the reason for the #if  preprocessor directive.  We will discuss the entry point of various other platforms later on, so don’t worry about this too much.  Also note, if you didn’t select Windows as your platform when creating this project, your own Program.cs file contents may look slightly different.  Again, don’t worry about this right now, the most important thing is to realize that Program creates and runs your Game derived class.

Predefined Platform Values

One of the major features of MonoGame over XNA is the addition of several other supported platforms. In addition to WINDOWS and LINUX symbols, the following platforms have been defined:

  • ANDROID
  • IOS
  • LINUX
  • MONOMAC
  • OUYA
  • PSM
  • WINDOWS
  • WINDOWS_PHONE
  • WINDOWS_PHONE81
  • WINDOWSRT
  • WEB

Of course, new platforms are being added all of the time, so this list may not still be current. You can look up the definitions in the MonoGame sources in the file /Build/Projects/MonoGame.Framework.definition in the MonoGame GitHub repository.

Please note, there are plenty of other defines per platform, for example iOS, Android, MacOS, Ouya and WindowsGL all also define OPENGL. You can use these predefined symbols for implementing platform or library specific code.

Now let's move on to our Game class

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace Example1
{
    public class Game1 : Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }

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

        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);
        }

        protected override void UnloadContent()
        {
        }

        protected override void Update(GameTime gameTime)
        {
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == 
                ButtonState.Pressed || Keyboard.GetState().IsKeyDown(
                Keys.Escape))
                Exit();
            base.Update(gameTime);
        }

        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);
            base.Draw(gameTime);
        }
    }
}

Our game is derived from the class Microsoft.Xna.Framework.Game and is the heart of our application. The Game class is responsible for initializing the graphics device, loading content and most importantly, running the application game loop. The majority of our code is implemented by overriding several of Game’s protected methods.

 

Let’s take a look at the code, starting from the top.  We create two member variables, graphics and spriteBatch.  GraphicsDeviceManager requires a reference to a Game instanceWe will cover the GraphicsDeviceManager and SpriteBatch classes shortly in the graphics chapter, so please ignore them for now.

 

Next we override the Initialize(), LoadContent and UnLoadContent methods.  The most immediate question you’ve probably got is, why have an Initialize() method at all, why not just do initialization in the constructor.  First, behavior of calling a virtual function from a constructor can lead to all kinds of hard to find bugs in a C# application.  Second, you don’t generally want to do any heavy lifting in a constructor.  The existence of LoadContent() however, will often leave you will an empty Initialize() method.  As a general rule, perform inititalizations that are required ( like GraphicsDeviceManager’s allocation ) for the object to be valid in the constructor, perform long running initializations ( such as procedural generation of terrain ) in Initialize() and load all game content in LoadContent().

 

Next we override Update() and Draw(), which is essentially the heart of your application’s game loop.  Update() is responsible for updating the state of your game world, things like polling input or moving entities, while Draw is responsible for drawing your game world.  In our default Update() call, we check for the player hitting the back button or escape key and exit if they do.  Don’t worry about the details, we will cover Input shortly.  In Draw() we simply clear the screen to CornFlower Blue ( an XNA tradition ).  You will notice in both examples we call the base class as well.

 

What's a game loop?


A game loop is essentially the heart of a game, what causes the game to actually run. The following is a fairly typical game loop:

void gameLoop(){
   while (game != DONE){
      getInput();
      physicsEngine.stepForward();
      updateWorld();
      render();
   }
   cleanup();
}

 

As you can see, it's quite literally a loop that calls the various functions that make your game a game.  This is obviously a rather primitive example but really 90% of game loops end up looking very similar to this.

 

However, once you are using a game engine or framework, things behave slightly different.  All this stuff still happens, it’s just no longer your code’s responsibility to create the loop.  Instead the game engine performs the loop and each iteration it then calls back to your game code. This is where the various overridden function such as update() and draw() are called.  Looking at our sample loop above though, you might notice a physics engine call.  XNA doesn’t have a built in physics engine, so instead of the game loop updating the physics, you will have to do it yourself in your games update() call.

 

When you run this code you should see:

image

 

Please note, depending on what platform you are running on, this window may or may not be created full screen.  On Windows it defaults to windowed, while on MacOS it defaults to full screen.  Hit the Escape key, or Back button if you have a controller installed, to exit the application.

 

Let’s take a quick look at a program’s lifecycle with this handy graphic.

programFlow

In a nutshell the game is created, initialized, content is loaded, the game loop runs until Exit() is called, then the game cleans up and exits.  There are actually a few more methods behind the scenes, such as BeginDraw() and EndDraw(), but for most games, this is sufficient detail.

 

Our current example isn’t exactly exciting because absolutely nothing happens.  Let’s create a slightly more interesting example, one that draws a rectangle on screen and rolls it across the screen.  Don’t worry about the specifics, we will cover graphics in more detail shortly.

 

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

// This example simply adds a red rectangle to the screen
// then updates it's position along the X axis each frame.
namespace Example2
{
    public class Game1 : Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        Texture2D texture;
        Vector2 position;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            position = new Vector2(0, 0);
        }

        protected override void Initialize()
        {
            texture = new Texture2D(this.GraphicsDevice, 100, 100);
            Color[] colorData = new Color[100 * 100];
            for (int i = 0; i < 10000; i++)
                colorData[i] = Color.Red;

            texture.SetData<Color>(colorData);
            base.Initialize();
        }

        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);
        }

        protected override void UnloadContent()
        {
        }

        protected override void Update(GameTime gameTime)
        {
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == 
                ButtonState.Pressed || Keyboard.GetState().IsKeyDown(
                Keys.Escape))
                Exit();

            position.X += 1;
            if (position.X > this.GraphicsDevice.Viewport.Width)
                position.X = 0;
            base.Update(gameTime);
        }

        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            spriteBatch.Begin();
            spriteBatch.Draw(texture,position);
            spriteBatch.End();

            base.Draw(gameTime);
        }
    }
}

 

When we run this we will see:

gif1

 

Nothing exciting, but at least our game does something now.  Again, don’t worry overly about the details of how, we will cover this all later.  What we want to do is look at a few key topics when dealing about dealing with the game loop. 

 

Pausing Your Game

 

Pausing your game is a pretty common task, especially when an application loses focus.  If you take the above example, minimize the application, then restore it and you will notice the animation continued to occur, even when the game didn’t have focus.  Implementing pause functionality is pretty simple though, let’s take a look at how:

        protected override void Update(GameTime gameTime)
        {
            if (IsActive)
            {
                if (GamePad.GetState(PlayerIndex.One).Buttons.Back == 
                    ButtonState.Pressed || Keyboard.GetState().
                    IsKeyDown(Keys.Escape))
                    Exit();
                position.X += 1;
                if (position.X > this.GraphicsDevice.Viewport.Width)
                    position.X = 0;
                base.Update(gameTime);
            }
        }

Well that was simple enough.  There is a flag set, IsActive, when your game is active or not.  The definition of IsActive depends on the platform it’s running. On a desktop platform, an app is active if its not minimized AND has input focus.  On console it’s active if an overlay like the XBox guide is not being shown, while on phones it’s active if it’s the running foreground application and not showing a system dialog of some kind.  As you can see, the can pause the game by simply not performing Game::Update() calls.

 

There may be times when you wish to perform some activity when you gain/lose active status.  This can be done with a pair of event handlers:

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            position = new Vector2(0, 0);

            this.Activated += (sender, args) => {  this.Window.Title = 
                              "Active Application"; };
            this.Deactivated += (sender, args) => { this.Window.Title 
                                = "InActive Application"; };
        }

Or by overriding the functions OnActivated and OnDeactivated, which is the recommended approach:

        protected override void OnActivated(object sender, System.
                                            EventArgs args)
        {
            this.Window.Title = "Active Application";
            base.OnActivated(sender, args);
        }

        protected override void OnDeactivated(object sender, System.
                                              EventArgs args)
        {
            this.Window.Title = "InActive Application";
            base.OnActivated(sender, args);
        }
 

Controlling the Game Loop

Another challenge games face is controlling the speed games run at across a variety of different devices.  In our relatively simple example there is no problem for two reasons.  First, it’s a very simple application and not particularly taxing, meaning any machine should be able to run it extremely quickly.  Second, the speed is actually being capped by two factors, we are running at a fixed time step ( more on that later ) and we have vsync enabled, which on many modern monitors, refresh at a 59 or 60hz rate.  If we turn both of these features off and let our game run at maximum speed, then suddenly the speed of the computer it’s running on becomes incredibly important:

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            position = new Vector2(0, 0);
            this.IsFixedTimeStep = false;
            this.graphics.SynchronizeWithVerticalRetrace = false;
        }

 

By setting IsFixedTimeStep to false and Graphics.SyncronizeWithVerticalRetrace to false, our application will now run as fast as the game is capable.  The problem is, this will result in our rectangle being drawn extremely fast and at different speeds on different machines.  We obviously don’t want this, but fortunately there is an easy fix.  Take a look at Update() and you will notice it is being passed in a GameTime parameter.   This value contains the amount of time that occurred since the last time Update() was called.  You may also notice Draw() also has this parameter.  This value can be used to smooth out motion so it runs at a consistent speed across machines.  Let’s change our update call so position instead of being ++’ed each frame, we now move at a fixed rate, say 200 pixels per second.  That can be accomplished with this change to your Update() position code:

position.X += 200.0f * (float)gameTime.ElapsedGameTime.TotalSeconds;

TotalSeconds will contain the fraction of a second that have elapsed since Update() was last called, assuming of course your game is running at at least 1 frame a second!  For example, if your game is updating a 60hz ( second times per second ), then TotalSeconds will have a value of 0.016666 ( 1/60 ).  Assuming your stays pretty steady at 60fps, this results in your Update code updating the position by 3.2 pixels per frame ( 200 * 0.016 ).  However, on a computer running at 30fps, this same logic would then update position by 6.4 pixels per frame ( 2000 * (1/30) ).  The end result is the behavior of the game on both machines is identical even though one draws twice as fast as the other.

The GameTime class contains a couple useful pieces of information:

image

ElapsedGameTime holds information on how much time has happened since the last call to Update (or Draw,  completely separate values by the way ).  As you just saw, this value can be used to normalize behavior regardless to how fast the underlying machine actually performs.  The value TotalGameTime on the other hand is the amount of time that elapsed since game started, including time spend paused.  Finally there is the IsRunningSlowly flag, which is set if the game isn’t hitting it’s target elapsed time, something we will discuss momentarily.  Game also has a property called InactiveSleepTime, which along with TotalGameTime can be used to calculate the amount of time a game spent running.

 

Fixed Vs. Variable TimeStep

 

Finally let’s discuss using a FixedStep game loop instead.  Instead of messing with all of this frame normalization stuff you can instead say “Hey, Monogame, I want you to run my game at this speed” and it will do it’s best.  Lets look at this process:

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            position = new Vector2(0, 0);
            this.IsFixedTimeStep = true;
            this.graphics.SynchronizeWithVerticalRetrace = true;
            this.TargetElapsedTime = new System.TimeSpan(0, 0, 0, 0, 33); // 33ms = 30fps
        }

This will then try to call the Update() exactly 30 times per second.  If it can’t maintain this speed, it will set the IsRunningSlowly flag to true.  At the same time, the engine will try to call Draw() as much as possible, but if the machine isn’t fast enough to hit the TargetElapsedTime it will start skipping Draw() frames, calling only Update() until it’s “caught up”.  If your  You can control the maximum amount of draw calls that will be skipped per Update() by setting MaxElapsedTime.  This value is a MonoGame specific extension and is not in the original XNA.  If you don’t specify a TargetElapsedTime, the default is 60fps (1/60).

 

Ultimately the decision between using a Fixed Step game loop or not is ultimately up to you.  A fixed step game loop has the advantage of being easier to implement and provides a more consistent experience.  A variable step loop is a bit more complicated, but can result in smoother graphics on higher end machines.  The result is more pronounced on a 3D game than a 2D one, resulting in smoother controls and slightly cleaner visuals.  In a 2D game the difference is much less pronounced.

 

In the next chapter we will move on to the much more exciting topic of 2D graphics.

 

The Video

Programming , , ,

blog comments powered by Disqus

Month List

Popular Comments