Introducción al Desarrollo de Videojuegos con GameMaker · Módulo 4: Controles, movimientos y manejo del jugador.
Movimiento horizontal y velocidad
Clase 17 de 60 · 60 min
También disponible en: EN
Del registro de teclas a la traslación física en pantalla
En la lección anterior aprendiste a capturar las intenciones del jugador a través de las funciones de entrada de GameMaker, abstrayendo las pulsaciones de teclado o gamepad en un valor direccional normalizado dentro del rango [-1, 1]. No obstante, saber que el usuario desea desplazarse a la derecha o a la izquierda es únicamente la mitad del trabajo; la otra mitad consiste en traducir ese deseo en una respuesta cinemática convincente, precisa y coherente con el género de plataformas retro que estamos construyendo.
En el desarrollo de videojuegos bidimensionales, la traslación de un objeto a lo largo del eje X puede abordarse desde dos filosofías de diseño mecánico fundamentales: el movimiento rígido o instantáneo, donde la entidad alcanza su velocidad de desplazamiento en el mismo fotograma en que se detecta la pulsación y se detiene en seco al soltarla, y el movimiento con aceleración gradual e inercia (fricción), donde la velocidad incrementa progresivamente con el tiempo y decae con suavidad al cesar la entrada. A lo largo de esta lección descompondremos paso a paso ambas aproximaciones, analizando su implementación matemática en GameMaker Language (GML Code), el uso de variables especializadas y el impacto que tienen en el control del personaje.
El sistema de coordenadas de GameMaker y el eje X
Antes de escribir una sola línea de código para mover al jugador, es imprescindible repasar cómo gestiona GameMaker el espacio bidimensional dentro de una habitación (room). A diferencia del plano cartesiano convencional utilizado en matemáticas escolares, el sistema de coordenadas de pantalla sitúa el origen (0, 0) en la esquina superior izquierda:
| Eje | Dirección positiva (+) | Dirección negativa (-) | Propiedad nativa de la instancia |
|---|---|---|---|
| Eje horizontal | Hacia la derecha (suma a la posición) | Hacia la izquierda (resta a la posición) | x |
| Eje vertical | Hacia abajo (suma a la posición) | Hacia arriba (resta a la posición) | y |
Cada instancia posee de forma predeterminada las variables nativas 'x' e 'y', que almacenan sus coordenadas en píxeles con precisión de punto flotante. Si incrementamos 'x', la instancia se desplaza a la derecha; si reducimos 'x', la instancia se desplaza a la izquierda. Sin embargo, modificar directamente la variable 'x' sumándole directamente una constante (como 'x += 4') sin una estructura intermedia de velocidad nos impedirá gestionar rebotes, fuerzas externas, inercia o las colisiones que abordaremos en el siguiente módulo.
💡 Buenas prácticas: Aunque GameMaker incluye variables integradas de movimiento como 'hspeed', 'vspeed', 'speed' y 'direction', en el desarrollo profesional de plataformas 2D se recomienda utilizar variables personalizadas (por ejemplo, 'hsp' o 'vel_x'). Las variables nativas actualizan automáticamente la posición de la instancia antes de que puedas evaluar colisiones personalizadas con precisión de píxel.
Arquitectura de variables en el evento Create
Para construir un sistema de movimiento robusto en nuestro objeto del jugador ('obj_player'), debemos inicializar una serie de variables de control en el evento 'Create'. Este evento se ejecuta una sola vez cuando la instancia se genera en la room, lo que lo convierte en el lugar idóneo para definir los parámetros físicos del personaje.
Abre el proyecto que venimos desarrollando en el curso, selecciona 'obj_player', abre su evento 'Create' y añade las variables de velocidad horizontal que utilizaremos a partir de ahora:
/// @description Configuración inicial de movimiento horizontal
// obj_player - Evento Create
// Velocidad horizontal actual en píxeles por fotograma
hsp = 0;
// Velocidad máxima que el jugador puede alcanzar en el eje X
move_speed = 3.5;
// Tasa de aceleración (cuántos píxeles por fotograma gana al presionar una tecla)
accel = 0.3;
// Tasa de fricción o desaceleración (cuántos píxeles por fotograma pierde al soltar)Analicemos el propósito de cada una de estas variables: 1. 'hsp' (Horizontal Speed): Almacena la velocidad real calculada para el fotograma en curso. Puede ser positiva (movimiento a la derecha), negativa (movimiento a la izquierda) o cero (reposo absoluto). 2. 'move_speed': Establece la cota superior del movimiento regular. Con 3.5, el personaje recorrerá un máximo de 3.5 píxeles en cada ciclo de actualización si la tasa de fotogramas es estable. 3. 'accel': Define la rapidez con la que el personaje gana impulso. Un valor bajo (ej. 0.05) crea una sensación resbaladiza o pesada, mientras que un valor cercano a 'move_speed' hace que la aceleración sea prácticamente inmediata. 4. 'fric': Determina la fuerza de frenado del suelo. Un valor moderado como 0.4 permite que el personaje patine brevemente antes de detenerse.
Aproximación 1: Movimiento horizontal instantáneo (Digital/Arcade)
El enfoque más simple y directo consiste en aplicar la velocidad de forma inmediata multiplicando la dirección de entrada por la velocidad máxima. Este modelo es característico de clásicos como 'Mega Man' o títulos de acción donde la precisión quirúrgica prima sobre la inercia física.
En el evento 'Step' de 'obj_player', combinamos la captura de controles vista en la clase previa con la actualización directa de 'hsp' y de la coordenada 'x':
/// @description Movimiento horizontal instantáneo
// obj_player - Evento Step
// 1. Obtención del vector de entrada (-1, 0, 1)
var _key_right = keyboard_check(vk_right) || keyboard_check(ord("D"));
var _key_left = keyboard_check(vk_left) || keyboard_check(ord("A"));
var _move = _key_right - _key_left;
// 2. Cálculo directo de velocidad
hsp = _move * move_speed;
// 3. Aplicación a la posición horizontal
x += hsp;El funcionamiento matemático es impecable en su simplicidad: si el jugador pulsa la tecla derecha, '_move' vale 1 y 'hsp' pasa a valer 3.5 en un solo fotograma; la posición 'x' aumenta 3.5 píxeles en ese instante. Si el jugador pulsa la tecla izquierda, '_move' vale -1 y 'hsp' es -3.5, desplazando al personaje a la izquierda. Al soltar las teclas, '_move' es 0, lo que anula de inmediato 'hsp' y detiene la instancia en el sitio exacto.
Aproximación 2: Aceleración gradual y fricción realista
Aunque el movimiento instantáneo es muy responsivo, muchos juegos de plataformas (como 'Super Mario World' o 'Sonic the Hedgehog') emplean una física más orgánica que simula el peso del cuerpo y el agarre sobre la superficie. Para lograr esto, no asignamos la velocidad directamente; en su lugar, la modificamos de forma acumulativa fotograma tras fotograma.
El proceso lógico se divide en dos ramas condicionales dentro del evento 'Step': - Si hay entrada activa (_move != 0): incrementamos 'hsp' en la dirección indicada a razón de 'accel', asegurándonos de no exceder 'move_speed'. - Si no hay entrada activa (_move == 0): reducimos 'hsp' hacia 0 a razón de 'fric', impidiendo que sobrepase el reposo y cambie de sentido involuntariamente.

