Input
Static methods for reading mouse, keyboard, modifier keys, and gamepad state. All methods are static.
Input.mouseX() / Input.mouseY()
Section titled “Input.mouseX() / Input.mouseY()”Returns: Num — Current mouse X or Y position, in window coordinates, exactly as SDL reports them.
var x = Input.mouseX()var y = Input.mouseY()Gui.label("Mouse: %(x), %(y)")Input.mouseLeft() / Input.mouseRight() / Input.mouseMiddle()
Section titled “Input.mouseLeft() / Input.mouseRight() / Input.mouseMiddle()”Returns: Bool — true if the left, right, or middle button is currently held.
if (Input.mouseLeft()) { // drag or hold logic}Input.mouseJustPressedLeft() / Input.mouseJustPressedRight()
Section titled “Input.mouseJustPressedLeft() / Input.mouseJustPressedRight()”Returns: Bool — true if the button was just pressed this frame (edge-triggered, so only true for one frame per press).
if (Input.mouseJustPressedLeft()) { tryStartDrag()}Input.mouseWheelX() / Input.mouseWheelY()
Section titled “Input.mouseWheelX() / Input.mouseWheelY()”Returns: Num — Scroll delta for this frame (horizontal and vertical wheel). Zero when not scrolling.
Mouse capture & cursor
Section titled “Mouse capture & cursor”For first-person and orbit cameras you usually want to capture the mouse: hide the cursor, lock it to the window center, and read motion as per-frame deltas — so looking around never lets the cursor drift off the window or stall at its edge. Provide your own key to release it (for menus, GUI, or quitting).
Input.setRelativeMouse(v) / Input.relativeMouse()
Section titled “Input.setRelativeMouse(v) / Input.relativeMouse()”Enter (true) or leave (false) relative mode: the cursor is hidden and pinned to the window center,
and mouse motion is delivered as deltas via Input.mouseDeltaX/Y. The built-in cameras (Cam.firstPerson,
Cam.freeFly, orbit) automatically use those deltas, so they look correctly the instant relative mode is on.
Input.relativeMouse() returns the current desired state.
The engine also auto-releases capture when the window loses focus (alt-tab / cmd-tab) and restores it on return, so the cursor is never trapped.
Input.setCursorVisible(v) / Input.cursorVisible()
Section titled “Input.setCursorVisible(v) / Input.cursorVisible()”Show (true) or hide (false) the OS cursor. Relative mode hides the cursor itself, so this only has a
visible effect when relativeMouse() is false.
Input.mouseDeltaX() / Input.mouseDeltaY()
Section titled “Input.mouseDeltaX() / Input.mouseDeltaY()”Returns: Num — Relative mouse motion this frame (pixels). Works in both normal and relative mode; use
it to drive a custom camera’s look so it never stalls when the cursor reaches the window edge.
class Game { init() { Input.setRelativeMouse(true) } // capture on start
update(dt) { // free the cursor for a menu / to quit if (Input.keyJustPressed("escape")) { Input.setRelativeMouse(false) Input.setCursorVisible(true) } }}Keyboard
Section titled “Keyboard”Input.key(name)
Section titled “Input.key(name)”Returns: Bool — true if the key with the given name is currently held.
Parameters:
name(String) — SDL scancode name, matched case-insensitively (e.g."a","space","escape","Left","Right"). Keys are read by scancode, so they follow physical key position, not the user’s keyboard layout —"w"is the same physical key on QWERTY and AZERTY.
if (Input.key("a")) position = position - speed * dtif (Input.key("d")) position = position + speed * dtInput.keyJustPressed(name)
Section titled “Input.keyJustPressed(name)”Returns: Bool — true if the key was just pressed this frame (edge-triggered).
Parameters:
name(String) — SDL scancode name, as forInput.key.
if (Input.keyJustPressed("space")) { Audio.play(_jumpSound)}Input.modShift() / Input.modCtrl() / Input.modAlt()
Section titled “Input.modShift() / Input.modCtrl() / Input.modAlt()”Returns: Bool — true if the Shift, Ctrl, or Alt modifier is held.
if (Input.keyJustPressed("s") && Input.modCtrl()) saveGame()Gamepad
Section titled “Gamepad”Up to 4 gamepads are tracked. Buttons and axes are addressed by numeric index only — see the tables below.
Input.gamepadCount()
Section titled “Input.gamepadCount()”Returns: Num — One past the highest occupied gamepad slot — not the number of gamepads currently connected.
Input.gamepadButton(player, button)
Section titled “Input.gamepadButton(player, button)”Returns: Bool — Whether the given button is held. Returns false if player or button is out of range, or if no gamepad occupies that slot.
Parameters:
player(Num) — Gamepad slot index,0–3.button(Num) — Button index, 0-based. Numeric only — there is no name lookup.
Button indices are SDL3’s gamepad button order, which is positional (South is the bottom face button — A on Xbox, Cross on PlayStation, B on a Nintendo layout):
| Index | Button | Index | Button |
|---|---|---|---|
| 0 | South (Xbox A) | 8 | Right stick click |
| 1 | East (Xbox B) | 9 | Left shoulder |
| 2 | West (Xbox X) | 10 | Right shoulder |
| 3 | North (Xbox Y) | 11 | D-pad up |
| 4 | Back | 12 | D-pad down |
| 5 | Guide | 13 | D-pad left |
| 6 | Start | 14 | D-pad right |
| 7 | Left stick click | 15+ | Misc / paddles / touchpad |
// Slot 0, button 0 = bottom face button (Xbox A / PS Cross).if (Input.gamepadButton(0, 0)) jump()Input.gamepadAxis(player, axis)
Section titled “Input.gamepadAxis(player, axis)”Returns: Num — Axis value, normalized. Sticks read -1 to 1; triggers read 0 to 1. Returns 0 if player or axis is out of range, or if no gamepad occupies that slot.
Parameters:
player(Num) — Gamepad slot index,0–3.axis(Num) — Axis index, 0-based. Numeric only — there is no name lookup.
| Index | Axis | Range |
|---|---|---|
| 0 | Left stick X | -1 → 1 |
| 1 | Left stick Y | -1 → 1 |
| 2 | Right stick X | -1 → 1 |
| 3 | Right stick Y | -1 → 1 |
| 4 | Left trigger | 0 → 1 |
| 5 | Right trigger | 0 → 1 |
// Left stick on slot 0, with a hand-rolled dead zone.var moveX = Input.gamepadAxis(0, 0)var moveY = Input.gamepadAxis(0, 1)if (moveX.abs < 0.25) moveX = 0if (moveY.abs < 0.25) moveY = 0