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 Functions

Controller input handling for buttons, analog sticks, and triggers.

Controller Layout

Emberware ZX uses a modern PS2/Xbox-style controller:

         [LB]                    [RB]
         [LT]                    [RT]
        +-----------------------------+
       |  [^]              [Y]        |
       | [<][>]    [=][=]  [X] [B]    |
       |  [v]              [A]        |
       |       [SELECT] [START]       |
       |        [L3]     [R3]         |
        +-----------------------------+
           Left      Right
           Stick     Stick
  • D-Pad: 4 directions (digital)
  • Face buttons: A, B, X, Y (digital)
  • Shoulder bumpers: LB, RB (digital)
  • Triggers: LT, RT (analog 0.0-1.0)
  • Sticks: Left + Right (analog -1.0 to 1.0, clickable L3/R3)
  • Menu: Start, Select (digital)

Button Constants

#![allow(unused)]
fn main() {
// D-Pad
const BUTTON_UP: u32 = 0;
const BUTTON_DOWN: u32 = 1;
const BUTTON_LEFT: u32 = 2;
const BUTTON_RIGHT: u32 = 3;

// Face buttons
const BUTTON_A: u32 = 4;
const BUTTON_B: u32 = 5;
const BUTTON_X: u32 = 6;
const BUTTON_Y: u32 = 7;

// Shoulder bumpers
const BUTTON_LB: u32 = 8;
const BUTTON_RB: u32 = 9;

// Stick clicks
const BUTTON_L3: u32 = 10;
const BUTTON_R3: u32 = 11;

// Menu
const BUTTON_START: u32 = 12;
const BUTTON_SELECT: u32 = 13;
}

Individual Button Queries

button_held

Check if a button is currently held down.

Signature:

#![allow(unused)]
fn main() {
fn button_held(player: u32, button: u32) -> u32
}

Parameters:

NameTypeDescription
playeru32Player index (0-3)
buttonu32Button constant (0-13)

Returns: 1 if held, 0 otherwise

Example:

#![allow(unused)]
fn main() {
fn update() {
    // Continuous movement while held
    if button_held(0, BUTTON_RIGHT) != 0 {
        player.x += MOVE_SPEED * delta_time();
    }
    if button_held(0, BUTTON_LEFT) != 0 {
        player.x -= MOVE_SPEED * delta_time();
    }
}
}

See Also: button_pressed, button_released


button_pressed

Check if a button was just pressed this tick (edge detection).

Signature:

#![allow(unused)]
fn main() {
fn button_pressed(player: u32, button: u32) -> u32
}

Parameters:

NameTypeDescription
playeru32Player index (0-3)
buttonu32Button constant (0-13)

Returns: 1 if just pressed this tick, 0 otherwise

Example:

#![allow(unused)]
fn main() {
fn update() {
    // Jump only triggers once per press
    if button_pressed(0, BUTTON_A) != 0 && player.on_ground {
        player.velocity_y = JUMP_VELOCITY;
        play_sound(jump_sfx, 1.0, 0.0);
    }

    // Cycle weapons
    if button_pressed(0, BUTTON_RB) != 0 {
        current_weapon = (current_weapon + 1) % NUM_WEAPONS;
    }
}
}

See Also: button_held, button_released


button_released

Check if a button was just released this tick.

Signature:

#![allow(unused)]
fn main() {
fn button_released(player: u32, button: u32) -> u32
}

Parameters:

NameTypeDescription
playeru32Player index (0-3)
buttonu32Button constant (0-13)

Returns: 1 if just released this tick, 0 otherwise

Example:

#![allow(unused)]
fn main() {
fn update() {
    // Variable jump height (release early = smaller jump)
    if button_released(0, BUTTON_A) != 0 && player.velocity_y < 0.0 {
        player.velocity_y *= 0.5; // Cut upward velocity
    }

    // Charged attack
    if button_released(0, BUTTON_X) != 0 {
        let power = charge_time.min(MAX_CHARGE);
        fire_charged_attack(power);
        charge_time = 0.0;
    }
}
}

See Also: button_held, button_pressed


Bulk Button Queries

For better performance when checking multiple buttons, use bulk queries to reduce FFI overhead.

buttons_held

Get a bitmask of all currently held buttons.

Signature:

#![allow(unused)]
fn main() {
fn buttons_held(player: u32) -> u32
}

Parameters:

NameTypeDescription
playeru32Player index (0-3)

Returns: Bitmask where bit N is set if button N is held

Example:

#![allow(unused)]
fn main() {
fn update() {
    let held = buttons_held(0);

    // Check multiple buttons efficiently
    if held & (1 << BUTTON_A) != 0 { /* A held */ }
    if held & (1 << BUTTON_B) != 0 { /* B held */ }

    // Check for combo (A + B held together)
    let combo = (1 << BUTTON_A) | (1 << BUTTON_B);
    if held & combo == combo {
        perform_combo_attack();
    }
}
}

See Also: buttons_pressed, buttons_released


buttons_pressed

Get a bitmask of all buttons pressed this tick.

Signature:

#![allow(unused)]
fn main() {
fn buttons_pressed(player: u32) -> u32
}

Parameters:

NameTypeDescription
playeru32Player index (0-3)

Returns: Bitmask where bit N is set if button N was just pressed

Example:

#![allow(unused)]
fn main() {
fn update() {
    let pressed = buttons_pressed(0);

    // Check if any face button pressed
    let face_buttons = (1 << BUTTON_A) | (1 << BUTTON_B) |
                       (1 << BUTTON_X) | (1 << BUTTON_Y);
    if pressed & face_buttons != 0 {
        // Handle menu selection
    }
}
}

