Log inGet started

Input

Input in Zero is built around named actions, not raw key polling. Rather than scattering is the W key down? checks through your code, you bind physical inputs (keys, mouse, gamepad, gestures) to…

The input library

local Zin = require("@builtin::modules.zinput")

zinput is a full input system: action bindings, analog axes, gamepad and pointer support, chords, gestures, contexts, and rebinding. Because the surface is broad, discover it rather than guessing — asset.inspect("@builtin::modules.zinput") and its submodules (@builtin::modules.zinput.actions, .axes, .map, .touch, .gestures, .surface, .bindings, .pointer, …) show what each piece does, and lsp resolves their exports. Read the library; it's the source of truth for the call shapes.

Reading input

local move = Zin.axes.value("move")   -- {x, y} movement vector
local look = Zin.axes.value("look")   -- {x, y} look delta, gated

if Zin.actions.pressed("jump") then jump() end
if Zin.actions.held("sprint") then speed = runSpeed end

No setup is required: the first action or axis read activates the default control scheme and starts the input tick. Code written against actions and axes plays identically on keyboard, touch, and gamepad — on touch surfaces the virtual movement stick, action buttons, and drag-look zone appear on screen automatically, with no opt-in from the world.

Per-frame input handling belongs in a long-lived loop or a component (the scripting-and-tasks and components guides), where you read the current action/axis state each frame.

Default control scheme

The default scheme's action and axis names are canonical — every builtin controller and demo reads these. vehicle_* entries are active only while the "vehicle" input context is pushed; everything else is active by default.

NameKindDefault kbm bindingTouch behavior
move_forwardactionWnone — move axis carries movement on touch
move_backactionSnone
move_leftactionAnone
move_rightactionDnone
move_upactionSpace, Enone
move_downactionCtrl, Qnone
sprintactionShiftbutton, lower-right
crouchactionCtrlbutton, lower-right
jumpactionSpacebutton, lower-right
pointer_lock_toggleactionEscapenone
look_dragactionright mouse buttonnone
fly_upactionE, Spacenone
fly_downactionQ, Shiftnone
vehicle_throttleactionW, none
vehicle_reverseactionS, none
vehicle_brakeactionSpacebutton, lower-right
moveaxis (vector)WASDleft virtual stick
move_verticalaxis (scalar)Space / Ctrlnone
lookaxis (vector)mouse delta, gated on pointer-lock or held right mouse buttonright drag zone, gated on drag activity
look_xaxis (scalar)mouse delta x, same gate as looknone
look_yaxis (scalar)mouse delta y, same gate as looknone
scroll_yaxis (scalar)mouse wheeltwo-finger pinch
vehicle_steeraxis (scalar)D / Aleft virtual stick

Custom schemes

A custom scheme is authored as an .inputMap asset: explicit per-device-class bindings (kbm, touch, gamepad) for each action and axis, optionally extending another map. Zin.map.bake(name) writes the current live scheme out as a new .inputMap instance — a convenient starting point to hand-edit rather than writing bindings from scratch. Activate a scheme with Zin.map.activate(record), or Zin.map.ensureActive() to fall back to the default when nothing else is active.

Touch-button layout opts

B.touchButton(opts) takes three options beyond zone/label/icon that shape how the touch overlay lays a button out:

OptTypeDefaultEffect
prioritynumber50Lower renders first / more prominently. Zin.map's synthesis (a boolean kbm action with no explicit touch class) assigns 100, so an authored button always outranks a synthesized one at the same context.
size"small" | "medium" | "large""medium"Circle radius: 40 / 56 / 72px.
groupstring?noneButtons sharing a group render adjacently — the whole group is positioned by its lowest-priority member.

Design your touch layout

The touch overlay budgets buttons into a two-column band along the right edge; a scheme with more than four touch buttons drops every button one size step and, past the two-column budget, collapses the overflow behind a fan button that opens a grid sheet on tap. A scheme with a handful of actions never notices this — it's built for schemes with a dozen-plus touch buttons (fighting games, crafting menus, vehicle HUDs) that would otherwise overflow a phone screen as one tall unusable column. Set priority on the actions that matter most in the moment (the ones a player reaches for without looking), and group on button clusters that read as one gesture (attack + block, a radial weapon-select) so budgeting never splits them apart.

Raw state is for tooling, not gameplay

Zin.state.keyDown and friends read the raw per-frame snapshot underneath the binding layer — the layer that rebind capture, the touch-controls key-emulation floor, and dev tooling operate on directly. Polling it from gameplay code makes that code keyboard-only (it never sees touch or gamepad input) and draws the engine-raw-key-poll diagnostic at author time. Bind an action or axis instead.

Simulating input

The sim toolbox drives synthetic input — for testing a control scheme, scripting a demo, or an agent driving the world from execute(). Run its tools with tools.use("sim", "<name>", ...); key arguments are browser KeyboardEvent.code strings ("KeyW", "Space", "ShiftLeft", "Enter", "Escape", "ArrowUp"), not bare letters. They inject the same events real devices would, so anything zinput reads, sim can produce:

tools.use("sim", "key", "KeyW")            -- tap a key (optional trailing arg = hold seconds)
tools.use("sim", "key", "KeyW", 2)         -- hold W for 2 seconds
tools.use("sim", "keyDown", "ShiftLeft"); tools.use("sim", "keyUp", "ShiftLeft")
tools.use("sim", "mouseMove", x, y); tools.use("sim", "click")
tools.use("sim", "scroll", dy)
tools.use("sim", "lockPointer", true)
tools.use("sim", "recordMacro"); tools.use("sim", "saveMacro", name); tools.use("sim", "macro", name)   -- record + replay sequences

The model

Bind physical inputs to named actions with zinput, read those actions each frame, and use sim when you need to produce input rather than receive it. Reach for asset.inspect on the zinput modules for the exact API — the named-action approach is the part worth internalising.

  • documentation
  • guide