Introducción al Desarrollo de Videojuegos con GameMaker · Module 4: Controls, Movement, and Player Handling
Reading Keyboard and Gamepad Inputs
Lesson 16 of 60 · 50 min
Also available in: ES
Introduction to Input Handling in GameMaker
Every interactive video game relies on a continuous bridge of communication between the player's intentions and the digital world on screen. This bridge is built by capturing inputs. When a player presses a key on their keyboard or tilts a gamepad stick, the hardware generates electrical signals that the operating system translates and exposes to GameMaker. Our role as developers is to query the engine on every single frame to determine which controls were activated and what actions must execute as a result.
Throughout earlier modules, you set up your initial objects, learned the anatomy of the event lifecycle (Create, Step, Draw), and practiced fundamental GML Code syntax. Now that we have our player object (`obj_player`), it is time to give it direct interactivity. In this lesson, we will focus on reading input data both from a standard physical keyboard and from typical gamepads (such as XInput- or DirectInput-compatible controllers), laying the architectural groundwork for movement, jumping, and real-time responsiveness in our 2D platformer.
While GameMaker supports visual scripting via GML Visual nodes, we will consistently use GML Code. Writing code in text form provides a deeper understanding of logic operations, precise control over data flow, and substantial flexibility when supporting mixed or remappable control schemes.
The Event Lifecycle and Keyboard Functions
To respond immediately to the player, input checks must occur inside the Step event. As covered in Module 2, the Step event executes once per game frame (by default, 60 times per second in a room set to 60 FPS). If we checked for key presses inside the Create event, the engine would only evaluate the keyboard once when the instance spawns and would never detect any keys pressed afterward.
GameMaker categorizes keyboard queries into three distinct temporal states, represented by three essential native functions:
| GML Function | Activation Behavior | Typical Use Case |
|---|---|---|
| keyboard_check(key) | Returns true continuously across all frames where the key remains held down. | Walking continuously, accelerating a vehicle, holding down aim. |
| keyboard_check_pressed(key) | Returns true only on the exact frame the key transitions from released to pressed. | Jumping, firing a semi-automatic weapon, pausing the game. |
| keyboard_check_released(key) | Returns true only on the exact frame the key transitions from pressed to released. | Cutting a jump short to modulate jump height, releasing a rope or dropped item. |
Understanding the distinction between `keyboard_check` and `keyboard_check_pressed` is critical. If you bound jumping to `keyboard_check`, the character would attempt to launch upward across all 60 frames per second that the player held the key, resulting in erratic behavior or infinite upward acceleration. Conversely, if you used `keyboard_check_pressed` for walking, the player would have to mash the arrow or letter key repeatedly just to advance one pixel per tap.
Keyboard Constants and the ord() Function
To specify which key to evaluate with these functions, GameMaker relies on two mechanisms: built-in keyboard constants (special identifiers prefixed with `vk_`, for virtual key) and the `ord()` function for basic alphanumeric characters.
The most common virtual constants include `vk_left` (left arrow), `vk_right` (right arrow), `vk_up` (up arrow), `vk_down` (down arrow), `vk_space` (spacebar), `vk_shift` (Shift key), and `vk_enter` (Enter). These constants represent navigation or control keys and are passed directly into the function without quotes.
In contrast, when checking individual letters of the alphabet (such as standard W, A, S, D layouts) or top-row numbers, you must wrap the character in the `ord()` function. Furthermore, GameMaker strictly requires passing uppercase letters enclosed in double quotes, regardless of the user's keyboard state or Caps Lock setting—for example: `ord("A")` or `ord("D")`.
// Example of reading inputs inside obj_player's Step event
var _tecla_derecha = keyboard_check(vk_right);
var _tecla_d = keyboard_check(ord("D"));
// We can combine both logical conditions with the OR operator (||)
var _moverse_derecha = _tecla_derecha || _tecla_d;Notice how we declare a temporary local variable prefixed with the `var` keyword. As covered in Module 3 on variable scope, local variables exist only during the execution of the current code block and are automatically cleared from memory once the frame finishes. This behavior is ideal for input handling, as input states should be queried fresh every frame without persisting after computing that frame's movement.