Control de límites con clamp() y evaluación de sentido con sign()
Para programar la aceleración y la inercia sin generar anomalías numéricas, GameMaker ofrece dos funciones matemáticas fundamentales:
1. 'clamp(val, min, max)': Devuelve el valor 'val' acotado estrictamente entre un mínimo y un máximo. Si 'val' es inferior a 'min', devuelve 'min'; si supera 'max', devuelve 'max'. Esto nos permite evitar que la aceleración incremente la velocidad al infinito. 2. 'sign(val)': Devuelve 1 si el número es positivo, -1 si es negativo, y 0 si es exactamente cero. Esta función nos permite conocer el sentido del movimiento actual sin importar su magnitud.
Veamos cómo implementar la lógica completa de aceleración y fricción en el evento 'Step':
/// @description Movimiento horizontal con aceleración y fricción
// obj_player - Evento Step
// 1. Lectura de controles
var _key_right = keyboard_check(vk_right) || keyboard_check(ord("D"));
var _key_left = keyboard_check(vk_left) || keyboard_check(ord("A"));
var _move = _key_right - _key_left;
// 2. Gestión de aceleración y desaceleración
if (_move != 0)
{
// Hay intención de movimiento: aceleramos en esa dirección
hsp += _move * accel;
// Restringimos la velocidad para no superar el límite permitido
hsp = clamp(hsp, -move_speed, move_speed);
}
else
{
// No hay teclas presionadas: aplicamos fricción para frenar
if (abs(hsp) <= fric)
{
// Si la velocidad es menor o igual a la fricción, nos detenemos por completo
hsp = 0;
}
else
{
// Restamos la fricción en el sentido opuesto al desplazamiento actual
hsp -= sign(hsp) * fric;
}
}
// 3. Aplicación del desplazamiento al eje X
x += hsp;Fíjate con especial atención en la condición 'if (abs(hsp) <= fric)'. La función 'abs()' calcula el valor absoluto de un número (su magnitud sin signo). Si un personaje viaja a 'hsp = 0.2' y la fricción es de '0.4', al restar simplemente la fricción obtendríamos '0.2 - 0.4 = -0.2'. El personaje comenzaría a moverse hacia la izquierda sin que nadie pulse una tecla, entrando en una oscilación infinita de un lado a otro. Al verificar si la velocidad restante es inferior al paso de fricción, garantizamos una parada perfecta a cero absoluto.
El método alternativo: Función personal de aproximación (approach)
En el desarrollo moderno con GameMaker, es habitual ver el uso de funciones auxiliares que simplifican la reducción progresiva de valores. En lugar de escribir bloques condicionales extensos con 'abs()' y 'sign()', muchos programadores crean un script reutilizable llamado 'approach'.
Para crear esta función en tu proyecto, dirígete al panel del Explorador de Activos (Asset Browser), haz clic derecho sobre la carpeta 'Scripts' (creada en el Módulo 3) y selecciona 'Create' -> 'Script'. Nombra el archivo 'scr_math' y pega el siguiente código:
/// @function approach(current, target, amount)
/// @description Acerca un valor actual hacia un objetivo a una tasa determinada sin sobrepasarlo.
/// @param {Real} _current Valor actual
/// @param {Real} _target Valor objetivo al que queremos llegar
/// @param {Real} _amount Cantidad de cambio por paso
function approach(_current, _target, _amount)
{
if (_current < _target)
{
return min(_current + _amount, _target);
}
else
{
return max(_current - _amount, _target);
}
}Gracias a 'approach', el bloque de movimiento de 'obj_player' en el evento 'Step' puede escribirse de una forma mucho más compacta, elegante y legible:
/// @description Movimiento limpio usando la función approach
// obj_player - Evento Step
var _move = (keyboard_check(vk_right) || keyboard_check(ord("D"))) -
(keyboard_check(vk_left) || keyboard_check(ord("A")));
var _target_speed = _move * move_speed;
var _rate = (_move != 0) ? accel : fric;
// Aproximamos la velocidad horizontal actual hacia la velocidad deseada
hsp = approach(hsp, _target_speed, _rate);
// Desplazamos al jugador
x += hsp;Este fragmento calcula una velocidad objetivo ('_target_speed'). Si pulsamos derecha, la velocidad objetivo es 3.5. Si no pulsamos nada, la velocidad objetivo es 0. Luego, evalúa si estamos acelerando o frenando para elegir la tasa correspondiente mediante el operador ternario '(_move != 0) ? accel : fric' y ajusta gradualmente 'hsp' hasta converger exactamente en la meta.
Comportamiento sub-píxel y renderizado en pantalla
Al sumar valores con decimales (como 0.3 o 3.5) a la coordenada 'x', la posición de la instancia se convierte en un número con coma flotante (por ejemplo, 'x = 104.7'). GameMaker maneja internamente las coordenadas fraccionarias sin inconvenientes y redondea la posición en pantalla en el momento de renderizar el sprite en el fotograma.
Sin embargo, en juegos de estética pixel art con cámaras de baja resolución nativa (por ejemplo, 320x180 o 640x360 píxeles), el renderizado en posiciones fraccionarias puede provocar ocasionalmente distorsiones visuales leves o parpadeos en los bordes de los sprites (conocido como 'pixel shimmering'). Más adelante, en el Módulo 6 y en el Módulo 10 cuando configuremos las vistas de cámara y los estados gráficos, aprenderemos a redondear el dibujo en el evento Draw sin alterar la física matemática subyacente de 'x' e 'y'.
Actividades prácticas y ejercicios de consolidación
Para asegurar el dominio de los conceptos explicados, completa las tres actividades propuestas a continuación directamente en tu proyecto de GameMaker.
Ejercicio 1: Mecánica de carrera rápida (Sprint)
Objetivo: Implementar una tecla modificadora (por ejemplo, la tecla Shift / Mayús) que aumente temporalmente la velocidad máxima y la aceleración del jugador mientras permanezca presionada.
Pasos sugeridos: 1. En el evento 'Create' de 'obj_player', agrega dos variables: 'walk_speed = 3.0;' y 'run_speed = 5.5;'. Inicializa 'move_speed = walk_speed;'. 2. En el evento 'Step', detecta la tecla de sprint mediante 'keyboard_check(vk_shift)'. 3. Si la tecla está presionada, asigna 'run_speed' a 'move_speed'. En caso contrario, restablece 'move_speed' a 'walk_speed'. 4. Verifica en tiempo de ejecución que el personaje acelere a una velocidad superior al correr y vuelva a su ritmo estándar al soltar la tecla.
Ejercicio 2: Simulación de terreno resbaladizo (Superficie de hielo)
Objetivo: Modificar dinámicamente las variables de control cinemático para simular una superficie con baja adherencia.
Pasos sugeridos: 1. Declara una variable booleana 'on_ice = false;' en el evento 'Create'. 2. En el evento 'Step', añade una comprobación temporal: si presionas la tecla 'H' (puedes usar 'keyboard_check_pressed(ord("H"))'), conmuta el estado de 'on_ice = !on_ice;'. 3. Modifica la selección de aceleración y fricción: si 'on_ice' es verdadero, utiliza 'accel = 0.05;' y 'fric = 0.03;'; si es falso, conserva los valores habituales ('accel = 0.3;' y 'fric = 0.4;'). 4. Prueba la room y comprueba cómo el personaje tarda notablemente más en frenar y en cambiar de sentido cuando la modalidad de hielo está activa.
Ejercicio 3: Detección y depuración de velocidad en tiempo real
Objetivo: Visualizar numéricamente el valor exacto de 'hsp' en la ventana de depuración (Output Console) para verificar que el freno a cero absoluto funcione sin fluctuaciones residuales.
Pasos sugeridos: 1. Al final del evento 'Step' de 'obj_player', incluye la función: 'show_debug_message("HSP actual: " + string(hsp));'. 2. Ejecuta el juego, muévete a la derecha y luego suelta los controles. 3. Observa la pestaña 'Output' en la parte inferior del entorno de GameMaker. 4. Comprueba que, tras soltar las teclas, 'hsp' decrezca de forma continua hasta alcanzar exactamente 0.00, sin oscilar entre valores positivos y negativos.
Resumen
- El eje horizontal (X) en GameMaker aumenta hacia la derecha y disminuye hacia la izquierda, tomando como origen la esquina superior izquierda de la room.
- Se deben utilizar variables personalizadas como 'hsp' en lugar de las integradas ('hspeed') para retener el control manual sobre las colisiones y la física del jugador.
- El movimiento instantáneo ofrece alta precisión y respuesta arcade, mientras que el modelo de aceleración y fricción añade inercia física y peso.
- La función 'clamp()' previene que la velocidad acumulada exceda los límites máximos establecidos.
- Para detener al personaje con suavidad sin provocar oscilaciones infinitas alrededor de cero, se debe evaluar si la magnitud restante de la velocidad es menor o igual a la fricción antes de restar.
- La función auxiliar 'approach()' proporciona una herramienta reutilizable y limpia para aproximar linealmente cualquier valor numérico hacia un destino.
Practica lo aprendido
Ejercicio 1 · 1 punto
¿Por qué en el desarrollo profesional de videojuegos de plataformas 2D se recomienda utilizar variables personalizadas como `hsp` en lugar de las variables nativas de GameMaker como `hspeed`?
Ejercicio 2 · 1 punto
Relaciona cada función matemática o variable utilizada en la lección con su propósito correspondiente en el movimiento horizontal:
- clamp(val, min, max)
- sign(val)
- abs(val)
- move_speed
Ejercicio 3 · 1 punto
Completa el código del evento Step para implementar el movimiento horizontal instantáneo multiplicando la dirección por la velocidad máxima y actualizando la coordenada adecuada: ```gml // 2. Cálculo directo de velocidad hsp = _move * ; // 3. Aplicación a la posición horizontal += hsp; ```
Ingresa para guardar tu progreso — Ingresar