DEMIENGINEDOCUMENTATION Everything is free
Project & file formats · Start here

What bindings are, what vector does, deadzones, and presets.

Input bindings

A binding is one line that says “when this physical input happens, feed that action”. Actions are the names your gameplay code reads; bindings are how devices fill them. This page explains every binding field from the ground up.

Actions first

Before bindings, declare the action they feed:

"input": {
  "actions": {
    "move":  { "type": "vector2", "context": "gameplay", "bindings": [] },
    "jump":  { "type": "button",  "context": "gameplay", "bindings": [] }
  }
}
FieldMeaning
typebutton (pressed or not), axis1d (one number −1…1), or vector2 (two numbers).
contextWhich action group this belongs to. Only enabled contexts respond. Defaults to gameplay.
playerOptional local player slot (0, 1, …) for shared-screen games.
bindingsList of physical inputs. The action’s value is the combination of everything currently active in this list.

What a binding looks like

"bindings": [
  { "input": "key:a", "vector": [-1, 0] },
  { "input": "key:d", "vector": [ 1, 0] },
  { "input": "gamepad:stick:left", "deadzone": 0.18 },
  { "input": "virtual:move" }
]

Each object is one binding. The only required field is input, which names the device control. The rest tune how it contributes.

The input field

PatternWhat it matches
key:a, key:space, key:left_shiftA keyboard key. Names are case-insensitive.
mouse:left, mouse:right, mouse:middleA mouse button.
gamepad:south, gamepad:start, gamepad:dpad_upA gamepad button (south/east/west/north face, bumpers, stick clicks, D-pad, select/guide/start).
gamepad:axis:left_triggerAn analog axis: left_x, left_y, right_x, right_y, left_trigger, right_trigger.
gamepad:stick:left / gamepad:stick:rightA full 2D stick (x and y together).
virtual:move, virtual:fireAn on-screen HUD control (see below).
virtual:move:x / virtual:move:yOne axis of an on-screen stick.

What vector does

{ "input": "key:a", "vector": [-1, 0] }

A key is a button: it is pressed or not. A vector2 action needs a direction. vector assigns one.

  • [-1, 0] means “while this key is held, contribute left on the x axis”.
  • [1, 0] is right, [0, -1] is up, [0, 1] is down.
  • Values outside −1…1 are scaled; most bindings use 0 and ±1.

The action’s value is the sum of all active bindings. Input.vector then clamps the combined result to unit length; a binding’s own normalize flag normalizes only that binding’s contribution. With A and D both bound as above:

Keys heldInput.vector("move")
none0, 0
A-1, 0
D1, 0
A + D0, 0 (they cancel)
W (vector: [0, -1]) + D1, -1, clamped by Input.vector to about 0.71, -0.71

Without vector, a key bound to a vector2 action contributes nothing useful — give every key a direction, or bind a stick that already has one.

For button and axis1d actions you usually omit vector and use scale / invert instead.

Other binding fields

FieldWhat it doesExample
scaleMultiplies the binding’s contribution. Range −1…1. Use -1 to flip an axis.{"input": "gamepad:axis:right_trigger", "scale": 0.5}
invertFlips the sign. Same idea as scale: -1, clearer for sticks.{"input": "gamepad:stick:left", "invert": true}
deadzoneIgnores small deflection so a worn stick does not drift. 0…0.99.{"input": "gamepad:stick:left", "deadzone": 0.18}
normalizeNormalizes this binding’s own contribution to unit length. (Input.vector separately clamps the whole action.){"input": "gamepad:stick:left", "normalize": true}
playerRoutes this binding to a specific local player.{"input": "key:w", "player": 0}
vectorDirection this key contributes to a vector2 action.{"input": "key:a", "vector": [-1, 0]}

Deadzone, step by step

{ "input": "gamepad:stick:left", "deadzone": 0.18 }
  1. The stick reports a raw value from 0 (center) to 1 (fully tilted) on each axis.
  2. Values below 0.18 are treated as 0 — that is the deadzone.
  3. Values above are rescaled so a slight tilt past the deadzone starts near 0 and full tilt reaches 1.

Use 0.15–0.25 for analog sticks, 0.05 for triggers, and none for digital buttons. Too large and the stick feels unresponsive; too small and the character creeps.

What a virtual input is

{ "input": "virtual:move" }

A virtual input is an on-screen control — a touch stick or button drawn by the HUD. It is not a device driver input. You author it in the HUD document:

{
  "id": "move_stick",
  "type": "virtual_stick",
  "control": "move"
}

The control name becomes the binding target virtual:move. When a player drags that stick, the action receives the same kind of value a physical stick would. Face buttons work the same way:

{ "id": "jump_btn", "type": "virtual_button", "control": "jump" }

This is how one gameplay script serves keyboard, gamepad, and phone. Details: Virtual & touch input.

Presets

"input": {
  "presets": ["wasd_arrows", "confirm", "gamepad_confirm", "move_3d"]
}
PresetInstalls
wasd_arrowsTwo axis1d actions: move_x (A/Left −1, D/Right +1) and move_y (S/Down −1, W/Up +1).
confirmA confirm button action from Space, Enter, and the gamepad south button.
gamepad_confirmInstalls the same confirm action (Space, Enter, gamepad south).
move_3dTwo axis1d actions: move_right (A −1, D +1) and move_forward (S −1, W +1).

Presets are starting points. Declare your own action with the same name and it overrides the preset’s definition field by field.

Reading the result in Lua

local Input = require("demi.input")

function Player:on_update(dt)
    local x, y = Input.vector("move")   -- the combined binding value
    if Input.pressed("jump") then
        -- jump is a button action; pressed is true this frame only
    end
end

Input.vector returns two numbers. Input.pressed/down/released return booleans. See Actions explained for the full query set.

Worked example

"input": {
  "presets": ["confirm"],
  "actions": {
    "move": {
      "type": "vector2",
      "context": "gameplay",
      "bindings": [
        { "input": "key:w", "vector": [0, -1] },
        { "input": "key:s", "vector": [0, 1] },
        { "input": "key:a", "vector": [-1, 0] },
        { "input": "key:d", "vector": [1, 0] },
        { "input": "key:up", "vector": [0, -1] },
        { "input": "key:down", "vector": [0, 1] },
        { "input": "key:left", "vector": [-1, 0] },
        { "input": "key:right", "vector": [1, 0] },
        { "input": "gamepad:stick:left", "deadzone": 0.18, "normalize": true },
        { "input": "virtual:move", "normalize": true }
      ]
    },
    "fire": {
      "type": "button",
      "context": "gameplay",
      "bindings": [
        { "input": "mouse:left" },
        { "input": "key:space" },
        { "input": "gamepad:right_bumper" },
        { "input": "virtual:fire" }
      ]
    }
  }
}

Read this as: “move is a 2D direction filled by WASD, arrows, the left stick, and the on-screen stick. fire is a button filled by the mouse, Space, the right bumper, and the on-screen button.”