CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-pygame

Cross-platform library for developing multimedia applications and video games in Python built on top of SDL

Overview
Eval results
Files

joystick-gamepad.mddocs/

Joystick and Gamepad Control

Complete joystick and gamepad input system for handling controllers, analog sticks, buttons, and haptic feedback. Pygame's joystick module provides cross-platform support for game controllers with both basic input reading and advanced features like rumble feedback.

Import Statements

import pygame
import pygame.joystick

# Initialize joystick system
pygame.joystick.init()

Capabilities

System Initialization

Initialize and manage the joystick subsystem.

def init() -> None:
    """
    Initialize the joystick module.
    Must be called before using joystick functions.
    Safe to call multiple times.
    """

def quit() -> None:
    """
    Uninitialize the joystick module.
    Automatically called when pygame quits.
    """

def get_init() -> bool:
    """
    Check if joystick module is initialized.

    Returns:
        bool: True if joystick module is initialized
    """

Device Enumeration

Discover and count available joystick devices.

def get_count() -> int:
    """
    Get number of joysticks connected to the system.

    Returns:
        int: Number of available joystick devices
    """

Joystick Control

Individual joystick device management and input reading.

class Joystick:
    def __init__(self, id: int):
        """
        Create a Joystick object for device control.

        Args:
            id (int): Joystick device index (0 to get_count()-1)
        """

    def init(self) -> None:
        """
        Initialize the joystick device.
        Must be called before reading input from this joystick.
        """

    def quit(self) -> None:
        """
        Uninitialize the joystick device.
        Releases resources and stops input reading.
        """

    def get_init(self) -> bool:
        """
        Check if this joystick is initialized.

        Returns:
            bool: True if joystick is initialized and ready
        """

    def get_id(self) -> int:
        """
        Get the device index for this joystick.

        Returns:
            int: Device index used to create this joystick
        """

    def get_instance_id(self) -> int:
        """
        Get unique instance ID for this joystick.
        Instance ID remains constant while device is connected.

        Returns:
            int: Unique instance identifier
        """

    def get_name(self) -> str:
        """
        Get the system name of the joystick device.

        Returns:
            str: Device name (e.g., "Xbox One Controller")
        """

    def get_guid(self) -> str:
        """
        Get unique identifier string for joystick type.
        GUID is consistent across platforms for same controller model.

        Returns:
            str: Globally unique identifier string
        """

Analog Input Reading

Read continuous analog values from sticks and triggers.

class Joystick:
    def get_numaxes(self) -> int:
        """
        Get number of analog axes on this joystick.

        Returns:
            int: Number of analog axes (typically 2-6 for modern gamepads)
        """

    def get_axis(self, axis: int) -> float:
        """
        Get current value of an analog axis.

        Args:
            axis (int): Axis index (0 to get_numaxes()-1)

        Returns:
            float: Axis value (-1.0 to 1.0, 0.0 at rest)
        """

Button Input Reading

Read digital button states from controller buttons.

class Joystick:
    def get_numbuttons(self) -> int:
        """
        Get number of buttons on this joystick.

        Returns:
            int: Number of digital buttons
        """

    def get_button(self, button: int) -> bool:
        """
        Get current state of a button.

        Args:
            button (int): Button index (0 to get_numbuttons()-1)

        Returns:
            bool: True if button is currently pressed
        """

D-Pad (Hat) Input Reading

Read directional pad input from hat switches.

class Joystick:
    def get_numhats(self) -> int:
        """
        Get number of hat switches (D-pads) on this joystick.

        Returns:
            int: Number of hat switches (usually 0 or 1)
        """

    def get_hat(self, hat: int) -> tuple[int, int]:
        """
        Get current position of a hat switch.

        Args:
            hat (int): Hat index (0 to get_numhats()-1)

        Returns:
            tuple[int, int]: (x, y) values each -1, 0, or 1
                            (0, 0) = center, (0, 1) = up, (1, 0) = right, etc.
        """

