Introducción al Desarrollo de Videojuegos con GameMaker · Módulo 3: Fundamentos de programación con GML Code.

Estructuras de control condicionales

Clase 12 de 60 · 60 min

También disponible en: EN

Toma de decisiones en el desarrollo de videojuegos

Un videojuego no es una secuencia fija de instrucciones que se ejecuta de principio a fin sin alteraciones; es un sistema reactivo y dinámico. En cada instante, el motor evalúa condiciones cambiantes: si el jugador presiona un botón, si un enemigo entra en contacto con una trampa, si la barra de vida llega a cero o si la cantidad de monedas recolectadas alcanza el umbral necesario para otorgar una vida extra. Para que un programa responda adecuadamente a tales eventos, requiere mecanismos que modifiquen el flujo de ejecución del código basándose en el estado del entorno.

En GameMaker, estas bifurcaciones lógicas se gestionan principalmente mediante estructuras condicionales. Aunque la herramienta ofrece un entorno visual denominado GML Visual (donde estas decisiones se representan mediante bloques de acción interconectados como 'If Variable' o 'If Collision'), en el desarrollo profesional y en este curso nos enfocaremos en GML Code. Programar mediante código ofrece una sintaxis precisa, flexible y rápida de mantener, permitiendo modelar mecánicas complejas sin las restricciones de espacio y velocidad inherentes a los diagramas visuales.

En la clase anterior establecimos las bases de la sintaxis y el almacenamiento de datos a través de variables como 'puntos_vida', 'velocidad_x' y 'monedas'. En esta lección, utilizaremos esas mismas variables para gobernar el comportamiento de los objetos mediante las sentencias 'if', 'else if', 'else' y 'switch', construyendo la lógica fundamental de nuestro juego de plataformas 2D.

Expresiones booleanas y operadores de comparación

Toda estructura de control condicional se apoya en una premisa matemática simple: la evaluación de una expresión booleana. Una expresión booleana es cualquier instrucción o cálculo que da como resultado exactamente uno de dos estados posibles: verdadero (true) o falso (false). En GameMaker, internamente, el valor booleano 'true' equivale al número entero 1 (o a cualquier valor numérico mayor que 0.5), mientras que 'false' equivale al número 0.

Para construir estas expresiones, se emplean operadores relacionales de comparación. Estos operadores inspeccionan dos operandos (como variables, constantes o cálculos) y resuelven si la relación matemática entre ellos es cierta o falsa. La siguiente tabla resume los operadores de comparación admitidos en GML Code:

OperadorNombreDescripciónEjemplo en GML
==IgualdadEvalúa si ambos valores son exactamente iguales.puntos_vida == 0
!=DesigualdadEvalúa si ambos valores son distintos entre sí.arma_actual != "espada"
<Menor queVerdadero si el valor de la izquierda es menor que el derecho.puntos_vida < 100
<=Menor o igual queVerdadero si el valor izquierdo es menor o exactamente idéntico al derecho.tiempo_restante <= 0
>Mayor queVerdadero si el valor de la izquierda supera al de la derecha.monedas > 50
>=Mayor o igual queVerdadero si el valor de la izquierda es mayor o idéntico al de la derecha.nivel_jugador >= 5

💡 Cuidado con la asignación frente a la comparación: en programación, el signo simple '=' se reserva para asignar un valor a una variable (por ejemplo: vida = 100;), mientras que el doble signo '==' se utiliza para comparar si dos valores son iguales (por ejemplo: if (vida == 100)). Aunque GameMaker tolera el uso de un solo '=' dentro de una condición 'if', es una mala práctica que genera confusión y errores graves en otros lenguajes. Utiliza siempre '==' para evaluar igualdades.

Operadores lógicos

Con frecuencia, una acción en un videojuego no depende de una única condición aislada, sino de la combinación de múltiples factores simultáneos o alternativos. Para enlazar diversas comparaciones booleanas, se utilizan los operadores lógicos:

