Introducción al Desarrollo de Videojuegos con GameMaker · Module 3: Programming Fundamentals with GML Code
Variable Scope and Best Practices
Lesson 14 of 60 · 50 min
Also available in: ES
Introduction to the Concept of Scope
When starting out in game programming, it is common to assume that any variable created can be read and modified from anywhere in the project. However, as a game grows in complexity and we add more mechanics (player movement, enemy patrols, coin counters, health, and user interfaces), this disorganization leads to issues known as 'spaghetti code' and naming collisions, where an action on one object unexpectedly modifies another's data.
Scope defines the lifetime and visibility of a variable: it determines exactly who can access it, who can modify it, and how long it persists in memory before being discarded. In GameMaker and GML Code, there are three primary scope levels every developer must master from day one: local (or temporary) variables, instance variables, and global variables. Learning to use them properly will not only prevent subtle, hard-to-trace bugs, but will also optimize performance and keep your project clean and professional.

Instance Variables: Each Object's Individual Memory
An instance variable belongs exclusively to a specific copy of an object placed in the room. If you have three coins (`obj_moneda`) or two enemies (`obj_enemigo_patrulla`) in the level, each has its own independent set of instance variables, even though they share the exact same underlying codebase.
Instance variables are typically initialized in an object's Create event and remain alive in memory throughout the lifetime of that specific instance. When the instance is destroyed (for example, with `instance_destroy()`) or when the room changes without the object marked as persistent, its instance variables are automatically freed from memory.
To declare an instance variable, you simply write its identifier followed by the assignment operator (`=`) and its initial value inside the Create event, without needing any special keyword. These variables can then be read or updated in any other event of that same instance, such as the Step or Draw events.
// Asset: Objects -> obj_jugador
// Event: Create
// Instance variables for player mechanics
velocidad_caminar = 4;
velocidad_salto = -9.5;
fuerza_gravedad = 0.4;
puntos_salud = 3;
estado_actual = "reposo";
ha_tocado_suelo = false;In the code above, each of these properties belongs to that `obj_jugador`. If we created a co-op mode with a second player (`obj_jugador_dos`), their `puntos_salud` or `ha_tocado_suelo` variables would work completely independently. While an instance can access another instance's variables using dot notation (for instance, `obj_jugador.puntos_salud`), overusing direct external access breaks encapsulation and should be handled with care.
Local Variables (var): Efficiency for Temporary Calculations
A local (or temporary) variable is declared using the `var` keyword. Unlike instance variables, local variables have a very short lifecycle: they only exist while the event, function, or code block where they were declared is executing. The moment GameMaker finishes processing that block, the variable is destroyed and the allocated memory is immediately freed.
Why are they essential? Imagine that every frame (in the Step event) you need to check whether the player is pressing the left or right keys, calculate the resulting mathematical direction, and apply that force to movement. If you declare that direction as an instance variable, the object will retain that value in memory for the rest of the game, even though it was only needed for a split second to compute position updates. Multiplying this bad habit across hundreds of objects and calculations per second clutters memory and makes your code harder to follow.
// Asset: Objects -> obj_jugador
// Event: Step
// Declare temporary local variables to process keyboard input
var tecla_derecha = keyboard_check(vk_right) || keyboard_check(ord("D"));
var tecla_izquierda = keyboard_check(vk_left) || keyboard_check(ord("A"));
// Calculate temporary horizontal axis: 1 (right), -1 (left), or 0 (idle)
var eje_horizontal = tecla_derecha - tecla_izquierda;
// Apply calculation to position using the velocidad_caminar instance variable
x = x + (eje_horizontal * velocidad_caminar);
// At the end of the Step event, 'tecla_derecha', 'tecla_izquierda',
// and 'eje_horizontal' are completely cleared from memory.Local variables are also the standard and indispensable tool for loops, such as the `for` loops studied in previous lessons. Declaring the iteration index with `var i = 0` ensures you do not pollute object properties with transient variables that only have meaning within the loop.
💡 Golden Rule: If a value is only needed to resolve an intermediate mathematical operation, a quick check, or a loop within a single event, always declare it with 'var'. If the data needs to be remembered for the next frame (such as health, inventory, or velocity), it must be an instance variable.
Global Variables: The General State of the Game
Some data does not belong to any specific character, enemy, or block, but rather to the game as a whole. Common examples include: the player's total accumulated score across multiple rooms, the current level being played, the remaining life count, or a flag indicating whether the game is paused.
For these cases, we use global variables. A global variable is not tied to any room or instance: once initialized, it exists permanently in memory throughout the entire game session and can be read or modified from any object, script, or event in the project.
In modern GML standards, global variables are declared and accessed by prefixing the variable name with `global.`. There is an older syntax based on the `globalvar` keyword, but it is now deprecated and discouraged because it introduces ambiguity into code; you should always use `global.variable_name`.
// Declaring and modifying modern global variables
global.puntuacion_total = 0;
global.vidas_jugador = 3;
global.juego_pausado = false;
global.monedas_recolectadas = 0;Where and when should global variables be created? This is one of the most common questions from beginners. If you initialize a global variable in the Create event of your player (`obj_jugador`), and the player is destroyed or you restart the room, the variable will reinitialize from zero, wiping out your accumulated score. Worse, if a room loads before that object exists, other objects will try to read a global variable that hasn't been created yet, crashing the game.
The industry best practice is to create a dedicated manager object, such as `obj_controlador_juego`, and place it in the game's initial boot room (such as a loading screen or title menu, marking this object as Persistent in its properties). In this controller's Create event, all initial global variables are defined once.
Comparing Variable Scopes in GML
To clearly see the operational and technical differences across the three main scopes, review the following comparison table:
| Scope Type | Declaration Syntax | Lifecycle | Visibility | Recommended Use |
|---|---|---|---|---|
| Local / Temporary | var name = value; | Only while the current event or function is executing. | Exclusive to the code block where it was declared. | Intermediate calculations, quick input checks, loop counters (for, while). |
| Instance | name = value; | From when the instance is created until it is destroyed or leaves the room. | Owned by the instance. Accessible to others via 'id.variable' or 'with()'. | Speed, gravity, individual health, internal timers, animation states. |
| Global | global.name = value; | Throughout the entire game session (persists across rooms). | Total. Any object, event, or script can read and modify it. | Accumulated score, general settings, global lives, pause flags. |
Best Coding Practices and Clean Code in GameMaker
Writing code is not just about getting the game to run on screen; it's about making it understandable, scalable, and maintainable. When you return to your project weeks after taking a break, disorganized code will be confusing even to you. Below, we review essential guidelines for maintaining professional standards in GameMaker.
1. Clear and Consistent Naming Conventions
In GameMaker, it is strongly recommended to use three-letter prefixes for project assets, preventing confusion between visual assets, objects, or audio files:
• Sprites: `spr_personaje_caminar`, `spr_bloque_piso`, `spr_moneda`. • Objects: `obj_jugador`, `obj_enemigo_patrulla`, `obj_bloque_solido`. • Sounds: `snd_salto`, `snd_moneda`, `mus_nivel_1`. • Rooms: `rm_menu_inicio`, `rm_nivel_1`, `rm_pantalla_victoria`. • Scripts: `scr_calcular_danio`, `scr_guardar_partida`.
For variables, 'snake_case' (lowercase words separated by underscores) is recommended, using descriptive names that explain their purpose without guesswork. Avoid single-letter generic names like `a = 10;` or `temp = true;`. Instead, use `velocidad_maxima = 10;` or `esta_invencible = true;`.
2. Avoiding Magic Numbers with Variables and Macros
A 'magic number' is a hardcoded numeric value placed directly in game logic without clear context. For example, writing `if (y > 720) { instance_destroy(); }` in a Step event. If you later decide to change screen resolution or level height, you will have to manually hunt down every instance of '720' scattered across dozens of objects.
Instead, declare a descriptively named variable or define a global macro using the `#macro` directive. Macros replace an identifier with a constant value before compilation and cannot be altered at runtime:
// Macro declarations in a global script (for example: scr_configuracion)
#macro LIMITE_INFERIOR_PANTALLA 720
#macro GRAVEDAD_BASE 0.4
#macro MAX_VIDAS 3
// Clean usage in the Step event of obj_jugador:
if (y > LIMITE_INFERIOR_PANTALLA) {
scr_reiniciar_posicion_jugador();
}3. Meaningful Comments and Visual Formatting
Code comments should explain the 'why' behind a design decision, rather than merely repeating what the syntax already makes obvious. GML supports single-line comments with `//` and multi-line comment blocks with `/* ... */`.
// Redundant comment (provides no real value):
// Add 5 to x
x = x + 5;
// Clean, descriptive comment (explains design intent):
// Add constant velocity to simulate conveyor belt movement
x = x + velocidad_cinta;Practical Example: Clean Refactoring of Player and Controller
Let's apply everything we've learned to the architecture of our retro platformer. We will coordinate two clean elements: the general management object `obj_controlador_juego` and a refactored version of basic movement for our `obj_jugador` using instance, local, and global variables.
Step 1: Create the object `obj_controlador_juego`. In the GameMaker Inspector, check the 'Persistent' box. In its Create event, initialize the global state:
// Asset: Objects -> obj_controlador_juego
// Event: Create
// Initialize global game variables
global.puntuacion = 0;
global.vidas = 3;
global.nivel_completado = false;
// Confirmation message in the GameMaker debug console
show_debug_message("Game system initialized successfully.");Step 2: Now open `obj_jugador`. In its Create event, declare only its instance variables, avoiding temporary variables that are only needed for momentary calculations:
// Asset: Objects -> obj_jugador
// Event: Create
// Instance variables: velocity and physical state
velocidad_movimiento = 3.5;
velocidad_vertical = 0;
velocidad_horizontal = 0;
fuerza_salto = -8;
gravedad_personaje = 0.35;
estrellado_contra_suelo = true;Step 3: In the Step event of `obj_jugador`, use local variables (`var`) to capture keyboard inputs and compute frame velocities. Notice how none of these intermediate variables clutter the instance's persistent memory:
// Asset: Objects -> obj_jugador
// Event: Step
// 1. Read input using local variables
var presiona_izquierda = keyboard_check(vk_left) || keyboard_check(ord("A"));
var presiona_derecha = keyboard_check(vk_right) || keyboard_check(ord("D"));
var presiona_salto = keyboard_check_pressed(vk_space) || keyboard_check_pressed(ord("W"));
// 2. Vector calculation of horizontal axis using a local variable
var direccion_horizontal = presiona_derecha - presiona_izquierda;
// 3. Assign movement to instance variables
velocidad_horizontal = direccion_horizontal * velocidad_movimiento;
// Apply gravity to instance vertical velocity
velocidad_vertical += gravedad_personaje;
// 4. Provisional basic jump when pressed
if (presiona_salto && estrellado_contra_suelo) {
velocidad_vertical = fuerza_salto;
estrellado_contra_suelo = false;
}
// 5. Apply physics to the instance's built-in coordinates
x += velocidad_horizontal;
y += velocidad_vertical;
// Provisional floor clamp for testing (actual collision will be covered in Module 5)
if (y >= 400) {
y = 400;
velocidad_vertical = 0;
estrellado_contra_suelo = true;
}By clearly separating transient variables (`presiona_izquierda`, `direccion_horizontal`) from sustained entity states (`velocidad_vertical`, `estrellado_contra_suelo`) and global values (`global.vidas`), we build a robust, readable technical foundation ready for complex systems without scope collisions or memory waste.
Hands-on Exercises
Test your understanding of the three variable scopes and GML best practices by completing the following three guided exercises:
Exercise 1: Refactoring Code with Leaked Scope Analyze the following problematic snippet written in the Step event of a projectile fired by the player (`obj_proyectil`). A beginner developer forgot to use `var` on variables only needed for an immediate trigonometric calculation: ```gml // Problematic snippet: angulo_rad = degtorad(image_angle); componente_x = cos(angulo_rad) * 6; componente_y = -sin(angulo_rad) * 6; x += componente_x; y += componente_y; ``` Rewrite the snippet correcting the scope so that these three intermediate variables (`angulo_rad`, `componente_x`, `componente_y`) become strictly temporary local variables. Verification check: Verify that by prepending the `var` keyword to these variables, the projectile continues moving smoothly across the screen while no longer appearing as persistent instance properties in the debugger's 'Variables' tab.
Exercise 2: Creating a Collectible Coin Object with a Global Variable Create a new object in your project named `obj_moneda`. Assign it a simple square or circular gold sprite (you can draw it in GameMaker's sprite editor at 16x16 pixels). In the Collision event between `obj_moneda` and `obj_jugador`, implement the following actions: 1. Add 25 points to the global variable `global.puntuacion`. 2. Display a console message using `show_debug_message()` showing the new point total. 3. Destroy the collected coin instance with `instance_destroy()`. Verification check: Place three coins in your room `rm_nivel_1`. Run the game, collect them, and watch GameMaker's Output window; the counter should sequentially increase to 25, 50, and 75 as the coins disappear from the room.
Exercise 3: Spotting and Resolving Variable Shadowing Imagine a developer wrote in the player's Create event: `velocidad = 5;`. Then, in the Step event, they accidentally wrote: `var velocidad = 10; x += velocidad;`. Explain in writing what happens to the original instance variable during that frame and what visual effect it produces on the player. Modify the code so the variables have clear, distinct names and do not shadow each other. Verification check: Verify that the player moves at the intended instance speed setting without a temporary variable shadowing the object's instance variable.
Summary
- Instance variables belong to a specific instance of an object, are typically declared in the Create event, and persist until the instance is destroyed.
- Local variables are defined using the 'var' keyword, exist only while the enclosing event or script runs, and are immediately freed, optimizing memory and preventing naming collisions.
- Global variables use the 'global.' prefix, are accessible from any object, script, or event, and persist across room transitions—making them ideal for scores, lives, and game-wide settings.
- Global variables should ideally be initialized once within a persistent manager object (such as obj_controlador_juego) in the game's startup room.
- Adhering to clear naming conventions (asset prefixes like obj_, spr_, snd_, and snake_case), avoiding magic numbers with macros (#macro), and writing descriptive comments ensures a clean, professional, and scalable project.
Practise what you learned
Exercise 1 · 1 point
Match each variable scope in GameMaker with its defining characteristic and lifecycle:
- Local variable
- Instance variable
- Global variable
Exercise 2 · 1 point
Why is it considered bad practice to initialize global variables (such as score or lives) inside the Create event of the player object (`obj_player`)?
Exercise 3 · 1 point
Complete the following statements about variable scope in GameMaker: 1. To perform a temporary calculation and discard it as soon as the event or block finishes, precede the variable name with the keyword. 2. In modern GML, to declare and access a variable that belongs to the entire game rather than a specific object, use the prefix. 3. An variable is typically declared in the Create event without extra keywords and stores data unique to each individual copy of an object.
Sign in to save your progress — Sign in