Introducción al Desarrollo de Videojuegos con GameMaker · Module 3: Programming Fundamentals with GML Code
Creating and Calling Custom Scripts
Lesson 15 of 60 · 60 min
Also available in: ES
Code Modularization and Reusability in GameMaker
As a game grows in complexity, writing all logic directly inside object events (such as Create, Step, or Draw) quickly becomes unsustainable. If you need to calculate the distance between two entities, cap the player's maximum health, or apply a damage formula across ten different objects, duplicating the same block of code introduces hard-to-trace bugs, complicates maintenance, and drastically increases development time. In programming, this issue is solved by applying the fundamental DRY (Don't Repeat Yourself) principle.
In GameMaker, the primary tool for structuring and reusing code is scripts. A script is an Asset Browser resource that acts as a global container where we define one or more custom functions using GML Code. Once a function is declared inside a script, any object, alarm, or event in your game can call it just like a built-in engine function (such as instance_create_layer, place_meeting, or point_distance), passing specific arguments and receiving processed values.

Creating a Script Resource in the Asset Browser
To create a script in GameMaker, locate the Asset Browser panel (usually on the right side of the workspace). Right-click the Scripts group or any empty space in the panel, select Create, and click Script. A new code file will be created immediately, prompting you to name it.
It is good naming practice to use the prefix scr_ followed by the functional domain grouping the functions within that file. For example, scr_math, scr_combat, scr_movement, or scr_utilities. In modern versions of GameMaker (version 2.3 and later), a single script resource can contain multiple independent function declarations, allowing you to organize your project into thematic modules rather than having hundreds of individual single-line files.
💡 Important historical note: In older versions of GameMaker Studio (1.4 and pre-2.3), a script file was equivalent to a single function carrying the name of the file and receiving arguments via the built-in argument[0], argument[1], etc. array. In modern GameMaker, scripts are container files that declare formal functions using the function keyword with named parameters.
Anatomy and Syntax of a Custom Function
A function is a block of statements that performs a specific task, accepts input data (called parameters or arguments), executes internal processing, and optionally returns a result to the caller via the return statement. The basic structure of a function in GML Code is as follows:
// Basic function declaration inside a Script
function calcular_area_rectangulo(_ancho, _alto)
{
var _area = _ancho * _alto;
return _area;
}Let's break down each component of this structure: first, the 'function' keyword tells the GameMaker compiler that we are declaring a new executable routine. Next, we define the function name (calcular_area_rectangulo), which should be descriptive, lowercase, and use snake_case per standard GML conventions. Inside parentheses, we place parameters separated by commas—in this case, _ancho and _alto. Inside the function body, enclosed in curly brackets {}, we perform the necessary operations and use the 'return' keyword to send the result back.
Using local variables declared with 'var' (such as _area) for intermediate calculations within a function is essential. Local variables only exist while the function is actively executing and are immediately cleared from memory upon completion, avoiding memory clutter in the calling object or accidentally overwriting instance variables with the same name.
Documentation with JSDoc Comments and Autocompletion
One of GameMaker's most powerful features is its code autocompletion (IntelliSense) system. When you type a built-in function like instance_destroy, the editor displays in the status bar what arguments it expects and what it does. You can achieve the exact same professional behavior in your custom functions by adding JSDoc-formatted comments immediately before the function declaration, using triple-slash (///) syntax.
/// @function calcular_dano_mitigado(_dano_base, _defensa)
/// @description Calculates the final damage received after defense percentage reduction.
/// @param {Real} _dano_base Base incoming damage before mitigation.
/// @param {Real} _defensa Receiver's defense points (range 0 to 100).
function calcular_dano_mitigado(_dano_base, _defensa)
{
// Ensure defense stays between 0 and 90 so it never blocks 100% of damage
var _defensa_segura = clamp(_defensa, 0, 90);
var _factor_reduccion = 1 - (_defensa_segura / 100);
var _dano_final = _dano_base * _factor_reduccion;
return _dano_final;
}By including these four lines of JSDoc comments, whenever you begin typing 'calcular_dano_mitigado' in any event across any object, GameMaker will automatically suggest the function, highlight parameter names, and display the expected data types at the bottom of the code editor.
Calling Functions and Execution Scope
Calling or invoking a custom function is as simple as typing its name followed by the required arguments in parentheses inside an object event. However, to avoid logic errors, it is crucial to understand the execution context or 'scope' of the function when it runs.
When a function declared in a script is called from an object (for instance, from the Step event of obj_jugador), the context of the 'self' keyword belongs to that specific calling instance. This means the function can directly read and modify that object's instance variables, unless it is designed as a purely mathematical function.
| Function Type | Primary Purpose | Return Value | Instance Interaction |
|---|---|---|---|
| Pure / Mathematical Function | Perform calculations based strictly on received arguments. | Always returns a value (real, string, boolean, etc.). | Does not read or modify instance variables of the calling object. |
| Action / State Function | Modify the state or properties of the calling object. | Optional (can return nothing or return true/false on success). | Directly modifies instance variables such as x, y, hp, or state. |
| Check / Query Function | Evaluate complex game conditions and validate gameplay rules. | Always returns a boolean value (true or false). | Reads environment conditions, collisions, or instance variables. |
Knowing when to design a pure function versus an action function is a fundamental skill. Pure functions are easier to debug and can be safely reused anywhere in your project. Action functions dramatically simplify bloated events, turning an overloaded Step event into a clean, readable sequence of function calls.
Default Parameter Values
In game development, we frequently want certain function arguments to have default fallback values if the caller omits them. GameMaker allows default values right in the parameter list using the assignment operator (=).
/// @function aplicar_impulso(_fuerza_x, _fuerza_y = 0)
/// @param {Real} _fuerza_x Horizontal force to apply
/// @param {Real} _fuerza_y Optional vertical force (defaults to 0)
function aplicar_impulso(_fuerza_x, _fuerza_y = 0)
{
// Modifies instance variables of the calling object
velocidad_x += _fuerza_x;
velocidad_y += _fuerza_y;
}With this declaration, calling 'aplicar_impulso(5)' from the player sets _fuerza_x to 5 while _fuerza_y automatically defaults to 0 without throwing a missing argument error. If we call 'aplicar_impulso(5, -12)', both parameters take their explicitly supplied values.
Integration into the 2D Platformer Project
In earlier lessons in this module, we defined variables for our player character obj_jugador, such as health points (hp, hp_maxima), collected coins, and damage multipliers. We will now structure these mechanics using dedicated scripts, laying the foundation for movement and collisions in upcoming modules.
Step by step, we will implement a core script for managing vitality and combat called scr_combate, along with another script for movement calculations called scr_matematicas_plataformas. Let's create the first file in the Asset Browser.
/// Script: scr_combate
/// @function recibir_dano(_hp_actual, _dano)
/// @description Applies damage to a health value, guaranteeing it does not drop below zero.
/// @param {Real} _hp_actual Entity's current health.
/// @param {Real} _dano Damage points received.
function recibir_dano(_hp_actual, _dano)
{
var _nueva_hp = _hp_actual - _dano;
if (_nueva_hp < 0)
{
_nueva_hp = 0;
}
return _nueva_hp;
}
/// @function curar_entidad(_hp_actual, _curacion, _hp_maxima)
/// @description Restores health without exceeding the defined maximum health limit.
/// @param {Real} _hp_actual Entity's current health.
/// @param {Real} _curacion Amount of health to restore.
/// @param {Real} _hp_maxima Maximum allowed health points.
function curar_entidad(_hp_actual, _curacion, _hp_maxima)
{
var _resultado = _hp_actual + _curacion;
if (_resultado > _hp_maxima)
{
_resultado = _hp_maxima;
}
return _resultado;
}Now let's examine how these functions are consumed by the player object. In previous classes, we created obj_jugador. Open its Create event and its Step event to test out the functions we just declared.
// Object: obj_jugador
// Event: Create
hp_maxima = 100;
hp = hp_maxima;
esta_vivo = true;
// Debug variable to test hit impacts
contador_invulnerabilidad = 0;// Object: obj_jugador
// Event: Step
// Decrease invulnerability timer if active
if (contador_invulnerabilidad > 0)
{
contador_invulnerabilidad -= 1;
}
// Test simulation: Pressing 'H' (Hurt) applies 25 points of damage
if (keyboard_check_pressed(ord("H")) && contador_invulnerabilidad == 0)
{
hp = recibir_dano(hp, 25);
contador_invulnerabilidad = 30; // Half a second grace period at 60 FPS
show_debug_message("Damage taken. Remaining health: " + string(hp));
}
// Test simulation: Pressing 'C' (Cure/Heal) restores 20 points of health
if (keyboard_check_pressed(ord("C")))
{
hp = curar_entidad(hp, 20, hp_maxima);
show_debug_message("Healing applied. Current health: " + string(hp));
}
// Vital status check
if (hp <= 0 && esta_vivo)
{
esta_vivo = false;
show_debug_message("The player has been defeated.");
}Notice the clarity and simplicity of the Step event. Instead of nesting complex mathematical checks inside the player's event, the line 'hp = recibir_dano(hp, 25);' communicates the mechanic's intent cleanly. Even better: when we build a patrolling enemy like obj_enemigo_patrulla in Module 7, we can use these exact same 'recibir_dano' and 'curar_entidad' functions without rewriting a single line of math logic.
Mathematical Functions for Platformer Physics: Linear Approach
A common problem in retro platformers is having velocity decelerate smoothly to zero (friction) without overshooting the target value or oscillating between positive and negative numbers. In GML, the built-in lerp() function interpolates on a percentage curve, but for constant, fixed-step acceleration and friction, an approach function is standard practice. We will create a second script called scr_matematicas_plataformas to house this essential utility.
/// Script: scr_matematicas_plataformas
/// @function aproximar_valor(_actual, _objetivo, _paso)
/// @description Shifts an initial value toward a target by a fixed step without overshooting.
/// @param {Real} _actual Starting numeric value.
/// @param {Real} _objetivo Target value to reach.
/// @param {Real} _paso Positive change magnitude per step.
function aproximar_valor(_actual, _objetivo, _paso)
{
if (_actual < _objetivo)
{
return min(_actual + _paso, _objetivo);
}
else if (_actual > _objetivo)
{
return max(_actual - _paso, _objetivo);
}
return _objetivo;
}// Object: obj_jugador
// Event: Step (Friction snippet)
// Assuming velocidad_x has residual velocity and no movement keys are pressed
var _friccion = 0.5;
velocidad_x = aproximar_valor(velocidad_x, 0, _friccion);If velocidad_x was 1.2, subtracting 0.5 brings it to 0.7, then 0.2, and on the subsequent step, instead of falling to -0.3 (which would cause jittery backward movement), max(0.2 - 0.5, 0) snaps the value cleanly to 0. This provides a crisp, responsive stop perfectly suited to high-precision retro platformers.
Best Practices for Structuring Scripts
To keep your project scalable and maintainable as we progress toward more complex modules, keep these design guidelines in mind:
1. One purpose per function: Each function should do one job well. If a function named 'manejar_jugador()' calculates lives, reads keyboard inputs, plays audio, and moves the camera simultaneously, the codebase will quickly become tangled. Separate that logic into 'actualizar_combate()', 'leer_controles()', and 'actualizar_posicion()'.
2. Consistent prefixes: Use clear prefixes for function arguments (such as leading underscores like _velocidad or _limite) to instantly distinguish them from instance or global variables (like velocidad or limite).
3. Avoid hidden dependencies: Whenever feasible, design functions so they receive everything they need through parameters rather than assuming a global variable exists. This makes your functions modular and portable—ready to be reused across future GameMaker projects without modification.
Practical Exercises
Complete the following three practical exercises in your project to consolidate creating, documenting, and calling custom scripts and functions in GML Code.
Summary
- Scripts in GameMaker serve as global libraries containing reusable custom functions accessible to any object in the game.
- Starting in GameMaker 2.3+, the 'function' keyword followed by a name and parameter list inside braces is used to declare structured functions.
- The 'return' statement halts function execution and returns a processed result to the call site, keeping logic modular and clean.
- JSDoc comments (starting with ///) allow GameMaker's code editor to provide intelligent autocompletion and contextual descriptions for your custom functions.
- Using local variables ('var') inside functions prevents memory leaks and avoids accidentally overwriting object instance variables.
- Structuring code into modular scripts (such as scr_combate and scr_matematicas_plataformas) leaves the project clean and primed for the movement and collision mechanics in the next module.
Practise what you learned
Exercise 1 · 1 point
Why is it essential to declare intermediate variables with the 'var' keyword inside a custom function in GameMaker?
Exercise 2 · 1 point
Match each GameMaker scripting element or convention to its corresponding description:
- scr_
- ///
- return
- =
Exercise 3 · 2 points
Following the lesson example, write the custom function named 'calcular_area_rectangulo'. The function must take the parameters '_ancho' and '_alto', calculate their product into a local variable '_area' using 'var', and return that value with 'return'.
Your code is not executed: specific requirements are checked on the text.
Sign in to save your progress — Sign in