OperadorSímboloComportamientoEjemplo práctico
Y lógico (AND)&&Devuelve true únicamente si todas las condiciones evaluadas son verdaderas.(puntos_vida > 0) && (en_el_suelo == true)
O lógico (OR)||Devuelve true si al menos una de las condiciones evaluadas es verdadera.(tecla_espacio == true) || (boton_gamepad == true)
Negación (NOT)!Invierte el valor de verdad. Convierte true en false y viceversa.!esta_congelado

La sentencia 'if': ejecución condicional básica

La sentencia 'if' es el bloque elemental sobre el que se sustentan todas las decisiones de programación. Su funcionamiento es directo: evalúa la expresión booleana colocada entre paréntesis; si la expresión resulta ser verdadera, el intérprete de GameMaker entra al bloque de código delimitado por llaves '{ }' y ejecuta las líneas que contiene. Si la expresión resulta falsa, el bloque de llaves es completamente ignorado y la ejecución continúa en la línea inmediatamente posterior.

// Sintaxis general de una sentencia if
if (expresion_booleana)
{
    // Bloque de código que se ejecuta solo si la expresión es verdadera
    instruccion_uno();
    instruccion_dos();
}

Veamos un ejemplo contextualizado en nuestro personaje principal, el objeto 'obj_player'. En la clase anterior inicializamos en su evento Create la variable 'monedas' en 0 y la variable 'vidas' en 3. Supongamos que deseamos que, cada vez que el jugador reúna 100 monedas, se le otorgue una vida adicional y su contador de monedas vuelva a cero.

// Objeto: obj_player
// Evento: Step

// Verificamos si se ha alcanzado la meta de monedas recolectadas
if (monedas >= 100)
{
    vidas += 1;
    monedas -= 100;
    show_debug_message("¡Recompensa! Vidas actuales: " + string(vidas));
}

El evento Step se ejecuta en cada ciclo del juego (normalmente 60 veces por segundo). En cada uno de esos fotogramas, GameMaker lee el valor actual de 'monedas'. Mientras 'monedas' oscile entre 0 y 99, la expresión '(monedas >= 100)' devuelve false, por lo que el motor ignora por completo las tres líneas interiores. En el fotograma exacto en el que 'monedas' alcance o supere 100, la expresión se vuelve true: la variable 'vidas' se incrementa en uno, se descuentan 100 monedas para no otorgar vidas infinitas de inmediato y se emite un mensaje por consola.

Rutas alternativas: las cláusulas 'else' y 'else if'

En el diseño de mecánicas es habitual encontrarse con situaciones donde no solo deseamos actuar cuando una condición se cumple, sino también tomar una acción diametralmente opuesta cuando no se cumple. Para este patrón se utiliza la cláusula 'else' (que significa 'de lo contrario'). La cláusula 'else' nunca lleva una condición propia entre paréntesis; se enlaza directamente a un 'if' previo y se ejecuta únicamente cuando la condición de dicho 'if' resulta ser falsa.

// Objeto: obj_player
// Evento: Step

if (esta_envenenado == true)
{
    // Si el jugador está sufriendo envenenamiento, pierde salud progresivamente
    puntos_vida -= 0.1;
}
else
{
    // Si no está envenenado, recupera energía de forma pasiva
    if (puntos_vida < salud_maxima)
    {
        puntos_vida += 0.05;
    }
}

Cuando existen más de dos caminos posibles y estos son mutuamente excluyentes, encadenar múltiples sentencias 'if' aisladas es ineficiente y puede provocar comportamientos no deseados. En su lugar, se recurre a la construcción 'else if'. Esta estructura evalúa una segunda condición únicamente si la primera resultó ser falsa, permitiendo encadenar tantas ramas como requiera la lógica del juego, culminando opcionalmente con un 'else' final que actúe como red de seguridad para cualquier caso no previsto.

Diagrama de flujo lógico que muestra la bifurcación condicional: una condición inicial evalúa la salud del jugador, derivando en estado crítico si es menor o igual a 25, en derrota si es menor o igual a 0, o en estado normal a través del bloque final else.
Figura 1: Estructura de bifurcación condicional mediante cadenas 'if', 'else if' y 'else'. · Imagen generada con IA

