Introducción al Desarrollo de Videojuegos con GameMaker · Módulo 7: Enemigos, inteligencia artificial básica y mecánicas de juego.

Diseño y programación de un enemigo patrullero básico

Clase 31 de 60 · 55 min

También disponible en: EN

Introducción al comportamiento autónomo en videojuegos 2D

Hasta este punto del curso, la interacción en nuestro proyecto de plataformas ha estado impulsada principalmente por la entrada del usuario: el jugador presiona una tecla o mueve una palanca, y el personaje responde en pantalla. Sin embargo, para que un mundo de videojuego se sienta vivo, desafiante y dinámico, es imprescindible introducir entidades cuyos movimientos respondan a reglas lógicas internas e independientes de los controles del jugador. Este conjunto de reglas predefinidas constituye lo que en desarrollo de videojuegos denominamos Inteligencia Artificial (IA) básica o scripting de comportamiento.

El arquetipo fundamental de la IA en los juegos de plataformas bidimensionales es el enemigo patrullero. Desde los icónicos Goombas de Super Mario Bros. hasta los metools de Mega Man o los caballeros errantes de Hollow Knight, el patrón de desplazamiento horizontal constante de izquierda a derecha, rebotando contra las paredes o evitando precipicios, es una convención de diseño clásica. En esta clase, crearemos desde cero un enemigo de patrulla que se desplazará de forma autónoma por el escenario, interactuará con las plataformas existentes (`obj_wall`) y cambiará de dirección al detectar un obstáculo sólido.

Preparación de recursos: Sprite y máscara de colisión

Antes de escribir una sola línea de código, debemos preparar la representación gráfica de nuestro enemigo y configurar adecuadamente su geometría de colisión en GameMaker. La precisión física y visual dependerá directamente de cómo definamos el origen del sprite y su máscara.

Comenzaremos creando un nuevo recurso de tipo Sprite en el panel del Asset Browser (clic derecho en la carpeta de Sprites -> Create -> Sprite) y le asignaremos el nombre canónico `spr_enemy_patrol`. Para mantener la coherencia estética con nuestro protagonista retro, utilizaremos una resolución de 16x16 o 32x32 píxeles. Si aún no cuentas con una animación final de caminado, puedes dibujar un bloque o criatura estilizada de color distintivo (por ejemplo, rojo o púrpura) con dos ojos que apunten claramente hacia la derecha.

💡 Regla de oro de orientación: Por convención universal en el desarrollo de videojuegos 2D, todos los sprites con dirección lateral deben dibujarse orientados hacia la DERECHA de forma predeterminada. Esto simplifica los cálculos matemáticos, ya que la dirección positiva del eje horizontal (+X) se corresponde directamente con un escalado positivo de la imagen.

Existen dos parámetros críticos que deben configurarse dentro del editor de sprites de `spr_enemy_patrol`:

PropiedadAjuste RecomendadoJustificación Técnica
Punto de Origen (Origin)Bottom Centre (o Middle Centre)Al invertir horizontalmente el sprite mediante `image_xscale`, el eje de rotación/escalado debe estar exactamente en el centro horizontal. Si estuviera en Top Left (0,0), el enemigo daría un salto brusco hacia atrás de varios píxeles al darse la vuelta.
Máscara de Colisión (Collision Mask)Rectangle / Manual o Full ImageDebe ser perfectamente simétrica. Una máscara asimétrica provocará que el enemigo quede atrapado dentro de una pared en el instante exacto en que cambie su orientación.
Interpolación / TamañoCoincidente con el jugador (ej. 32x32 px)Permite que las aperturas y túneles del nivel diseñados para el jugador funcionen coherentemente con las dimensiones del enemigo.

Una vez creado y configurado el sprite, crearemos el objeto correspondiente. Haz clic derecho en la carpeta de Objetos, selecciona Create -> Object, nómbralo `obj_enemy_patrol` y asígnale el sprite `spr_enemy_patrol`. No es necesario marcar la casilla 'Solid' integrada de GameMaker; gestionaremos todas las interacciones físicas mediante programación explícita en GML Code para garantizar un control exacto y libre de comportamientos anómalos.

Estructura de variables en el Evento Create

