Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Input

A Pixel8 console has six buttons: the four directions plus two action buttons, O and X. On a keyboard:

buttonkeys
Button::Left / Right / Up / Downarrow keys
Button::OZ (also C, N)
Button::XX (also V, M)

On retro handhelds and in web exports on touch screens, the d-pad and two face buttons map to the same six. Design for six buttons and your game runs everywhere Pixel8 does.

Held vs. pressed

Input is read in update, from the Context:

fn update(&mut self, ctx: &mut Context) {
    // Held: true every frame while the key is down. Movement.
    if ctx.is_button_down(Button::Right) {
        self.x += 1;
    }
    // Pressed: true on the frame it goes down (then key-repeat after a
    // short delay: 15 frames, then every 4). Jumping, menus, toggles.
    if ctx.is_button_pressed(Button::O) {
        self.jump();
    }
}

The aliases btn and btnp are the same two functions with PICO-8’s names.

Whole-state reads

buttons_down() and buttons_pressed() return all six as a BitFlags<Button> set — often tidier than six ifs, and the natural shape for diagonals:

let held = ctx.buttons_down();
if held.contains(Button::UP_RIGHT) {   // Up and Right together
    // ...
}
let dx = i16::from(held.contains(Button::Right)) - i16::from(held.contains(Button::Left));
let dy = i16::from(held.contains(Button::Down)) - i16::from(held.contains(Button::Up));

Button::UP_LEFT, UP_RIGHT, DOWN_LEFT and DOWN_RIGHT are provided as ready-made two-button sets.

Smooth sub-pixel movement: Body

Speeds don’t have to be whole pixels — keep positions as f32 and cast when drawing. But there’s a classic gotcha: a sprite moving diagonally at less than a pixel per frame zigzags, because x and y cross their pixel boundaries on different frames. (PICO-8 has this too; it’s integer-grid geometry, not a bug.)

The SDK’s opt-in Body fixes it. It owns the exact position, and emits a phase-coherent pixel position for drawing — both axes step together, so the diagonal is a clean staircase:

struct Mob { body: Body }   // Body::new(x, y) to create

fn update(&mut self, ctx: &mut Context) {
    let speed = 0.6;
    let held = ctx.buttons_down();
    let mut dx = 0.0;
    let mut dy = 0.0;
    if held.contains(Button::Left)  { dx -= speed; }
    if held.contains(Button::Right) { dx += speed; }
    if held.contains(Button::Up)    { dy -= speed; }
    if held.contains(Button::Down)  { dy += speed; }
    self.body.move_by(dx, dy);
}

fn draw(&self, gfx: &mut Graphics) {
    gfx.clear(Color::BLACK);
    // Draw at the coherent pixel; collide against the exact body.x()/y().
    gfx.sprite(SpriteId(1), self.body.draw_x(), self.body.draw_y());
}

The drawn pixel never strays more than one pixel from the true position, so collision against x()/y() stays honest. The platformer’s hero rides a Body, which is why a running jump doesn’t shimmer.

There is no mouse, and that’s fine

No mouse, no text entry, no gamepad rumble — six buttons is the entire input model. Like the 128×128 screen, it’s a constraint that designs half your control scheme for you.