Input: keyboard, mouse, gamepads
Input is read from ctx in any system. Every kind works the same way:
_down while held, _pressed on the frame it went down, _released on the
frame it came up. A tap shorter than a frame still shows up as pressed and
released.
Keyboard
#![allow(unused)]
fn main() {
use ponko::*;
fn update(ctx: &mut Ctx, pos: &mut Vec2) {
if ctx.key_down(Key::Right) { pos.x += 200.0 * ctx.dt(); }
if ctx.key_pressed(Key::Space) { println!("jump"); }
}
}
Keys are named by their position on a US keyboard: Key::W is the key
above S on every layout, so WASD controls stay in place for AZERTY and
Dvorak players too. In C++: ctx.key_down(CRABBY_KEY_RIGHT).
Mouse
ctx.mouse_pos(): in window pixels;ctx.mouse_world(): through the camera, in world units.ctx.mouse_down(MouseButton::Left)(and_pressed,_released).ctx.mouse_delta()since the last frame, andctx.mouse_wheel().ctx.mouse_over_ui():trueover a UI panel or the inspector, so a click on a button doesn’t also act in the world.
Mouse look
For a first-person or orbit camera, lock the cursor and read raw motion:
ctx.set_cursor_locked(true)hides the cursor and holds it in the window (where the platform can’t hold it in place, it’s kept inside the window instead);falselets it go. It takes effect after the frame and comes back when the window regains focus.ctx.mouse_motion(): how far the mouse moved this frame in raw device units (+y is down), even while the cursor is locked and can’t move.mouse_delta()stops at the window’s edge; this doesn’t.
C++: ctx.set_cursor_locked(true), ctx.mouse_motion(). See
3D physics for a walker that uses both.
Gamepads
Xbox, PlayStation, Switch and most other controllers work, plugged in or
over Bluetooth, and can come and go while the game runs.
ctx.gamepads() lists the connected ones. Each keeps its slot while
connected (pad.slot(): 0 is player one), and a pad plugged in later takes
the lowest free slot.
#![allow(unused)]
fn main() {
fn move_players(ctx: &mut Ctx) {
let dt = ctx.dt();
// Read the pads first: the query below borrows the world.
let mut sticks = [Vec2::ZERO; MAX_GAMEPADS];
let mut spins = [false; MAX_GAMEPADS];
let mut grow = [0.0; MAX_GAMEPADS];
for &pad in ctx.gamepads() {
sticks[pad.slot()] = ctx.gamepad_stick(pad, GamepadStick::Left);
spins[pad.slot()] = ctx.gamepad_pressed(pad, GamepadButton::South);
grow[pad.slot()] = ctx.gamepad_axis(pad, GamepadAxis::RightTrigger);
}
for (t, player) in ctx.world.query::<(&mut Transform2D, &mut Player)>().iter() {
let Some(slot) = player.slot else { continue };
t.position += sticks[slot] * SPEED * dt;
if spins[slot] {
player.spin = std::f32::consts::TAU;
}
t.scale = Vec2::splat(1.0 + 2.0 * grow[slot]);
}
}
}
struct Player { std::uint32_t slot; float spin; }; // one per connected pad
static void move_players(crabby::ctx& ctx) {
const float dt = ctx.dt();
// Read the pads first: the loop below borrows the world.
crabby::vec2 sticks[CRABBY_MAX_GAMEPADS] = {};
bool spins[CRABBY_MAX_GAMEPADS] = {};
float grow[CRABBY_MAX_GAMEPADS] = {};
for (crabby::gamepad pad : ctx.gamepads()) {
sticks[pad.slot] = ctx.gamepad_stick(pad, crabby::stick::left);
spins[pad.slot] = ctx.gamepad_pressed(pad, CRABBY_GAMEPAD_SOUTH);
grow[pad.slot] = ctx.gamepad_axis(pad, CRABBY_GAMEPAD_RIGHT_TRIGGER_AXIS);
}
ctx.world().each([&](crabby::transform2d& t, Player& player) {
t.position += sticks[player.slot] * 400 * dt;
if (spins[player.slot]) player.spin = 6.2831853f;
t.scale = crabby::vec2{1, 1} * (1 + 2 * grow[player.slot]);
});
}
Buttons are named by position, so a game’s controls mean the same on every brand:
GamepadButton | Xbox | PlayStation | Nintendo |
|---|---|---|---|
South | A | Cross | B |
East | B | Circle | A |
North | Y | Triangle | X |
West | X | Square | Y |
LeftBumper / RightBumper | LB / RB | L1 / R1 | L / R |
LeftTrigger / RightTrigger | LT / RT | L2 / R2 | ZL / ZR |
Select / Start | View / Menu | Share / Options | Minus / Plus |
LeftStick / RightStick | stick clicks | L3 / R3 | stick clicks |
DPadUp, DPadDown, DPadLeft, DPadRight | d-pad | d-pad | d-pad |
Sticks and triggers are axes:
ctx.gamepad_stick(pad, GamepadStick::Left)is aVec2from-1to1, y down like screen coordinates, so adding it to a position moves that way. Small wobbles around the center read as zero.ctx.gamepad_axis(pad, GamepadAxis::RightTrigger)goes from0(released) to1(fully pulled): use it for throttles and charge-ups. The triggers also count as buttons once pulled past halfway.
examples/gamepad.rs gives every connected pad its own crab:
cargo run --example gamepad.
Gamepads need the gamepad cargo feature, on by default. Without it, or in
headless runs, ctx.gamepads() is empty. On Linux, building
it needs the udev headers (sudo apt-get install libudev-dev).