Source code for nqp.core.input
from __future__ import annotations
import dataclasses
import logging
import time
from typing import Mapping, Tuple, TYPE_CHECKING
import pygame
from pygame.locals import *
from .constants import GamepadAxes, GamepadButton
from .debug import Timer
if TYPE_CHECKING:
from nqp.core.game import Game
__all__ = ["Input"]
[docs]@dataclasses.dataclass
class ControllerConfig:
deadzone: float
axes: Mapping[int, GamepadAxes]
hat: Mapping[int, Tuple[GamepadButton, GamepadButton]]
buttons: Mapping[int, GamepadButton]
# specific to xbox gamepad layout, should not be changed by player
# add more configs for other gamepad types: ps3,4,5, switch, etc
xbox_gamepad_config = ControllerConfig(
deadzone=0.25,
axes={
0: GamepadAxes.LEFT_X,
1: GamepadAxes.LEFT_Y,
# 2: None, # l. shoulder
3: GamepadAxes.RIGHT_X,
4: GamepadAxes.RIGHT_Y,
# 5: None, # r. shoulder
},
hat={
0: (GamepadButton.LEFT, GamepadButton.RIGHT),
1: (GamepadButton.DOWN, GamepadButton.UP),
},
buttons={
0: GamepadButton.SOUTH,
1: GamepadButton.EAST,
2: GamepadButton.WEST,
3: GamepadButton.NORTH,
4: GamepadButton.LEFT_SHOULDER_1,
5: GamepadButton.RIGHT_SHOULDER_1,
6: GamepadButton.SELECT_OR_BACK,
7: GamepadButton.START,
8: GamepadButton.LEFT_STICK,
9: GamepadButton.RIGHT_STICK,
},
)
# generic mapping for gamepad buttons to game input labels
# if the player wants to change config, this would need to be changed
gamepad_label_map = {
GamepadButton.UP: "up",
GamepadButton.LEFT: "left",
GamepadButton.DOWN: "down",
GamepadButton.RIGHT: "right",
GamepadButton.EAST: "cancel",
GamepadButton.SOUTH: "select",
GamepadButton.NORTH: "view_troupe",
}
# map analog axis to digital buttons
gamepad_axis_direction_map = {
GamepadAxes.LEFT_X: [GamepadButton.LEFT, GamepadButton.RIGHT],
GamepadAxes.LEFT_Y: [GamepadButton.UP, GamepadButton.DOWN],
}
[docs]class Input:
[docs] def __init__(self, game: Game):
with Timer("Input: initialised"):
self._game: Game = game
self.states = {
"right": False,
"left": False,
"up": False,
"down": False,
"hold_right": False,
"hold_left": False,
"hold_up": False,
"hold_down": False,
"select": False,
"cancel": False,
"view_troupe": False,
"shift": False,
"ctrl": False,
"backspace": False,
"toggle_dev_console": False,
"typing_enter": False,
"tab": False,
"speed_slow": False,
"speed_normal": False,
"speed_fast": False,
"speed_fastest": False,
}
self.mouse_state = {"left": False}
self.mode = "default"
self.char_buffer = ""
self.backspace_hold = 0
self.mouse_moved = False
self.mouse_pos: pygame.Vector2 = pygame.Vector2(0, 0)
self.mouse_pos_raw: pygame.Vector2 = self.mouse_pos
self._old_mouse: pygame.Vector2 = self.mouse_pos_raw
self._gamepads = dict()
self._scan_gamepads()
[docs] def soft_reset(self):
"""
Resets inputs that should only be activated for 1 frame.
"""
self.states["select"] = False
self.states["cancel"] = False
self.mouse_state["left"] = False
[docs] def reset(self):
"""
Set all input to false.
"""
for key in self.states.keys():
self.states[key] = False
[docs] def update(self, delta_time: float):
self.soft_reset()
self.mouse_pos = pygame.Vector2(pygame.mouse.get_pos())
self.mouse_pos_raw = self.mouse_pos
self.mouse_pos[0] *= self._game.window.base_resolution[0] / self._game.window.scaled_resolution[0]
self.mouse_pos[1] *= self._game.window.base_resolution[1] / self._game.window.scaled_resolution[1]
self.mouse_moved = self.mouse_pos_raw != self._old_mouse
self._old_mouse = self.mouse_pos_raw
chars = [
".",
"-",
"_",
"a",
"b",
"c",
"d",
"e",
"f",
"g",
"h",
"i",
"j",
"k",
"l",
"m",
"n",
"o",
"p",
"q",
"r",
"s",
"t",
"u",
"v",
"w",
"x",
"y",
"z",
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
]
if self.mode == "typing":
if self.states["backspace"]:
self.backspace_hold += self._game.window.delta_time
if self.backspace_hold > 0.7:
self.backspace_hold -= 0.035
self.char_buffer.append("backspace")
else:
self.backspace_hold = 0
for event in pygame.event.get():
if event.type == QUIT:
self._game.quit()
if event.type == MOUSEBUTTONDOWN:
if event.button == 1:
self.mouse_state["left"] = True
if event.type == KEYDOWN:
if event.key == K_ESCAPE:
self._game.quit()
if event.key in [K_LSHIFT, K_RSHIFT]:
self.states["shift"] = True
if event.key in [K_LCTRL, K_RCTRL]:
self.states["ctrl"] = True
if event.key == K_BACKSPACE:
self.states["backspace"] = True
if event.key == K_BACKQUOTE:
self.states["toggle_dev_console"] = True
if event.key == K_TAB:
self.states["tab"] = True
if self.mode == "typing":
if event.key == K_RETURN:
self.states["typing_enter"] = True
if event.type == KEYUP:
if event.key in [K_LSHIFT, K_RSHIFT]:
self.states["shift"] = False
if event.key == K_BACKSPACE:
self.states["backspace"] = False
# input outside of typing
if self.mode == "default":
if event.type == KEYDOWN:
if event.key == K_x:
self.states["cancel"] = True
if event.key == K_RIGHT:
self.states["right"] = True
self.states["hold_right"] = True
if event.key == K_LEFT:
self.states["left"] = True
self.states["hold_left"] = True
if event.key == K_UP:
self.states["up"] = True
self.states["hold_up"] = True
if event.key == K_DOWN:
self.states["down"] = True
self.states["hold_down"] = True
if event.key == K_RETURN:
self.states["select"] = True
if event.key == K_v:
self.states["view_troupe"] = True
if event.key == K_F1:
self.states["speed_slow"] = True
if event.key == K_F2:
self.states["speed_normal"] = True
if event.key == K_F3:
self.states["speed_fast"] = True
if event.key == K_F4:
self.states["speed_fastest"] = True
if event.type == KEYUP:
if event.key == K_RIGHT:
self.states["hold_right"] = False
if event.key == K_LEFT:
self.states["hold_left"] = False
if event.key == K_UP:
self.states["hold_up"] = False
if event.key == K_DOWN:
self.states["hold_down"] = False
if event.type == JOYBUTTONDOWN:
button = xbox_gamepad_config.buttons[event.button]
label = gamepad_label_map.get(button)
if label:
self.states[label] = True
self.states["hold_" + label] = True
if event.type == JOYBUTTONUP:
button = xbox_gamepad_config.buttons[event.button]
label = gamepad_label_map.get(button)
if label:
self.states["hold_" + label] = False
if event.type == JOYHATMOTION:
for axis, value in enumerate(event.value):
endstops = xbox_gamepad_config.hat[axis]
button = None
if value == -1:
button = endstops[0]
elif value == 1:
button = endstops[1]
if button:
label = gamepad_label_map[button]
self.states[label] = True
self.states["hold_" + label] = True
else:
for button in endstops:
label = gamepad_label_map[button]
self.states["hold_" + label] = False
if event.type == JOYAXISMOTION:
axis = xbox_gamepad_config.axes[event.axis]
endstops = gamepad_axis_direction_map.get(axis)
if endstops is not None:
if abs(event.value) >= xbox_gamepad_config.deadzone:
button = None
if event.value < 0:
button = endstops[0]
elif event.value > 0:
button = endstops[1]
if button:
label = gamepad_label_map[button]
hold_label = "hold_" + label
if not self.states[hold_label]:
self.states[label] = True
self.states[hold_label] = True
else:
for button in endstops:
label = gamepad_label_map[button]
self.states["hold_" + label] = False
elif self.mode == "typing":
if event.type == KEYDOWN:
for char in chars:
if event.key == ord(char):
if self.states["shift"]:
char = char.upper()
if char == "-":
char = "_"
self.char_buffer.append(char)
if event.key == K_BACKSPACE:
self.char_buffer.append("backspace")
if event.key == K_SPACE:
self.char_buffer.append(" ")
def _scan_gamepads(self):
for index in range(pygame.joystick.get_count()):
gamepad = pygame.joystick.Joystick(index)
gamepad.init()
# if the Joystick object is deleted, then event loop
# will not have joystick events, so we need to keep a ref.
self._gamepads[gamepad.get_guid()] = gamepad