What an action is and how to query it in gameplay.
Actions explained
An action is a named logical control — “jump”, “move”, “fire” — that gameplay code reads. It exists so your scripts never care which key, stick, trigger, or on-screen button produced the signal. You declare actions once in the project file, devices fill them through bindings, and Lua queries the action by name. This page explains the three action types and every query you can make.
Why actions instead of keys
If gameplay read “the space key”, every player without a keyboard would be stuck, and a remap would mean editing scripts. An action decouples intent from hardware: the project says jump is a button, bindings say Space and the gamepad south button fill it, and the script asks only “was jump pressed?”. The same script serves keyboard, gamepad, and a phone touch pad. How devices attach is covered in Input bindings.
Declaring an action
"input": {
"actions": {
"jump": { "type": "button", "context": "gameplay", "bindings": [] },
"aim": { "type": "axis1d", "context": "gameplay", "bindings": [] },
"move": { "type": "vector2", "context": "gameplay", "bindings": [] }
}
}
Each entry in input.actions is one action keyed by its name. Fields:
type— the shape of the value:button,axis1d, orvector2. Required.context— which action group this belongs to. Defaults togameplay. Only enabled contexts respond.player— optional local player slot (0,1, …) for shared-screen games.bindings— the list of physical inputs that feed this action. May be empty while you prototype.
Button, axis1d, vector2
button
A button is either pressed or not. Use it for jump, fire, confirm, pause — anything with a single moment of intent.
"jump": {
"type": "button",
"context": "gameplay",
"bindings": [
{ "input": "key:space" },
{ "input": "gamepad:south" },
{ "input": "virtual:jump" }
]
}
This declares jump as a button filled by the keyboard space bar, the gamepad south face button, and an on-screen virtual button named jump. In Lua the action reports 0 or 1 through Input.value and true/false through the pressed queries.
axis1d
An axis1d is one number, usually from −1 to 1. Use it for throttle, steering trim, zoom, or any “how far” control.
"accelerate": {
"type": "axis1d",
"context": "gameplay",
"bindings": [
{ "input": "key:w", "scale": 1 },
{ "input": "key:s", "scale": -1 },
{ "input": "gamepad:axis:right_trigger", "scale": 1 }
]
}
Here W pushes the axis positive, S pushes it negative, and the right trigger reports how hard it is pulled. Input.value("accelerate") returns the combined number — a trigger can return 0.37 while a key returns 1 or 0.
vector2
A vector2 is two numbers — an x/y direction. Use it for movement, aiming, or cursor nudge.
"move": {
"type": "vector2",
"context": "gameplay",
"bindings": [
{ "input": "key:a", "vector": [-1, 0] },
{ "input": "key:d", "vector": [1, 0] },
{ "input": "key:w", "vector": [0, -1] },
{ "input": "key:s", "vector": [0, 1] },
{ "input": "gamepad:stick:left", "deadzone": 0.18, "normalize": true },
{ "input": "virtual:move", "normalize": true }
]
}
Keys are on/off, so each key binding carries a vector direction. The left stick already has an x/y, and the on-screen stick publishes one. All active contributions are summed into the action’s current value.
Full query script
Below is one complete Skyforge player controller using every action type. Read the comments as the contract for each call.
local Input = require("demi.input")
local Transform2D = require("demi.transform2d")
local Player = {}
function Player:on_create()
self.speed = 220
end
function Player:on_update(dt)
-- button timing: true on one frame each
if Input.pressed("jump") then
-- start the game's jump action
end
if Input.released("fire") then
-- finish the game's fire action
end
-- button state: true every frame while held
if Input.down("fire") then
-- continue the game's fire action
end
-- axis1d: one number
local throttle = Input.value("accelerate")
-- vector2: two numbers, clamped to the unit circle
local x, y = Input.vector("move")
-- unclamped variant when speed should scale with tilt
local rx, ry = Input.raw_vector("move")
local px, py = Transform2D.get_position(self.id)
if px ~= nil then
Transform2D.set_position(self.id,
px + x * self.speed * throttle * dt,
py + y * self.speed * throttle * dt)
end
end
return Player
require("demi.input")imports the input service; nothing is global.Input.pressed("jump")returnstrueonly on the frame the action went down, elsefalse.Input.down("fire")returnstrueevery frame while the action is held.Input.released("fire")returnstrueonly on the frame the action came up.Input.value("accelerate")returns one number:0or1for buttons, −1…1 for axes.Input.vector("move")returnsx, yclamped to a unit circle — safe to feed straight into movement.Input.raw_vector("move")returnsx, yunclamped, so a full diagonal can exceed length 1.
pressed, down, released across frames
These three queries describe the same hold over time. Imagine a player taps the fire button: it goes down on frame 10 and up on frame 14.
| Frame | pressed | down | released |
|---|---|---|---|
| 9 (before) | false | false | false |
| 10 (goes down) | true | true | false |
| 11–13 (held) | false | true | false |
| 14 (comes up) | false | false | true |
| 15 (after) | false | false | false |
In prose: pressed is the start edge, down is the whole hold, released is the end edge. Each is a single boolean. If the action’s context is disabled, all three read as false — the action looks released. If you need the same edge in two places in one frame, call the query once and store the boolean.
value, vector, raw_vector, source
These return the action’s current magnitude or direction rather than a press state.
local v = Input.value("accelerate")
local x, y = Input.vector("move")
local rx, ry = Input.raw_vector("move")
local src = Input.source("jump")
Return examples for a Skyforge frame where the left stick is held halfway up-right and the player also holds D:
| Call | Returns | Meaning |
|---|---|---|
Input.value("accelerate") | 0.37 | Right trigger is a little under halfway. |
Input.value("jump") | 0 or 1 | Buttons report 0 or 1. |
Input.vector("move") | 0.71, 0.71 | Diagonal, clamped to unit length. |
Input.raw_vector("move") | 1.2, 0.5 | Sum of contributions, not clamped. |
Input.source("jump") | "key:space" | Name of the binding that is currently driving the action. |
valueis how hard the control is pressed. Use it for throttle and zoom.vectoris the normalized direction. Use it for movement so diagonals are not faster than straights.raw_vectorkeeps the unclamped sum. Use it when stick tilt should scale speed.sourcereturns a binding name string such as"key:space","gamepad:south", or"virtual:jump". Use it for “press [key] to jump” prompts.
Two buttons acting as one axis
When two buttons should act like a one-dimensional axis, declare two actions and combine their values in Lua — there is no Input.axis helper:
"input": {
"actions": {
"turn_left": { "type": "axis1d", "context": "gameplay", "bindings": [ { "input": "key:a", "scale": 1 } ] },
"turn_right": { "type": "axis1d", "context": "gameplay", "bindings": [ { "input": "key:d", "scale": 1 } ] }
}
}
local turn = Input.value("turn_right") - Input.value("turn_left")
- Each side is its own action; the Lua code subtracts the negative side from the positive side.
- If both are held, the result is
0— they cancel, matching two opposed keys on a vector binding. - For plain buttons you can also read them directly:
if Input.down("turn_left") then … end. - For analog input, prefer one
axis1daction fed by a stick or trigger soInput.valuereturns the combined strength directly.
Raw key helpers
For debug overlays and editor tools you can bypass actions and read hardware directly:
if Input.key_down("left_shift") then end
if Input.key_pressed("f1") then end
if Input.key_released("f1") then end
Each takes a key name and returns a boolean with the same timing as the action queries. These do not respect contexts and do not remap — keep them out of normal gameplay so rebinding still works.
Next
- Input bindings — every binding field, presets, deadzones.
- Contexts & rebinding — menus, remaps, saved bindings.
- Virtual & touch input — on-screen controls and touches.
- Input — the overview page.