El enemigo necesita almacenar en memoria su estado cinemático: a qué velocidad camina, hacia qué lado se mueve en cada instante, su velocidad vertical actual y la fuerza de gravedad que lo mantiene anclado al suelo. Todas estas variables deben inicializarse en el evento **Create** de `obj_enemy_patrol`.

Abre `obj_enemy_patrol`, añade el evento **Create** y escribe el siguiente bloque de código en GML:

/// @description Inicialización del enemigo patrullero

// Parámetros de movimiento horizontal
move_speed = 1.5;         // Magnitud de desplazamiento en píxeles por fotograma
move_dir = 1;             // Dirección: 1 = Derecha, -1 = Izquierda
hsp = 0;                  // Velocidad horizontal calculada (hspeed personalizada)

// Parámetros de movimiento vertical y gravedad
vsp = 0;                  // Velocidad vertical actual
grav = 0.3;               // Fuerza gravitatoria descendente
vsp_max = 8;              // Límite terminal de caída libre

// Sincronización visual inicial
image_xscale = move_dir;

Analicemos la lógica detrás de estas variables. En lugar de utilizar las variables integradas `hspeed` y `vspeed` de GameMaker —las cuales mueven la instancia automáticamente de forma interna antes de que podamos comprobar colisiones precisas—, declaramos `hsp` y `vsp`. Esta técnica, que ya utilizamos al programar al jugador en módulos anteriores, nos permite calcular exactamente hacia dónde desea moverse el objeto y verificar si el espacio está libre antes de modificar sus coordenadas reales `x` e `y`.

La variable `move_dir` actúa como un vector unitario unidimensional. Solo tomará dos valores en esta implementación: `1` (hacia la derecha) o `-1` (hacia la izquierda). Al multiplicar `move_dir` por `move_speed`, obtenemos de forma directa la velocidad y sentido exactos del movimiento sin necesidad de ramificar el código con múltiples condicionales.

Diagrama esquemático que ilustra la lógica de patrulla en 2D: cálculo de velocidad horizontal, vector de predicción de colisión frontal con place_meeting e inversión de sentido al detectar una pared.
Mecánica predictiva de patrulla: el enemigo comprueba la colisión antes de desplazarse; al encontrar un sólido, invierte su vector de dirección. · Imagen generada con IA

Lógica de movimiento y colisiones en el Evento Step

El evento **Step** se ejecuta una vez por cada fotograma del juego (habitualmente 60 veces por segundo). Aquí procesaremos la física elemental, evaluaremos las colisiones con la geometría sólida (`obj_wall`) e invertiremos la dirección de avance cuando el enemigo impacte de frente contra un obstáculo.

Añade el evento **Step -> Step** a `obj_enemy_patrol` y añade el siguiente código estructurado en tres fases: cálculo de fuerzas, resolución de colisiones y actualización de coordenadas.

/// @description Lógica de patrulla y colisiones físicas

// =========================================================================
// 1. APLICACIÓN DE GRAVEDAD
// =========================================================================
if (vsp < vsp_max) {
    vsp += grav;
}

// =========================================================================
// 2. CÁLCULO DE VELOCIDAD HORIZONTAL
// =========================================================================
hsp = move_dir * move_speed;

// =========================================================================
// 3. DETECCIÓN Y RESOLUCIÓN DE COLISIÓN HORIZONTAL (PAREDES)
// =========================================================================
// Comprobamos si en la posición hacia la que nos movemos existe un objeto sólido
if (place_meeting(x + hsp, y, obj_wall)) {
    // Nos aproximamos píxel a píxel hasta quedar pegados al obstáculo
    while (!place_meeting(x + sign(hsp), y, obj_wall)) {
        x += sign(hsp);
    }
    
    // Detenemos el desplazamiento en este fotograma
    hsp = 0;
    
    // Invertimos el sentido de la marcha para los siguientes fotogramas
    move_dir = -move_dir;
}

// Aplicamos el desplazamiento horizontal resultante en el eje X
x += hsp;

// =========================================================================
// 4. DETECCIÓN Y RESOLUCIÓN DE COLISIÓN VERTICAL (SUELOS Y TECHOS)
// =========================================================================
if (place_meeting(x, y + vsp, obj_wall)) {
    // Nos aproximamos píxel a píxel verticalmente hacia la superficie
    while (!place_meeting(x, y + sign(vsp), obj_wall)) {
        y += sign(vsp);
    }
    
    // Anulamos la velocidad vertical al impactar contra el suelo o techo
    vsp = 0;
}

