Implementing a Timer and Callback System in MonoGame

Build it, set it, and forget it

As I discussed in my recent case for MonoGame in modern times, the MonoGame framework gives you nearly total freedom to build your game your way. The other side of that: it gives you little else. In this post, I aim to walk through one of the first big features I couldn’t find anywhere else, and how I went about making up for it.

One of the projects I started to relearn MonoGame–I worked my way through an XNA book more than a decade ago while my wife and I were still dating–is a collection of pencil and paper games inspired by my childhood kitchen table gaming sessions with my grandpa. The first game was Tic Tac Toe.

Tic Tac Toe against the computer feels weird if the computer opponent takes its turn milliseconds after you take yours in the next drawn frame. To make it feel like the computer is thinking about it for a moment, I needed some kind of timer or callback.

I was surprised to discover MonoGame doesn’t seem to have any appointed method for this. The consensus is that you have the GameTime object in your Update method, so you should be able to do anything you need to do. Searches online seem to bring back discussion of different ways to perform a delay with a simple variable or what kind of additional libraries you can add to your project to gain this functionality. Forums also included a lot of muddled theories about how to do the callback portion. Some are still unresolved.

I ended up building my own little system for this that allows you to create one or multiple concurrent timers, each with their own callback method that fires as soon as time is up.

Here’s how it works:

The Timer Class

Yes, we’re creating our own custom class for this to keep the code from cluttering up Game1.cs, although you could do this in that file too if you must.

// Timer.cs

using System;
using Microsoft.Xna.Framework;

namespace YourProject;

public class Timer{
    public Timer(float seconds, Action callbackMethod){
        TotalTime = seconds;
        CallbackMethod = callbackMethod;
    }
    public float ElapsedTime{get;set;}
    private float TotalTime{get;}
    public Action CallbackMethod{get;}
    public bool isDone = false;
    public void Update(GameTime gameTime){
        ElapsedTime += (float)gameTime.ElapsedGameTime.TotalSeconds;
        if(ElapsedTime >= TotalTime){
            CallbackMethod();
            isDone = true;
        }
    }
}

Nothing extraordinary here. 20ish lines of code depending how you space it. You’ll need to include Microsoft.Xna.Framework so you can work with the GameTime object when the Game1 class throws it to you during the Update method. System allows you to use Actions which we’ll touch on in a moment. The constructor accepts (and requires) the duration for the timer in seconds and the callback method you want to execute when time is up.

The key to executing a callback method is the Action delegate. C# has two delegate keywords: Func lets you refer to methods that can accept parameters and returns a value, and Action can accept parameters but returns void. Why does it work this way? That’s a serious question. I have no idea. Nevertheless, if we pass our callback method into the constructor as an action, we’ll be able to execute it later.

We have member variables to track elapsed time, total time, the callback method, and a bool indicating whether the timer is done. You could optionally simplify by using one time variable and subtracting from it each cycle instead of adding to a second variable, but this gives me the option to implement a timer reset later because I’ll still have the original value.

The update method adds the time since the last cycle to the elapsed time variable, and checks to see if we’ve met or exceeded the timer’s total intended duration. If so, we call our callback method and set the done bool to true.

How to use a Timer in the Game class

Here’s what to add to Game1.cs to make your game march in the Debug Console.

// in the Game1 member variables:
List<Timer> timers;

// in LoadContent()
for(int i = 0; i< 10; i++){
    if(i % 2 == 0){
        timers.add(new Timer(i,PrintLeft));
    }else{
        timers.add(new Timer(i,PrintRight));
    }
}

// in Update()
UpdateTimers(gameTime);

// add this to update timers
protected void UpdateTimers(GameTime gameTime){
    for(int i = 0; i < timers.Count; i++){
        var timer = timers[i];
        timer.Update(gameTime);
        if(timer.isDone){
            timers.Remove(timer);
        }
    }
}

// here's your PrintLeft method
protected void PrintLeft(){
    Console.WriteLine("Left");
}

// here's your PrintRight method
protected void PrintRight(){
    Console.WriteLine("Right");
}

When this game runs, your game should output “Left,” then “Right,” one second apart for ten steps. Each is the result of a timer expiring and calling its designated callback method.

There you have it: a reliable but lightweight timer and callback system that’s hopefully ready for just about anything.

Did you solve this problem some other way? By all means, shout it out in the comments.

Leave a Comment