[GameMaker] Making a proper frame counter.

Categories: GameMaker

Note: This is specifically for versions GameMaker 2022.5.0.8 and above, due to the following code relying on Time Sources.

I’ll admit, I have personally never needed a frame counter in my projects, especially given that I mostly focus on library-related work. That said, the thought does cross my mind from time to time. In the past, frame counters I’ve made only check for the current frames that have already been processed within the game. However I was at one point asked for a method to compare frames against the current step. While that particular use case was to make a simple alarm, it did spark my interest. So today, I want to introduce to you current_step. The rest of the blog post covers the whole process and how I handled it specifically. If you just want the full snippet, you may scroll down to the bottom of the post. Otherwise…

Why do we need a frame counter?

Typically the main reason you’d want to use a frame counter is to

  • Check how many frames your game has processed so far. (Yes, this is a literal use case.)
  • Execute a particular set of code once every X frames. (Aka a simplified alarm.)

The first one helps us see how many steps our game has passed so far. The second one is really handy for only executing a small chunk of code every step, without having to rely on an alarm or a timer that we have to keep track of ourselves. Typically current_time and get_timer() are the two most popular options for tracking this, but they have some drawbacks that I will go over later in this post.

How were frame counters usually done in GameMaker?

If you’re familiar with GameMaker, you will know about objects and their many different events. They are used for a wide variety of things, but some in particular are used for managing specific systems. The main focus comes to mind is the begin step, step and end step events. With the exclusion of `draw events` and `alarm events`, these are the only events that process once per instance, per frame. In the order of:

  • Begin Step Event
  • Step Event
  • End Step Event

You can read more in detail about the step events and the other events (besides alarms) by clicking here. But for this part of the post, we’re going to be solely focusing on these three events for now.

Traditionally, if you wanted to make a frame counter work with an object, you needed to design your game with a specific setup in mind. The first room within the game (In GameMaker 2.3.0+, this is managed by the Room Order section. In older versions, the first room that’s on top of all of the other rooms within the Room folder) had to contain a preferably persistent object (i.e. obj_game_controller), stored within a room that sets up most of your game before moving into another room (such as the main menu or game intro). Much of the code for a frame counter was setup like this.

// Game Start Event
global.frame_counter = 0;

// Begin Step Event
++global.frame_counter;

The reason we call it in the begin step event is because now any time an object wishes to get the current frame counter, they can just fetch global.frame_counter. Here’s two reasons why I don’t like this approach.

  1. It solely relies on an object to manage it.
  2. While not likely, it is possible to modify global.frame_counter either by accident, or via an external scripting API (think modding API), which could cause other unintended issues.

The second one is more of a specific rare case, but the first is more problematic. This is because the counter itself is managed by a single instance of that object. If you know anything about objects and instances… It’s possible to accidentally destroy said instance, thus rendering your frame counter completely and utterly useless! Of course, with good standard practices and proper management, both of these are mostly avoidable.

But what if you didn’t want to rely on an object?

Solution 1: Use current_time!

Now when I came across the question, one of my first thoughts was “Why not just use current_time?” Since it doesn’t use an object, and the value itself can’t be changed alone. We need to keep in mind however, current_time increments in milliseconds, whereas get_timer() increments in microseconds. The reason I’m using current_time here is more of simplicity sake. The code I came up with for this was shoved into a macro for easy use. If you’re unfamiliar with what a macro is, it’s essentially a keyword that gets replaced during code compiling with the specificied value or code in place. Some examples of macros in use:

#macro FPS_SPEED ("Current FPS: " + string(fps) + "\nFPS Target: " + string((game_get_speed(gamespeed_fps))))

// In the draw event

// Print FPS stats
draw_text(8, 8, FPS_SPEED);

During compiling, this gets expanded to

draw_text(8, 8, "Current FPS: " + string(fps) + "\nFPS Target: " + string((game_get_speed(gamespeed_fps))));

The idea is that we’ll be using macros to simplify this along with current_time. “But wait”, you say, “doesn’t current_time count in milliseconds?” You’re right, but we can work around that by doing a bit of maths. We’ll call our macro current_step.

#macro current_step (current_time div game_get_speed(gamespeed_fps))

This is good, but it has one flaw with this kind of thinking. It’s quite hard to tell from a glance, but did you know that both current_time and get_timer() tick upwards separately from the rest of your game code? “Obviously it shouldn’t cause some kind of issue, right?” you ask. Well unless your intention is to have your frame counter count up once every real world second, any moments where your game freezes up is enough to cause a desync while it’s still executing. Especially anywhere within the current step, alarms or draw events! Let’s also not forget that we’re relying on game_get_speed(), which is a function that can be altered by game_set_speed() (or room_speed for those who still use it), and can influence the current frame counter. It also may at times start above 0, which we don’t want that kind of inconsistency. Ultimately, current_time, and by extension, get_timer() make for a pretty bad frame counter overall. There’s got to be something better, right?

Solution 2: Use Time Sources!

Up until Game Maker 2022.5.0.8, objects were the one and only true way you could process logic every frame. However, since version 2022.5.0.8, GameMaker has introduced time sources! “So what is this time source you speak of?” Think about it like an object’s alarm event, except it’s not an object. It works solely independently from any and all objects. Aha, now we’re getting fancy here! “But how do we use a time source?”

