Quick answer: gamepad_is_connected(0) only checks slot 0 — loop over all 12 slots (0–11) to find connected gamepads. On Windows, non-Xbox controllers need an XInput wrapper, and HTML5 exports require a user gesture before the Gamepad API activates.

Controller support is one of those features players expect to “just work” on day one, and GameMaker’s gamepad functions are actually solid once you understand how the slot system works. The most common reports of “controller not detected” fall into four categories: wrong slot index, XInput vs DirectInput mismatch on Windows, HTML5 browser restrictions, and unrecognized device layouts. This guide covers every case with working code you can drop straight into your project.

The Slot System: Never Assume Slot 0

GameMaker supports up to 12 simultaneous gamepads, assigned to slot indices 0 through 11 in the order they are connected. The critical mistake is checking only slot 0 with a hardcoded call to gamepad_is_connected(0). If your player connected a keyboard-mapped controller, a flight stick, or any other HID device before their actual gamepad, that controller might be sitting in slot 3 or 5 while slot 0 reports nothing.

The correct pattern is to scan all slots at startup and again in the Async — System event (which fires when devices connect or disconnect):

/// obj_input_manager — Create event
active_gamepad = -1; // -1 means no gamepad found yet

var _max_slots = gamepad_get_device_count();
for (var _i = 0; _i < _max_slots; _i++) {
  if (gamepad_is_connected(_i)) {
    active_gamepad = _i;
    break;
  }
}

Store the slot index in a global or controller manager object. Re-run this scan in the Async — System event so your game responds when players plug in or unplug controllers mid-session. The constant gamepad_get_device_count() returns the platform’s maximum supported slot count, which varies by export target.

Reading Input: Modern Functions vs Direct Functions

GameMaker Studio 2 has two sets of gamepad input functions. The standard functions — gamepad_button_check(), gamepad_axis_value(), gamepad_button_check_pressed() — map to named constants like gp_face1, gp_axislh, gp_shoulderl. These constants are abstracted across controller types so that gp_face1 means the bottom face button regardless of whether it is “A” on Xbox or “Cross” on PlayStation.

/// Step event — reading input from active slot
if (active_gamepad != -1) {
  // Jump on any face button
  if (gamepad_button_check_pressed(active_gamepad, gp_face1)) {
    do_jump();
  }

  // Left stick horizontal axis (-1.0 to 1.0)
  var _move_x = gamepad_axis_value(active_gamepad, gp_axislh);

  // Apply a deadzone to avoid stick drift
  if (abs(_move_x) < 0.15) _move_x = 0;
  x += _move_x * move_speed;
}

The older gamepad_button_check_direct() family bypasses the named constant mapping and reads raw button indices. These are unreliable across controller types because button index 0 means different physical buttons on different controllers. Avoid the direct functions unless you are building a custom remapping system.

Windows: XInput vs DirectInput and Non-Xbox Controllers

GameMaker on Windows uses XInput as its primary gamepad API. XInput is Microsoft’s modern controller API designed for Xbox controllers and Xbox-compatible devices. It has a fixed button layout, supports rumble, and works out of the box in GameMaker with the standard gp_* constants.

The problem is that PlayStation controllers, generic USB gamepads, and many third-party controllers use DirectInput instead. GameMaker will either not detect them at all, or detect them with gamepad_button_count(slot) returning 0, indicating the device is connected but its layout is unrecognized.

The solution for PlayStation controllers is DS4Windows (PS4/PS5) or DualSenseX, which create a virtual XInput device that GameMaker sees correctly. For generic controllers, x360ce provides the same XInput wrapping. Communicate this requirement to players in your game’s documentation or controller settings screen.

/// Detect unrecognized device (button count of 0)
if (gamepad_is_connected(active_gamepad)) {
  var _btn_count = gamepad_button_count(active_gamepad);
  if (_btn_count == 0) {
    // Controller connected but not recognized by XInput
    show_debug_message("Warning: gamepad in slot " +
      string(active_gamepad) +
      " has 0 buttons. May need XInput wrapper.");
  }
}

HTML5 Export: The Gamepad API Gesture Requirement

Browser security policy requires a user gesture (a key press, mouse click, or button press) before JavaScript APIs that access physical devices are permitted to activate. The Web Gamepad API follows this rule: until the player interacts with the page, gamepad_is_connected() returns false for all slots even if a controller is physically plugged in and the operating system reports it as connected.

This means your HTML5 export should not attempt to detect controllers during the game’s loading screen or initial splash sequence. Instead, show a prompt such as “Press any key or button to continue” before the main menu. Once the player interacts, the browser grants Gamepad API access and subsequent calls to gamepad_is_connected() will correctly reflect the hardware state.

A practical pattern is to poll for a gesture in a title screen object and only enable gamepad scanning after it fires:

/// obj_title_screen — Step event
if (!gesture_received) {
  // Any keyboard or mouse input counts as the gesture
  if (keyboard_check_pressed(vk_anykey) ||
      mouse_check_button_pressed(mb_any)) {
    gesture_received = true;
    // Now safe to scan for gamepads
    with (obj_input_manager) scan_gamepads();
  }
}

Using the GMS2 Input Library for Cross-Platform Abstraction

If your game targets multiple platforms — Windows, macOS, HTML5, mobile, console — managing gamepad differences per-platform manually becomes expensive. The community-maintained Input library by Juju Adams and Alynne Keith is the de facto standard for cross-platform input in GameMaker. It wraps the native gamepad functions, handles XInput vs DirectInput detection, provides a clean binding system, and manages the HTML5 gesture requirement automatically.

With Input, controller detection reduces to a few calls:

/// Using the Input library (input.yymps)
// Check if any gamepad is connected
if (input_player_connected(0)) {
  // Read a named verb rather than a raw button
  if (input_check_pressed("jump", 0)) {
    do_jump();
  }
}

The library handles slot scanning, device type detection, deadzone normalization, and per-player binding profiles. For any game shipping on more than one platform, it is worth the integration time.

“Gamepad support bugs are almost always an assumption about slot 0 or platform input APIs — the fix is straightforward once you know which assumption broke.”

Test controller support with at least one non-Xbox controller and the HTML5 export before you ship — those are the two cases that almost always break in the wild.