Advanced Features

Power management and haptic feedback capabilities (when supported).

class Joystick:
    def get_power_level(self) -> int:
        """
        Get battery power level of wireless joystick.
        Only available on some platforms and controllers.

        Returns:
            int: Power level constant or -1 if not supported
                 JOYSTICK_POWER_UNKNOWN = -1
                 JOYSTICK_POWER_EMPTY = 0
                 JOYSTICK_POWER_LOW = 1
                 JOYSTICK_POWER_MEDIUM = 2
                 JOYSTICK_POWER_FULL = 3
                 JOYSTICK_POWER_WIRED = 4
                 JOYSTICK_POWER_MAX = 5
        """

    def rumble(self, low_freq: float, high_freq: float, duration: int) -> bool:
        """
        Start haptic rumble feedback.
        Only available on controllers with rumble support.

        Args:
            low_freq (float): Low frequency rumble intensity (0.0 to 1.0)
            high_freq (float): High frequency rumble intensity (0.0 to 1.0)
            duration (int): Rumble duration in milliseconds

        Returns:
            bool: True if rumble started successfully
        """

    def stop_rumble(self) -> None:
        """
        Stop all rumble feedback on this joystick.
        Only available on controllers with rumble support.
        """

Joystick Event Constants

Event types generated by joystick input changes.

# Joystick events (from pygame.event)
JOYAXISMOTION: int     # Analog axis value changed
JOYBUTTONDOWN: int     # Button was pressed
JOYBUTTONUP: int       # Button was released
JOYHATMOTION: int      # Hat/D-pad direction changed
JOYDEVICEADDED: int    # New joystick connected
JOYDEVICEREMOVED: int  # Joystick disconnected

# Power level constants
JOYSTICK_POWER_UNKNOWN: int = -1   # Power level unknown
JOYSTICK_POWER_EMPTY: int = 0      # Battery empty
JOYSTICK_POWER_LOW: int = 1        # Battery low
JOYSTICK_POWER_MEDIUM: int = 2     # Battery medium
JOYSTICK_POWER_FULL: int = 3       # Battery full
JOYSTICK_POWER_WIRED: int = 4      # Wired connection
JOYSTICK_POWER_MAX: int = 5        # Maximum power level

Event Attributes

Common attributes available in joystick events.

# JOYAXISMOTION event attributes
event.joy: int         # Joystick device index
event.axis: int        # Axis number that moved
event.value: float     # New axis value (-1.0 to 1.0)

# JOYBUTTONDOWN/JOYBUTTONUP event attributes
event.joy: int         # Joystick device index
event.button: int      # Button number pressed/released

# JOYHATMOTION event attributes
event.joy: int         # Joystick device index
event.hat: int         # Hat number that moved
event.value: tuple     # (x, y) hat position (-1, 0, 1)

# JOYDEVICEADDED/JOYDEVICEREMOVED event attributes
event.device_index: int  # Device index for added/removed joystick

Usage Examples

Basic Joystick Detection

import pygame

pygame.init()
pygame.joystick.init()

print(f"Number of joysticks: {pygame.joystick.get_count()}")

# List all connected joysticks
joysticks = []
for i in range(pygame.joystick.get_count()):
    joy = pygame.joystick.Joystick(i)
    joy.init()
    joysticks.append(joy)

    print(f"Joystick {i}:")
    print(f"  Name: {joy.get_name()}")
    print(f"  GUID: {joy.get_guid()}")
    print(f"  Axes: {joy.get_numaxes()}")
    print(f"  Buttons: {joy.get_numbuttons()}")
    print(f"  Hats: {joy.get_numhats()}")

    # Check power level if supported
    try:
        power = joy.get_power_level()
        if power != -1:
            power_names = ["Empty", "Low", "Medium", "Full", "Wired", "Max"]
            print(f"  Power: {power_names[power]}")
    except AttributeError:
        print("  Power: Not supported")

pygame.quit()

Real-Time Gamepad Input

