Introducción al Desarrollo de Videojuegos con GameMaker · Módulo 4: Controles, movimientos y manejo del jugador.

Lectura de entradas del teclado y gamepad

Clase 16 de 60 · 50 min

También disponible en: EN

Introducción a la lectura de entradas en GameMaker

Todo videojuego interactivo depende de un puente de comunicación constante entre las intenciones del jugador y el mundo digital en pantalla. Este puente se construye mediante la captura de entradas (inputs). Cuando un usuario presiona una tecla en su teclado o inclina la palanca de un mando de control (gamepad), el hardware genera señales eléctricas que el sistema operativo traduce y pone a disposición de GameMaker. Nuestro rol como desarrolladores consiste en interrogar al motor en cada fotograma para saber qué botones han sido accionados y qué acciones deben ejecutarse como consecuencia.

A lo largo de los módulos anteriores configuraste tus primeros objetos, entendiste la anatomía del ciclo de eventos (Create, Step, Draw) y aprendiste la sintaxis esencial de GML Code. Ahora que disponemos del objeto del jugador (`obj_player`), es momento de dotarlo de interactividad directa. En esta lección nos enfocaremos en comprender cómo funciona la lectura de datos tanto desde el teclado físico tradicional como desde gamepads estándar (como mandos compatibles con XInput o DirectInput), sentando las bases arquitectónicas para el movimiento, el salto y la respuesta en tiempo real de nuestro plataformas 2D.

Aunque GameMaker ofrece compatibilidad visual mediante nodos en GML Visual, utilizaremos de forma sistemática GML Code. Escribir el código en texto otorga una comprensión profunda de las operaciones lógicas, un control milimétrico sobre el flujo de datos y una flexibilidad notable al momento de soportar esquemas de control mixtos o personalizables.

El ciclo de eventos y las funciones de teclado

Para responder inmediatamente al usuario, la comprobación de botones debe realizarse dentro del evento Step. Como vimos en el Módulo 2, el evento Step se ejecuta una vez por cada fotograma del juego (por defecto, 60 veces por segundo en una sala configurada a 60 FPS). Si verificáramos la pulsación en el evento Create, la computadora solo evaluaría el teclado una única vez al nacer la instancia y jamás volvería a enterarse si presionamos una tecla más tarde.

GameMaker clasifica la lectura de teclas en tres estados temporales bien definidos, representados por tres funciones nativas esenciales:

Función GMLComportamiento de activaciónCaso de uso típico
keyboard_check(key)Devuelve true continuamente durante todos los fotogramas en que la tecla permanezca presionada.Caminar de forma continua, acelerar un vehículo, mantener apuntada un arma.
keyboard_check_pressed(key)Devuelve true únicamente en el fotograma exacto en que la tecla pasa de reposo a presionada.Efectuar un salto, disparar un proyectil semiautomático, pausar el juego.
keyboard_check_released(key)Devuelve true únicamente en el fotograma en que la tecla pasa de presionada a liberada.Interrumpir un salto para modular la altura, soltar una cuerda o soltar una carga.

Comprender la diferencia entre `keyboard_check` y `keyboard_check_pressed` es vital. Si asociáramos la acción de saltar a `keyboard_check`, el personaje intentaría propulsarse hacia arriba en cada uno de los 60 fotogramas por segundo que mantengamos el dedo sobre la tecla, lo cual generaría un comportamiento caótico o un ascenso infinito. Del mismo modo, si utilizáramos `keyboard_check_pressed` para caminar, el jugador tendría que golpear repetidamente la flecha o la letra correspondiente para avanzar un píxel por pulsación.

Constantes de teclado y la función ord()

Para especificar qué tecla queremos evaluar con las funciones anteriores, GameMaker utiliza dos mecanismos: constantes de teclado nativas (identificadores especiales que comienzan con el prefijo `vk_`, por virtual key) y la función `ord()` para caracteres alfanuméricos simples.