La sentencia 'switch': ramificaciones múltiples optimizadas

A medida que un videojuego crece, es muy común encontrarse con variables que actúan como selectores: variables que pueden albergar múltiples valores discretos (como un número entero de estado o una cadena de texto) que determinan qué debe hacer una entidad. Si intentáramos modelar un sistema de cinco o diez estados diferentes mediante 'else if', el código resultante se volvería engorroso, propenso a errores tipográficos y difícil de leer.

Para estos escenarios, GameMaker ofrece la sentencia 'switch'. Esta estructura toma el valor de una única variable y lo compara contra una serie de valores definidos llamados 'case' (casos). Cuando encuentra una coincidencia, ejecuta el bloque de código asociado hasta encontrar la instrucción 'break'.

// Sintaxis general de una sentencia switch
switch (variable_a_evaluar)
{
    case valor_1:
        // Código si variable_a_evaluar == valor_1
        break;
        
    case valor_2:
        // Código si variable_a_evaluar == valor_2
        break;
        
    default:
        // Código que se ejecuta si ningún caso anterior coincidió
        break;
}

Existen dos palabras clave críticas dentro del funcionamiento de un 'switch':

1. 'break': Es la orden de escape. Le indica a GameMaker que dé por finalizada la ejecución del 'switch' y salte fuera de las llaves. Si olvidas colocar un 'break' al final de un caso, el intérprete continuará ejecutando las líneas del siguiente caso de forma secuencial (un fenómeno conocido como caída o fall-through), lo cual casi siempre resulta en errores de lógica inadvertidos.

2. 'default': Es el equivalente al 'else' dentro del 'switch'. Se ubica habitualmente al final y define qué debe ocurrir si el valor de la variable evaluada no coincidió con ninguno de los bloques 'case' especificados. Es indispensable para garantizar la robustez del juego ante estados inesperados o datos corruptos.

Aplicación práctica: máquina de estados básica para el jugador

En los juegos de plataformas 2D, el personaje principal suele regirse por una arquitectura conocida como máquina de estados finitos (FSM, por sus siglas en inglés). El jugador no puede realizar todas las acciones simultáneamente: si está trepando una escalera, no puede estar ejecutando la animación de salto; si ha sido golpeado por un proyectil y está aturdido, no debe responder a los comandos de movimiento.

Veamos cómo implementar un controlador de estados elemental en el objeto 'obj_player' utilizando 'switch'. Primero, en el evento Create inicializamos la variable que contendrá el estado actual:

// Objeto: obj_player
// Evento: Create

// Inicialización de variables de movimiento y control
velocidad_horizontal = 0;
velocidad_vertical = 0;
velocidad_caminar = 4;

// Definición del estado de control inicial
estado_jugador = "quieto"; // Posibles estados: "quieto", "caminando", "saltando", "herido"

A continuación, en el evento Step, implementamos la toma de decisiones que determinará qué conjunto de reglas físicas y de control se aplicará en cada ciclo de actualización:

// Objeto: obj_player
// Evento: Step

switch (estado_jugador)
{
    case "quieto":
        velocidad_horizontal = 0;
        // Si el jugador pulsa las teclas de movimiento (que profundizaremos en el Módulo 4)
        // podemos cambiar la variable a "caminando"
        break;
        
    case "caminando":
        // Asignamos la velocidad regular de desplazamiento horizontal
        velocidad_horizontal = velocidad_caminar;
        break;
        
    case "saltando":
        // Durante el salto la velocidad vertical se modifica
        velocidad_vertical += 0.5; // Simulación de gravedad temporal
        break;
        
    case "herido":
        // El jugador pierde el control momentáneamente
        velocidad_horizontal = -2; // Retroceso automático por impacto
        break;
        
    default:
        // Si por error se asignó un estado no válido, devolvemos al personaje a un estado seguro
        show_debug_message("Advertencia: Estado no reconocido: " + string(estado_jugador));
        estado_jugador = "quieto";
        break;
}

