Introducción al Desarrollo de Videojuegos con GameMaker · Module 3: Programming Fundamentals with GML Code
Conditional Control Structures
Lesson 12 of 60 · 60 min
Also available in: ES
Decision-Making in Game Development
A video game is not a static sequence of instructions that executes from start to finish without interruption; it is a dynamic, reactive system. At every moment, the engine evaluates changing conditions: whether the player presses a button, whether an enemy touches a hazard, whether the health bar reaches zero, or whether collected coins reach the threshold for an extra life. For a program to respond appropriately to these events, it needs mechanisms that branch execution flow based on the environment's current state.
In GameMaker, these logical branches are managed primarily through conditional structures. While GameMaker offers a visual scripting environment called GML Visual (where these decisions are represented through interconnected action blocks such as 'If Variable' or 'If Collision'), in professional development and throughout this course we focus on GML Code. Writing code provides precise, flexible, and maintainable syntax, allowing you to model complex mechanics without the spatial and speed constraints inherent to visual flowcharts.
In the previous lesson, we established the basics of syntax and data storage using variables such as 'puntos_vida', 'velocidad_x', and 'monedas'. In this lesson, we will use those same variables to govern object behavior using 'if', 'else if', 'else', and 'switch' statements, building the core gameplay logic for our 2D platformer.
Boolean Expressions and Comparison Operators
Every conditional control structure rests on a straightforward mathematical premise: evaluating a boolean expression. A boolean expression is any instruction or calculation that evaluates to exactly one of two possible states: true or false. Internally in GameMaker, the boolean value 'true' evaluates to the integer 1 (or any numeric value greater than 0.5), while 'false' evaluates to 0.
To construct these expressions, we use relational comparison operators. These operators inspect two operands (such as variables, constants, or expressions) and resolve whether the mathematical relation between them is true or false. The following table summarizes the comparison operators supported in GML Code:
| Operator | Name | Description | GML Example |
|---|---|---|---|
| == | Equality | Evaluates whether both values are exactly equal. | puntos_vida == 0 |
| != | Inequality | Evaluates whether both values are different from each other. | arma_actual != "espada" |
| < | Less than | True if the left-hand value is strictly less than the right-hand value. | puntos_vida < 100 |
| <= | Less than or equal to | True if the left-hand value is less than or identical to the right-hand value. | tiempo_restante <= 0 |
| > | Greater than | True if the left-hand value is strictly greater than the right-hand value. | monedas > 50 |
| >= | Greater than or equal to | True if the left-hand value is greater than or identical to the right-hand value. | nivel_jugador >= 5 |
💡 Beware of assignment vs. comparison: in programming, a single '=' sign assigns a value to a variable (e.g., vida = 100;), while the double '==' sign tests whether two values are equal (e.g., if (vida == 100)). Although GameMaker historically permits a single '=' inside an 'if' condition, relying on this is poor practice that causes subtle bugs and confusion across other programming languages. Always use '==' to check equality.
Logical Operators
Frequently, game behavior depends not on a single isolated condition, but on a combination of multiple simultaneous or alternative factors. To chain multiple boolean comparisons together, we use logical operators:
| Operator | Symbol | Behavior | Practical Example |
|---|---|---|---|
| Logical AND | && | Returns true only if every evaluated condition is true. | (puntos_vida > 0) && (en_el_suelo == true) |
| Logical OR | || | Returns true if at least one evaluated condition is true. | (tecla_espacio == true) || (boton_gamepad == true) |
| Logical NOT | ! | Inverts the truth value. Converts true to false and vice versa. | !esta_congelado |
The 'if' Statement: Basic Conditional Execution
The 'if' statement is the foundational building block for programmatic decision-making. Its operation is straightforward: it evaluates the boolean expression inside parentheses. If the expression evaluates to true, GameMaker's runtime enters the code block enclosed by curly braces '{ }' and executes the statements inside. If the expression evaluates to false, the braced block is skipped entirely, and execution resumes immediately after it.
// General syntax of an if statement
if (expresion_booleana)
{
// Code block executed only if the expression evaluates to true
instruccion_uno();
instruccion_dos();
}Let's look at an example using our main character, the 'obj_player' object. In the previous lesson, we initialized 'monedas' to 0 and 'vidas' to 3 inside its Create event. Suppose we want to grant an extra life and deduct 100 coins whenever the player collects 100 coins.
// Object: obj_player
// Event: Step
// Check whether the collected coin milestone has been reached
if (monedas >= 100)
{
vidas += 1;
monedas -= 100;
show_debug_message("Reward! Current lives: " + string(vidas));
}The Step event executes on every game frame (typically 60 times per second). On every frame, GameMaker checks the current value of 'monedas'. As long as 'monedas' is between 0 and 99, the expression '(monedas >= 100)' returns false, so the engine skips the three statements inside. On the exact frame 'monedas' reaches or exceeds 100, the expression evaluates to true: 'vidas' increases by one, 100 coins are deducted to prevent awarding endless lives, and a message prints to the output console.
Alternative Paths: The 'else' and 'else if' Clauses
When designing mechanics, you will often need to execute one action when a condition is met, and a completely different action when it is not. For this pattern, we use the 'else' clause. The 'else' clause never takes a condition of its own in parentheses; it attaches directly to an preceding 'if' and runs only when that 'if' condition evaluates to false.
// Object: obj_player
// Event: Step
if (esta_envenenado == true)
{
// If the player is poisoned, drain health progressively
puntos_vida -= 0.1;
}
else
{
// If not poisoned, passively regenerate health
if (puntos_vida < salud_maxima)
{
puntos_vida += 0.05;
}
}When you have more than two mutually exclusive paths, chaining multiple independent 'if' statements is inefficient and can cause unexpected bugs. Instead, use an 'else if' structure. This structure checks a second condition only if the previous condition evaluated to false, allowing you to link as many branches as your game logic requires, optionally ending with a final 'else' fallback to handle any unhandled cases.