Las constantes virtuales más comunes incluyen `vk_left` (flecha izquierda), `vk_right` (flecha derecha), `vk_up` (flecha arriba), `vk_down` (flecha abajo), `vk_space` (barra espaciadora), `vk_shift` (tecla Shift) y `vk_enter` (Enter). Estas constantes representan teclas de control o de navegación del sistema y se pasan directamente sin comillas a la función.

En contraste, cuando queremos escuchar letras individuales del abecedario (como la típica configuración W, A, S, D) o números del teclado superior, debemos envolver la letra dentro de la función `ord()`. Además, es un requisito estricto de GameMaker escribir la letra siempre en mayúscula entre comillas dobles, independientemente de cómo esté configurado el teclado del jugador, por ejemplo: `ord("A")` o `ord("D")`.

// Ejemplo de lectura dentro del evento Step de obj_player
var _tecla_derecha = keyboard_check(vk_right);
var _tecla_d = keyboard_check(ord("D"));

// Podemos combinar ambas condiciones lógicas con el operador OR (||)
var _moverse_derecha = _tecla_derecha || _tecla_d;

Observemos cómo creamos una variable temporal local precedida por la palabra clave `var`. Como aprendiste en el Módulo 3 sobre ámbito de variables, las variables locales solo existen mientras se ejecuta el bloque de código actual y se eliminan automáticamente de la memoria al terminar el fotograma. Esto es ideal para los controles, ya que los valores de entrada deben consultarse de cero en cada ciclo y no necesitan persistir tras haber calculado el movimiento del fotograma actual.

Diagrama esquemático que muestra el flujo de señales desde el teclado y gamepad hacia el procesamiento lógico en GameMaker, distinguiendo señales continuas de señales de pulsación única.
Figura 1: Esquema de procesamiento donde las señales de hardware se evalúan en cada ciclo Step y se normalizan en variables lógicas. · Imagen generada con IA

Fundamentos de lectura de Gamepad en GML

Hoy en día, una gran parte de los jugadores espera conectar un mando USB o Bluetooth y controlar su personaje de inmediato. GameMaker incorpora soporte nativo sumamente robusto para mandos mediante un conjunto de funciones dedicadas con el prefijo `gamepad_`.

Para trabajar con un mando, primero debemos tener en cuenta que el sistema asigna un índice o ranura numérica (denominada comúnmente 'slot') a cada dispositivo conectado. Estos índices van típicamente desde 0 hasta 11. En los sistemas operativos estándar como Windows, los mandos compatibles con XInput (como los mandos convencionales de consolas modernas) ocupan las ranuras 0, 1, 2 y 3. Por lo tanto, el mando del jugador 1 usualmente reside en la ranura 0.

Antes de exigirle al juego que lea un mando, es una buena práctica verificar si dicho dispositivo se encuentra realmente conectado mediante la función `gamepad_is_connected(device)`. Si intentamos consultar un gamepad inexistente, GameMaker devolverá 0 de forma segura, pero comprobar su presencia nos permite activar interfaces visuales dinámicas o habilitar configuraciones específicas.

De manera idéntica a lo visto con el teclado, los botones digitales de un mando (como la cruceta direccional o los botones frontales de acción) cuentan con sus equivalentes temporales:

Función GML de GamepadParámetros requeridosDescripción
gamepad_is_connected(device)device (entero del slot, ej: 0)Devuelve true si el dispositivo especificado está conectado y reconocido.
gamepad_button_check(device, button)device (ej: 0), button (constante)Devuelve true continuamente mientras el botón se mantenga presionado.
gamepad_button_check_pressed(device, button)device (ej: 0), button (constante)Devuelve true solo en el fotograma en que el botón pasa a presionado.
gamepad_axis_value(device, axis)device (ej: 0), axis (constante)Devuelve un valor analógico continuo de punto flotante entre -1.0 y 1.0.