// Aplicamos el desplazamiento vertical resultante en el eje Y
y += vsp;

// =========================================================================
// 5. ORIENTACIÓN VISUAL (ESPEJADO DE SPRITE)
// =========================================================================
if (move_dir != 0) {
    image_xscale = move_dir;
}

Desglose matemático de la inversión de dirección

Observa con atención la línea `move_dir = -move_dir;`. Esta elegante operación matemática elimina la necesidad de escribir sentencias complejas con `if/else`. Si `move_dir` vale `1`, `-1 * 1` resulta en `-1`. En el siguiente ciclo, si vale `-1`, `-(-1)` resulta en `1`. Con una única línea conmutamos la orientación indefinidamente cada vez que se produce una colisión frontal.

La función `sign(hsp)` devuelve `1` si el valor es positivo, `-1` si es negativo y `0` si es cero. Al combinarla con el bucle `while (!place_meeting(x + sign(hsp), y, obj_wall))`, logramos un contacto 'pixel-perfect' (exacto al píxel). El enemigo nunca quedará suspendido a 0.5 píxeles de la pared ni se incrustará dentro de ella antes de dar media vuelta, eliminando vibraciones gráficas o atascamientos indeseados.

La función place_meeting frente a otras alternativas

En GameMaker existen tres funciones habituales para comprobar contactos entre objetos: `place_meeting`, `position_meeting` e `instance_place`. Es fundamental comprender sus diferencias para no utilizarlas erróneamente en el diseño de IA:

FunciónQué evalúaValor de retornoUso principal en este contexto
place_meeting(x, y, obj)La máscara completa de la instancia actual posicionada en (x, y) contra las máscaras de 'obj'.Boolean (true / false)Ideal para saber si el enemigo chocará físicamente como cuerpo completo con una pared o suelo.
position_meeting(x, y, obj)Un único punto infinitesimal (x, y) en las coordenadas globales contra las máscaras de 'obj'.Boolean (true / false)Excelente para sensores puntuales, como sondas en el suelo frente al enemigo.
instance_place(x, y, obj)La máscara completa de la instancia en (x, y) contra las máscaras de 'obj'.Id de instancia (o noone)Necesario cuando necesitamos saber exactamente con qué instancia colisionamos para modificar sus variables.

Prevención de caídas: Detección de precipicios (Ledge Detection)

Con el código implementado hasta este punto, el enemigo patrullará de pared a pared de forma impecable. No obstante, si situamos al enemigo sobre una plataforma suspendida en el aire que no cuenta con paredes a los lados, el enemigo caminará hacia el borde y caerá por efecto de la gravedad hasta aterrizar en el fondo del escenario.

En el diseño de niveles, frecuentemente deseamos que ciertos patrulleros permanezcan confinados a su plataforma asignada, custodiando un segmento elevado sin arrojarse al vacío. Para conseguir esto, dotaremos a nuestro enemigo de un 'sensor de suelo adelantado'.

La lógica consiste en proyectar una comprobación virtual ligeramente por delante del enemigo y unos píxeles por debajo de sus pies. Si en ese punto proyectado no hay suelo (`!position_meeting` o `!place_meeting`), significa que el enemigo se encuentra al borde de un abismo, por lo que debe frenar e invertir su marcha exactamente igual que si hubiera impactado contra un muro.

Esta comprobación debe ejecutarse antes de aplicar `x += hsp`, permitiendo que el enemigo reaccione de inmediato al borde antes de dar el paso que lo haría caer al vacío.

Código completo y refinado de obj_enemy_patrol

A continuación, presentamos la integración limpia y modular de todo el sistema en los eventos correspondientes, lista para ser probada en cualquier nivel de tu proyecto.

Evento Create de obj_enemy_patrol

/// @description Configuración inicial del patrullero

// Constantes y variables de desplazamiento
move_speed = 1.5;            // Velocidad constante de patrulla
move_dir = 1;                // 1 = Derecha, -1 = Izquierda
hsp = 0;                     // Velocidad horizontal actual

// Variables de gravedad
vsp = 0;                     // Velocidad vertical actual
grav = 0.35;                 // Aceleración por gravedad
vsp_max = 9;                 // Velocidad terminal

