Implement game controller support and add keybindings for gamepad input
This commit is contained in:
+94
-11
@@ -12,6 +12,36 @@ from PIL import Image
|
||||
from runtime_paths import resolve_bundle_path
|
||||
|
||||
|
||||
CONTROLLER_BUTTON_NAMES = {
|
||||
sdl2.SDL_CONTROLLER_BUTTON_A: "a",
|
||||
sdl2.SDL_CONTROLLER_BUTTON_B: "b",
|
||||
sdl2.SDL_CONTROLLER_BUTTON_X: "x",
|
||||
sdl2.SDL_CONTROLLER_BUTTON_Y: "y",
|
||||
sdl2.SDL_CONTROLLER_BUTTON_BACK: "back",
|
||||
sdl2.SDL_CONTROLLER_BUTTON_GUIDE: "guide",
|
||||
sdl2.SDL_CONTROLLER_BUTTON_START: "start",
|
||||
sdl2.SDL_CONTROLLER_BUTTON_LEFTSTICK: "leftstick",
|
||||
sdl2.SDL_CONTROLLER_BUTTON_RIGHTSTICK: "rightstick",
|
||||
sdl2.SDL_CONTROLLER_BUTTON_LEFTSHOULDER: "leftshoulder",
|
||||
sdl2.SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: "rightshoulder",
|
||||
sdl2.SDL_CONTROLLER_BUTTON_DPAD_UP: "dpad_up",
|
||||
sdl2.SDL_CONTROLLER_BUTTON_DPAD_DOWN: "dpad_down",
|
||||
sdl2.SDL_CONTROLLER_BUTTON_DPAD_LEFT: "dpad_left",
|
||||
sdl2.SDL_CONTROLLER_BUTTON_DPAD_RIGHT: "dpad_right",
|
||||
}
|
||||
|
||||
|
||||
def _decode_sdl_string(value):
|
||||
if not value:
|
||||
return None
|
||||
if isinstance(value, (bytes, bytearray)):
|
||||
return bytes(value).decode("utf-8", errors="ignore")
|
||||
try:
|
||||
return value.decode("utf-8", errors="ignore")
|
||||
except AttributeError:
|
||||
return str(value)
|
||||
|
||||
|
||||
class GameWindow:
|
||||
def __init__(self, width, height, cell_size, title="Default", key_callback=None):
|
||||
# Display configuration
|
||||
@@ -76,6 +106,10 @@ class GameWindow:
|
||||
self.trigger = key_callback
|
||||
self.button_cursor = [0, 0]
|
||||
self.buttons = {}
|
||||
self.joystick = None
|
||||
self.game_controller = None
|
||||
self.input_device_kind = "keyboard"
|
||||
self.input_device_name = None
|
||||
|
||||
# Audio system initialization
|
||||
self._init_audio_system()
|
||||
@@ -412,9 +446,43 @@ class GameWindow:
|
||||
# ======================
|
||||
|
||||
def load_joystick(self):
|
||||
"""Initialize joystick support"""
|
||||
sdl2.SDL_Init(sdl2.SDL_INIT_JOYSTICK)
|
||||
sdl2.SDL_JoystickOpen(0)
|
||||
"""Initialize joystick and game controller support."""
|
||||
sdl2.SDL_Init(sdl2.SDL_INIT_JOYSTICK | sdl2.SDL_INIT_GAMECONTROLLER)
|
||||
sdl2.SDL_JoystickEventState(sdl2.SDL_ENABLE)
|
||||
if hasattr(sdl2, "SDL_GameControllerEventState"):
|
||||
sdl2.SDL_GameControllerEventState(sdl2.SDL_ENABLE)
|
||||
|
||||
num_joysticks = sdl2.SDL_NumJoysticks()
|
||||
if num_joysticks < 1:
|
||||
print("[input] no joystick detected")
|
||||
return
|
||||
|
||||
for device_index in range(num_joysticks):
|
||||
if hasattr(sdl2, "SDL_IsGameController") and sdl2.SDL_IsGameController(device_index):
|
||||
controller = sdl2.SDL_GameControllerOpen(device_index)
|
||||
if controller:
|
||||
self.game_controller = controller
|
||||
self.input_device_kind = "gamecontroller"
|
||||
self.input_device_name = _decode_sdl_string(
|
||||
sdl2.SDL_GameControllerName(controller)
|
||||
)
|
||||
print(f"[input] game controller detected: {self.input_device_name}")
|
||||
return
|
||||
|
||||
self.joystick = sdl2.SDL_JoystickOpen(0)
|
||||
if self.joystick:
|
||||
self.input_device_kind = "joystick"
|
||||
self.input_device_name = _decode_sdl_string(sdl2.SDL_JoystickName(self.joystick))
|
||||
print(f"[input] raw joystick detected: {self.input_device_name}")
|
||||
|
||||
def get_input_device_summary(self):
|
||||
return {
|
||||
"kind": self.input_device_kind,
|
||||
"name": self.input_device_name,
|
||||
}
|
||||
|
||||
def _controller_button_name(self, button):
|
||||
return CONTROLLER_BUTTON_NAMES.get(button, str(button))
|
||||
|
||||
# ======================
|
||||
# MAIN GAME LOOP
|
||||
@@ -456,15 +524,24 @@ class GameWindow:
|
||||
elif event.type == sdl2.SDL_MOUSEMOTION:
|
||||
self.trigger(f"mousemove_{event.motion.x}, {event.motion.y}")
|
||||
elif event.type == sdl2.SDL_JOYBUTTONDOWN:
|
||||
key = event.jbutton.button
|
||||
self.trigger(f"joybuttondown_{key}")
|
||||
if self.game_controller is None:
|
||||
key = event.jbutton.button
|
||||
self.trigger(f"joybuttondown_{key}")
|
||||
elif event.type == sdl2.SDL_JOYBUTTONUP:
|
||||
key = event.jbutton.button
|
||||
self.trigger(f"joybuttonup_{key}")
|
||||
if self.game_controller is None:
|
||||
key = event.jbutton.button
|
||||
self.trigger(f"joybuttonup_{key}")
|
||||
elif event.type == sdl2.SDL_JOYHATMOTION:
|
||||
hat = event.jhat.hat
|
||||
value = event.jhat.value
|
||||
self.trigger(f"joyhatmotion_{hat}_{value}")
|
||||
if self.game_controller is None:
|
||||
hat = event.jhat.hat
|
||||
value = event.jhat.value
|
||||
self.trigger(f"joyhatmotion_{hat}_{value}")
|
||||
elif hasattr(sdl2, "SDL_CONTROLLERBUTTONDOWN") and event.type == sdl2.SDL_CONTROLLERBUTTONDOWN:
|
||||
button_name = self._controller_button_name(event.cbutton.button)
|
||||
self.trigger(f"controllerbuttondown_{button_name}")
|
||||
elif hasattr(sdl2, "SDL_CONTROLLERBUTTONUP") and event.type == sdl2.SDL_CONTROLLERBUTTONUP:
|
||||
button_name = self._controller_button_name(event.cbutton.button)
|
||||
self.trigger(f"controllerbuttonup_{button_name}")
|
||||
|
||||
|
||||
|
||||
@@ -537,7 +614,7 @@ class GameWindow:
|
||||
if event.type == sdl2.SDL_QUIT:
|
||||
self.running = False
|
||||
return
|
||||
elif event.type in (sdl2.SDL_KEYDOWN, sdl2.SDL_JOYBUTTONDOWN):
|
||||
elif event.type in (sdl2.SDL_KEYDOWN, sdl2.SDL_JOYBUTTONDOWN, sdl2.SDL_CONTROLLERBUTTONDOWN):
|
||||
skipped = True
|
||||
|
||||
if skipped:
|
||||
@@ -645,6 +722,12 @@ class GameWindow:
|
||||
|
||||
def close(self):
|
||||
"""Close the game window and cleanup"""
|
||||
if self.game_controller:
|
||||
sdl2.SDL_GameControllerClose(self.game_controller)
|
||||
self.game_controller = None
|
||||
if self.joystick:
|
||||
sdl2.SDL_JoystickClose(self.joystick)
|
||||
self.joystick = None
|
||||
self.running = False
|
||||
sdl2.ext.quit()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user