Las constantes de botones digitales en GameMaker inician con el prefijo `gp_`. Las más empleadas en juegos de plataformas son `gp_padu`, `gp_padd`, `gp_padl`, `gp_padr` (para arriba, abajo, izquierda y derecha en la cruceta D-Pad) y los botones faciales como `gp_face1` (botón inferior, usualmente 'A' en mandos de estilo Xbox o 'Cruz' en PlayStation) y `gp_face2` (botón derecho, 'B' o 'Círculo').

Palancas analógicas y la zona muerta (Deadzone)

A diferencia de las teclas y los botones que entregan valores binarios (0 para desactivado y 1 para activado), los sticks o palancas analógicas leen un espectro gradual de inclinación a través de la función `gamepad_axis_value(device, axis)`. Para el eje horizontal del stick izquierdo, usamos la constante `gp_axislh` (GamePad Axis Left Horizontal). Cuando la palanca está completamente a la izquierda entrega un valor de `-1.0`, en el centro entrega `0.0`, y a la derecha alcanza `1.0`.

Sin embargo, los mandos analógicos físicos poseen pequeñas imperfecciones mecánicas. Cuando el jugador suelta la palanca, es muy raro que regrese a un cero perfecto; puede quedarse oscilando en valores minúsculos como `0.04` o `-0.07`. Si utilizáramos ese valor en crudo, nuestro personaje parecería tener vida propia y comenzaría a deslizarse lentamente solo por la pantalla (fenómeno conocido comúnmente como stick drift).

Para remediar esto, GameMaker incluye la función `gamepad_set_axis_deadzone(device, deadzone)`. Esta función se suele ejecutar una sola vez en el evento Create del objeto de control o del jugador. Establece un umbral por debajo del cual cualquier lectura analógica se redondeará automáticamente a cero estricto. El valor del umbral suele ser un decimal entre `0.1` y `0.3` (lo que representa entre un 10% y un 30% de margen de tolerancia física).

💡 Buenas prácticas: Configura siempre la zona muerta en el evento Create de tu objeto jugador ejecutando gamepad_set_axis_deadzone(0, 0.2);. De este modo, evitarás que el personaje camine solo debido a las tolerancias mecánicas del potenciómetro de la palanca.

Unificación de entradas: Creando un sistema híbrido

Uno de los errores más comunes cometidos por desarrolladores novatos es programar la física o el desplazamiento del personaje directamente dentro de múltiples bloques de lectura individuales. Por ejemplo, escribir un bloque para mover a la derecha si se presiona la flecha, otro bloque aparte para la letra D, y otro completamente separado para la palanca del mando. Esto fragmenta el código, produce duplicación innecesaria y hace que cualquier cambio en la física deba replicarse en cuatro o cinco lugares distintos.

La arquitectura profesional y ordenada consiste en crear un sistema unificado de variables de entrada al comienzo del evento Step. Leemos todas las fuentes posibles (teclas de flechas, teclado WASD, botones de gamepad y palancas analógicas) y condensamos toda esa información en variables simples, limpias y booleanas o numéricas. El resto del código de movimiento sólo leerá estas variables resultantes, sin importarle en lo absoluto qué hardware físico utilizó el jugador para activarlas.

/// @description Evento Step de obj_player: Captura unificada de entradas

// 1. Lectura del Teclado
var _teclado_izq   = keyboard_check(vk_left) || keyboard_check(ord("A"));
var _teclado_der   = keyboard_check(vk_right) || keyboard_check(ord("D"));
var _teclado_salto = keyboard_check_pressed(vk_space) || keyboard_check_pressed(ord("W"));

// 2. Variables para almacenar la entrada del Gamepad
var _pad_izq   = false;
var _pad_der   = false;
var _pad_salto = false;