buttons_released

Get a bitmask of all buttons released this tick.

Signature:

#![allow(unused)]
fn main() {
fn buttons_released(player: u32) -> u32
}

Parameters:

NameTypeDescription
playeru32Player index (0-3)

Returns: Bitmask where bit N is set if button N was just released


Analog Sticks

left_stick_x

Get the left stick horizontal axis.

Signature:

#![allow(unused)]
fn main() {
fn left_stick_x(player: u32) -> f32
}

Parameters:

NameTypeDescription
playeru32Player index (0-3)

Returns: Value from -1.0 (left) to 1.0 (right), 0.0 at center

Example:

#![allow(unused)]
fn main() {
fn update() {
    let stick_x = left_stick_x(0);

    // Apply deadzone
    let deadzone = 0.15;
    if stick_x.abs() > deadzone {
        player.x += stick_x * MOVE_SPEED * delta_time();
    }
}
}

left_stick_y

Get the left stick vertical axis.

Signature:

#![allow(unused)]
fn main() {
fn left_stick_y(player: u32) -> f32
}

Returns: Value from -1.0 (down) to 1.0 (up), 0.0 at center


right_stick_x

Get the right stick horizontal axis.

Signature:

#![allow(unused)]
fn main() {
fn right_stick_x(player: u32) -> f32
}

Returns: Value from -1.0 (left) to 1.0 (right)

Example:

#![allow(unused)]
fn main() {
fn update() {
    // Camera control with right stick
    camera_yaw += right_stick_x(0) * CAMERA_SPEED * delta_time();
}
}

right_stick_y

Get the right stick vertical axis.

Signature:

#![allow(unused)]
fn main() {
fn right_stick_y(player: u32) -> f32
}

Returns: Value from -1.0 (down) to 1.0 (up)


left_stick

Get both left stick axes in a single FFI call (more efficient).

Signature:

#![allow(unused)]
fn main() {
fn left_stick(player: u32, out_x: *mut f32, out_y: *mut f32)
}

Parameters:

NameTypeDescription
playeru32Player index (0-3)
out_x*mut f32Pointer to write X value
out_y*mut f32Pointer to write Y value

Example:

#![allow(unused)]
fn main() {
fn update() {
    let mut x: f32 = 0.0;
    let mut y: f32 = 0.0;
    left_stick(0, &mut x, &mut y);

    // Calculate magnitude for circular deadzone
    let mag = (x * x + y * y).sqrt();
    if mag > 0.15 {
        let nx = x / mag;
        let ny = y / mag;
        player.x += nx * MOVE_SPEED * delta_time();
        player.y += ny * MOVE_SPEED * delta_time();
    }
}
}

right_stick

Get both right stick axes in a single FFI call.

Signature:

#![allow(unused)]
fn main() {
fn right_stick(player: u32, out_x: *mut f32, out_y: *mut f32)
}

Analog Triggers

trigger_left

Get the left trigger (LT) value.

Signature:

#![allow(unused)]
fn main() {
fn trigger_left(player: u32) -> f32
}

Parameters:

NameTypeDescription
playeru32Player index (0-3)

Returns: Value from 0.0 (released) to 1.0 (fully pressed)

Example:

#![allow(unused)]
fn main() {
fn update() {
    let lt = trigger_left(0);

    // Brake with analog pressure
    if lt > 0.1 {
        vehicle.speed *= 1.0 - (lt * BRAKE_FORCE * delta_time());
    }
}
}

trigger_right

Get the right trigger (RT) value.

Signature:

#![allow(unused)]
fn main() {
fn trigger_right(player: u32) -> f32
}

Returns: Value from 0.0 (released) to 1.0 (fully pressed)

Example:

#![allow(unused)]
fn main() {
fn update() {
    let rt = trigger_right(0);

    // Accelerate with analog pressure
    if rt > 0.1 {
        vehicle.speed += rt * ACCEL_FORCE * delta_time();
    }

    // Aiming zoom
    let zoom = 1.0 + rt * 2.0; // 1x to 3x zoom
    camera_fov(60.0 / zoom);
}
}

Complete Input Example

#![allow(unused)]
fn main() {
const BUTTON_A: u32 = 4;
const BUTTON_B: u32 = 5;
const MOVE_SPEED: f32 = 100.0;
const DEADZONE: f32 = 0.15;

static mut PLAYER_X: f32 = 0.0;
static mut PLAYER_Y: f32 = 0.0;
static mut ON_GROUND: bool = true;
static mut VEL_Y: f32 = 0.0;

#[no_mangle]
pub extern "C" fn update() {
    unsafe {
        let dt = delta_time();

        // Movement with left stick
        let mut sx: f32 = 0.0;
        let mut sy: f32 = 0.0;
        left_stick(0, &mut sx, &mut sy);

        if sx.abs() > DEADZONE {
            PLAYER_X += sx * MOVE_SPEED * dt;
        }

        // Jump with A button
        if button_pressed(0, BUTTON_A) != 0 && ON_GROUND {
            VEL_Y = -300.0;
            ON_GROUND = false;
        }

        // Gravity
        VEL_Y += 800.0 * dt;
        PLAYER_Y += VEL_Y * dt;

        // Ground collision
        if PLAYER_Y >= 200.0 {
            PLAYER_Y = 200.0;
            VEL_Y = 0.0;
            ON_GROUND = true;
        }
    }
}
}