Introducción al Desarrollo de Videojuegos con GameMaker · Módulo 5: Colisiones, gravedad y plataformas.
Detección de contacto con el suelo (Grounded)
Clase 24 de 60 · 50 min
También disponible en: EN
Detección de contacto con el suelo (Grounded): Fundamentos y necesidad
En el desarrollo de un videojuego de plataformas bidimensional, una de las preguntas fundamentales que el motor de juego debe responder en cada fotograma es: ¿está el personaje apoyado sobre una superficie sólida o se encuentra suspendido en el aire? A simple vista, para el ojo humano esto resulta evidente al observar la pantalla; sin embargo, para el procesador un objeto no es más que una colección de coordenadas numéricas (x, y) y una caja de colisión (bounding box). El ordenador no asume que el personaje está 'de pie' a menos que nosotros escribamos una comprobación lógica explícita que lo verifique.
Saber si el jugador está pisando tierra firme (concepto comúnmente denominado en inglés como estar 'grounded') es el pilar sobre el cual se apoyan prácticamente todas las mecánicas esenciales de un videojuego de plataformas. Si no disponemos de un método confiable para detectar el suelo, el personaje podría ejecutar saltos infinitos en pleno vuelo con solo presionar repetidamente el botón de salto, flotar de manera errática o reproducir la animación de caminar mientras cae hacia un abismo. Asimismo, esta detección es crucial para reiniciar mecánicas avanzadas como el doble salto, permitir la fricción horizontal adecuada o detonar efectos sonoros de impacto al aterrizar.
A lo largo de las lecciones previas de este módulo, configuramos una aceleración constante hacia abajo mediante la variable de gravedad (`grav`) y resolvimos el contacto físico vertical contra objetos sólidos (`obj_solid`), deteniendo la velocidad vertical (`vspd = 0`) para evitar que el avatar atraviese las plataformas. En esta clase daremos un paso más allá en la arquitectura de nuestro personaje: crearemos una bandera booleana dedicada (`is_grounded`), explicaremos cómo interrogar al motor de colisiones un píxel por debajo de los pies del jugador sin alterar su posición física y organizaremos el código para que el estado de contacto sea coherente durante todo el ciclo del fotograma.
¿Qué es una bandera lógica (Boolean Flag) en GML?
En informática y desarrollo de software, una bandera lógica (o flag) es una variable que solo puede albergar dos estados posibles: verdadero (`true`) o falso (`false`). En GameMaker Language (GML), los booleanos son tratados a nivel interno como valores numéricos donde `true` equivale a 1 y cualquier valor menor o igual a 0 se interpreta como `false`. No obstante, escribir código legible requiere utilizar explícitamente las constantes `true` y `false`, o bien evaluar expresiones condicionales directas.
Al crear una bandera lógica como `is_grounded`, estamos abstrayendo una condición geométrica compleja en una respuesta binaria simple y accesible desde cualquier parte de nuestro proyecto. En lugar de ejecutar costosas comprobaciones de colisión cada vez que el código necesita saber si el jugador puede saltar, si debe reproducir el sonido de pasos o si debe cambiar su sprite de 'caída' a 'reposo', la comprobación geométrica se efectúa una única vez por fotograma y el resultado se almacena en la variable booleana.
Esta separación entre la detección física y la toma de decisiones no solo vuelve el código más limpio y comprensible para cualquier programador principiante, sino que además ahorra recursos de procesamiento y previene inconsistencias lógicas (bugs conocidos como 'race conditions' o estados desincronizados dentro del mismo fotograma).