Observa la claridad estructural del bloque anterior: cada comportamiento está rigurosamente aislado en su propio compartimento lógico. Agregar un nuevo estado en el futuro (como "nadando" o "deslizándose") solo requerirá añadir un nuevo 'case' con su respectivo 'break', sin peligro de romper la lógica de los estados ya probados.

Anidamiento de condicionales y buenas prácticas

Se denomina anidamiento (nesting) a la técnica de colocar una estructura condicional dentro de otra. Esto es perfectamente válido y sumamente común en la lógica de videojuegos. Sin embargo, un anidamiento excesivo genera lo que en ingeniería de software se conoce como 'código espagueti' o 'código piramidal', el cual resulta extremadamente difícil de leer, depurar y extender.

Analicemos un caso típico: un cofre de tesoro representado por el objeto 'obj_chest'. Para abrir el cofre, el jugador debe estar cerca del objeto, debe presionar la tecla de interacción y debe poseer en su inventario al menos una llave dorada.

💡 Cláusulas de guarda (Early Return / Guard Clauses): En eventos extensos o scripts complejos, una práctica recomendada es descartar primero las condiciones que impiden la ejecución. Por ejemplo, en lugar de envolver todo el código de un enemigo dentro de 'if (vida > 0)', puedes colocar al inicio: 'if (vida <= 0) { exit; }'. La función 'exit' detiene de inmediato el evento para esa instancia en ese fotograma, manteniendo el resto del código limpio y sin anidamientos.

Diferencias clave: cuándo usar 'if-else' y cuándo usar 'switch'

La elección entre una cadena de 'if-else if' y una estructura 'switch' depende de la naturaleza de los datos evaluados y la claridad del código resultante:

CriterioEstructura 'if' / 'else if'Estructura 'switch'
Tipo de comparacionesRangos numéricos, desigualdades (<, >, <=, >=) o evaluaciones complejas.Valores fijos discretos (enteros, constantes, strings específicos).
Variables evaluadasPuede comparar diferentes variables en una misma condición (ej: vida < 10 && balas == 0).Evalúa estrictamente una única variable o expresión central contra varios casos.
Rendimiento del motorEvalúa cada condición secuencialmente hasta encontrar una verdadera.El compilador puede optimizar la búsqueda de casos, siendo ligeramente más ágil con muchas ramas.
Uso ideal en videojuegosComprobación de límites de salud, colisiones espaciales y umbrales de tiempo.Máquinas de estados del jugador/enemigos, selección de armas y gestión de menús.

Ejercicios prácticos

Aplica los conceptos aprendidos completando los tres ejercicios siguientes. Asegúrate de escribir el código con precisión sintáctica en tu proyecto de GameMaker.

Ejercicio 1: Sistema de bonificación y umbral de puntuación (Programación en Evento Step)

En el objeto 'obj_player', crea en su evento Create la variable 'puntos' con un valor inicial de 0, y 'multiplicador' con valor 1. Luego, en su evento Step, escribe una estructura condicional 'if-else if-else' que actualice 'multiplicador' según las siguientes reglas: - Si los puntos son 1000 o más, el multiplicador debe ser 4. - Si los puntos están entre 500 y 999 (inclusive), el multiplicador debe ser 2. - Si los puntos son menores a 500, el multiplicador debe mantenerse en 1. Criterio de comprobación: Modifica manualmente el valor inicial de 'puntos' en el evento Create con valores como 200, 750 y 1200. Utiliza la función 'show_debug_message("Multiplicador actual: " + string(multiplicador));' en el evento Step para corroborar por consola que el valor asignado corresponde con el rango esperado.

Ejercicio 2: Selector de tipo de munición con sentencia 'switch'

