Skip to main content

Handling User Input

Gamepad#

The main input device is the gamepad, consisting of 4 directions and 2 action buttons. On computers, players use the arrow keys and the X and Z keys. On mobile, a virtual gamepad overlay is displayed that players can tap.

The gamepad state is stored by WASM-4 as one byte in memory, with each button stored as a single bit. For example, the right directional button is stored in bit 5. We can mask the gamepad with the BUTTON_RIGHT constant to check if the right button is pressed.

(data (i32.const 0x2000) "Right button is down!\00")
(local $gamepad i32)(local.set $gamepad (i32.load8_u (global.get $GAMEPAD1)))
(if (i32.and (local.get $gamepad) (global.get $BUTTON_RIGHT))  (then    (call $trace (i32.const 0x2000))  ))
Gamepad BitButton
0BUTTON_1 (1)
1BUTTON_2 (2)
2Unused
3Unused
4BUTTON_LEFT (16)
5BUTTON_RIGHT (32)
6BUTTON_UP (64)
7BUTTON_DOWN (128)

Checking if a button was pressed this frame#

GAMEPAD1 stores the current state of the gamepad. It's common to want to know if a button was just pressed this frame. Another way of putting it: if a button was not down last frame but is down this frame.

This can be handled by storing the previous gamepad state, and then bitwise XORing (^) it with the current gamepad state. This leaves us with a byte with only the buttons that were pressed this frame.

;; Store the previous gamepad state in a global variable rather than linear;; memory. It is defined as a mutable global with the initial value 0.(global $previous-gamepad (mut i32) (i32.const 0))
(data (i32.const 0x2000) "Right button was just pressed!\00")
(func (export "update")  (local $gamepad i32)  (local $pressed-this-frame i32)
  (local.set $gamepad (i32.load8_u (global.get $GAMEPAD1)))
  ;; Only the buttons that were pressed down this frame  (local.set $pressed-this-frame    (i32.and      (local.get $gamepad)      (i32.xor        (local.get $gamepad)        (global.get $previous-gamepad))))
  (global.set $previous-gamepad (local.get $gamepad))
  (if (i32.and (local.get $pressed-this-frame) (global.get $BUTTON_RIGHT))    (then      (call $trace (i32.const 0x2000))    )  ))

Mouse#

Mouse (or touchscreen) input is supported and will work for positions even outside of the game window on supported platforms. See the Memory Map reference for more details on MOUSE_X, MOUSE_Y, and MOUSE_BUTTONS.

On the example below, we can make a rectangle follow the mouse position and expand when clicked:

export function update (): void {    const mouse  = load<u8>(w4.MOUSE_BUTTONS);    const mouseX = load<i16>(w4.MOUSE_X);    const mouseY = load<i16>(w4.MOUSE_Y);
    if (mouse & w4.MOUSE_LEFT) {        store<u16>(w4.DRAW_COLORS, 4);        w4.rect(mouseX - 8, mouseY - 8, 16, 16);    } else {        store<u16>(w4.DRAW_COLORS, 2);        w4.rect(mouseX - 4, mouseY - 4, 8, 8);    }}