La técnica del píxel inferior: place_meeting(x, y + 1, obj_solid)
Para determinar si un cuerpo reposa sobre una superficie sin quedar atrapado dentro de ella, debemos proyectar una consulta hipotética. Si comprobáramos `place_meeting(x, y, obj_solid)`, la función devolvería `false` mientras el personaje esté perfectamente posado, ya que el algoritmo de colisión vertical que desarrollamos en la lección anterior se asegura precisamente de dejar al jugador en contacto tangencial con la superficie, pero sin penetrarla ni un solo píxel.
Si `place_meeting(x, y, obj_solid)` devolviera `true`, significaría que el personaje está incrustado dentro de la pared o del suelo, lo cual representaría un error de colisión. Por lo tanto, la técnica estándar en la industria consiste en consultar qué sucedería si desplazáramos al jugador exactamente un píxel hacia abajo en el eje vertical: `place_meeting(x, y + 1, obj_solid)`.
La función nativa `place_meeting(x_virtual, y_virtual, objeto)` no altera las variables de posición reales `x` e `y` de la instancia. Su comportamiento interno consiste en trasladar momentáneamente la máscara de colisión asignada a la instancia a las coordenadas indicadas, calcular si existe alguna intersección con la máscara de cualquier instancia del objeto especificado (en nuestro caso, `obj_solid`) y devolver un valor booleano (`true` o `false`). Acto seguido, la máscara regresa de inmediato a su posición original sin que nada visible haya cambiado en la pantalla.
Al sumar 1 a la coordenada vertical (`y + 1`), estamos formulando la siguiente pregunta al motor de físicas: 'Si el personaje diera un microscópico paso de 1 píxel hacia abajo, ¿colisionaría con un bloque sólido?'. Si la respuesta es afirmativa, significa que el jugador está exactamente a cero píxeles de distancia del suelo: está aterrizado. Si la respuesta es negativa, el jugador se encuentra a más de un píxel del suelo más cercano: está en el aire.
Declaración y configuración inicial en el evento Create
Siguiendo la arquitectura progresiva de nuestro proyecto de plataformas retro, abriremos el objeto del jugador (`obj_player`). En su evento **Create**, debemos asegurarnos de inicializar la variable `is_grounded`. Es una regla de oro en GameMaker: toda variable que vaya a ser evaluada en un evento cíclico como **Step** debe declararse previamente en **Create**, evitando errores de compilación o fallos de ejecución por variable no definida.
Revisemos cómo debe quedar el evento **Create** de `obj_player`, conservando las variables de movimiento horizontal, vertical y salto que hemos construido a lo largo de las lecciones del Módulo 4 y las clases anteriores de este Módulo 5:
/// Evento Create de obj_player
// --- Variables de movimiento horizontal ---
move_spd = 4; // Velocidad máxima de desplazamiento horizontal en píxeles por fotograma
hspd = 0; // Velocidad horizontal actual
// --- Variables de movimiento vertical y física ---
vspd = 0; // Velocidad vertical actual
grav = 0.3; // Aceleración de gravedad aplicada en cada fotograma
jsp = -7.5; // Fuerza de impulso de salto (negativa hacia arriba)
max_vspd = 10; // Velocidad terminal de caída libre
// --- Banderas lógicas de estado ---
is_grounded = false; // Indica si el jugador está actualmente en contacto con el sueloAl instanciarse el jugador en la sala (`Room`), asignamos `is_grounded = false` por precaución. No asumimos que el personaje nace sobre el suelo; permitiremos que el primer fotograma del juego evalúe su entorno y determine de forma autónoma su verdadero estado de contacto.
El orden de ejecución en el evento Step
En el desarrollo de videojuegos, el orden de las instrucciones dentro del evento **Step** es de vital importancia. Un algoritmo de físicas típico consta de cuatro etapas secuenciales que deben ejecutarse rigurosamente en este orden:
| Fase de la lógica | Propósito principal | Operación matemática / Función GML |
|---|---|---|
| 1. Lectura de entradas | Capturar los botones o teclas presionados por el usuario. | keyboard_check, keyboard_check_pressed |
| 2. Cálculo de fuerzas | Calcular velocidades deseadas, aplicar aceleración y gravedad. | hspd = move_dir * move_spd; vspd += grav; |
| 3. Resolución de colisiones | Evitar que el personaje penetre sólidos en X e Y. | place_meeting(x + hspd, ...), place_meeting(x, y + vspd, ...) |
| 4. Actualización del estado (Grounded) | Revisar el estado final de contacto una vez resueltas las coordenadas. | is_grounded = place_meeting(x, y + 1, obj_solid); |
Si intentáramos evaluar `is_grounded` antes de calcular y resolver las colisiones verticales, la variable contendría el estado del fotograma anterior, produciendo un retraso imperceptible de un fotograma (frame lag) que puede provocar anomalías sutiles en los saltos o en las animaciones.
Implementación completa en el evento Step
A continuación, presentamos el bloque completo del evento **Step** de `obj_player`. Este código integra la captura de teclas, la gravedad, la comprobación de salto basada exclusivamente en la bandera `is_grounded`, las colisiones paso a paso contra `obj_solid` y la actualización definitiva de la bandera al término del movimiento.
/// Evento Step de obj_player
// 1. CAPTURA DE ENTRADAS DEL TECLADO
var _key_left = keyboard_check(vk_left) || keyboard_check(ord("A"));
var _key_right = keyboard_check(vk_right) || keyboard_check(ord("D"));
var _key_jump = keyboard_check_pressed(vk_space) || keyboard_check_pressed(ord("W"));
// 2. CÁLCULO DE MOVIMIENTO HORIZONTAL
var _move_input = _key_right - _key_left;
hspd = _move_input * move_spd;
// 3. APLICACIÓN DE GRAVEDAD
// La gravedad se suma a la velocidad vertical en cada fotograma, limitada por la velocidad terminal
vspd += grav;
if (vspd > max_vspd) {
vspd = max_vspd;
}
// 4. ACCIÓN DE SALTO CONTROLADA POR LA BANDERA GROUNDED
// Solo permitimos saltar si el jugador está firmemente parado en el suelo
if (is_grounded && _key_jump) {
vspd = jsp;
is_grounded = false; // Al impulsarse, se cancela inmediatamente el estado de suelo
}
// 5. RESOLUCIÓN DE COLISIÓN HORIZONTAL
if (place_meeting(x + hspd, y, obj_solid)) {
// Si vamos a colisionar, avanzamos píxel a píxel hasta quedar pegados al obstáculo
var _step_h = sign(hspd);
while (!place_meeting(x + _step_h, y, obj_solid)) {
x += _step_h;
}
hspd = 0;
}
x += hspd;
// 6. RESOLUCIÓN DE COLISIÓN VERTICAL
if (place_meeting(x, y + vspd, obj_solid)) {
// Si vamos a colisionar con techo o suelo, nos aproximamos al milímetro
var _step_v = sign(vspd);
while (!place_meeting(x, y + _step_v, obj_solid)) {
y += _step_v;
}
vspd = 0;
}
y += vspd;
// 7. ACTUALIZACIÓN DEFINITIVA DE LA BANDERA GROUNDED
// Verificamos si, en la nueva posición física real, hay un sólido exactamente 1 píxel abajo
is_grounded = place_meeting(x, y + 1, obj_solid);Examinemos detenidamente el paso 4 y el paso 7. En el paso 4, la condición para saltar exige `is_grounded && _key_jump`. Si el jugador se encuentra cayendo por un precipicio o en pleno arco de un salto previo, `is_grounded` será `false`, lo que descarta de inmediato la instrucción `vspd = jsp`. De este modo erradicamos por completo el error del salto infinito.
Adicionalmente, cuando el salto tiene éxito, forzamos de inmediato `is_grounded = false`. Si bien el paso 7 al final del evento se encargaría de reevaluar la situación, invalidar la bandera en el instante exacto de la orden añade una capa extra de protección lógica que previene cualquier efecto indeseado en scripts dependientes que pudieran ejecutarse en ese mismo fotograma.
Falsos negativos y el problema del 'Jittering' (Temblor)
Uno de los problemas más comunes a los que se enfrentan los desarrolladores principiantes al programar un plataformas es el 'jittering' o temblor del personaje cuando está en reposo. Esto ocurre cuando el personaje oscila continuamente entre estar en el suelo y estar en el aire en fotogramas alternos (por ejemplo: fotograma 1 grounded = true, fotograma 2 grounded = false, fotograma 3 grounded = true).
¿Por qué sucede esto? La causa principal radica en una mala sincronización entre la aplicación de la gravedad y la corrección de colisión. Si en cada fotograma aplicamos la gravedad empujando al jugador hacia abajo, pero no resolvemos la colisión adecuadamente hasta que el sprite penetra la superficie y luego un script torpe lo 'empuja' bruscamente hacia arriba, la distancia entre el jugador y el suelo variará de forma caótica.
Con nuestro algoritmo de aproximación milimétrica mediante el bucle `while (!place_meeting(x, y + sign(vspd), obj_solid))`, garantizamos que cuando `vspd` se anula (`vspd = 0`), la distancia entre la base de la máscara del jugador y la parte superior del bloque `obj_solid` sea exactamente igual a cero píxeles de holgura. De ese modo, al comprobar `place_meeting(x, y + 1, obj_solid)`, el resultado es matemática y físicamente consistente: `true` en el 100% de los fotogramas en que el jugador permanece apoyado.
💡 Atención al origen del Sprite y la máscara de colisión: Asegúrate de que las dimensiones de la caja de colisión (Collision Mask) de tu sprite no cambien de tamaño de forma impredecible entre diferentes cuadros o estados. Si cambias de un sprite con altura de 32 píxeles a uno de 30 píxeles sin ajustar el punto de origen en la parte inferior (Bottom Centre), el personaje se despegará del suelo temporalmente, provocando un falso negativo en is_grounded.
Detección del momento exacto del aterrizaje
Además de saber si el personaje está apoyado en el suelo de manera continua, con frecuencia necesitamos identificar el instante puntual en que el jugador toca tierra tras una caída. Este momento es ideal para detonar efectos visuales (como nubes de polvo), reproducir sonidos de impacto (como un sonido de pisada firme o crujido de grava) o aplicar una ligera deformación estética al personaje (squash and stretch).
Para detectar un cambio de estado de este tipo, podemos comparar el valor de `is_grounded` en el fotograma actual con su valor en el fotograma anterior. Bastará con declarar una variable adicional llamada `was_grounded` en el evento **Create** y actualizarla ordenadamente en el evento **Step**.
Analicemos la lógica detrás de este patrón: la condición `!was_grounded && is_grounded` solo puede ser verdadera en un único fotograma: aquel en el que pasamos de volar/caer (`was_grounded == false`) a estar en contacto con el suelo (`is_grounded == true`). Al fotograma siguiente, como `was_grounded` se actualiza al valor actual, la condición dejará de cumplirse de forma automática. Este es un patrón de diseño básico pero sumamente poderoso en la programación de videojuegos.
GML Visual vs GML Code para la detección de contacto
Aunque en este curso priorizamos GML Code por su flexibilidad, precisión y estándar profesional en el desarrollo indie, es importante señalar que GameMaker permite replicar este comportamiento mediante su sistema modular visual (GML Visual). Para aquellos estudiantes que sientan curiosidad por cómo se representa esta lógica en bloques, el procedimiento conceptual es idéntico:
En GML Visual se utiliza el bloque de acción 'If Collision at Object' (Si hay colisión en el objeto). En los parámetros del bloque se introduce la coordenada X como `x`, la coordenada Y como `y + 1`, y se selecciona `obj_solid`. Debajo de ese bloque condicional, se encadena un bloque 'Assign Variable' donde a la variable `is_grounded` se le asigna el valor `true`, seguido de un bloque 'Else' que asigna `false`. En código GML, esta misma estructura de cuatro bloques visuales se resume con una limpieza inigualable en una única línea autoexplicativa: `is_grounded = place_meeting(x, y + 1, obj_solid);`.
Ejercicios prácticos
Para afianzar los conceptos abordados en esta lección, realiza las tres actividades que se presentan a continuación directamente en tu proyecto de GameMaker.
Resumen
- El estado 'Grounded' identifica con precisión si el personaje está apoyado en una superficie física, evitando anomalías como el salto infinito en el aire.
- Una bandera lógica (booleana) en GML almacena true o false (1 o 0), permitiendo consultar de forma sencilla el estado físico del personaje.
- La técnica place_meeting(x, y + 1, obj_solid) consulta si existe un obstáculo exactamente 1 píxel por debajo de la caja de colisión sin modificar las coordenadas del jugador.
- El orden de ejecución en el evento Step es crucial: primero se procesan las entradas y fuerzas, luego se resuelven las colisiones físicas y finalmente se actualiza la bandera is_grounded.
- Al comparar el estado previo (was_grounded) con el estado actual (is_grounded), es posible identificar el fotograma exacto del aterrizaje para detonar sonidos, efectos y partículas.
Practica lo aprendido
Ejercicio 1 · 1 punto
¿La función place_meeting(x, y + 1, obj_solid) modifica de forma permanente las coordenadas reales 'y' del jugador en la sala?
Ejercicio 2 · 1 punto
¿Por qué la técnica estándar para detectar el suelo utiliza 'y + 1' en place_meeting en lugar de la coordenada 'y' actual?
Ejercicio 3 · 2 puntos
Completa la asignación de la variable booleana `is_grounded`. Utiliza la función `place_meeting` para verificar si existe una colisión con el objeto `obj_solid` exactamente 1 píxel por debajo de la posición del jugador.
Tu código no se ejecuta: se comprueban requisitos concretos sobre el texto.
Ingresa para guardar tu progreso — Ingresar