Core Loop & States

The Basic Game Loop

The game loop is the beating heart of every MonoGame project. This tutorial explains exactly what happens on every frame, how Update and Draw cooperate, why delta time matters, and the difference between fixed and variable timestep. By the end you will have a small, complete, runnable game that moves a box across the screen frame-rate independently.

Download the runnable sample project

Use the verified ZIP bundle for this article to review the extracted example files, run the sample host, and repackage the sources without bin or obj folders.

What is a game loop?

A game loop is an infinite loop that runs while your game is open. On each iteration — called a frame — the game does two jobs: it updates the world (move things, read input, run physics) and then draws the world to the screen. MonoGame runs this loop for you; you only fill in the behaviour.

💡 MonoGame's Game class already contains the loop. You never write while (true) yourself — you override the lifecycle methods below and MonoGame calls them at the right time.

The four lifecycle methods

Every MonoGame Game subclass overrides four methods. They run in a strict order: Initialize once, then LoadContent once, then Update and Draw repeatedly until you exit.

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

public class Game1 : Game
{
    private GraphicsDeviceManager _graphics;
    private SpriteBatch _spriteBatch;

    public Game1()
    {
        _graphics = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";
        IsMouseVisible = true;
    }

    protected override void Initialize()
    {
        // Runs once. Set up non-graphics state and configuration here.
        base.Initialize();
    }

    protected override void LoadContent()
    {
        // Runs once, after Initialize. Load textures, fonts, sounds here.
        _spriteBatch = new SpriteBatch(GraphicsDevice);
    }

    protected override void Update(GameTime gameTime)
    {
        // Runs every frame BEFORE Draw. Game logic goes here.
        base.Update(gameTime);
    }

    protected override void Draw(GameTime gameTime)
    {
        // Runs every frame AFTER Update. Rendering goes here.
        GraphicsDevice.Clear(Color.CornflowerBlue);
        base.Draw(gameTime);
    }
}

⚠️ Caution: Keep logic in Update and rendering in Draw. Mixing them is the single most common beginner mistake and causes flicker, inconsistent physics, and bugs that only appear at certain frame rates.

Delta time & frame independence

Different computers run at different frame rates. If you move an object by a fixed number of pixels per frame, it moves faster on fast machines and slower on slow machines. The fix is delta time: the number of seconds since the previous frame, available from gameTime.ElapsedGameTime.TotalSeconds. Multiply speeds by delta time so movement is measured in pixels per second, not pixels per frame.

private Vector2 _position = new Vector2(100, 100);
private float _speedPixelsPerSecond = 200f;

protected override void Update(GameTime gameTime)
{
    float dt = (float)gameTime.ElapsedGameTime.TotalSeconds;

    // Moves 200 pixels every second, regardless of frame rate.
    _position.X += _speedPixelsPerSecond * dt;

    base.Update(gameTime);
}

⚠️ Warning: A value like _position.X += 5; (no dt) ties speed to frame rate. On a 144 Hz monitor the object travels almost 2.5× faster than at 60 Hz. Always multiply movement, rotation, and timers by delta time.

Fixed vs variable timestep

MonoGame supports two update strategies, controlled by IsFixedTimeStep.

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

    // Fixed 60 Hz update (this is the default).
    IsFixedTimeStep = true;
    TargetElapsedTime = System.TimeSpan.FromSeconds(1d / 60d);

    // To use variable timestep instead, set:
    // IsFixedTimeStep = false;
    // _graphics.SynchronizeWithVerticalRetrace = false; // uncap frame rate
}

Complete example: a bouncing box

This is a full, self-contained Game1.cs. It generates its own texture in code — a single white pixel stretched to a square — so you do not need any image files. The box moves with delta time and bounces off the window edges.

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

public class Game1 : Game
{
    private GraphicsDeviceManager _graphics;
    private SpriteBatch _spriteBatch;

    // A 1x1 white texture we create in code, then stretch to draw a box.
    private Texture2D _pixel;
    private Vector2 _position = new Vector2(100, 100);
    private Vector2 _velocity = new Vector2(220, 160); // pixels per second
    private const int BoxSize = 48;

    public Game1()
    {
        _graphics = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";
        IsMouseVisible = true;
    }

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

        // Generate a solid-white 1x1 texture at runtime (no image file needed).
        _pixel = new Texture2D(GraphicsDevice, 1, 1);
        _pixel.SetData(new[] { Color.White });
    }

    protected override void Update(GameTime gameTime)
    {
        if (Keyboard.GetState().IsKeyDown(Keys.Escape))
            Exit();

        float dt = (float)gameTime.ElapsedGameTime.TotalSeconds;
        _position += _velocity * dt;

        var bounds = GraphicsDevice.Viewport.Bounds;

        // Bounce off the left/right edges.
        if (_position.X < 0) { _position.X = 0; _velocity.X = -_velocity.X; }
        if (_position.X + BoxSize > bounds.Width)
        {
            _position.X = bounds.Width - BoxSize;
            _velocity.X = -_velocity.X;
        }

        // Bounce off the top/bottom edges.
        if (_position.Y < 0) { _position.Y = 0; _velocity.Y = -_velocity.Y; }
        if (_position.Y + BoxSize > bounds.Height)
        {
            _position.Y = bounds.Height - BoxSize;
            _velocity.Y = -_velocity.Y;
        }

        base.Update(gameTime);
    }

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

        _spriteBatch.Begin();
        var rect = new Rectangle((int)_position.X, (int)_position.Y, BoxSize, BoxSize);
        _spriteBatch.Draw(_pixel, rect, Color.OrangeRed);
        _spriteBatch.End();

        base.Draw(gameTime);
    }
}

Run it with dotnet run. Press Escape to quit. Try changing _velocity or BoxSize and watch the behaviour change.

dotnet build
dotnet run

Common pitfalls

Next steps

Need larger tap targets or higher contrast? Toggle enhanced spacing and high contrast in the header. Your preference is saved in session storage.

🎓 Test Your Knowledge

Check your understanding of the key concepts from this tutorial.

What is the correct order MonoGame calls its lifecycle methods on startup?

What does gameTime.ElapsedGameTime.TotalSeconds represent?

With IsFixedTimeStep = true (the default), what happens if Update takes too long?

Why should you multiply movement speed by delta time?