The 'switch' Statement: Optimized Multi-Branching
As your game expands, you will frequently encounter variables acting as selectors: variables storing discrete values (such as an integer state code or a string identifier) that determine what an entity should do. Modeling a system with five or ten different states using chained 'else if' statements quickly results in verbose, error-prone, and hard-to-read code.
For these scenarios, GameMaker provides the 'switch' statement. This structure evaluates a single variable and matches it against a list of predefined 'case' values. Once a match is found, it runs the associated block of code until it hits a 'break' statement.
// General syntax of a switch statement
switch (variable_a_evaluar)
{
case valor_1:
// Code if variable_a_evaluar == valor_1
break;
case valor_2:
// Code if variable_a_evaluar == valor_2
break;
default:
// Code executed if none of the cases matched
break;
}There are two essential keywords to understand when working with 'switch':
1. 'break': The exit command. It instructs GameMaker to terminate execution of the 'switch' and jump past its closing brace. If you omit 'break' at the end of a case, the runtime continues executing code in subsequent cases sequentially (known as fall-through), which almost always causes unintended logical bugs.
2. 'default': The equivalent of 'else' inside a 'switch'. It is typically placed at the end and defines what code should execute if the evaluated variable matches none of the specified 'case' blocks. It is essential for safeguarding your game against unexpected states or corrupted data.
Practical Application: A Basic Player State Machine
In 2D platformers, player characters are typically governed by a design pattern called a finite state machine (FSM). A character cannot perform every action at once: if climbing a ladder, they should not trigger a jumping animation; if hit by a projectile and stunned, they should not respond to standard movement inputs.
Let's implement a foundational state controller in 'obj_player' using 'switch'. First, initialize the variable that tracks the current state in the Create event:
// Object: obj_player
// Event: Create
// Initialize movement and control variables
velocidad_horizontal = 0;
velocidad_vertical = 0;
velocidad_caminar = 4;
// Define initial state
estado_jugador = "quieto"; // Possible states: "quieto", "caminando", "saltando", "herido"Next, in the Step event, we implement decision-making logic that determines which movement and physics rules apply during each update cycle:
// Object: obj_player
// Event: Step
switch (estado_jugador)
{
case "quieto":
velocidad_horizontal = 0;
// When movement inputs are detected (covered in Module 4),
// update this variable to "caminando"
break;
case "caminando":
// Apply standard horizontal walking speed
velocidad_horizontal = velocidad_caminar;
break;
case "saltando":
// Vertical velocity changes while airborne
velocidad_vertical += 0.5; // Basic gravity simulation
break;
case "herido":
// Player temporarily loses movement control
velocidad_horizontal = -2; // Automatic knockback on hit
break;
default:
// If an invalid state is assigned, reset the player to a safe state
show_debug_message("Warning: Unrecognized state: " + string(estado_jugador));
estado_jugador = "quieto";
break;
}Notice the clean organization of this structure: each behavior is cleanly isolated in its own logical compartment. Adding a new state later on (such as "swimming" or "sliding") only requires adding a new 'case' with its corresponding 'break', without risking regressions in already tested states.
Conditional Nesting and Best Practices
Nesting refers to placing one conditional structure inside another. This is perfectly valid and very common in game programming. However, excessive nesting leads to what software engineering calls 'pyramid code' or 'arrow anti-pattern,' which is notoriously difficult to read, debug, and maintain.
Consider a typical example: a treasure chest represented by 'obj_chest'. To open the chest, the player must be near the chest, press the interact key, and carry at least one gold key in inventory.
💡 Guard Clauses (Early Return): In long events or complex scripts, a recommended practice is to check conditions that prevent execution right at the start. For example, instead of wrapping all enemy logic inside 'if (vida > 0)', place 'if (vida <= 0) { exit; }' at the very top. The 'exit' statement immediately stops event execution for that instance during that frame, keeping the remainder of your code flat and readable.
Key Differences: When to Use 'if-else' vs. 'switch'
Choosing between an 'if-else if' chain and a 'switch' statement depends on the nature of your data and the readability of your code:
| Criterion | 'if' / 'else if' Structure | 'switch' Structure |
|---|---|---|
| Comparison Types | Numeric ranges, inequalities (<, >, <=, >=), or compound logic. | Fixed discrete values (integers, constants, specific strings). |
| Variables Evaluated | Can evaluate multiple distinct variables in a single expression (e.g., vida < 10 && balas == 0). | Strictly evaluates a single central variable or expression across multiple cases. |
| Engine Performance | Evaluates conditions sequentially until the first true condition is met. | The compiler can optimize case lookups, making it slightly faster for many branches. |
| Ideal Game Use Cases | Checking health thresholds, spatial collisions, and time limits. | Player/enemy state machines, weapon selection, and menu navigation. |
Hands-On Exercises
Reinforce what you learned by completing the following three exercises. Write the code precisely inside your GameMaker project.
Exercise 1: Score Threshold and Multiplier System (Step Event Logic)
In 'obj_player', add two variables to the Create event: 'puntos' initialized to 0, and 'multiplicador' initialized to 1. In the Step event, write an 'if-else if-else' structure that updates 'multiplicador' according to these rules: - If points are 1000 or greater, the multiplier must be 4. - If points are between 500 and 999 (inclusive), the multiplier must be 2. - If points are less than 500, the multiplier must remain 1. Verification criteria: Manually test different initial values for 'puntos' in the Create event (such as 200, 750, and 1200). Use 'show_debug_message("Current multiplier: " + string(multiplicador));' in the Step event to confirm in the console that the assigned multiplier matches the expected range.
Exercise 2: Ammo Type Selector Using a 'switch' Statement
In the Create event of 'obj_player', declare the variable 'tipo_disparo' with the value "estandar" (supported values will be "estandar", "doble", "fuego", plus a fallback). In the Step event, implement a 'switch' statement that sets two numeric variables ('danio_proyectil' and 'costo_energia') depending on the shot type: - "estandar": damage 10, energy cost 0. - "doble": damage 18, energy cost 5. - "fuego": damage 30, energy cost 15. - default: log a warning message to the console and reset the type to "estandar" with its default values. Verification criteria: Deliberately assign an unrecognized string to 'tipo_disparo' (such as "laser") in the Create event and run the project in debug mode. The output console should display the warning, and the character should default to standard shot settings without halting execution.
Exercise 3: Compound Logical Collision Filter
Implement trap logic using conditional variables. In an object named 'obj_trampa', initialize variables in its Create event: 'trampa_activa = true;' and 'fuerza_danio = 25;'. In the Step event, write an 'if' condition that checks whether the trap is active AND a boolean variable 'jugador_encima' is true. If both conditions hold true, subtract damage from the player's health (e.g., 'obj_player.puntos_vida -= fuerza_danio;') and log the resulting health value to the console. Verification criteria: Toggle 'trampa_activa' to 'false' while 'jugador_encima' is true. Health loss must stop immediately, verifying that both conditions are strictly required by the '&&' operator.
Summary
- Boolean expressions evaluate to either 'true' (1) or 'false' (0) and form the foundation of all logical branching.
- Relational operators (==, !=, <, <=, >, >=) compare variables against reference values to drive game events.
- Logical operators (&&, ||, !) allow combining multiple conditions in a single statement, reducing unnecessary nesting.
- The 'if-else if-else' structure provides mutually exclusive branching paths, running only the first branch whose condition evaluates to true.
- The 'switch' statement provides a clean, maintainable, and efficient structure to check a single variable against discrete cases, making it ideal for 2D state machines.
- A 'break' statement is required at the end of each 'case' to prevent accidental fall-through, while 'default' guarantees safe fallback behavior for unrecognized inputs.
Practise what you learned
Exercise 1 · 1 point
What is the purpose of the 'break' keyword inside a 'switch' statement in GML Code?
Exercise 2 · 1 point
Complete these fundamental concepts about decision-making in GML: 1. To check whether two values are equal within a logical condition, use the relational operator . 2. The clause attaches to a preceding 'if', never takes its own condition in parentheses, and runs only when the evaluated condition is false.
Exercise 3 · 2 points
In the Step event of the `obj_player` object, write an `if` statement that checks whether the `monedas` variable is greater than or equal to 100 (`monedas >= 100`). Inside its block enclosed in curly braces `{ }`, increase `vidas` by 1 (`vidas += 1;`) and subtract 100 from `monedas` (`monedas -= 100;`).
Your code is not executed: specific requirements are checked on the text.
Sign in to save your progress — Sign in