Gamepad Input Fundamentals in GML
Modern players frequently expect to plug in a USB or Bluetooth controller and command their character right away. GameMaker provides robust built-in controller support through a dedicated family of functions prefixed with `gamepad_`.
When handling controllers, keep in mind that the system assigns an index or numeric slot to each connected device, typically ranging from 0 to 11. On standard platforms like Windows, XInput-compatible controllers (such as standard modern console gamepads) populate slots 0 through 3. Therefore, Player 1's controller almost always resides at slot 0.
Before querying a controller, it is best practice to verify whether the device is actually connected using `gamepad_is_connected(device)`. If you poll a disconnected slot, GameMaker safely returns 0, but checking explicitly allows you to toggle dynamic UI prompts or adjust input configurations cleanly.
Just like keyboard functions, digital controller buttons (such as the D-pad and face action buttons) offer equivalent temporal polling functions:
| GML Gamepad Function | Required Parameters | Description |
|---|---|---|
| gamepad_is_connected(device) | device (integer slot index, e.g., 0) | Returns true if the specified device is connected and recognized. |
| gamepad_button_check(device, button) | device (e.g., 0), button (constant) | Returns true continuously while the button remains held down. |
| gamepad_button_check_pressed(device, button) | device (e.g., 0), button (constant) | Returns true only on the frame the button transitions to pressed. |
| gamepad_axis_value(device, axis) | device (e.g., 0), axis (constant) | Returns a continuous floating-point analog reading between -1.0 and 1.0. |
Digital button constants in GameMaker begin with the `gp_` prefix. The most common constants for platformers are `gp_padu`, `gp_padd`, `gp_padl`, and `gp_padr` (directional D-pad inputs), along with face buttons like `gp_face1` (bottom button: 'A' on Xbox-style pads or 'Cross' on PlayStation) and `gp_face2` (right button: 'B' or 'Circle').
Analog Sticks and the Deadzone
Unlike keys and digital buttons that provide binary states (0 for off and 1 for on), analog thumbsticks return a continuous gradient of deflection via `gamepad_axis_value(device, axis)`. For the horizontal axis of the left stick, use the constant `gp_axislh` (GamePad Axis Left Horizontal). Deflecting the stick fully left yields `-1.0`, centering it yields `0.0`, and pushing it fully right produces `1.0`.
However, physical analog potentiometers have slight mechanical imperfections. When a player releases the thumbstick, it rarely returns to an absolute zero; it often rests on minute floating values like `0.04` or `-0.07`. If used raw, these values cause stick drift, making the player character slowly slide across the screen on its own.
To solve this, GameMaker provides `gamepad_set_axis_deadzone(device, deadzone)`. This function is typically executed once in the Create event of your controller manager or player object. It defines a threshold below which any analog reading is automatically rounded down to strict zero. This threshold is usually configured between `0.1` and `0.3` (representing a 10% to 30% physical tolerance margin).
💡 Best Practice: Always set your deadzone in the Create event of your player object using gamepad_set_axis_deadzone(0, 0.2);. This prevents unwanted phantom drifting caused by the potentiometer tolerances in analog sticks.
Unifying Inputs: Building a Hybrid Control System
A frequent mistake among beginner developers is embedding movement and physics logic directly into separate, isolated input-checking blocks—for instance, one block to move right on the right arrow key, a second block for the 'D' key, and a third for the controller stick. This fragments logic, duplicates code, and requires any physics tweaks to be manually mirrored across multiple locations.
A clean, professional architecture unifies all input readings at the very start of the Step event. We poll every available source (arrow keys, WASD keys, controller D-pad, and analog sticks) and boil that data down into clear boolean or numeric variables. The downstream movement logic only consumes these consolidated variables, regardless of which hardware peripheral produced the input.
/// @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("Player triggered the jump action!");
}Let us examine one of the most elegant techniques used in 2D gameplay programming: `var _direccion_horizontal = _tecla_derecha - _tecla_izquierda;`. This simple arithmetic trick neatly resolves several common control edge cases:
In GML, boolean values `true` and `false` evaluate numerically to `1` and `0` respectively. Consequently:
1. If the player presses only right: `_tecla_derecha` is 1 and `_tecla_izquierda` is 0. The subtraction yields `1 - 0 = 1`, representing positive rightward movement.
2. If the player presses only left: `_tecla_derecha` is 0 and `_tecla_izquierda` is 1. The subtraction yields `0 - 1 = -1`, representing negative leftward movement.
3. If the player presses neither key: `0 - 0 = 0`, representing complete rest.
4. If the player presses both directions simultaneously (for example, holding 'A' and 'D' together): `1 - 1 = 0`. The opposing forces cancel each other out instantly, preventing jitter or unpredictable movement.
Step-by-Step: Setting Up in the Platformer Project
To ensure continuity with the project built across earlier modules and prepare for subsequent lessons in Module 4, follow these steps in your GameMaker workspace:
Step 1: Open the 2D platformer project you have been developing since Module 2. In the 'Asset Browser' panel, locate `obj_player` and double-click it to open its event window.
Step 2: Open the **Create** event of `obj_player`. Keep any variables initialized in previous lessons, and append controller deadzone configuration at the end to stabilize analog axes:
/// @description Evento Create de obj_player
// Hardware adjustment: Gamepad 0 deadzone set to 20%
gamepad_set_axis_deadzone(0, 0.2);Step 3: Open the **Step** event of `obj_player` (if it does not exist yet, click 'Add Event', select 'Step', and pick the standard 'Step' event). Remove any temporary test code and paste the unified input capture routine demonstrated earlier.
Step 4: Run the project by pressing Run (F5). Open the 'Output' dock at the bottom of GameMaker to view the console log. Press the Spacebar, the W key, or the A button on a connected controller. Each time you trigger the jump action, you should see the debug log message appear regardless of which input device you used. This verifies that your input pipeline processes actions on the correct frame.
💡 Compatibility Note: GameMaker detects device connection and disconnection automatically via the Asynchronous - System event. However, at this introductory stage, polling slot 0 with gamepad_is_connected(0) inside the Step event is the most direct, practical, and beginner-friendly method to master input concepts.
Practical Exercises
Reinforce your understanding by completing these practical exercises in your project:
1. **Sprint Button Integration**: Modify the input capture block in `obj_player`'s Step event to include a variable named `_tecla_correr`. This variable should evaluate to `true` while the player holds down Left Shift (`vk_lshift`) on the keyboard or the right shoulder/bumper button (`gp_shoulderr`) on the controller. Test this by printing a message with `show_debug_message()` only when this variable is active.
2. **Jump Release Detection**: Implement a local variable named `_tecla_salto_soltada` using `keyboard_check_released()` for the Spacebar and `gamepad_button_check_released()` for the gamepad face button. Output the message `"Jump cut off"` to the debug console when triggered. *Verification criteria:* The message must output strictly on the single frame when you release the button after holding it down, never continuously.
3. **Inspecting Horizontal Analog Values**: Add a temporary conditional statement that prints the exact value returned by `gamepad_axis_value(0, gp_axislh)` each frame, but only when its absolute value (`abs()`) exceeds 0.2. Slowly tilt the stick from side to side to observe how the console output smoothly shifts between -1.0 and 1.0, verifying that the deadzone filters out center jitter.
Summary
- Inputs in GameMaker must be polled continuously inside the Step event to guarantee responsive per-frame feedback.
- keyboard_check operates continuously as long as a key is held, whereas keyboard_check_pressed triggers only on the first frame of contact.
- Alphanumeric keys require the ord("LETTER") function in uppercase, whereas special keys use system constants like vk_space or vk_left.
- Gamepads are addressed via numeric slot indices (slot 0 for Player 1) and checked using gamepad_is_connected.
- Analog sticks require setting gamepad_set_axis_deadzone() in the Create event to eliminate unwanted drifting caused by hardware tolerances.
- Consolidating keyboard and controller signals into unified variables and deriving direction through (_tecla_derecha - _tecla_izquierda) keeps code clean, professional, and ready for movement mechanics.
Practise what you learned
Exercise 1 · 1 point
What is the correct GML Code syntax to check whether the player is pressing the "D" key using the keyboard_check function?
Exercise 2 · 1 point
Match each GML identifier or function with its correct description:
- gp_axislh
- gamepad_set_axis_deadzone
- gp_face1
- vk_space
Exercise 3 · 1 point
True or false: Continuous keyboard input checks for moving a character must be placed in the Create event so they are evaluated 60 times per second.
Sign in to save your progress — Sign in