Controls and input
Usage guide of input.actions, input.scheme, input.remap, input.virtualpad and input.pointer_lock: the chain from devices to actions to a control scheme, choosing a scheme by kind of game, settings and defaults, labels of keys, intents and composed actions, hooks, rebinding, the on-screen pad, mouse capture, engines.
Controls and input of your game (input.* kits) #
How a game reads keyboard, mouse, gamepad and touch: named actions, a control scheme chosen by the kind of game, rebinding by the player, an on-screen pad and mouse capture. Readable offline: gripforge docs input, or the MCP tool gripforge_docs (topic input).
What it is for #
One chain, the same on every engine:
devices (keyboard, mouse, gamepad, touch)
-> input.actions named actions: pressed / justPressed / axis / stick
-> input.scheme the control scheme of the game, per device
-> consumers characters, units, interaction, vehicles: they read actions and intents, never a device
Kits never test a key code. A player can rebind controls, a touch screen feeds the same actions as a gamepad, and the prompt shown in your HUD always matches the real binding.
| Kit | Role | In a new game |
|---|---|---|
input.actions | the named actions, overrides, labels of keys | yes |
input.scheme | the control scheme: what the pointer does, which actions exist and which are switched off | chosen by the kind of game |
input.remap | the Controls tab of the options: the player rebinds | yes |
input.virtualpad | sticks and buttons on a touch screen | no: add it to a game meant for touch |
input.pointer_lock | mouse capture for games that look with the mouse | with the kits that look with the mouse |
Turn it on #
gripforge_gamekit_install { project_id: "gkp_…", id: "input.scheme" }
gripforge_gamekit_configure { project_id: "gkp_…", id: "input.scheme", config: { "scheme": "third_person" } }
gripforge_gamekit_install { project_id: "gkp_…", id: "input.virtualpad" } # a game for phones
gripforge_game_project { project_id: "gkp_…", action: "controls", … } # apply a scheme to the whole project
The control-scheme call rewrites the scheme entries of the input.actions overrides, the touch layout and the help bar in one step. Each kit's full reference is gripforge kit api <id>.
Choosing a scheme #
The game makes a gameplay choice and the kit implements it completely; what the scheme does not use stays off and invisible (no WASD in a click game on a mouse, no capture in a click game).
| Kind of game | Scheme |
|---|---|
| first-person shooter, survival | fps (WASD, captured mouse) |
| third-person action, open world | third_person |
| MOBA | moba_click (right click moves and attacks, abilities toward the cursor) |
| dungeon crawler, action RPG seen from above | arpg_click |
| top-down or twin-stick shooter | top_down |
| platformer | platformer (direct movement, a stick) |
| fighting | fighting (one key per blow, no pointer) |
| racing, flight | vehicle (throttle and brake, steering) |
| strategy, tower defense | rts |
| point-and-click, turn-based, cards, puzzle | point_click (cursor on screens) |
input.actions #
| Key | Default | Meaning |
|---|---|---|
gamepad | true | read gamepads (moddable) |
deadzone | 0.15 | stick deadzone (moddable) |
overrides | [] | { action, keys, buttons?, axes?, buttonAxes?, disabled?, source? }: replace the keys or gamepad buttons of an action of any installed kit, or switch it off |
scheme | "" | the scheme the project started from (informative; what binds is overrides) |
eventActions | none | extra button names that publish input.action pressed and released edges (native player and Rust host) |
- Reading:
pressed(action),justPressed(action),axis(action),stick(action)({ x, y }),axis2D(xAction, yAction),sticks(). Presses shorter than a tick are kept, focus loss releases everything. moveis one 2D action. On a gamepad it takes a whole source (left stick, right stick, the d-pad or nothing), never four directions; on a keyboard it is the four keys of its two axes.capture(kit)routes every action to one kit (used while driving).- Labels, the only way to show a key:
labelFor(action, { lang?, device? })(E,A/D,Space,-when unbound),promptFor(action, text?, opts?)({ key, text, label: "[E] Interact", device, icon, control, bound }),keyInfo,actionText,device(),setDevice(keyboard | gamepad | auto). The binding is the effective one (kits, project overrides, then the player's own), the device is the one used last (input.deviceis emitted on a switch). Never write a key in a HUD, a dialogue or a tutorial. - Events:
input.action { action, phase, pressed },input.device { device }.
input.scheme #
| Key | Default | Meaning |
|---|---|---|
scheme | "" | the active scheme |
allowed | [] | schemes the player may switch to in the settings |
devices, autoSwitch | [], true | the device follows the last one used |
variants | {} | per-device departures from a scheme (see below) |
followSeconds, followDistance | 0.15, 0.3 | a held click keeps the order following the cursor |
holdRepeatSeconds | 0.25 | a held click on an entity issues the order again at this pace |
attackInPlace | true | in arpg_click, Shift held with the click attacks without walking |
targetRange, targetCone, targetTags | 6, 60, ["enemy", "interactable"] | picking a target with the stick on a gamepad |
pickTags, pickRadius | [], 0.9 | a click on the ground disc of an entity carrying one of these tags designates it (a downed teammate) |
vehicleScheme | vehicle | pushed when the player sits in a vehicle |
uiContexts | menu, dialogue, shop, storage, crafting, journal, photo, cinematic, end screen | interfaces that are an input context |
- Variant per device (
schemeVariant):movement(direct, click, vehicle),pointer(captured, aim, orders, none),rightClick(context, aim, camera, none),frame(camera, screen, world),cameraRotate(mouse, right_drag, right_stick, none),aimAssist, plus actions brought back (enable) or switched off (disable). - Input contexts: while an interface is open only its own and the global actions are read; what is held is released when the context changes.
- Intents: a click on the ground is a
moveintent, on an entity atargetintent, a right click acontextintent. A controller readsmoveOrder()and callsclearMoveOrder().target()is what the player designates (viapointer, aim or stick);gesture(action)sayspressed,held,releasedoridle. - Composed actions: documents of the
compositescollection{ id, kind, of, holdSeconds, maxSeconds, window }, kindpress,release,hold,long,charge,tap,double,toggle,chord,sequence. Theidbecomes a button action every kit reads (justPressed("charge_shot")). - Changing scheme in play:
set(id)(amongallowed),push(id, owner)andpop(owner)(a vehicle, a map, a cinematic). Saved with the game. - It draws nothing: markers, highlights, prompts and cursors belong to the interaction display kits.
| What | Name | Detail |
|---|---|---|
| Hook | input.scheme.change { scheme, previous, reason } | scheme mutable, cancellable: redirect or refuse a change |
| Hook | input.intent { kind, action, point, entity, scheme, device } | kind, point, entity mutable, cancellable: block walking in a dialogue, turn a click on a door into a context action |
| Events | input.scheme.changed, input.scheme.device, input.scheme.refused, input.context.changed, input.intent.issued | |
| Registries | schemes, composites | live; a mod adds or patches |
| Moddable config | allowed, autoSwitch, variants, follow and target settings |
input.remap #
The player rebinds from the Controls tab of the options screen: per action a primary and a secondary keyboard or mouse control and a gamepad button, "press a key" capture, conflicts reported, reset per action or all. It runs on the web, Godot, Unity and Unreal and applies the bindings to each engine's own input. Remove it only on request.
| Key | Default | Meaning |
|---|---|---|
actions | [] | actions listed (empty: all) |
exclude | [] | actions hidden from the tab |
conflicts | ask | what happens when a control is used twice (moddable) |
reserved | ["Escape"] | controls that cannot be bound |
gamepad, secondary | true, true | show the gamepad column and the secondary slot (moddable) |
A stick action has one row (source, invert X, invert Y, deadzone 0 to 0.5); an axis on analog triggers has a gamepad slot on each of its two rows. Service: rows(lang), capture(row, slot), press(control), resolve(accept), cancel(), clear(row, slot), reset(row?), conflicts(), bindings(), sticks(), setStick, setInvert, setDeadzone. Events: remap.capture, remap.changed, remap.conflict, remap.cancelled, remap.stick, remap.reset.
input.virtualpad #
Sticks and buttons drawn on the screen that feed input.actions exactly like a physical gamepad, so every kit works unchanged on a phone. The whole pad is data. A control whose action no kit registered is not drawn, so one layout can serve several games.
A control: { id, kind (stick | button | dpad), action, x?, y?, mode, landscape, portrait, shape, opacity, icon, label, zone, directions, deadzone, visible }. Placement units are 1 % of the shorter side of the safe area (notch and system bars excluded), anchor is an edge, a corner or the centre, offset goes inward. A stick is fixed or floating; a button is hold or toggle; visible: "context" draws a button only while one of its icon rules holds.
| Key | Default | Meaning |
|---|---|---|
mode | auto | auto (touch screens only), shown, hidden |
layout, layouts | foot, built-in foot and vehicle | layouts of the game; setLayout(id) |
layoutRules | on foot / in a vehicle | { layout, tag } or { layout, var } picks the layout in use |
icons, iconSet, iconSets | rules and a default set | icons that follow the context ({ action, icon, var?, tag?, interaction? }) |
style | theme | color, fill, active, border, opacity, scale |
hideInMenus, haptics | true, true |
input.pointer_lock #
Captures the mouse for games that look around with it: the pointer is hidden and held, and its relative movement goes to the camera. The kit decides, the host executes; it never captures on a touch screen or when nothing wants it.
| Key | Default | Meaning |
|---|---|---|
capture | click | click (the first capture waits for a click, a browser needs a gesture), play, manual |
when | auto | auto (a kit asked, a look service is mounted, or the scheme captures), always, requests |
lookServices | ["camera.look"] | capabilities that receive add(dx, dy) |
cursorTopics | dialogue, shop, storage, crafting, journal, photo, end screen | event pairs that need the cursor |
fullscreenGrace | 0.5 | seconds around entering fullscreen in which a lost capture is re-armed |
The pointer is released by itself, with a reason in pointer.released (pause, menu, cursor, touch, unavailable, request, host). Several kits can ask at once; it is released when the last one calls release(owner). Service: capture(owner), release(owner), showCursor(owner), hideCursor(owner), isCaptured(), wanted(), available(), state(), movement(), consume().
Engines #
| Engine | Input |
|---|---|
| Web and native player | all five kits |
| Godot | the same model over the InputMap |
| Unity | the same model over the Input System (legacy Input Manager without it) |
| Unreal | the same model over Enhanced Input |
Project overrides of other kits' actions and the labels are applied by the web and native players; the engine deliveries bake the key overrides of the kit's own actions.
When it does not do what you expect #
| Symptom | Cause and fix |
|---|---|
| A key is shown in the HUD that is not the real one | the HUD writes the key itself: ask promptFor |
| The mouse is not captured | the browser needs a click first (pointer.refused, gesture); a menu or a cursor topic is open; the scheme does not capture |
| WASD moves in a click game | the scheme in input.actions.scheme is not the one in input.scheme: apply the project controls again |
| A touch control is missing | no kit registered its action, or mode: "hidden" |
| A player cannot rebind an action | it is in exclude, or input.remap was removed |