# 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: ```text 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 ```text 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 `. ## 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. - `move` is 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.device` is 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 `move` intent, on an entity a `target` intent, a right click a `context` intent. A controller reads `moveOrder()` and calls `clearMoveOrder()`. `target()` is what the player designates (`via` pointer, aim or stick); `gesture(action)` says `pressed`, `held`, `released` or `idle`. - **Composed actions**: documents of the `composites` collection `{ id, kind, of, holdSeconds, maxSeconds, window }`, kind `press`, `release`, `hold`, `long`, `charge`, `tap`, `double`, `toggle`, `chord`, `sequence`. The `id` becomes a button action every kit reads (`justPressed("charge_shot")`). - **Changing scheme in play**: `set(id)` (among `allowed`), `push(id, owner)` and `pop(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 |