Input System
Limon’s input system is built around named actions: string names hashed to uint64_t at compile time (or load time for XML-defined actions). Plugins query by hash, never by SDL keycode — this keeps game code independent of which physical key is bound to an action, and lets bindings be changed in Engine/inputBindings.xml without recompiling anything.
Inputs are split into two kinds:
Digital —
boolstate (pressed/released) and a one-frame event (state changed this frame).Analog —
floatvalue accumulated each frame (mouse delta, gamepad stick position).
Mouse motion and gamepad sticks both feed the same analog store, so the same plugin code handles either input device.
Built-in Action Names
These constants are defined in src/limonAPI/InputStates.h as constexpr uint64_t in the InputActions namespace. They match the names in Engine/inputBindings.xml.
Name |
Kind |
Description |
|---|---|---|
|
Digital |
Exit the application (Escape key). |
|
Digital |
Move forward (W key, gamepad left stick up). |
|
Digital |
Move backward (S key, gamepad left stick down). |
|
Digital |
Strafe left (A key, gamepad left stick left). |
|
Digital |
Strafe right (D key, gamepad left stick right). |
|
Digital |
Jump (Space, gamepad A button). |
|
Digital |
Run modifier (Shift, gamepad LB). |
|
Digital |
Left mouse button or gamepad right trigger. |
|
Digital |
Middle mouse button. |
|
Digital |
Right mouse button or gamepad RB. |
|
Digital |
Set for one frame whenever the mouse moves. Engine-internal — the engine sets this from |
|
Digital |
Scroll wheel up. |
|
Digital |
Scroll wheel down. |
|
Digital |
Shift key held (left or right). |
|
Digital |
Ctrl key held (left or right). |
|
Digital |
Alt key held. |
|
Digital |
Super/GUI key held. |
|
Digital |
Set for one frame when SDL delivers a text input event. Engine-internal — set automatically from |
|
Digital |
|
|
Digital |
|
|
Digital |
F4 key. |
|
Digital |
F5 key. |
|
Digital |
Toggle debug mode ( |
|
Digital |
Toggle in-game editor (F2 key). |
|
Analog |
Horizontal look delta this frame (mouse X + gamepad right stick X). |
|
Analog |
Vertical look delta this frame (mouse Y + gamepad right stick Y). |
Reading Input in Plugins
All extension types receive a const InputStates & from the engine each frame (as inputState in C++, input_states in Python). Query it with the action name hash:
C++ — use the compile-time HASH() macro or the InputActions:: constants:
// Check whether a key is currently held
if (inputState.getInputStatus(InputActions::JUMP)) { ... }
// Check for a state change this frame (e.g. fire on first press only)
if (inputState.getInputEvents(InputActions::MOUSE_BUTTON_LEFT)
&& inputState.getInputStatus(InputActions::MOUSE_BUTTON_LEFT)) { ... }
// Read the analog look delta this frame
float lookX = inputState.getAnalogValue(InputActions::LOOK_X);
float lookY = inputState.getAnalogValue(InputActions::LOOK_Y);
// Query a game-specific action that exists only in XML
if (inputState.getInputStatus(HASH("RELOAD"))) { ... }
Python — use limon.InputActions.* for built-in names; limon.hash("RELOAD") for XML-only actions:
if input_states.get_input_status(limon.InputActions.JUMP):
...
if (input_states.get_input_events(limon.InputActions.MOUSE_BUTTON_LEFT)
and input_states.get_input_status(limon.InputActions.MOUSE_BUTTON_LEFT)):
...
look_x = input_states.get_analog_value(limon.InputActions.LOOK_X)
Method (C++ / Python) |
Description |
|---|---|
|
Returns |
|
Returns |
|
Returns the accumulated float value this frame. Zero if no input this frame. |
|
Returns |
|
Fills four floats: absolute cursor position ( |
|
Returns the UTF-8 string typed this frame (non-empty only when |
Note
getInputStatus, getInputEvents, and getAnalogValue return a default value (false / 0.0f) for unknown hashes and never throw.
Detecting Active Input Device
The engine tracks which device the player last used. You can read it to switch UI prompts or control schemes at runtime:
if (inputState.getActiveDevice() == InputStates::ActiveDevice::GAMEPAD) {
showButtonPrompt("[A] to jump");
} else {
showButtonPrompt("[Space] to jump");
}
if input_states.get_active_device() == limon.ActiveDevice.GAMEPAD:
show_prompt("[A] to jump")
else:
show_prompt("[Space] to jump")
The device switches as soon as any input above the dead zone arrives from that device. Your code can add debounce logic (e.g. require the new device to be active for N frames) if you want to avoid flickering prompts when a connected-but-idle gamepad is present.
Simulating and Mutating Input (Python)
The Python API exposes additional InputStates methods that let scripts construct or inject synthetic input. These are primarily used with limon.simulate_input(input_states) to drive automated tests or replay sequences:
states = limon.InputStates()
limon.set_mouse_change(states, xPos=0.5, yPos=0.5, xChange=0.01, yChange=0.0)
limon.set_text_input(states, "a")
states.reset_all_input_events() # clear all digital events (not analog, not active device)
limon.simulate_input(states)
Method |
Description |
|---|---|
|
Writes absolute and relative mouse values into an |
|
Sets the typed text string on an |
|
Clears all one-frame digital events. Called by the engine each tick; call manually when constructing synthetic states. |
|
Injects the constructed |
Note
These mutation methods are Python-only. In C++ plugins the InputStates received in processInput is const — it cannot be written to.
Custom Game Actions
To add a game-specific action (such as RELOAD), add it to Engine/inputBindings.xml with the bindings you want. No engine recompile is needed.
<Action name="RELOAD">
<Binding source="keyboard" key="R"/>
<Binding source="gamepad_button" button="X"/>
</Action>
In your plugin, query it with the same name:
if (inputState.getInputEvents(HASH("RELOAD"))
&& inputState.getInputStatus(HASH("RELOAD"))) {
startReloadAnimation();
}
reload_hash = limon.hash("RELOAD")
if input_states.get_input_events(reload_hash) and input_states.get_input_status(reload_hash):
start_reload_animation()
Note
HASH("RELOAD") in C++ and limon.hash("RELOAD") in Python both produce the same uint64_t. The XML parser uses the same runtime hash function, so the integer is identical at load time.
inputBindings.xml Format
Engine/inputBindings.xml is loaded once at startup. If it is absent or unparseable the engine falls back to built-in defaults (identical to the shipped XML). Changes take effect on the next launch.
Root element: <InputBindings version="1">.
Each <Action> element names one logical action:
<Action name="ACTION_NAME" type="digital|analog">
<Binding source="..." .../>
...
</Action>
type defaults to digital and may be omitted for digital actions. type="analog" is required only for actions whose bindings carry a scale attribute (it has no effect on the behaviour — it serves as documentation).
Binding Sources
source=”keyboard”
<Binding source="keyboard" key="W"/>
<Binding source="keyboard" key="Left Shift"/>
<!-- Optional: keyboard key that adds a fixed float to an analog action each frame it is held -->
<Binding source="keyboard" key="D" analog_value="1.0"/>
Key names follow SDL naming (case-insensitive). Common names:
Letters |
|
Digits |
|
Special |
|
Modifiers |
|
Function |
|
Numpad |
|
source=”mouse_button”
<Binding source="mouse_button" button="LEFT"/>
<Binding source="mouse_button" button="X1"/> <!-- side button 4 -->
<Binding source="mouse_button" button="7"/> <!-- numeric fallback for 6+ button mice -->
Button names: LEFT, MIDDLE, RIGHT, X1 (button 4), X2 (button 5). For mice with more buttons use a numeric index ("6", "7", …).
source=”mouse_axis”
Wires normalised mouse motion to an analog action. Scale is automatic (xrel / screenWidth*0.5).
<Binding source="mouse_axis" axis="X"/> <!-- horizontal delta -->
<Binding source="mouse_axis" axis="Y"/> <!-- vertical delta -->
source=”mouse_wheel”
<Binding source="mouse_wheel" axis="UP"/>
<Binding source="mouse_wheel" axis="DOWN"/>
<Binding source="mouse_wheel" axis="LEFT"/> <!-- horizontal scroll -->
<Binding source="mouse_wheel" axis="RIGHT"/>
source=”gamepad_button”
<Binding source="gamepad_button" button="A"/>
<Binding source="gamepad_button" button="L3"/>
Button names:
Face buttons |
|
Shoulders |
|
Stick clicks |
|
System buttons |
|
D-pad |
|
Extra / elite |
|
source=”gamepad_axis”
A gamepad axis can appear in a digital binding (with threshold) and an analog binding (with scale) simultaneously, even in different actions.
<!-- Digital: fires MOVE_FORWARD when left stick is pushed up past 30 % -->
<Binding source="gamepad_axis" axis="LEFT_Y" threshold="-0.3"/>
<!-- Analog: accumulates scaled stick value into LOOK_X each frame -->
<Binding source="gamepad_axis" axis="RIGHT_X" scale="0.0104"/>
threshold: positive triggers when value ≥ threshold; negative triggers when value ≤ threshold. scale: per-frame multiplier applied to the normalised axis value [-1 .. 1].
Axis names: LEFT_X, LEFT_Y, RIGHT_X, RIGHT_Y, LEFT_TRIGGER, RIGHT_TRIGGER.
source=”joystick_button”
For buttons outside SDL’s GameController mapping — proprietary or extra buttons on flight sticks, racing wheels, etc.
<Binding source="joystick_button" index="8"/>
index is the raw SDL joystick button index (0-based). For standard gamepad buttons that SDL already maps (A, B, X, Y, …) prefer source="gamepad_button" instead to avoid double-firing.
Look Speed and Analog Alignment
Mouse and gamepad stick both contribute to LOOK_X/LOOK_Y through the same analog store and both pass through player_lookAroundSpeed in Engine/Options.xml. The analog values are expressed in the same unit (mouse pixel displacement normalised by screenWidth / 2), so a single player_lookAroundSpeed tuning applies to both devices equally.
The default gamepad look scale in inputBindings.xml is 0.0104, computed as 10.0 / (1920 / 2). For other screen resolutions adjust it proportionally: scale = 10.0 / (screenWidth / 2).
The gamepad dead zone is configurable via the gamepad_deadZone option (default 0.1). Values below this threshold on any analog axis are ignored.
See also
Engine Options Reference for player_lookAroundSpeed and gamepad_deadZone.
Complete Input Reference
Keyboard Keys (source="keyboard" key="…")
Key names are case-insensitive. Use them verbatim in the key attribute.
Letters
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
Digits and function keys
Digits |
|
Function |
|
Special and navigation keys
Key name |
Description |
|---|---|
|
Spacebar |
|
Escape |
|
Enter / Return |
|
Tab |
|
Backspace |
|
Delete |
|
Insert |
|
Home |
|
End |
|
Page Up |
|
Page Down |
|
Arrow Up |
|
Arrow Down |
|
Arrow Left |
|
Arrow Right |
|
Caps Lock |
|
Num Lock |
|
Scroll Lock |
|
Pause / Break |
|
Print Screen |
Modifier keys
Key name |
Description |
|---|---|
|
Left Shift |
|
Right Shift |
|
Left Control |
|
Right Control |
|
Left Alt |
|
Right Alt |
|
Left Super / Windows / Command key |
|
Right Super / Windows / Command key |
Numpad
Key name |
Description |
|---|---|
|
Numpad digit keys |
|
Numpad |
|
Numpad |
|
Numpad |
|
Numpad |
|
Numpad |
|
Numpad Enter |
Punctuation and symbols
Key name |
Description |
|---|---|
|
Minus / Hyphen |
|
Equals |
|
Left bracket |
|
Right bracket |
|
Backslash |
|
Semicolon |
|
Apostrophe |
`` ` `` |
Grave / Backtick |
|
Comma |
|
Period |
|
Slash |
Note
The complete SDL key name list is documented at https://wiki.libsdl.org/SDL3/SDL_Keycode.
SDL accepts key names case-insensitively, so "space", "Space", and "SPACE" are all valid.