Time sources are a bit more involved, and I’d recommend checking the documentation on all of their arguments on time source create here. But for our specific case, we’ll be actually just using this.

ts = time_source_create(time_source_global, 1, time_source_units_frames, callback, [], -1);

Time sources have two parent-based timers. time_source_global and time_source_game. We’re using time_source_global since it cannot be paused with time_source_pause() by accident (which can happen if you do time_source_pause(time_source_game)). The rest of the arguments determine that:

  • Waits 1 frame before running our callback function
  • Sets an array of paramaters to pass into the callback function (In our case, it’s an empty array)
  • Sets the repetitions, aka repeats, the time source should execute. (-1 makes it repeat forever.)

What we’ll be doing is taking one step further however, and creating a unique struct that contains our relative information. We’ll also be using said struct to store our frame counter value as well.

function __get_current_step() {
    // Initialize our static variable
    static _inst = undefined;

    // Check that our static variable is undefined.
    if (_inst == undefined) {
        _inst = {};
        _inst.ts = time_source_create(time_source_global, 1, time_source_units_frames, method(_inst, function() {
            ++currentStep;
        }), [], -1);
        time_source_start(_inst.ts);
         _inst.currentStep = 0;
	}
	
	return _inst.currentStep;
}
__get_current_step(); // We call this once at the start

So what’s going on here? What is static?
Static is basically a value that gets declared once and binded to the function. You can perform any future code with it to change or alter said value, or its contents. But ultimately, you can use it to keep a permanent reference to a value. In our case, we’re using it to store a struct. The other benefit we gain here is that unless we expose that struct by returning it, this is effectively untouchable by any outside force (Well, besides static_get()). As for method(), Game Maker allows functions to be called from any instance, object, or struct by binding the function to it. This means that whenever that method is called, it’s executing as said instance. This current code works just as fine, so we can assign this to a macro in it’s current state.

#macro current_step (__get_current_step())

“But, wait, when do time sources tick?” This may come as a shocker to some of you, they tick after the begin step event but before the step event! The goal of this is to also account for the begin step event, so does that mean time sources are also a dud?

Well yes, but actually no

We can make this work with the begin step event, it just requires a bit more logic on our end.

Extending Solution 2 to work in the Begin Step Event.

To begin, we’ll add a new variable to our struct

_inst.currentStep = 0;
_inst.executedInBeginStep = false;

Now just before we return currentStep, we also need this bit of code to be added.

    // Check if we're in the begin step event
    if ((event_type == ev_step) && (event_number == ev_step_begin) && 
	(!_inst.executedInBeginStep)) {
        _inst.executedInBeginStep = true;    
        ++_inst.currentStep;
    }
    
    return _inst.currentStep;
}

Basically, since our time source doesn’t tick until after begin step event, this small snippet of code ensures that we’ve already incremented ahead of time. Now, you’ll already notice that our time source code will also still increment it, causing a double increment. To fix this, we just add in a check to our callback function.

        _inst.ts = time_source_create(time_source_global, 1, time_source_units_frames, method(_inst, function() {
            if (executedInBeginStep) {
                --currentStep;
                executedInBeginStep = false;
            }
            ++currentStep;
        }), [], -1);

Everything is good, right? If you were to call current_step in the create event and begin step event, while having it run in the very first room of the game, you’ll quickly realize that it has incremented an entire step despite the fact the game only just started (shows 1 instead of 0)! This isn’t technically a bug, although I do like to make my frame counters a bit more consistent. By ensuring that between the very start of the game, to the next begin step, stays consistently as “frame 0”. So I came up with another solution.

Firstly we’ll change currentStep during our struct setup to -1.

     _inst.currentStep = -1;
     _inst.executedInBeginStep = false;

And then add this snippet of code just below our check for the begin step event.

    // Check if this is our frame counter is below 0, aka the very first frame.
    if (_inst.currentStep < 0) {
        return 0;   
    }

We check to see if currentStep is less than 0 and return 0, since we are technically on the very first frame. Which corrects all of the remaining oddities, and makes calling it anywhere more in sync.
Our code now is finally coming all together. What we end up with is a frame counter that not only updates every step event, but also actually starts from 0!

Conclusion

This concludes the blog post on how we went from a basic frame counter, to a proper frame counter!
I like to thank Homs from r/GameMaker’s Discord Server, for giving me a reason to spend some of my time thinking about the best possible solution to a frame counter! I also would like to give special thanks to Gizmo199 for helping me proof read all of this. (Staying up all night and writing does not mix very well.)

I’m always looking for more things to cover in regards to GameMaker, so if you ever want to see me tackle something else involving GameMaker, leave a comment below or join my Discord Server to talk to me directly! Also consider following me on Twitter or subscribing to my Substack if you’d like to receive updates on whenever I post a new blog post!

You can get the full code from here, with some slight changes down below.
https://gist.github.com/tabularelf/a54f338b1cc82f99e7a35cf0ad6f18cb

«

    Leave a Reply

    Your email address will not be published. Required fields are marked *

    This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.