Introducción al Desarrollo de Videojuegos con GameMaker · Módulo 3: Fundamentos de programación con GML Code.
Bucles y funciones básicas
Clase 13 de 60 · 55 min
También disponible en: EN
Introducción al flujo iterativo y a las funciones nativas en GameMaker
Hasta este punto del curso, hemos aprendido a almacenar información mediante variables y a tomar decisiones con estructuras condicionales como if, else y switch. Sin embargo, en el desarrollo de videojuegos existe una necesidad constante: ejecutar una misma operación decenas, cientos o miles de veces de forma automática, estructurada y sin escribir manualmente la misma línea de código una y otra vez. Para resolver este desafío, los lenguajes de programación proporcionan los bucles (también llamados ciclos o estructuras repetitivas), los cuales trabajan en estrecha colaboración con las funciones incorporadas del motor.
Uno de los errores conceptuales más frecuentes entre quienes inician en GameMaker consiste en confundir el ciclo natural del motor —el Evento Step, que se ejecuta continuamente a la velocidad del juego (por ejemplo, 60 veces por segundo)— con la ejecución de un bucle de código como for o while. Es fundamental comprender esta distinción: mientras que el Evento Step distribuye sus ciclos a lo largo del tiempo, permitiendo que la pantalla se actualice entre un fotograma y el siguiente, un bucle de código dentro de un evento se ejecuta por completo dentro del mismo fotograma, congelando la renderización visual hasta que todas sus repeticiones hayan finalizado. Si un bucle tarda demasiado o entra en un ciclo infinito, el juego entero se congelará.
En esta lección profundizaremos en el funcionamiento interno de las funciones nativas de GameMaker (GML Code), analizando cómo reciben parámetros y devuelven datos para realizar cálculos trigonométricos, matemáticos o de gestión de instancias. Posteriormente, estudiaremos a fondo las tres estructuras de repetición esenciales en GML: el ciclo repeat, el bucle for y el bucle while, complementados con las instrucciones de interrupción break y continue. Aplicaremos estos conceptos de manera directa sobre nuestro proyecto del juego de plataformas 2D, automatizando tareas como la generación de hileras de monedas coleccionables y la dispersión controlada de elementos visuales.
¿Qué es una función nativa y cómo opera en GML?
En términos de programación, una función es un bloque de código encapsulado diseñado para llevar a cabo una tarea específica. GameMaker incorpora cientos de funciones nativas creadas para facilitarnos la vida: desde calcular la distancia entre dos entidades en la sala hasta redondear un número decimal o generar valores aleatorios para otorgar dinamismo a las mecánicas del juego. Invocar o llamar a una función consiste en escribir su nombre seguido de un par de paréntesis dentro de los cuales podemos enviar datos de entrada conocidos como argumentos o parámetros.
Muchas funciones realizan una acción inmediata y no necesitan comunicar un resultado numérico (por ejemplo, instance_destroy() destruye la instancia actual sin devolver nada). Por otro lado, un grupo muy amplio de funciones realiza cálculos y 'devuelve' un valor resultante, el cual podemos almacenar directamente en una variable o utilizar en el interior de una expresión matemática o condicional. La sintaxis general para invocar una función con retorno consiste en asignar su resultado a una variable: mi_resultado = nombre_de_funcion(arg0, arg1, ...);
| Función GML | Argumentos principales | Valor devuelto | Propósito habitual en videojuegos 2D |
|---|---|---|---|
| clamp(val, min, max) | Valor a evaluar, límite inferior, límite superior | Número dentro del rango permitido | Limitar la velocidad de caída o asegurar que la salud no supere el máximo. |
| point_distance(x1, y1, x2, y2) | Coordenadas del punto 1 y del punto 2 | Distancia euclidiana en píxeles (Real) | Medir la cercanía entre el jugador y un enemigo o coleccionable. |
| irandom_range(min, max) | Valor entero mínimo, valor entero máximo | Número entero aleatorio inclusivo | Determinar botines aleatorios, daño crítico o retrasos variables. |
| choose(val0, val1, val2...) | Lista de valores separados por comas | Uno de los elementos provistos al azar | Elegir un sprite aleatorio, una dirección inicial o un sonido. |
| show_debug_message(string) | Texto o valor convertido a cadena | Ninguno (imprime en la Output Console) | Monitorear variables internas y rastrear errores de lógica. |
Observemos cómo interactúan estas funciones en un bloque real de programación. Supongamos que deseamos restringir la velocidad de nuestro personaje y verificar si un objeto coleccionable se encuentra dentro del rango de recolección de nuestro héroe:
// Ubicación: obj_player -> Evento Step
// 1. Limitar la velocidad horizontal entre -velocidad_maxima y velocidad_maxima
velocidad_horizontal = clamp(velocidad_horizontal, -velocidad_maxima, velocidad_maxima);
// 2. Comprobar la distancia con una posición de referencia (por ejemplo, el centro de un cofre)
var _distancia_al_objetivo = point_distance(x, y, 320, 240);
if (_distancia_al_objetivo < 64)
{
// Imprimir en la consola de salida para depuración
show_debug_message("El jugador está cerca del cofre interactivo.");
}La palabra clave 'var' que antecede a _distancia_al_objetivo define una variable local temporal. Las variables locales nacen y mueren exclusivamente dentro del bloque de código en el que se declaran, lo que optimiza la memoria y evita colisiones accidentales de nombres entre diferentes eventos u objetos del juego. Estudiaremos su alcance completo en la siguiente clase, pero es indispensable comenzar a utilizarlas al trabajar con bucles y funciones.
El ciclo repeat: iteraciones directas y eficientes
La estructura de bucle más sencilla y ligera en GameMaker es la sentencia repeat. Su propósito es directo: tomar una expresión numérica entera y ejecutar el bloque de instrucciones contenido entre llaves exactamente esa cantidad de veces. A diferencia de otros lenguajes de programación que carecen de esta construcción nativa, GameMaker ofrece repeat como una herramienta altamente optimizada para aquellos casos donde no necesitamos saber en qué número de iteración nos encontramos, sino simplemente repetir una acción N veces.
La sintaxis general de repeat es: repeat (cantidad_de_repeticiones) { // Código a ejecutar en cada ciclo } Si el valor dentro del paréntesis es menor o igual a cero, GameMaker omite por completo el bloque sin producir errores. Si el número contiene decimales, se redondea hacia abajo hacia el entero más cercano.
// Ubicación: obj_player -> Evento Destroy (o cuando el jugador es derrotado)
// Generamos 12 partículas visuales de impacto en la posición actual
repeat (12)
{
var _offset_x = irandom_range(-16, 16);
var _offset_y = irandom_range(-16, 16);
// Creamos una instancia decorativa en la capa 'Efectos'
instance_create_layer(x + _offset_x, y + _offset_y, "Instances", obj_particula_muerte);
}En el ejemplo anterior, cada una de las 12 ejecuciones del bloque repeat calcula un desfase aleatorio independiente mediante irandom_range y coloca una instancia de obj_particula_muerte. Todo esto ocurre de manera instantánea dentro del mismo fotograma en el que la instancia de obj_player deja de existir, ofreciendo al jugador una explosión visual coordinada sin necesidad de escribir doce veces la misma línea de instanciación.
El bucle for: iteración con índice y control estructurado
Aunque repeat resulta idóneo para acciones masivas ciegas, en la inmensa mayoría de las mecánicas de videojuegos necesitamos tener constancia de en qué iteración exacta nos encontramos. Por ejemplo, si queremos crear una fila de cinco bloques de suelo o posicionar diez monedas con una separación idéntica de 32 píxeles entre sí, necesitamos conocer el valor del contador en cada ciclo. Para este propósito, el bucle for es la estructura estándar por excelencia.
El bucle for organiza su lógica en tres secciones claramente delimitadas dentro de sus paréntesis, separadas por signos de punto y coma: for (inicialización; condición; incremento). Primero se inicializa una variable contadora (comúnmente llamada 'i' por convención técnica). Segundo, se comprueba la condición de continuidad antes de cada iteración; si es verdadera, el cuerpo del bucle se ejecuta, y si es falsa, el bucle termina inmediatamente. Tercero, tras finalizar el código interno de la iteración, se aplica el incremento o decremento al contador, pasando a la siguiente evaluación.
// Ubicación: obj_controlador_nivel -> Evento Create
// Generar una hilera horizontal de 6 monedas coleccionables separadas por 40 píxeles
var _inicio_x = 120;
var _posicion_y = 300;
var _separacion = 40;
for (var i = 0; i < 6; i++)
{
var _spawn_x = _inicio_x + (i * _separacion);
instance_create_layer(_spawn_x, _posicion_y, "Instances", obj_moneda);
show_debug_message("Moneda creada en el índice: " + string(i) + " en X=" + string(_spawn_x));
}Analicemos minuciosamente el recorrido matemático de esta rutina: en la primera vuelta, i vale 0, por lo que _spawn_x se calcula como 120 + (0 * 40) = 120. En la segunda vuelta, i se incrementa a 1, dando 120 + (1 * 40) = 160. El proceso continúa progresivamente hasta i = 5 (X = 320). Cuando i pasa a valer 6 gracias al operador de incremento 'i++', la condición i < 6 evalúa a falso y GameMaker salta a la siguiente instrucción fuera del for. Todo este cálculo e instanciación de 6 monedas toma apenas microsegundos dentro del primer fotograma en el que se inicia la sala.

El bucle while: repetición condicionada y prevención de bloqueos
A diferencia del bucle for, donde conocemos con precisión el número de repeticiones antes de empezar a iterar, el ciclo while se utiliza cuando la cantidad de vueltas depende de una condición dinámica que evoluciona mientras el bucle está en marcha. La sentencia while evalúa una condición booleana: mientras dicha condición siga siendo verdadera, el contenido del bloque se ejecutará una y otra vez de forma ininterrumpida.
La sintaxis básica de while es: while (condición) { // Instrucciones que deben modificar la condición para evitar un ciclo infinito } El peligro supremo del bucle while radica en el infame 'bucle infinito' (infinite loop). Si por un error de lógica la condición nunca se vuelve falsa, el procesador se quedará atrapado ejecutando esas líneas para siempre, impidiendo que el motor termine el fotograma actual. Como resultado, GameMaker dejará de responder, el sistema operativo indicará que la aplicación se ha congelado y la partida terminará de manera forzosa.
💡 Regla de oro de seguridad: Cuando utilices un bucle while, asegúrate siempre de que dentro del bloque exista una instrucción que altere las variables de la condición hacia su finalización. Es una excelente práctica de ingeniería de videojuegos añadir un contador de seguridad ('safety counter') que limite el número máximo de iteraciones a un valor razonable (por ejemplo, 500 vueltas) para evitar que el juego se cuelgue ante situaciones imprevistas.
Veamos un caso de uso clásico y muy útil de while: buscar una posición aleatoria en el nivel que se encuentre libre de colisiones sólidas antes de colocar un enemigo o un botín especial.
// Ubicación: obj_spawner_enemigos -> Evento Create
var _pos_valida = false;
var _intentos = 0;
var _max_intentos = 100;
var _coord_x = 0;
var _coord_y = 0;
while (!_pos_valida && _intentos < _max_intentos)
{
_intentos++;
// Elegimos una posición aleatoria dentro de los límites de la room
_coord_x = irandom_range(64, room_width - 64);
_coord_y = irandom_range(64, room_height - 64);
// position_meeting comprueba si en esa coordenada colisionamos con un muro sólido
if (!position_meeting(_coord_x, _coord_y, obj_solid))
{
_pos_valida = true;
}
}
// Si encontramos una posición libre dentro de los intentos permitidos, creamos la entidad
if (_pos_valida)
{
instance_create_layer(_coord_x, _coord_y, "Instances", obj_enemigo_patrulla);
}
else
{
show_debug_message("Aviso: No se encontró espacio libre tras " + string(_max_intentos) + " intentos.");
}Gracias al contador de seguridad _intentos, garantizamos que el motor jamás se bloqueará de forma indefinida incluso si la sala estuviera completamente repleta de bloques sólidos. El bucle se detiene tan pronto se cumple una de las dos condiciones: o encontramos una posición válida o alcanzamos el límite preventivo de intentos.
Instrucciones de control de flujo: break y continue
Dentro de cualquier estructura iterativa (sea for, while o repeat), disponemos de dos herramientas fundamentales para alterar de forma voluntaria el curso natural del ciclo: break y continue. Comprender el uso adecuado de estas palabras clave permite escribir código mucho más limpio, veloz y legible, evitando anidaciones innecesarias de comprobaciones condicionales.
La palabra clave 'break' detiene por completo la ejecución del bucle en ese instante exacto. La ejecución del programa salta inmediatamente a la primera línea situada después de la llave de cierre del bucle, ignorando cualquier repetición que quedara pendiente. Por el contrario, la instrucción 'continue' no detiene el bucle por completo: únicamente interrumpe la vuelta o iteración actual, saltándose el resto de código que quede en el cuerpo del bucle y pasando directamente al incremento y a la siguiente evaluación de la condición.
// Ubicación: obj_controlador_items -> Evento Create
// Demostración de break y continue en la generación de plataformas
for (var i = 0; i < 10; i++)
{
// Supongamos que no queremos colocar bloques en el índice central (i = 5)
if (i == 5)
{
// Saltamos esta iteración puntual; no se ejecutará la creación para i = 5
continue;
}
// Si detectamos un evento crítico o límite externo, abortamos todo el proceso
if (i > 8)
{
// Abandona el ciclo for de forma definitiva
break;
}
var _pos_x = 64 + (i * 32);
instance_create_layer(_pos_x, 384, "Instances", obj_platform);
}En el código anterior, los índices 0, 1, 2, 3 y 4 crearán sus plataformas de 32 píxeles normalmente. Cuando i alcanza el valor 5, la instrucción continue interrumpe el ciclo antes de llegar a instance_create_layer, dejando deliberadamente un hueco en la plataforma sin romper la estructura del bucle. Cuando i llega a 9, la condición i > 8 se cumple y la sentencia break aborta de inmediato el bucle, evitando que se dibuje nada en el índice 9. Dominar estas dos directivas otorga un control quirúrgico sobre cualquier algoritmo de generación en el videojuego.
Integración práctica: Generador procedural básico de coleccionables en el juego de plataformas
Para afianzar todos los conceptos abordados en esta lección, implementaremos una mecánica real para nuestro proyecto de plataformas 2D de estética retro. Hasta ahora, en las salas creadas en el Módulo 2 hemos ubicado manualmente las monedas mediante el editor de GameMaker. A continuación, crearemos un objeto controlador que utilice bucles for combinados con funciones matemáticas (como irandom_range y clamp) para generar un arco de monedas sobre una plataforma flotante.
En el Panel de Recursos (Asset Browser), haz clic secundario sobre la carpeta 'Objects' y selecciona 'Create -> Object'. Asígnale el nombre obj_generador_monedas. No es necesario asignarle un sprite, ya que actuará como una entidad lógica invisible. Abre el objeto y agrega el evento Create con el siguiente código detallado:
// Ubicación: obj_generador_monedas -> Evento Create
// Definimos la cantidad de monedas en el patrón y la separación horizontal
var _total_monedas = 7;
var _espaciado_x = 32;
// Altura base en píxeles de elevación del arco parabólico
var _altura_arco_max = 48;
for (var i = 0; i < _total_monedas; i++)
{
// Calculamos el desplazamiento horizontal continuo
var _pos_x = x + (i * _espaciado_x);
// Calculamos una curva parabólica simétrica sencilla:
// Cuando i está en los extremos (0 y 6), el desfase vertical es 0.
// Cuando i está en el centro (3), el desfase alcanza la altura máxima.
var _progreso_normalizado = (i - (_total_monedas - 1) / 2);
var _factor_curva = 1 - sqr(_progreso_normalizado / ((_total_monedas - 1) / 2));
// Limitamos el factor entre 0 y 1 usando clamp para máxima estabilidad
_factor_curva = clamp(_factor_curva, 0, 1);
var _pos_y = y - (_factor_curva * _altura_arco_max);
// Creamos la moneda en la capa 'Instances' del escenario
instance_create_layer(_pos_x, _pos_y, "Instances", obj_coin);
}
// Una vez cumplida su misión de spawn, el generador se autoelimina de la memoria
instance_destroy();Este ejemplo conjuga de forma magistral los pilares de la programación: utiliza una variable de control en un bucle for para iterar a través de las 7 monedas, calcula algebraicamente una elevación con forma de parábola, protege los límites numéricos mediante la función nativa clamp() y finaliza destruyendo la propia instancia generadora con instance_destroy() una vez que las monedas han sido sembradas en la escena. Si arrastras una instancia de obj_generador_monedas a tu nivel en la room room_nivel_1, verás que al pulsar F5 (Run) las monedas aparecerán suspendidas en un arco estético impecable, listo para ser recolectado mediante el salto de nuestro personaje.
Ejercicios prácticos
A continuación, se presentan tres ejercicios diseñados específicamente para poner en práctica las estructuras iterativas y las llamadas a funciones nativas en GML Code. Cada ejercicio cuenta con instrucciones detalladas y pautas de comprobación técnica objetiva.
Resumen
- Diferencia fundamental: El Evento Step distribuye su ejecución en el tiempo a lo largo de los fotogramas (FPS), mientras que los bucles repeat, for y while completan todas sus iteraciones en un instante dentro del mismo fotograma.
- Funciones nativas en GML: Son herramientas integradas que realizan tareas específicas; algunas realizan acciones sin retorno mientras que otras devuelven valores numéricos o cadenas tras procesar sus argumentos de entrada (ej. clamp, point_distance, irandom_range).
- El bucle repeat: Es la estructura de repetición más simple y rápida de GameMaker; ejecuta un conjunto de instrucciones un número entero de veces sin requerir una variable de conteo.
- El bucle for: Estructura fundamental para iterar con un índice definido mediante tres componentes: inicialización de la variable, condición lógica de continuidad e incremento/decremento.
- El bucle while y el riesgo de congelamiento: Ejecuta código mientras una condición sea verdadera. Es crucial incluir un contador de seguridad ('safety counter') para evitar ciclos infinitos que bloqueen el motor de juego.
- Control de iteración con break y continue: La instrucción break aborta inmediatamente el bucle entero, mientras que continue interrumpe únicamente la vuelta en curso y pasa de inmediato a la siguiente iteración.
Practica lo aprendido
Ejercicio 1 · 1 punto
¿Es verdadero o falso? En GameMaker, un bucle de código (como `for` o `repeat`) se ejecuta por completo dentro del mismo fotograma, congelando la renderización visual hasta que todas sus repeticiones hayan finalizado.
Ejercicio 2 · 1 punto
¿Qué estructura de repetición en GameMaker está diseñada específicamente para ejecutar un bloque de instrucciones una cantidad fija de veces cuando no se necesita conocer el número de iteración actual?
Ejercicio 3 · 1 punto
Ordena las tres secciones que componen la estructura interna de un bucle `for` en GameMaker, según el orden en que se escriben dentro de sus paréntesis: `for (... ; ... ; ...)`.
- 1Comprobación de la condición de continuidad
- 2Incremento o decremento del contador
- 3Inicialización de la variable contadora
Ingresa para guardar tu progreso — Ingresar