GMS 2.3+ [TEXT TUTORIAL] "Want to execute this only once" (Alarms/Flags/Counters)

GM Version: Studio 2.3+
Target Platform: Windows / All
Download: n/a
Links: n/a

[LEVEL]
Beginner

[PROBLEM]
Today tutorial is more specific and related to something that invades the programming forums and discord forums quite often, we will be talking about:

- I want to execute this code only once -

So I will explain the problem the usual mistakes and finally various solutions you can use to arrive to the desired behavior.
I'll do my best to keep it clean and simple to follow ;)

[INTRODUCTION]
Sometimes you might want your player/enemy/object to do something but only once, or even every X seconds/frames.
This might seem trivial and a very simple concept to grasp but believe me for a lot of new programmers this might not that be intuitive.

NOTE: The code examples throughout this tutorial can and should be followed so you can better understand the concepts.​


[EXPLANATION]

(1) ONE-TIME VS LOOPED EVENTS
GameMaker Studio workflow provides users with a lot of events they can use within an object create/draw/step/keyboard/mouse... just to name a few.
Almost all of these events belong to one of two groups: the one-time events and the looped events. Let's take a look at the diagram below:

once_looped.png

The one time events are events that run only when you tell them to and only run a single time... other events run every single frame autonomously and are managed by the engine itself. Take this simple example:
  • the create event this is an one time that is triggered once you call instance_create_* through GML (or by dragging and drop an instance inside the room editor)
  • on the other hand after the instance is created the step event will run continuously every game loop while the instance exists
  • when you call instance_destroy this will trigger the cleanup and destroy events only one time.
NOTE: The events above should be of no surprise to you but if they are there is no problem you can always check information about them on the manual.​
NOTE: You might get the feeling that KEYBOARD/MOUSE events are not always looped, for example on-key-pressed only executes if the key is pressed, and that is true​
but the event is still checked every step and when the condition is true (the key is pressed) then the code is executed.​

(2) ONE-TIME ACTIONS
Now that we looked at these two main groups of events we can start to answer the main question our initial "want to execute this code only once" problem (for the sake of this tutorial let's call this bit-of-code we want to execute only once "one-time action").
We will look into various approaches to the same behavior and I'll try to tackled some of the must common bugs you might find. This can be done using a lot of approaches and using various events (either one-time/looped)

(3) SETUP
In order to make the explanation more clear I will provide an initial setup:
  • We will have an instance that exists (dropped into the room)
  • We want to instance to draw "Alive" to the screen (every frame)
  • We want the instance to print to the terminal the word "Attack" (this will be our one-time action)

For this we will create an objTutorial and add the following [DRAW EVENT] to it:
GML:
// This event will override the default draw event

draw_text(x, y, "Alive"); // This will draw our text "Alive"

// NOTE: If you want to set a sprite to this object go ahead and do so
// but remember this EVENT will override the default event that draws the sprite
// so if you want to you can call the default draw event using "draw_self()"
// draw_self(); // <-- uncomment this line

(3.1) USING CREATE EVENT
The first approach we will give on one-time actions will be using an one-time event for this we will use the create event.
So let's create a new object objTutorialCreate and make it a child of objTutorial (this way we know our previous setup gets executed) and inside the [CREATE EVENT] we will write this code:
GML:
// We will print the message "Attack" to the console
show_debug_message("Attack");

// NOTE: This event is a one-time event that runs at instance creation so the code above will run only once.
Make sure the room is empty and go on and drag an instance of the objTutorialCreate to the middle of the room and run our game.

You will notice that we have the object with the text "Alive" drawn to the screen (this is inside the draw event so this is running every frame)
and if we look at the console we can see the message "Attack" printed (only once).
So we have DONE IT congratulations ;) but this is not very useful but it IS AN APPROACH and sometimes this is a good simple one specially if the action we want to perform is something that should occur at creation time. :p let's go further.


(3.2) USING TIMERS
With this approach we will be able to do the same as the above but postpone the one-time action, a given amount of time.
This is not an in-depth tutorial on timers but I'll do my best to be brief and deliver all the information. Timers in GMS are called alarms. The variable alarm in GML represents the collection of timers for a given instance:
  • we can access the various timers using - alarm[X] - where X is a number from 0-11 (meaning each instance can have up to 12 timers).
  • we can set a given alarm with a specific number of frames - alarm[0] = 120; - meaning the alarm 0 is set to 120 frames...
  • every step GMS under-the-hood decreases the alarm's value by 1 and when the value reaches -1 the code inside the respective alarm event is executed (this will be an one-time action)