import pygame
import sys

pygame.init()
pygame.joystick.init()

if pygame.joystick.get_count() == 0:
    print("No joysticks connected!")
    sys.exit()

# Initialize first joystick
joystick = pygame.joystick.Joystick(0)
joystick.init()

print(f"Using: {joystick.get_name()}")

screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("Gamepad Input Test")
clock = pygame.time.Clock()

# Player position controlled by joystick
player_x, player_y = 400, 300
player_speed = 5

running = True
while running:
    # Handle events
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        elif event.type == pygame.JOYBUTTONDOWN:
            print(f"Button {event.button} pressed")
            if event.button == 0:  # Usually "A" button
                print("Jump!")
        elif event.type == pygame.JOYBUTTONUP:
            print(f"Button {event.button} released")
        elif event.type == pygame.JOYHATMOTION:
            print(f"Hat {event.hat} moved to {event.value}")

    # Read analog stick for movement (usually axes 0 and 1)
    if joystick.get_numaxes() >= 2:
        x_axis = joystick.get_axis(0)  # Left stick X
        y_axis = joystick.get_axis(1)  # Left stick Y

        # Apply deadzone to prevent drift
        deadzone = 0.1
        if abs(x_axis) > deadzone:
            player_x += x_axis * player_speed
        if abs(y_axis) > deadzone:
            player_y += y_axis * player_speed

    # Keep player on screen
    player_x = max(25, min(775, player_x))
    player_y = max(25, min(575, player_y))

    # Draw everything
    screen.fill((50, 50, 50))
    pygame.draw.circle(screen, (255, 255, 255), (int(player_x), int(player_y)), 25)

    # Draw axis indicators
    if joystick.get_numaxes() >= 2:
        stick_x = 100 + joystick.get_axis(0) * 50
        stick_y = 100 + joystick.get_axis(1) * 50
        pygame.draw.circle(screen, (0, 255, 0), (int(stick_x), int(stick_y)), 10)
        pygame.draw.circle(screen, (100, 100, 100), (100, 100), 50, 2)

    pygame.display.flip()
    clock.tick(60)

pygame.quit()

Advanced Gamepad Features

import pygame
import sys
import math

pygame.init()
pygame.joystick.init()

if pygame.joystick.get_count() == 0:
    print("No joysticks connected!")
    sys.exit()

joystick = pygame.joystick.Joystick(0)
joystick.init()

screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("Advanced Gamepad Features")
clock = pygame.time.Clock()

