Cross-platform library for developing multimedia applications and video games in Python built on top of SDL
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 pygame
import pygame.joystick
# Initialize joystick system
pygame.joystick.init()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
"""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
"""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
"""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)
"""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
"""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.
"""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.
"""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 levelCommon 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 joystickimport 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()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()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()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.