1use audio_code::prelude::*;
8
9const COINS: usize = 4;
10
11struct Platformer {
12 player: BodyId,
13 lift: BodyId,
14 run_sound: Sound,
15 score: usize,
16}
17
18impl Platformer {
19 fn new(ctx: &mut Context) -> Self {
20 let audio = &mut ctx.audio;
21 audio.register("jump", Synth::square(300.0).sweep_to(700.0).length(0.15).decay(10.0).volume(0.2));
22 audio.register("land", Synth::noise().length(0.1).decay(40.0).with(Synth::sine(90.0).length(0.1).decay(30.0)));
23 audio.register("coin", Synth::triangle(988.0).length(0.08).then(Synth::triangle(1319.0).length(0.17).decay(15.0)));
24 audio.register("run", Synth::noise().length(0.06).decay(60.0).volume(0.3).then(Synth::silence(0.09)));
25 audio.register("lift", Synth::saw(55.0).length(1.0).volume(0.1));
26 audio.register("push", Synth::noise().length(0.3).volume(0.15));
27
28 let physics = &mut ctx.physics;
29 physics.add(Body::fixed(vec3(0.0, 0.0, -0.5)).with_box(200.0, 4.0, 1.0).with_tag("ground"));
30 for (x, z) in [(8.0, 2.0), (14.0, 4.0), (20.0, 2.5)] {
31 physics.add(Body::fixed(vec3(x, 0.0, z)).with_box(4.0, 4.0, 0.5).with_tag("platform"));
32 }
33 let lift = physics.add(Body::kinematic(vec3(40.0, 0.0, 0.0)).with_box(3.0, 4.0, 0.5).with_tag("lift"));
35 physics.add(Body::dynamic(vec3(30.0, 0.0, 0.5)).with_box(1.0, 1.0, 1.0).with_mass(5.0).with_tag("crate"));
36 let player = physics
38 .add(Body::dynamic(vec3(0.0, 0.0, 1.0)).with_box(0.8, 0.8, 1.6).with_max_speed(25.0).with_tag("player"));
39
40 ctx.follow(player);
41 ctx.loop_on(lift, "lift");
42 for (x, z) in [(5.0, 1.0), (14.0, 5.5), (25.0, 1.0), (40.0, 8.0)] {
44 let coin = ctx.physics.add(Body::sensor(vec3(x, 0.0, z)).with_sphere(0.6).with_tag("coin"));
45 ctx.loop_on(coin, "coin").set_volume(0.3);
46 }
47
48 let run_sound = ctx.audio.sound("run").looped().volume(0.0).play();
49 Self { player, lift, run_sound, score: 0 }
50 }
51}
52
53impl Scene for Platformer {
54 fn start(&mut self, ctx: &mut Context) {
55 ctx.speak("Collect the coins. Left and right to run, space to jump, S for score, escape to quit.");
56 }
57
58 fn update(&mut self, ctx: &mut Context) {
59 if ctx.pressed(Key::Escape) {
60 ctx.quit();
61 }
62
63 let lift_up = (ctx.time.elapsed() % 8.0) < 4.0;
65 ctx.physics.set_velocity(self.lift, vec3(0.0, 0.0, if lift_up { 2.0 } else { -2.0 }));
66
67 let run = ctx.input.axis(Key::Left, Key::Right);
68 let mut velocity = ctx.physics.velocity(self.player);
69 velocity.x = run * 6.0;
70 ctx.physics.set_velocity(self.player, velocity);
71 if ctx.pressed(Key::Space) && ctx.physics.jump(self.player, 2.5) {
72 ctx.play("jump");
73 }
74 let running = ctx.physics.on_ground(self.player) && run != 0.0;
75 self.run_sound.set_volume(if running { 0.6 } else { 0.0 });
76
77 if let Some(speed) = ctx.physics.landed(self.player) {
78 ctx.audio.sound("land").volume((speed / 12.0).clamp(0.2, 1.0)).play();
79 if speed > 15.0 {
80 ctx.speak("Ouch!");
81 }
82 }
83 if let Some((other, _)) = ctx.physics.collided(self.player)
84 && ctx.physics.tag(other) == "crate"
85 {
86 ctx.play_on(other, "push");
87 }
88 if let Some(coin) = ctx.physics.entered_tag(self.player, "coin") {
89 ctx.physics.remove(coin); self.score += 1;
91 ctx.audio.sound("coin").pitch(1.5).play();
92 ctx.speak(format!("{} of {COINS} coins", self.score));
93 if self.score == COINS {
94 ctx.ui.message("won", "You collected every coin! Press enter to quit.");
95 }
96 }
97 if ctx.ui.closed("won") {
98 ctx.quit();
99 }
100
101 if ctx.pressed(Key::S) {
102 let x = ctx.physics.position(self.player).x;
103 ctx.speak(format!("Score {}, position {x:.0}", self.score));
104 }
105 }
106}
107
108fn main() -> audio_code::Result<()> {
109 Engine::new("Platformer").gravity(EARTH_GRAVITY * 2.0).echo_speech(true).run_with(Platformer::new)
110}