6.1 KiB
Input System
Source: src/dusk/input/, platform layers in src/dusk<platform>/input/
Architecture
The input system has two layers:
- Action layer (
inputaction_t) -- named gameplay inputs, e.g. UP, DOWN, ACCEPT, CANCEL. This is what game code reads. - Button layer (
inputbutton_t) -- physical hardware inputs, e.g. keyboard key, gamepad button, analog axis, mouse axis. Multiple buttons can bind to the same action (the highest value wins).
The platform layer implements two hooks:
inputUpdatePlatform()-- read hardware state once per frameinputButtonGetValuePlatform()-- return the analog value [0.0, 1.0] for a given button
Defined actions
Actions are defined in src/dusk/input/input.csv and code-generated
into the inputaction_t enum. Current values:
| Constant | Meaning |
|---|---|
INPUT_ACTION_NULL |
Invalid / sentinel (0) |
INPUT_ACTION_UP |
Up direction |
INPUT_ACTION_DOWN |
Down direction |
INPUT_ACTION_LEFT |
Left direction |
INPUT_ACTION_RIGHT |
Right direction |
INPUT_ACTION_ACCEPT |
Confirm / primary action |
INPUT_ACTION_CANCEL |
Back / secondary action |
INPUT_ACTION_RAGEQUIT |
Quit the application |
INPUT_ACTION_CONSOLE |
Toggle debug console |
INPUT_ACTION_POINTERX |
Mouse / pointer X axis |
INPUT_ACTION_POINTERY |
Mouse / pointer Y axis |
INPUT_ACTION_COUNT |
Total count (not a valid action) |
Global state
extern input_t INPUT;
// INPUT.actions[INPUT_ACTION_COUNT] -- all action states
// INPUT.platform -- platform-specific data
Reading actions (game code)
// Analog value this frame (0.0 - 1.0)
float_t inputGetCurrentValue(inputaction_t action);
// Analog value last frame
float_t inputGetLastValue(inputaction_t action);
// Boolean helpers (built on current/last values)
bool_t inputIsDown(inputaction_t action);
bool_t inputWasDown(inputaction_t action);
bool_t inputPressed(inputaction_t action); // was up, now down
bool_t inputReleased(inputaction_t action); // was down, now up
// Single axis from a neg + pos pair of actions (returns [-1, 1]):
float_t inputAxis(inputaction_t neg, inputaction_t pos);
// 2D axis from four actions (negX/posX/negY/posY):
void inputAxis2D(
inputaction_t negX, inputaction_t posX,
inputaction_t negY, inputaction_t posY,
vec2 result
);
// Same four-action axis, normalized to a unit vector via atan2:
void inputAngle2D(
inputaction_t negX, inputaction_t posX,
inputaction_t negY, inputaction_t posY,
vec2 result
);
// Deadzone filter (applied to raw axis values)
float_t inputDeadzone(float_t value, float_t deadzone);
Binding buttons to actions
void inputBind(inputaction_t action, inputbutton_t button);
Each platform's init function calls inputBind to wire its hardware
buttons to the standard action IDs. Game code should never need to call
inputBind -- it is set up once during platform init.
Button types
INPUT_BUTTON_TYPE_KEYBOARD // SDL scancode (SDL2 targets only)
INPUT_BUTTON_TYPE_POINTER // Mouse axes: X, Y, Z, WHEEL_X, WHEEL_Y
INPUT_BUTTON_TYPE_TOUCH // Touch (defined, not fully implemented)
INPUT_BUTTON_TYPE_GAMEPAD // Digital gamepad buttons
INPUT_BUTTON_TYPE_GAMEPAD_AXIS // Analog axes (-1.0 to 1.0 internally)
Events
Each action has onPressed and onReleased event callbacks. Subscribe
via the event system (see .claude/events.md):
eventSubscribe(&INPUT.actions[ACTION_ACCEPT].onPressed, myCallback, NULL);
Platform implementations
SDL2 (src/dusksdl2/input/)
Handles Linux, Knulli, and PSP (PSP adds its own button mapping layer on top of SDL2).
- Keyboard: SDL scancode array from
SDL_GetKeyboardState() - Pointer: normalized mouse position (0.0-1.0), scroll axes
- Gamepad: first available
SDL_GameController; axis values normalized to [-1.0, 1.0] with deadzone (default 0.2f viainputGetDeadzoneSDL2)
Dolphin -- GameCube / Wii (src/duskdolphin/input/)
Uses libogc PAD API. No keyboard or pointer input -- trying to use
those button types is a compile-time #error.
- Gamepad:
PAD_ScanPads()+PAD_ButtonsHeld()for pad 0 - Axes: left stick X/Y, C-stick X/Y, L/R triggers (6 total)
- Deadzone: hardcoded 0.2f
- Default bindings set at init: D-pad/L-stick = directional actions, A = ACCEPT, B = CANCEL, X = CONSOLE, Start = RAGEQUIT
PSP (src/duskpsp/input/)
Layered on top of SDL2. inputInitPSP() remaps SDL2 controller button
constants to PSP button names, then calls inputBind to wire them:
| PSP button | SDL2 constant |
|---|---|
| Cross | SDL_CONTROLLER_BUTTON_A |
| Circle | SDL_CONTROLLER_BUTTON_B |
| Triangle | SDL_CONTROLLER_BUTTON_Y |
| Square | SDL_CONTROLLER_BUTTON_X |
| L / R | SDL_CONTROLLER_BUTTON_LEFTSHOULDER / RIGHTSHOULDER |
| L-Stick | SDL_CONTROLLER_AXIS_LEFTX/Y |
Vita (src/duskvita/input/)
Layered on top of SDL2 (via vitaSDL2). Behaviour is similar to PSP -- no keyboard, no pointer, gamepad only.
JS module (Input)
The input system is exposed to JS as the global Input object with
static methods. Action constants are pre-defined as numeric properties
on the Input object (e.g. Input.ACCEPT, Input.UP):
// Check if the accept button is held this frame:
if(Input.isDown(Input.ACCEPT)) { ... }
// Was the cancel button just pressed?
if(Input.pressed(Input.CANCEL)) { ... }
// Analog value for the right trigger:
var val = Input.getValue(Input.RIGHT);
// Single axis (-1 to 1) from a neg/pos pair:
var h = Input.axis(Input.LEFT, Input.RIGHT);
All Input.* action constants match the INPUT_ACTION_* enum values
from the C layer (UP, DOWN, LEFT, RIGHT, ACCEPT, CANCEL, RAGEQUIT,
CONSOLE, POINTERX, POINTERY).
Platform capability notes
| Feature | Linux/Knulli | PSP | Vita | GameCube/Wii |
|---|---|---|---|---|
| Keyboard | Yes (SDL2) | No | No | No |
| Pointer/Mouse | Yes (SDL2) | No | No | No |
| Gamepad | Yes (SDL2) | Yes (SDL2) | Yes (SDL2) | Yes (PAD) |
| Analog axes | Yes | L-Stick only | L-Stick, R-Stick | L-Stick, C-Stick, Triggers |
| Touch | Defined, not implemented | -- | -- | -- |