// Comportamiento de plataforma
avoid_ledges = true;         // true: no cae al vacío; false: cae de la repisa

// Ajuste visual inicial
image_xscale = move_dir;

Evento Step de obj_enemy_patrol

/// @description Rutina de patrullaje, colisiones y sensores

// 1. Gravedad
if (vsp < vsp_max) {
    vsp += grav;
}

// 2. Comprobación de borde/precipicio (si la opción está habilitada)
var _is_grounded = place_meeting(x, y + 1, obj_wall);

if (avoid_ledges && _is_grounded) {
    // Proyectamos el sensor frente al pie del enemigo en la dirección actual
    var _probe_x = x + (move_dir * (abs(sprite_width) * 0.5));
    var _probe_y = bbox_bottom + 2;
    
    // Si no hay suelo sólido bajo el punto explorado, invertimos la marcha
    if (!position_meeting(_probe_x, _probe_y, obj_wall)) {
        move_dir = -move_dir;
    }
}

// 3. Velocidad horizontal objetivo
hsp = move_dir * move_speed;

// 4. Colisión y rebote contra paredes sólidas
if (place_meeting(x + hsp, y, obj_wall)) {
    while (!place_meeting(x + sign(hsp), y, obj_wall)) {
        x += sign(hsp);
    }
    hsp = 0;
    move_dir = -move_dir;
}
x += hsp;

// 5. Colisión vertical con suelo o techos
if (place_meeting(x, y + vsp, obj_wall)) {
    while (!place_meeting(x, y + sign(vsp), obj_wall)) {
        y += sign(vsp);
    }
    vsp = 0;
}
y += vsp;

// 6. Volteo del sprite según la dirección activa
if (move_dir != 0) {
    image_xscale = move_dir;
}

Colocación y pruebas en la Room

Para verificar el correcto funcionamiento de nuestro enemigo, abriremos una de las salas de prueba creadas en módulos anteriores (por ejemplo, `rm_level_1` o una sala de pruebas como `rm_test_mechanics`).

Sigue estos pasos dentro del editor de salas:

1. Selecciona la capa de instancias (`Instances`) en el panel de capas a la izquierda de la pantalla. Recuerda que nunca debes soltar objetos lógicos en capas de mosaicos (Tilemaps) o fondos (Backgrounds).

2. Arrastra una instancia de `obj_enemy_patrol` y colócala en un pasillo cerrado entre dos columnas de bloques de `obj_wall`.

3. Arrastra una segunda instancia de `obj_enemy_patrol` sobre una plataforma flotante elevada sin muros a los lados para poner a prueba el sensor de precipicios.

4. Ejecuta el juego presionando F5 (o el botón de reproducción en la barra de herramientas superior). Observa el comportamiento de ambas instancias:

El enemigo en el pasillo debe avanzar fluidamente hacia la derecha, impactar con precisión milimétrica contra el muro, invertir de inmediato su sprite para mirar hacia la izquierda y regresar hasta la pared opuesta, repitiendo el ciclo de forma continua. Por su parte, el enemigo situado en la plataforma elevada llegará hasta el borde sin precipitarse, girando hacia el centro de la plataforma.

💡 Depuración rápida: Si notas que el enemigo se queda atascado vibrando indefinidamente en una pared, verifica inmediatamente dos cosas: 1) Que el punto de origen de `spr_enemy_patrol` esté centrado horizontalmente en 'Bottom Centre'. 2) Que la máscara de colisión esté configurada como un rectángulo perfectamente simétrico en lugar de usar detección 'Precise per-frame'.

Ejercicios prácticos

Realiza las siguientes actividades para afianzar la comprensión de los algoritmos de patrulla y la manipulación de variables de instancia en GameMaker.

Ejercicio 1: Variación individual de velocidad mediante Variable Definitions

**Consigna:** En lugar de que todos los enemigos patrullen a la misma velocidad fija (`move_speed = 1.5;`), abre la ventana de `obj_enemy_patrol` y haz clic en la sección 'Variable Definitions'. Añade una variable llamada `move_speed` con valor por defecto `1.5` de tipo Real. Elimina la asignación directa de `move_speed` en el código del evento Create para no sobreescribirla. Luego, en la Room, haz doble clic sobre dos enemigos diferentes colocados en el escenario y cambia el valor de su velocidad individual (por ejemplo, un enemigo lento con velocidad `0.8` y uno rápido con `3.0`).