So let's look at a practical example, we create an objTutorialTimer make it a child of objTutorial (this way we know our previous setup gets executed) and inside the [CREATE EVENT] we will write this code:
GML:
// We are setting the alarm 0 to trigger after 120 frames
alarm[0] = 120;

// NOTE: Usually our game will run at 60fps so 120 frames will represent 2 seconds
Now we can go to the [ALARM 0 EVENT] and place this code there:
GML:
show_debug_message("attack"); // This is our one-time action

// NOTE: The code above can be whatever action you want to perform
Make sure the room is empty and go on and drag an instance of the objTutorialTimer to the middle of the room and run our game.

You will notice that we have the object with the text "Alive" drawn to the screen (this is inside the draw event so this is running every frame)
and if we look at the console after 2 seconds the message "Attack" will be printed (only once). So we have DONE IT congratulations ;) but out tutorial is far from finished.

CHALLENGE: So we have the code that waits 2 seconds and then runs (prints "Attack") and after that isn't executed again. Now that we know how to make a bit-of-code run only once I have a challenge for you, can you make it run every 2 seconds. Go ahead try it yourself and then come back for the solution (PS.: the idea is to use the alarm system) :)
SOLUTION: A simple solution to this challenge is adding a line of code to the [ALARM 0 EVENT] itself:​
GML:
// Append this line to the end of the event

alarm[0] = 120; // this will reset the alarm to 120 frames
When we run the program we will now see that every 120 frames (two seconds) we have our "Attack" message being printed to the console. If you didn't manage to find the answer or got some kind of bug look at the section belong as I'll try to explain a very common mistake.​

MISTAKES: A very common mistake when during recurring actions is placing the code above in the [STEP EVENT] this will not work and is not the correct approach. But the question new programmers seem to have is - Why?​
As I stated above "every step GMS under-the-hood decreases the alarm's value by 1 and when the value reaches -1 the alarm code is executed". If we use the step event to set the alarm back to the start value (ie.: 120) then the value will never decrease and never reach -1 and therefore it will never trigger the [ALARM EVENT]. So keep in mind that alarms should (in most cases) be reset inside there own event.​


(3.3) USING FLAGS
The timers approach allows for a better/complex control of the one-time action as we could see from the example above (you can delay them, you can even repeat them after some time).
Let's go deep into the rabbit hole with yet another approach, I call the flag approach. A flag is nothing more than a variable that can be either true or false.

So let's look at a practical example, we create an objTutorialFlags make it a child of objTutorial (this way we know our previous setup gets executed) and inside the [CREATE EVENT] we will write this code:
GML:
canAttack = true; // this variable will act as a flag and is set to true.

// NOTE: I named the variable canAttack because it is more descriptive of it's purpose you can name it whatever you want
This time we will use the [STEP EVENT] to trigger the one-time action, so inside this event we add the code:
GML:
if (keyboard_check_pressed(vk_space) and canAttack == true) {  // using 'and' means both condition need to be true

    show_debug_message("Attack"); // This is the one-time action

    canAttack = false; // We now set the flag to false

    // NOTE: The above line of code will make it so the 'if statement' will never be true again.
}
Make sure the room is empty and go on and drag an instance of the objTutorialFlags to the middle of the room and run our game.

We have an instance of an object place in the room it is waiting for the keyboard space key to be pressed, once we press it it will run our one-time action (prints "Attack") and even if you press the key again the action won't be triggered again.

Let's analyze what is happening, the step event belongs to the looped events group (it is an event that is constantly being call) so we need a way to block the execution of our one-time action for that we use a flag (true/false variable) combined with an if statement. Inside the this statement we set the flag to false and now our one-time action will not execute again. (If you don't know what an if statement is please refer to the manual).
So the recipe for your code would be something like:

GML:
if (condition and flag == true) {
  
    // action here

    flag = false;
}
Where condition is the behavior that triggers the action (in our example the press of the space bar) and flag is our execution guard that will limit the execution of the action.

Now we have user control on when the one-time action should be executed. But it is time for another challenge ;)

