Source code for core.input_map
"""Named input action mapping.
Decouples gameplay logic from specific keys/buttons so rebinding is trivial.
Usage::
from core.input_map import InputMap
import pygame
# Register actions (typically at startup or from config)
InputMap.register("jump", [pygame.K_SPACE, pygame.K_w])
InputMap.register("fire", [pygame.K_f])
# Query in scripts
if InputMap.is_pressed("jump"):
...
if InputMap.is_just_pressed("jump"):
...
Actions can also be loaded from ``project.config`` via
``InputMap.load_from_config(config_dict)`` where the config contains::
{
"input_actions": {
"jump": [32, 119],
"fire": [102]
}
}
"""
from __future__ import annotations
import pygame
[docs]
class InputMap:
"""Static action-to-key/button mapping registry."""
_actions: dict[str, list[int]] = {}
_prev_keys: dict[int, bool] = {}
_curr_keys: dict[int, bool] = {}
[docs]
@classmethod
def register(cls, action: str, keys: list[int]):
"""Register or overwrite an action with a list of key codes."""
cls._actions[action] = list(keys)
[docs]
@classmethod
def unregister(cls, action: str):
"""Remove an action."""
cls._actions.pop(action, None)
[docs]
@classmethod
def get_bindings(cls, action: str) -> list[int]:
"""Return the key codes bound to *action*, or an empty list."""
return list(cls._actions.get(action, []))
[docs]
@classmethod
def get_all_actions(cls) -> dict[str, list[int]]:
"""Return a copy of all registered actions."""
return {k: list(v) for k, v in cls._actions.items()}
[docs]
@classmethod
def clear(cls):
"""Remove all registered actions."""
cls._actions.clear()
cls._prev_keys.clear()
cls._curr_keys.clear()
# ------------------------------------------------------------------
# Config loading
# ------------------------------------------------------------------
[docs]
@classmethod
def load_from_config(cls, config: dict):
"""Load actions from a config dict containing an ``input_actions`` key.
Expected format::
{"input_actions": {"jump": [32, 119], "fire": [102]}}
"""
actions = config.get("input_actions")
if not isinstance(actions, dict):
return
for action_name, key_list in actions.items():
if isinstance(key_list, list):
cls._actions[str(action_name)] = [int(k) for k in key_list]
# ------------------------------------------------------------------
# Per-frame update (call once per frame BEFORE queries)
# ------------------------------------------------------------------
[docs]
@classmethod
def update(cls):
"""Snapshot current keyboard state for just-pressed / just-released detection.
Should be called once per frame after ``Input.update()``."""
cls._prev_keys = dict(cls._curr_keys)
try:
keys = pygame.key.get_pressed()
# Store only the keys we care about
all_keys: set[int] = set()
for bindings in cls._actions.values():
all_keys.update(bindings)
cls._curr_keys = {k: bool(keys[k]) for k in all_keys if k < len(keys)}
except Exception:
pass
# ------------------------------------------------------------------
# Queries
# ------------------------------------------------------------------
[docs]
@classmethod
def is_pressed(cls, action: str) -> bool:
"""Return True if any key bound to *action* is currently held down."""
bindings = cls._actions.get(action)
if not bindings:
return False
for key in bindings:
if cls._curr_keys.get(key, False):
return True
return False
[docs]
@classmethod
def is_just_pressed(cls, action: str) -> bool:
"""Return True if any key bound to *action* was pressed this frame."""
bindings = cls._actions.get(action)
if not bindings:
return False
for key in bindings:
if cls._curr_keys.get(key, False) and not cls._prev_keys.get(key, False):
return True
return False
[docs]
@classmethod
def is_just_released(cls, action: str) -> bool:
"""Return True if any key bound to *action* was released this frame."""
bindings = cls._actions.get(action)
if not bindings:
return False
for key in bindings:
if not cls._curr_keys.get(key, False) and cls._prev_keys.get(key, False):
return True
return False
[docs]
@classmethod
def get_action_strength(cls, action: str) -> float:
"""Return 1.0 if any key in *action* is pressed, else 0.0."""
return 1.0 if cls.is_pressed(action) else 0.0