**Criterio de comprobación:** Al pulsar F5, ambos enemigos deben coexistir en el mismo nivel patrullando a velocidades visiblemente distintas, demostrando el principio de variables de instancia personalizadas sin haber duplicado el objeto.

Ejercicio 2: Marcadores invisibles de patrulla (Patrol Markers)

**Consigna:** Crea un nuevo objeto llamado `obj_marker_patrol`. Asígnale un sprite semitransparente de color brillante (por ejemplo, amarillo o cian de 16x16 px) y desmarca la casilla 'Visible' del objeto (o mantenla visible para depuración). Modifica el evento Step del enemigo para que, además de chocar con `obj_wall`, también invierta su marcha al colisionar con `obj_marker_patrol`. Coloca dos marcadores en medio de un pasillo abierto y sitúa al enemigo entre ellos.

**Criterio de comprobación:** El enemigo debe patrullar de un marcador a otro rebotando limpiamente en el aire abierto sin necesidad de paredes físicas sólidas, permitiendo delimitar zonas de patrullaje libremente durante el diseño de niveles.

Ejercicio 3: Estado de pausa temporal al girar (Turn Delay)

**Consigna:** Por lo general, los enemigos mecánicos o más realistas no cambian de dirección de manera instantánea; se detienen por una fracción de segundo antes de retomar la marcha. Utilizando una alarma (`alarm[0]`) o una variable temporizadora manual (`turn_timer`), programa al enemigo para que, tras tocar un muro o borde, su velocidad horizontal se vuelva `0` durante 30 fotogramas (medio segundo a 60 FPS) antes de empezar a caminar en la dirección contraria.

**Criterio de comprobación:** Al llegar a un obstáculo, el enemigo se detiene inmóvil durante medio segundo, gira su mirada hacia el lado contrario y reanuda el movimiento a su velocidad habitual.

Resumen

  • Un enemigo patrullero básico se fundamenta en un vector de velocidad horizontal autónomo combinado con una gravedad constante aplicada en cada fotograma.
  • La convención estándar de GameMaker exige dibujar sprites orientados hacia la derecha con un punto de origen centrado simétricamente (Bottom Centre) para permitir volteos con image_xscale sin saltos espaciales.
  • La inversión de marcha se resuelve limpiamente mediante la operación move_dir = -move_dir al detectar colisiones inminentes con place_meeting.
  • El algoritmo pixel-perfect con un bucle while y sign(hsp) asegura que la instancia alcance exactamente el borde de las superficies sin incrustarse en los bloques sólidos.
  • La detección de precipicios anticipa el vacío usando sensores virtuales con position_meeting por delante de la base del sprite, evitando que el patrullero se precipite de plataformas suspendidas.

Practica lo aprendido

Ejercicio 1 · 1 punto

¿Por qué en la lección se programa el movimiento con variables personalizadas como `hsp` y `vsp` en lugar de utilizar las variables integradas `hspeed` y `vspeed` de GameMaker?

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

Ejercicio 2 · 1 punto

Ordena cronológicamente las acciones que realiza el código del evento Step para detectar y resolver la colisión horizontal del patrullero con una pared (`obj_wall`):

  1. 1Detener la velocidad horizontal del fotograma actual asignando `hsp = 0`.
  2. 2Avanzar píxel a píxel hasta quedar pegado a la pared con `while (!place_meeting(x + sign(hsp), y, obj_wall))`.
  3. 3Aplicar el desplazamiento horizontal resultante sumando `x += hsp`.
  4. 4Invertir el sentido de marcha para los próximos fotogramas con `move_dir = -move_dir`.
  5. 5Comprobar con `place_meeting(x + hsp, y, obj_wall)` si habrá colisión en la posición futura.
Practicas sin cuenta: no se guarda nada. Regístrate para guardar tu progreso.

Ejercicio 3 · 2 puntos

Completa el código GML del evento Step para: 1) Calcular la velocidad horizontal `hsp` multiplicando `move_dir` por `move_speed`. 2) Invertir matemáticamente la dirección `move_dir` con la fórmula enseñada en la lección (sin usar condicionales if/else).

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