// 3. Verificación y lectura del Gamepad (Dispositivo 0)
if (gamepad_is_connected(0)) {
    // Lectura de cruceta digital (D-Pad)
    var _dpad_izq = gamepad_button_check(0, gp_padl);
    var _dpad_der = gamepad_button_check(0, gp_padr);
    
    // Lectura de palanca analógica izquierda (eje horizontal)
    var _stick_h = gamepad_axis_value(0, gp_axislh);
    var _stick_izq = (_stick_h < -0.3);
    var _stick_der = (_stick_h > 0.3);
    
    // Unificación de controles del gamepad
    _pad_izq = _dpad_izq || _stick_izq;
    _pad_der = _dpad_der || _stick_der;
    
    // Salto con el botón A / Cruz (gp_face1)
    _pad_salto = gamepad_button_check_pressed(0, gp_face1);
}

// 4. Variables finales consolidadas para el resto del juego
var _tecla_izquierda = _teclado_izq || _pad_izq;
var _tecla_derecha   = _teclado_der || _pad_der;
var _tecla_salto     = _teclado_salto || _pad_salto;

// 5. Cálculo del vector de dirección horizontal (-1, 0, 1)
var _direccion_horizontal = _tecla_derecha - _tecla_izquierda;

// Demostración didáctica: imprimir en consola si el jugador salta
if (_tecla_salto) {
    show_debug_message("¡El jugador ha ejecutado la acción de salto!");
}

Analicemos con detalle una de las líneas más elegantes y poderosas utilizadas en la programación de videojuegos 2D: `var _direccion_horizontal = _tecla_derecha - _tecla_izquierda;`. Esta técnica aritmética simple resuelve de un solo golpe varios problemas comunes de control:

En GML, los valores booleanos `true` y `false` equivalen numéricamente a `1` y `0` respectivamente. Por lo tanto:

1. Si el jugador presiona únicamente a la derecha: `_tecla_derecha` vale 1 y `_tecla_izquierda` vale 0. La resta da `1 - 0 = 1`, indicando movimiento positivo hacia la derecha.

2. Si el jugador presiona únicamente a la izquierda: `_tecla_derecha` vale 0 y `_tecla_izquierda` vale 1. La resta da `0 - 1 = -1`, indicando movimiento negativo hacia la izquierda.

3. Si el jugador no presiona ninguna tecla: `0 - 0 = 0`, indicando reposo absoluto.

4. Si el jugador presiona ambas direcciones simultáneamente (por ejemplo, manteniendo apretadas la tecla 'A' y la tecla 'D' a la vez): `1 - 1 = 0`. El sistema neutraliza las fuerzas opuestas de forma automática e instantánea, impidiendo comportamientos anómalos o temblores del personaje.

Paso a paso: Configuración en el proyecto de plataformas

Para asegurar la continuidad con los proyectos construidos en los módulos previos y preparar el terreno para las siguientes clases de este Módulo 4, llevaremos a cabo los siguientes pasos dentro del entorno de desarrollo de GameMaker:

Paso 1: Abrir el proyecto de plataformas 2D que venimos construyendo desde el Módulo 2. Localiza en el panel 'Asset Browser' el objeto denominado `obj_player` y haz doble clic sobre él para abrir su ventana de eventos.

Paso 2: Accede al evento **Create** de `obj_player`. Si ya tenías variables inicializadas de lecciones anteriores, manténlas y añade al final la configuración de la zona muerta del mando para garantizar la estabilidad de los ejes analógicos:

/// @description Evento Create de obj_player

// Ajuste de hardware: zona muerta del gamepad 0 en un 20%
gamepad_set_axis_deadzone(0, 0.2);

Paso 3: Abre el evento **Step** de `obj_player` (si no existe, haz clic en el botón 'Add Event', selecciona 'Step' y luego el evento normal 'Step'). Borra cualquier código de prueba previo y pega la rutina unificada explicada en el ejemplo anterior.

Paso 4: Ejecuta el proyecto presionando el botón de reproducir (F5). Abre la pestaña 'Output' en la parte inferior de GameMaker para visualizar la consola. Presiona la barra espaciadora, la tecla W, o el botón A si tienes un mando conectado. Cada vez que pulses la tecla de salto, verás aparecer el mensaje correspondiente en el registro sin importar qué periférico hayas pulsado. Esto confirma que el motor interpreta tus comandos en el tiempo correcto.