CHALLENGE: So we have the code that executes once when the user presses the space key runs (prints "Attack") and after that isn't executed again. I now challenge you to create a cool down. When you press space it will print "Attack" (just like it is right now) but then you will have to wait 2 seconds until the one-time action can be performed again (PS.: you can do this easily with the help of timers) :)
SOLUTION: Before we start let's be clear there are a lot of ways of doing this. I'll be presenting one and I'll try and use the knowledge from the previous topics.​
As I hinted above you can do this by using timers but usually the main problem is to understand where should I set my timer? Before looking to the code let's think... we need to cool down after the one-time action as occurred this is a very important thing to realize and will help us know where to set the alarm. We use the if statement to execute our action so, after the action is executed inside the if statement let's add this line of code:​
GML:
if (keyboard_check_pressed(vk_space) and canAttack == true) {  // using 'and' means both condition need to be true

    show_debug_message("Attack"); // This is the one-time action

    alarm[0] = 120; // We will set the alarm 0 to trigger after 120 frames.

    canAttack = false; // We now set the flag to false

    // NOTE: The above line of code will make it so the 'if statement' won't run repeatedly.
}
We are not done yet, though. Now that we will trigger the [ALARM 0 EVENT] we need to add some code to it:​

GML:
canAttack = true;

// NOTE: With the line above we are setting the flag back to true and so we are allowing the one-time action to execute again
When we run the program we can now use the space key to trigger our one-time action (prints "Attack") and then we are not able to execute it again until 120 frames have passed. So we now created a cool-down system (this is getting interesting) ;)


(3.4) USING COUNTERS
This one will be VERY, VERY similar to one concept we've already talked above and I'm referring to alarms. What is an alarm/timer in its essence? An alarm is a variable that counts down until -1. We can actually achieve the same behavior using a simple variable.

So let's look at a practical example, we create an objTutorialCounter make it a child of objTutorial (by now you already know the drill) and inside the [CREATE EVENT] we will write this code:
GML:
canAttack = true;
attackCounter = -1; // This will be our custom "timer"

// NOTE: We set the timer to -1 because we want to be turned off at start.
Now we need to add a [STEP EVENT] and append this code:
GML:
if (attackCounter == -1) { // This replicated the default alarm when it reach -1 do something

     canAttack = true; // We now set the flag to true
}
else {
    attackCounter -= 1; // This will decrease the counter by 1
}

if (keyboard_check_pressed(vk_space) and canAttack == true) {
    show_debuge_message("Attack"); // Our (now famous) one-time action

    canAttack = false; // We set flag to false to lock this 'if statement'
    attackCounter = 120; // We reset the counter but to 120
}
Make sure the room is empty and go on and drag an instance of the objTutorialCounter to the middle of the room and run our game.

With the knowledge we gathered up until now the example should be pretty self-explanatory we just replaced the alarm[0] code and event with some more code on the step event. Why would we want to do this? It seems to be more complicated - you might ask and you actually my be true, it is more complex to do it this way but eventually if your code grows bigger and bigger you might need more than 12 timers (this is the internal alarm limit per instance) and knowing your way around it can be useful. Of course no example comes without a challenge so my challenge this time is:

CHALLENGE: Is there a way to make the code even more similar to the builtin alarm system without creating a lot of counter variables?! Can you think of an alternative way of doing it? Go ahead and try it out... ;)

SOLUTION: So here it goes an option way of doing the same thing but similar to the builtin alarm system. So the [CREATE EVENT] now becomes this:​
GML:
canAttack = true;
timers = array_create(20, -1); // This will be our custom timer collection (much like the [B]alarm[/B] variable)

// NOTE: We created an array of size 20 meaning we now can have up until 20 alarms.
Now for the [STEP EVENT] lets rearrange the code:
GML:
if (timers[0] == -1) { // This replicated the default alarm when it reach -1 do something
     canAttack = true; // We now set the flag to true
}

if (keyboard_check_pressed(vk_space) and canAttack) {
    show_debuge_message("Attack"); // Our (now famous) one-time action

    canAttack = false; // We set flag to false to lock this 'if statement'
    timers[0] = 120; // We reset the counter to 120
}
Lastly we will need to introduce some code that I'll place under the [END STEP EVENT] this is not demanding you could as well add this code to the [STEP EVENT]:​
GML:
// We will use a for loop to update every timer (if you don't know what a for loop is check the manual for more information)
for (var _i = 0, _length = array_length(timers); _i < _length; ++_i) {
    timers[_i] -= 1; // Decrease the timer value
}
Now we can use the system as we use the alarm system, using timers[X] to set/get the current timer value and we have no more limits.​

[CONCLUSION]
And that's it, "want to execute this only once" in a nutshell!
  • There is no right approach to do this
  • However there are a lot of wrong approaches that will lead to bugs.
  • I tried to cover the most simple to explain ones alarms/flags/counters
  • If you are curious to learn even more about the alternatives you can check StateMachines (these are more complex)
That was a good amount of information so remember to go through it a couple of times to understand it well.

Here xD from xDGameStudios,
Good coding to you all.
 
Last edited:
Top