Skip to main content

gamepad/
gamepad.rs

1//! Gamepads: every connected controller gets a crab. Left stick moves it,
2//! the bottom face button (A, Cross) spins it, the right trigger grows it.
3//! Plug pads in and out while it runs. Without a pad, the arrow keys move
4//! a crab of their own.
5//!
6//!   `cargo run --example gamepad`
7
8use ponko::*;
9
10/// The crab of the pad in `slot`, or of the keyboard (`slot: None`).
11struct Player {
12    slot: Option<usize>,
13    spin: f32,
14}
15
16const SPEED: f32 = 400.0;
17
18/// A tint per player, so pads can tell their crabs apart.
19const PLAYER_COLORS: [Color; MAX_GAMEPADS] = [
20    Color::rgb(1.0, 0.6, 0.6),
21    Color::rgb(0.6, 0.8, 1.0),
22    Color::rgb(0.6, 1.0, 0.6),
23    Color::rgb(1.0, 1.0, 0.5),
24    Color::rgb(1.0, 0.6, 1.0),
25    Color::rgb(0.6, 1.0, 1.0),
26    Color::rgb(1.0, 0.8, 0.5),
27    Color::rgb(0.8, 0.8, 0.8),
28];
29
30// ANCHOR: gamepad
31fn move_players(ctx: &mut Ctx) {
32    let dt = ctx.dt();
33    // Read the pads first: the query below borrows the world.
34    let mut sticks = [Vec2::ZERO; MAX_GAMEPADS];
35    let mut spins = [false; MAX_GAMEPADS];
36    let mut grow = [0.0; MAX_GAMEPADS];
37    for &pad in ctx.gamepads() {
38        sticks[pad.slot()] = ctx.gamepad_stick(pad, GamepadStick::Left);
39        spins[pad.slot()] = ctx.gamepad_pressed(pad, GamepadButton::South);
40        grow[pad.slot()] = ctx.gamepad_axis(pad, GamepadAxis::RightTrigger);
41    }
42    for (t, player) in ctx.world.query::<(&mut Transform2D, &mut Player)>().iter() {
43        let Some(slot) = player.slot else { continue };
44        t.position += sticks[slot] * SPEED * dt;
45        if spins[slot] {
46            player.spin = std::f32::consts::TAU;
47        }
48        t.scale = Vec2::splat(1.0 + 2.0 * grow[slot]);
49    }
50}
51// ANCHOR_END: gamepad
52
53/// Arrow keys move the keyboard's crab.
54fn move_keyboard_player(ctx: &mut Ctx) {
55    let dt = ctx.dt();
56    let mut dir = Vec2::ZERO;
57    for (key, d) in [
58        (Key::Left, -Vec2::X),
59        (Key::Right, Vec2::X),
60        (Key::Up, -Vec2::Y),
61        (Key::Down, Vec2::Y),
62    ] {
63        if ctx.key_down(key) {
64            dir += d;
65        }
66    }
67    let spin = ctx.key_pressed(Key::Space);
68    for (t, player) in ctx.world.query::<(&mut Transform2D, &mut Player)>().iter() {
69        if player.slot.is_none() {
70            t.position += dir.normalize_or_zero() * SPEED * dt;
71            if spin {
72                player.spin = std::f32::consts::TAU;
73            }
74        }
75    }
76}
77
78/// One crab per connected pad: spawn on plug-in, despawn on unplug.
79fn join_and_leave(ctx: &mut Ctx) {
80    let mut connected = [false; MAX_GAMEPADS];
81    for pad in ctx.gamepads() {
82        connected[pad.slot()] = true;
83    }
84    let mut has_crab = [false; MAX_GAMEPADS];
85    for (e, player) in ctx.world.query::<(Entity, &Player)>().iter() {
86        if let Some(slot) = player.slot {
87            if connected[slot] {
88                has_crab[slot] = true;
89            } else {
90                ctx.commands.despawn(e);
91            }
92        }
93    }
94    for slot in (0..MAX_GAMEPADS).filter(|&s| connected[s] && !has_crab[s]) {
95        let mut sprite = Sprite::new(ctx.load_texture("crab.png"));
96        sprite.color = PLAYER_COLORS[slot];
97        let at = vec2(200.0 + slot as f32 * 120.0, 360.0);
98        ctx.world.spawn((
99            Transform2D::at(at),
100            sprite,
101            Player {
102                slot: Some(slot),
103                spin: 0.0,
104            },
105        ));
106    }
107}
108
109/// Spins play out over half a second.
110fn spin(ctx: &mut Ctx) {
111    let dt = ctx.dt();
112    for (t, player) in ctx.world.query::<(&mut Transform2D, &mut Player)>().iter() {
113        let step = player.spin.min(4.0 * std::f32::consts::TAU * dt);
114        t.rotation += step;
115        player.spin -= step;
116    }
117}
118
119fn hud(ctx: &mut Ctx) {
120    let pads = ctx.gamepads().len();
121    let text = match pads {
122        0 => "No gamepad: plug one in, or use the arrow keys and Space".to_string(),
123        n => format!("{n} gamepad(s): left stick moves, A/Cross spins, right trigger grows"),
124    };
125    ctx.draw_text(&text, vec2(20.0, 20.0), 20.0, Color::WHITE);
126}
127
128fn main() {
129    Game::new("Crabby Engine: gamepads", 1280, 720)
130        .system(join_and_leave)
131        .system(move_players)
132        .system(move_keyboard_player)
133        .system(spin)
134        .system(hud)
135        .run(|ctx| {
136            let crab = ctx.load_texture("crab.png");
137            ctx.world.spawn((
138                Transform2D::at(vec2(640.0, 560.0)),
139                Sprite::new(crab),
140                Player { slot: None, spin: 0.0 },
141            ));
142        });
143}