💡 Nota de compatibilidad: GameMaker detecta automáticamente la desconexión y reconexión de dispositivos mediante el evento Asynchronous - System. Sin embargo, en esta etapa inicial de desarrollo, interrogar al dispositivo 0 con gamepad_is_connected(0) en el evento Step es el método más simple, directo y pedagógico para familiarizarse con las entradas.

Ejercicios prácticos

Pon a prueba tus conocimientos realizando las siguientes actividades prácticas dentro de tu proyecto:

1. **Integración de botón para correr rápido (Sprint)**: Modifica el bloque de captura de entradas en el evento Step de `obj_player` para incluir una variable llamada `_tecla_correr`. Esta variable debe ser `true` mientras el jugador mantenga presionada la tecla Shift izquierda (`vk_lshift`) en el teclado, o bien el gatillo o botón de hombro derecho (`gp_shoulderr`) en el mando. Comprueba su funcionamiento imprimiendo un mensaje con `show_debug_message()` únicamente cuando la variable sea positiva.

2. **Detección de corte de salto**: Implementa una variable local llamada `_tecla_salto_soltada` utilizando la función `keyboard_check_released()` para la barra espaciadora y `gamepad_button_check_released()` para el botón facial del mando. Haz que aparezca el mensaje `"Salto interrumpido"` en el registro de salida cuando se active. *Criterio de comprobación:* El mensaje debe registrarse exclusivamente en el fotograma exacto en el que liberas el botón después de mantenerlo presionado, nunca de forma continuada.

3. **Inspección de palanca analógica horizontal**: Escribe una sentencia condicional temporal que imprima el valor exacto devuelto por `gamepad_axis_value(0, gp_axislh)` en cada fotograma solo si el valor absoluto (`abs()`) es superior a 0.2. Inclina la palanca despacio de un extremo al otro para observar en la consola cómo los valores cambian de forma progresiva entre -1.0 y 1.0, validando el correcto filtrado de la zona muerta.

Resumen

  • Las entradas en GameMaker deben evaluarse de forma continua en el evento Step para garantizar una respuesta ágil a cada fotograma.
  • keyboard_check opera de forma continua mientras se mantenga presionada una tecla, mientras que keyboard_check_pressed detecta únicamente el primer fotograma de pulsación.
  • Las teclas alfanuméricas requieren la función ord("LETRA") en mayúscula, mientras que teclas especiales utilizan constantes de sistema como vk_space o vk_left.
  • Los mandos se identifican mediante ranuras numéricas (slot 0 para el jugador 1) y se verifican con gamepad_is_connected.
  • Las palancas analógicas requieren el ajuste de gamepad_set_axis_deadzone() en el evento Create para evitar desplazamientos accidentales por desgaste del mando.
  • Unificar teclado y gamepad en variables temporales consolidadas y calcular la dirección como (_tecla_derecha - _tecla_izquierda) asegura un código limpio, profesional y preparado para el movimiento horizontal.

Practica lo aprendido

Ejercicio 1 · 1 punto

¿Cuál es la sintaxis correcta en GML Code para comprobar si el jugador presiona la tecla de la letra "D" usando la función keyboard_check?

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

Ejercicio 2 · 1 punto

Relaciona cada identificador o función de GML con su descripción correspondiente según el contenido de la lección:

  • gp_axislh
  • gamepad_set_axis_deadzone
  • gp_face1
  • vk_space
Practicas sin cuenta: no se guarda nada. Regístrate para guardar tu progreso.

Ejercicio 3 · 1 punto

¿Es verdadero o falso que la comprobación continua de entradas del teclado para mover al personaje debe realizarse dentro del evento Create para que se evalúe 60 veces por segundo?

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

Ingresa para guardar tu progreso — Ingresar