143 lines
4.4 KiB
Python
143 lines
4.4 KiB
Python
"""Tastatur und Controller → abstrakte Aktionen.
|
||
|
||
Die Aktionen sind absichtlich generisch gehalten. Die spätere Namenseingabe
|
||
per Controller benutzt dieselben Richtungen (Buchstabe weiter/zurück,
|
||
Position wechseln), deshalb gibt es hier keine spielspezifischen Namen.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import enum
|
||
|
||
import pygame
|
||
|
||
from . import config
|
||
|
||
|
||
class Action(enum.Enum):
|
||
"""Abstrakte Eingabe – unabhängig davon, ob Tastatur oder Controller."""
|
||
|
||
PRIMARY = "primary" # Space / Enter / Controller A
|
||
LEFT = "left" # Pfeil links / D-Pad links
|
||
RIGHT = "right" # Pfeil rechts / D-Pad rechts
|
||
UP = "up" # Pfeil hoch / D-Pad hoch
|
||
DOWN = "down" # Pfeil runter / D-Pad runter
|
||
BACK = "back" # Esc / Controller B
|
||
|
||
|
||
#: Tastaturbelegung
|
||
KEY_ACTIONS: dict[int, Action] = {
|
||
pygame.K_SPACE: Action.PRIMARY,
|
||
pygame.K_RETURN: Action.PRIMARY,
|
||
pygame.K_KP_ENTER: Action.PRIMARY,
|
||
pygame.K_LEFT: Action.LEFT,
|
||
pygame.K_RIGHT: Action.RIGHT,
|
||
pygame.K_UP: Action.UP,
|
||
pygame.K_DOWN: Action.DOWN,
|
||
pygame.K_ESCAPE: Action.BACK,
|
||
}
|
||
|
||
#: Controller-Tasten (Xbox-Layout: 0=A, 1=B, 2=X, 3=Y)
|
||
BUTTON_ACTIONS: dict[int, Action] = {
|
||
0: Action.PRIMARY,
|
||
1: Action.BACK,
|
||
2: Action.LEFT,
|
||
3: Action.UP,
|
||
}
|
||
|
||
#: D-Pad, das als Hat gemeldet wird
|
||
HAT_ACTIONS: dict[tuple[int, int], Action] = {
|
||
(-1, 0): Action.LEFT,
|
||
(1, 0): Action.RIGHT,
|
||
(0, -1): Action.UP,
|
||
(0, 1): Action.DOWN,
|
||
}
|
||
|
||
#: Analogstick: ab diesem Ausschlag gilt eine Richtung als gewollt
|
||
AXIS_DEADZONE = 0.6
|
||
|
||
#: Offene Joystick-Objekte. Sie müssen am Leben bleiben: gibt Python das
|
||
#: Objekt frei, schliesst pygame das Gerät – und SDL liefert danach **keine**
|
||
#: Joystick-Events mehr. Genau das war der Grund, weshalb ein erkannter
|
||
#: Controller im Spiel nichts bewirkte.
|
||
_OPEN_CONTROLLERS: list[pygame.joystick.Joystick] = []
|
||
|
||
|
||
def open_controllers() -> list[str]:
|
||
"""Öffnet alle angeschlossenen Controller und hält sie offen.
|
||
|
||
Nach ``pygame.init()`` aufrufen, und erneut, wenn ein Controller an- oder
|
||
abgesteckt wird (``JOYDEVICEADDED`` / ``JOYDEVICEREMOVED``).
|
||
"""
|
||
_OPEN_CONTROLLERS.clear()
|
||
names: list[str] = []
|
||
if not pygame.joystick.get_init():
|
||
try:
|
||
pygame.joystick.init()
|
||
except pygame.error:
|
||
return names
|
||
try:
|
||
count = pygame.joystick.get_count()
|
||
except pygame.error:
|
||
return names
|
||
for index in range(count):
|
||
try:
|
||
stick = pygame.joystick.Joystick(index)
|
||
stick.init()
|
||
except pygame.error:
|
||
continue
|
||
_OPEN_CONTROLLERS.append(stick)
|
||
names.append(stick.get_name())
|
||
return names
|
||
|
||
|
||
def opened_controllers() -> list[pygame.joystick.Joystick]:
|
||
"""Die aktuell offenen Controller (für Diagnose-Tools)."""
|
||
return list(_OPEN_CONTROLLERS)
|
||
|
||
|
||
def controller_names() -> list[str]:
|
||
return [stick.get_name() for stick in _OPEN_CONTROLLERS]
|
||
|
||
|
||
def is_device_event(event: pygame.event.Event) -> bool:
|
||
return event.type in (pygame.JOYDEVICEADDED, pygame.JOYDEVICEREMOVED)
|
||
|
||
|
||
def actions_from_event(event: pygame.event.Event) -> list[Action]:
|
||
"""Übersetzt ein pygame-Event in null bis eine Aktion."""
|
||
if event.type == pygame.KEYDOWN:
|
||
action = KEY_ACTIONS.get(event.key)
|
||
return [action] if action else []
|
||
|
||
if event.type == pygame.JOYBUTTONDOWN:
|
||
action = BUTTON_ACTIONS.get(event.button)
|
||
return [action] if action else []
|
||
|
||
if event.type == pygame.JOYHATMOTION:
|
||
action = HAT_ACTIONS.get(tuple(event.value))
|
||
return [action] if action else []
|
||
|
||
if event.type == pygame.JOYAXISMOTION:
|
||
if event.axis == 0 and abs(event.value) >= AXIS_DEADZONE:
|
||
return [Action.RIGHT if event.value > 0 else Action.LEFT]
|
||
if event.axis == 1 and abs(event.value) >= AXIS_DEADZONE:
|
||
return [Action.DOWN if event.value > 0 else Action.UP]
|
||
return []
|
||
|
||
|
||
def describe_controller_setup() -> str:
|
||
names = controller_names()
|
||
if not names:
|
||
return "Controller: keiner erkannt (Tastatur: Space=Snap, ←=Drehen, ↑=OnTop, Esc=Ende)"
|
||
return "Controller: " + ", ".join(names)
|
||
|
||
|
||
def controller_hint_lines() -> list[str]:
|
||
"""Kurzhilfe fürs Log beim Start."""
|
||
return [
|
||
describe_controller_setup(),
|
||
f"Zielauflösung {config.SCREEN_WIDTH}x{config.SCREEN_HEIGHT}, "
|
||
f"{config.FPS} fps",
|
||
]
|