Declara en el evento Create de 'obj_player' la variable 'tipo_disparo' con valor "estandar" (las opciones posibles serán "estandar", "doble", "fuego" y cualquier valor imprevisto). En el evento Step, implementa una sentencia 'switch' que asigne valores a dos variables numéricas ('danio_proyectil' y 'costo_energia') de acuerdo al tipo de disparo: - "estandar": daño 10, costo 0. - "doble": daño 18, costo 5. - "fuego": daño 30, costo 15. - default: emitir un mensaje de aviso en consola y forzar el tipo a "estandar" con sus valores correspondientes. Criterio de comprobación: Asigna intencionadamente un valor no reconocido a 'tipo_disparo' (por ejemplo, "laser") en el evento Create y ejecuta el juego en depuración. La consola de salida debe mostrar el mensaje de error y el juego debe restablecer los valores del disparo estándar sin detener la ejecución.

Ejercicio 3: Filtro de interacción de colisión lógica condicional

Crea una situación lógica para una trampa de pinchos representada por variables. En un objeto 'obj_trampa', declara en su evento Create: 'trampa_activa = true;' y 'fuerza_danio = 25;'. En el evento Step, escribe una condición 'if' que evalúe si la trampa está activa Y si una variable booleana 'jugador_encima' es verdadera. Si ambas condiciones son ciertas, reduce los 'puntos_vida' del jugador (puedes acceder mediante 'obj_player.puntos_vida -= fuerza_danio;') e imprime en consola la salud resultante. Criterio de comprobación: Asegúrate de comprobar qué sucede cuando 'trampa_activa' pasa a 'false'. La reducción de puntos de vida debe cesar inmediatamente, demostrando que ambas condiciones son estrictamente obligatorias gracias al operador '&&'.

Resumen

  • Las expresiones booleanas se evalúan a 'true' (1) o 'false' (0) y son la base de todas las bifurcaciones lógicas.
  • Los operadores relacionales (==, !=, <, <=, >, >=) permiten contrastar variables con valores de referencia para determinar eventos del juego.
  • Los operadores lógicos (&&, ||, !) posibilitan la combinación de múltiples condiciones en una sola instrucción, reduciendo el anidamiento innecesario de código.
  • La estructura 'if-else if-else' permite crear secuencias de caminos alternativos y mutuamente excluyentes, ejecutando únicamente la rama cuya condición se satisfaga primero.
  • La sentencia 'switch' ofrece un formato ordenado, mantenible y eficiente para evaluar una misma variable frente a múltiples casos discretos, siendo la herramienta estándar para máquinas de estados en 2D.
  • El uso de la instrucción 'break' es obligatorio al final de cada 'case' dentro de un 'switch' para evitar la ejecución no deseada de los casos subsiguientes, y 'default' proporciona un respaldo seguro ante contingencias.

Practica lo aprendido

Ejercicio 1 · 1 punto

¿Qué función cumple la palabra clave 'break' dentro de una sentencia 'switch' en GML Code?

Practicas sin cuenta: no se guarda nada. Regístrate para guardar tu progreso.

Ejercicio 2 · 1 punto

Completa los conceptos fundamentales sobre la toma de decisiones en GML: 1. Para comprobar si dos valores son iguales dentro de una condición lógica se debe emplear el operador relacional . 2. La cláusula se enlaza a un 'if' previo, nunca lleva una condición propia entre paréntesis y se ejecuta únicamente cuando la condición evaluada resulta falsa.

Practicas sin cuenta: no se guarda nada. Regístrate para guardar tu progreso.

Ejercicio 3 · 2 puntos

En el evento Step del personaje `obj_player`, escribe una sentencia `if` que compruebe si la variable `monedas` es mayor o igual a 100 (`monedas >= 100`). Dentro de su bloque entre llaves `{ }`, incrementa `vidas` en 1 (`vidas += 1;`) y resta 100 a `monedas` (`monedas -= 100;`).

Tu código no se ejecuta: se comprueban requisitos concretos sobre el texto.

Practicas sin cuenta: no se guarda nada. Regístrate para guardar tu progreso.

Ingresa para guardar tu progreso — Ingresar