Input module
Input splits the player's input into actions: "jump", "shoot", "left". Each action is a number from 0 to 15, and with map() you tie it to a key (on Windows) or to the GPIO pin of a button (on the boards). The game then asks about the action with pressed(), without knowing what is behind it. With set(), another source (an I2C gamepad, the touch screen) can drive the same actions.

use Input
Platforms
| Windows | ESP32 / ESP32-S3 | ESP32-P4 | ESP32-S31 | |
|---|---|---|---|---|
What map() ties | A key, by its name | A GPIO pin, from 0 to 63 | A GPIO pin, from 0 to 63 | A GPIO pin, from 0 to 63, or a key of a USB keyboard |
| When what it reads changes | The keys, only inside Display.update() | When you query it | When you query it | When you query it |
Each platform takes only one kind: on Windows, a pin number throws ERR_TYPE_MISMATCH, and on the boards a key name throws it (except on the ESP32-S31). For the same program to work on all of them, choose the mapping with platform():
use Input
const JUMP = 0
if (platform() == HW_WIN32) then
Input.map(JUMP, "SPACE") // on Windows, the space bar
else
Input.map(JUMP, 15) // on the board, a button between pin 15 and GND
end
- Which pin each button of your board has, the I2C gamepad and the USB keyboard of the ESP32-S31: Buttons, pins and gamepad.
- In a game with rooms, the controls of the Game tab are mapped by
SceneInputMap(): see The room and the game.
Functions
| Function | What it does | Returns |
|---|---|---|
map(action, key or pin) | Ties the action to a key or a pin. | true |
pressed(action) | Tells whether the action is pressed. | true or false |
justPressed(action) | Tells whether the action has just been pressed. | true or false |
released(action) | Tells whether the action has just been released. | true or false |
set(action, state) | Sets the state of the action from your program. | true |
update() | Reads the inputs again. | true |
All the functions throw their errors, so try catches them:
- the wrong number of arguments:
ERR_INVALID_ARG_NUMBER; - an action that is not an integer:
ERR_TYPE_MISMATCH; - an action outside 0 to 15:
ERR_INVALID_VALUE.
map
map(action, key or pin) ties the action to a key or a pin.
use Input
// A keyboard game
Input.map(0, "W") // up
Input.map(1, "S") // down
Input.map(2, "A") // left
Input.map(3, "D") // right
Input.map(4, "SPACE") // jump
Input.map(5, "CTRL") // shoot
| Parameter | Type | What it is |
|---|---|---|
action | integer | The action, from 0 to 15. |
key or pin | string or integer | On Windows, the name of the key. On the boards, the pin number, from 0 to 63. On the ESP32-S31, either of them. |
It returns true. Besides the errors of all the functions, it throws:
- with a type the platform does not take (a number on Windows, a name on the boards):
ERR_TYPE_MISMATCH; - with a key name that does not exist, or a pin outside 0 to 63:
ERR_INVALID_VALUE.
Mapping an action again changes its key or pin, clears whatever set() had put and leaves it released.
On the boards, the pin becomes an input with the internal pull-up resistor: connect the button between the pin and GND. The action is pressed while the pin is low. The VM does not check that the pin exists on your board: only that it is between 0 and 63.
On the ESP32 (not on the S3), pins 34 to 39 have no internal pull-up resistor. A button on them, without an external resistor between the pin and 3.3 V, leaves the pin floating and the action does not read the button properly. Use another pin or add the resistor. The pins that do not work for a button on each chip are in Buttons, pins and gamepad.
The keys
On Windows and on the USB keyboard of the ESP32-S31, the keys are named like this, in capitals ("space" gives ERR_INVALID_VALUE):
| Group | Keys |
|---|---|
| Letters | A to Z |
| Numbers | 0 to 9 |
| Arrows | UP, DOWN, LEFT, RIGHT |
| Special | SPACE, ENTER, ESC, TAB |
| Modifiers | SHIFT, CTRL, ALT (the left ones) |
pressed
pressed(action) returns true while the action is pressed: by its key or pin, or by set(). An unmapped action returns false.
use Input
const LEFT = 2
const RIGHT = 3
var x = 160
var speed = 2
Input.map(LEFT, "A")
Input.map(RIGHT, "D")
// In each frame: continuous movement while it is held down
if (Input.pressed(LEFT)) then
x = x - speed
end
if (Input.pressed(RIGHT)) then
x = x + speed
end
It is the safe query: if you read it once per frame, you see every press that lasts at least one frame (see When the inputs are read).
justPressed
justPressed(action) returns true if the action has gone from released to pressed since the previous reading of the inputs. It is for what happens once per press: jumping, shooting.
use Input
const JUMP = 4
Input.map(JUMP, "SPACE")
if (Input.justPressed(JUMP)) then
println("Jump!")
end
justPressed can miss presses in 0.9.7The VM reads the inputs again every time you query an action in a different millisecond from the previous reading, and each reading turns the current state into the "previous" one (see When the inputs are read). The edge of justPressed() and released() only lasts until the next reading. If within one frame you query the inputs several times (the same action or others) and the millisecond changes between one query and the next, the second reading wipes out the edge and the press is lost. It is a known issue of 0.9.7.
Read pressed() only once per frame for each action and work out the edges yourself:
use Input
const JUMP = 4
Input.map(JUMP, "SPACE")
// Before the main loop
var jumpBefore = false
// In each frame
var jump = Input.pressed(JUMP)
var jumpPressed = jump and not jumpBefore // instead of Input.justPressed(JUMP)
var jumpReleased = not jump and jumpBefore // instead of Input.released(JUMP)
jumpBefore = jump
released
released(action) returns true if the action has gone from pressed to released since the previous reading of the inputs. It misses edges just like justPressed(): use the same workaround.
use Input
const JUMP = 4
Input.map(JUMP, "SPACE")
if (Input.released(JUMP)) then
println("You let go of the jump")
end
set
set(action, state) sets the state of an action from your program. It is the external source of the action: an I2C gamepad driver, a port expander or the touch screen can feed the same actions as the keyboard or the GPIOs, and the game keeps reading pressed(), justPressed() and released() without knowing what is behind them.
use Input
const JUMP = 4
var buttonA = false // what your gamepad driver reads
// In each frame, the gamepad driver sets the state of the button
Input.set(JUMP, buttonA)
if (Input.justPressed(JUMP)) then
println("Jump!")
end
| Parameter | Type | What it is |
|---|---|---|
action | integer | The action, from 0 to 15. |
state | boolean | true if the action is pressed. An integer works too: 0 is released. Any other type throws ERR_TYPE_MISMATCH. |
It returns true.
- If the action also has a key or a pin, the two sources add up: the action is pressed if either of them is. The edge is worked out as always, when the inputs are read again.
- If the action has no key or pin,
set()leaves it mapped to this source only, and the edge ofjustPressed()orreleased()is worked out inset()itself: it lasts until the next call, however many times you query in between. Callset()once per frame. map()on that action clears the external source.
update
update() reads the inputs again, as the next query would, and returns true. If it falls in the same millisecond as the previous reading, it does nothing. It is hardly ever needed: pressed(), justPressed() and released() already read by themselves.
When the inputs are read
The VM does not read the inputs at a fixed rate:
- It reads them when you query them. Every time you call
pressed(),justPressed(),released()orupdate()in a different millisecond from the previous reading, the VM reads again all the actions that have a key or a pin. The queries that fall in the same millisecond share the reading. - Each reading turns the current state into the "previous" one.
justPressed()andreleased()compare the two: that is why their edge only lasts until the next reading. - On Windows, the keyboard only changes inside
Display.update(), which is where the window receives the key presses. WithoutDisplay.update()in the loop, the keys never change. On the boards, the pin and the USB keyboard are read at that moment. - An action that only
set()drives is not read again: its state changes at eachset().
The safe way is to read each action once per frame with pressed(), keep the value in a variable and work out the edges you need yourself, as in the Full example.
Full example
A square that moves with the arrows, counts the jumps and closes with ESC. It reads each action once per frame and works out the edge of the jump itself.
use Display, Input
const LEFT = 0
const RIGHT = 1
const JUMP = 2
const QUIT = 3
if (platform() == HW_WIN32) then
Input.map(LEFT, "LEFT")
Input.map(RIGHT, "RIGHT")
Input.map(JUMP, "SPACE")
Input.map(QUIT, "ESC")
else
// Example pins: use the ones of your board's buttons
Input.map(LEFT, 32)
Input.map(RIGHT, 33)
Input.map(JUMP, 25)
Input.map(QUIT, 26)
end
Display.open(320, 240)
Display.mode(2)
Display.fps = 30
var draw = Display.draw
var x = 160
var jumps = 0
var jumpBefore = false
var quit = false
while (not quit)
// Each action, once per frame
var left = Input.pressed(LEFT)
var right = Input.pressed(RIGHT)
var jump = Input.pressed(JUMP)
quit = Input.pressed(QUIT)
if (left) then
x = x - 3
end
if (right) then
x = x + 3
end
if (jump and not jumpBefore) then // the edge, by hand
jumps = jumps + 1
end
jumpBefore = jump
draw.cls(0x000000)
draw.color = 0x00FF00
draw.rect(x - 8, 112, 16, 16, true)
draw.text(8, 8, "Jumps: " + jumps.toString(), 0xFFFFFF)
Display.update() // on Windows, the keys change here
end
Display.close()
Pitfalls and known issues
justPressed()andreleased()can miss presses if you query the inputs several times in the same frame: see justPressed.- On Windows, without
Display.update()no key changes. The keyboard is read from the window messages, which are handled there. - Keys in capitals:
"SPACE", not"space". - At most 16 actions, from 0 to 15. If you need more, make the same action mean different things depending on the moment of the game (in a menu, during play).
- On the ESP32-S31, an action has a pin or a key, not both: the last
map()replaces the previous one. To read both, use two actions, orset(). - Input does not filter button bounce. A mechanical button can open and close several times when pressed, and each change that falls in a different reading is an edge. If it happens to you, ignore the changes for a few milliseconds after each one.
- When the program ends, the actions go back to unmapped, also when the board loads the program again without restarting.