# Game state
player_x, player_y = 400, 300
rumble_timer = 0
trigger_threshold = 0.5

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        elif event.type == pygame.JOYBUTTONDOWN:
            if event.button == 0:  # A button - trigger rumble
                try:
                    # Rumble for 500ms with medium intensity
                    if joystick.rumble(0.7, 0.3, 500):
                        print("Rumble started")
                        rumble_timer = 500
                    else:
                        print("Rumble not supported")
                except AttributeError:
                    print("Rumble not available on this platform")

            elif event.button == 1:  # B button - stop rumble
                try:
                    joystick.stop_rumble()
                    print("Rumble stopped")
                    rumble_timer = 0
                except AttributeError:
                    pass

    # Read all axes for advanced control
    left_stick_x = joystick.get_axis(0) if joystick.get_numaxes() > 0 else 0
    left_stick_y = joystick.get_axis(1) if joystick.get_numaxes() > 1 else 0

    # Right stick for rotation (if available)
    right_stick_x = joystick.get_axis(3) if joystick.get_numaxes() > 3 else 0
    right_stick_y = joystick.get_axis(4) if joystick.get_numaxes() > 4 else 0

    # Triggers (if available)
    left_trigger = joystick.get_axis(2) if joystick.get_numaxes() > 2 else 0
    right_trigger = joystick.get_axis(5) if joystick.get_numaxes() > 5 else 0

    # Movement with deadzone
    deadzone = 0.15
    if abs(left_stick_x) > deadzone or abs(left_stick_y) > deadzone:
        # Calculate magnitude for smooth movement
        magnitude = math.sqrt(left_stick_x**2 + left_stick_y**2)
        if magnitude > 1.0:
            magnitude = 1.0

        # Apply magnitude-based speed
        speed = magnitude * 8
        player_x += left_stick_x * speed
        player_y += left_stick_y * speed

    # Keep player on screen
    player_x = max(25, min(775, player_x))
    player_y = max(25, min(575, player_y))

    # Calculate rotation from right stick
    rotation = 0
    if abs(right_stick_x) > deadzone or abs(right_stick_y) > deadzone:
        rotation = math.atan2(right_stick_y, right_stick_x)

    # Update rumble timer
    if rumble_timer > 0:
        rumble_timer -= clock.get_time()

    # Draw everything
    screen.fill((30, 30, 30))

    # Draw player with rotation indicator
    player_color = (255, 255, 255)
    if left_trigger > trigger_threshold:
        player_color = (255, 100, 100)  # Red when left trigger pressed
    elif right_trigger > trigger_threshold:
        player_color = (100, 100, 255)  # Blue when right trigger pressed

    pygame.draw.circle(screen, player_color, (int(player_x), int(player_y)), 25)

    # Draw rotation indicator
    if rotation != 0:
        end_x = player_x + math.cos(rotation) * 40
        end_y = player_y + math.sin(rotation) * 40
        pygame.draw.line(screen, (255, 255, 0), (player_x, player_y), (end_x, end_y), 3)

    # Draw stick visualizers
    # Left stick
    stick_center = (100, 500)
    pygame.draw.circle(screen, (100, 100, 100), stick_center, 50, 2)
    stick_pos = (stick_center[0] + left_stick_x * 40, stick_center[1] + left_stick_y * 40)
    pygame.draw.circle(screen, (0, 255, 0), stick_pos, 8)

    # Right stick
    stick_center = (700, 500)
    pygame.draw.circle(screen, (100, 100, 100), stick_center, 50, 2)
    stick_pos = (stick_center[0] + right_stick_x * 40, stick_center[1] + right_stick_y * 40)
    pygame.draw.circle(screen, (255, 0, 255), stick_pos, 8)

    # Draw trigger indicators
    trigger_width = 100
    trigger_height = 20

    # Left trigger
    left_bar_width = int((left_trigger + 1) * 0.5 * trigger_width)  # Convert -1,1 to 0,1
    pygame.draw.rect(screen, (100, 100, 100), (50, 50, trigger_width, trigger_height), 2)
    if left_bar_width > 0:
        pygame.draw.rect(screen, (255, 0, 0), (50, 50, left_bar_width, trigger_height))

    # Right trigger
    right_bar_width = int((right_trigger + 1) * 0.5 * trigger_width)
    pygame.draw.rect(screen, (100, 100, 100), (650, 50, trigger_width, trigger_height), 2)
    if right_bar_width > 0:
        pygame.draw.rect(screen, (0, 0, 255), (650, 50, right_bar_width, trigger_height))

    # Show rumble status
    if rumble_timer > 0:
        font = pygame.font.Font(None, 36)
        text = font.render("RUMBLING", True, (255, 255, 0))
        screen.blit(text, (350, 100))

    # Hat/D-pad visualization
    if joystick.get_numhats() > 0:
        hat_value = joystick.get_hat(0)
        hat_center = (400, 500)
        pygame.draw.circle(screen, (150, 150, 150), hat_center, 30, 2)

        if hat_value != (0, 0):
            hat_pos = (hat_center[0] + hat_value[0] * 20, hat_center[1] - hat_value[1] * 20)
            pygame.draw.circle(screen, (255, 255, 0), hat_pos, 10)

    pygame.display.flip()
    clock.tick(60)

pygame.quit()

Gamepad Hot-Plugging Support

import pygame
import sys

pygame.init()
pygame.joystick.init()

screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("Gamepad Hot-Plug Support")
clock = pygame.time.Clock()

# Track active joysticks
joysticks = {}

def add_joystick(device_index):
    """Add a new joystick when connected."""
    try:
        joystick = pygame.joystick.Joystick(device_index)
        joystick.init()
        joysticks[device_index] = joystick
        print(f"Joystick {device_index} connected: {joystick.get_name()}")
    except pygame.error as e:
        print(f"Failed to initialize joystick {device_index}: {e}")

def remove_joystick(device_index):
    """Remove a joystick when disconnected."""
    if device_index in joysticks:
        joystick = joysticks[device_index]
        print(f"Joystick {device_index} disconnected: {joystick.get_name()}")
        joystick.quit()
        del joysticks[device_index]

# Initialize any connected joysticks
for i in range(pygame.joystick.get_count()):
    add_joystick(i)

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

        elif event.type == pygame.JOYDEVICEADDED:
            print(f"Device added at index {event.device_index}")
            add_joystick(event.device_index)

        elif event.type == pygame.JOYDEVICEREMOVED:
            print(f"Device removed at index {event.device_index}")
            remove_joystick(event.device_index)

        elif event.type == pygame.JOYBUTTONDOWN:
            joystick = joysticks.get(event.joy)
            if joystick:
                print(f"Button {event.button} pressed on {joystick.get_name()}")

        elif event.type == pygame.JOYAXISMOTION:
            # Only print significant axis movements
            if abs(event.value) > 0.5:
                joystick = joysticks.get(event.joy)
                if joystick:
                    print(f"Axis {event.axis} = {event.value:.2f} on {joystick.get_name()}")

    screen.fill((40, 40, 40))

    # Display connected joysticks
    font = pygame.font.Font(None, 36)
    y_offset = 50

    if not joysticks:
        text = font.render("No joysticks connected", True, (255, 255, 255))
        screen.blit(text, (50, y_offset))
        text = font.render("Connect a gamepad to test hot-plugging", True, (200, 200, 200))
        screen.blit(text, (50, y_offset + 40))
    else:
        text = font.render(f"Connected joysticks: {len(joysticks)}", True, (255, 255, 255))
        screen.blit(text, (50, y_offset))
        y_offset += 50

        for device_index, joystick in joysticks.items():
            text = font.render(f"{device_index}: {joystick.get_name()}", True, (200, 255, 200))
            screen.blit(text, (70, y_offset))
            y_offset += 30

            # Show some live input data
            if joystick.get_numaxes() >= 2:
                x_axis = joystick.get_axis(0)
                y_axis = joystick.get_axis(1)
                text = font.render(f"  Stick: ({x_axis:.2f}, {y_axis:.2f})", True, (150, 150, 150))
                screen.blit(text, (90, y_offset))
                y_offset += 25

            # Show pressed buttons
            pressed_buttons = []
            for i in range(joystick.get_numbuttons()):
                if joystick.get_button(i):
                    pressed_buttons.append(str(i))

            if pressed_buttons:
                text = font.render(f"  Buttons: {', '.join(pressed_buttons)}", True, (255, 255, 0))
                screen.blit(text, (90, y_offset))
            y_offset += 40

    pygame.display.flip()
    clock.tick(60)

# Clean up all joysticks
for joystick in joysticks.values():
    joystick.quit()

pygame.quit()

This documentation provides comprehensive coverage of the pygame.joystick module, following the same high-quality structure as the existing pygame documentation. It includes all the API functions with proper type hints, detailed explanations, practical examples, and follows the established format with { .api } blocks and JSDoc-style parameter documentation.

Install with Tessl CLI

npx tessl i tessl/pypi-pygame

docs

advanced-drawing.md

audio-sound.md

core-system.md

drawing-shapes.md

event-input.md

game-objects.md

graphics-display.md

index.md

input-devices.md

joystick-gamepad.md

math-utils.md

surface-image.md

text-font.md

time-animation.md

transform-image.md

tile.json