1use audio_code::prelude::*;
15
16const BIRDS: [Vec3; 4] = [
17 vec3(10.0, 15.0, 8.0), vec3(-12.0, 5.0, 1.0), vec3(0.0, -14.0, 14.0), vec3(14.0, -10.0, 4.0),
21];
22
23struct Flight {
24 drone: BodyId,
25 birds: usize,
26 blocked: bool,
28}
29
30impl Flight {
31 fn new(ctx: &mut Context) -> Self {
32 ctx.audio.register("bird", Synth::sine(2200.0).sweep_to(3200.0).length(0.07).then(Synth::silence(0.05)).then(Synth::sine(2600.0).length(0.06)).then(Synth::silence(0.7)));
33 ctx.audio.register("hum", Synth::saw(70.0).length(1.0).volume(0.08));
34 ctx.audio.register("bump", Synth::sine(80.0).length(0.15).decay(25.0).volume(0.8));
35 ctx.audio.register("found", Synth::triangle(660.0).sweep_to(1320.0).length(0.4).decay(6.0).volume(0.3));
36
37 let physics = &mut ctx.physics;
40 physics.add(Body::fixed(vec3(0.0, 0.0, -0.5)).with_box(40.0, 40.0, 1.0).with_tag("floor"));
41 physics.add(Body::fixed(vec3(0.0, 0.0, 16.5)).with_box(40.0, 40.0, 1.0).with_tag("roof"));
42 physics.add(Body::fixed(vec3(0.0, 20.5, 8.0)).with_box(40.0, 1.0, 16.0).with_tag("north wall"));
43 physics.add(Body::fixed(vec3(0.0, -20.5, 8.0)).with_box(40.0, 1.0, 16.0).with_tag("south wall"));
44 physics.add(Body::fixed(vec3(20.5, 0.0, 8.0)).with_box(1.0, 40.0, 16.0).with_tag("east wall"));
45 physics.add(Body::fixed(vec3(-20.5, 0.0, 8.0)).with_box(1.0, 40.0, 16.0).with_tag("west wall"));
46
47 let drone = ctx.physics.add(Body::kinematic(vec3(0.0, 0.0, 2.0)).with_sphere(0.5).with_tag("drone"));
49 ctx.follow(drone); ctx.audio.play_looped("hum");
51
52 for spot in BIRDS {
53 let bird = ctx.physics.add(Body::sensor(spot).with_sphere(1.0).with_tag("bird"));
54 ctx.loop_on(bird, "bird");
55 }
56 Self { drone, birds: BIRDS.len(), blocked: false }
57 }
58
59 fn fly(&mut self, ctx: &mut Context, result: MoveResult) {
60 if result.blocked && !self.blocked
61 && let Some(&hit) = result.hits.first()
62 {
63 ctx.play("bump");
64 ctx.speech.speak(ctx.physics.tag(hit));
65 }
66 self.blocked = result.blocked;
67 }
68}
69
70impl Scene for Flight {
71 fn start(&mut self, ctx: &mut Context) {
72 ctx.speak("Find the four birds. Arrows fly and turn, page up and down look up and down, E and Q rise and sink. C locates the nearest bird.");
73 }
74
75 fn update(&mut self, ctx: &mut Context) {
76 if ctx.pressed(Key::Escape) {
77 ctx.quit();
78 }
79 if ctx.pressed(Key::Left) || ctx.pressed(Key::Right) {
80 let facing = ctx.physics.turn(self.drone, if ctx.pressed(Key::Left) { -30.0 } else { 30.0 });
81 ctx.speak(math::direction_name(facing));
82 }
83 if ctx.pressed(Key::PageUp) || ctx.pressed(Key::PageDown) {
84 let pitch = ctx.physics.tilt(self.drone, if ctx.pressed(Key::PageUp) { 30.0 } else { -30.0 });
85 ctx.speak(describe_pitch(pitch));
86 }
87
88 let speed = 4.0 * ctx.time.delta();
90 let forward = ctx.input.axis(Key::Down, Key::Up);
91 if forward != 0.0 {
92 let result = ctx.physics.move_forward(self.drone, forward * speed);
93 self.fly(ctx, result);
94 }
95 let rise = ctx.input.axis(Key::Q, Key::E);
96 if rise != 0.0 {
97 let result = ctx.physics.move_up(self.drone, rise * speed);
98 self.fly(ctx, result);
99 }
100
101 if ctx.pressed(Key::C) {
102 let position = ctx.physics.position(self.drone);
103 match ctx.physics.nearest_with_tag(position, "bird") {
104 Some(bird) => {
105 let text = ctx.physics.describe(self.drone, bird); ctx.speak(text);
107 }
108 None => ctx.speak("No birds left"),
109 }
110 }
111 if ctx.pressed(Key::F) {
112 let (facing, pitch) = (ctx.physics.facing(self.drone), ctx.physics.pitch(self.drone));
113 ctx.speak(format!("Facing {}, {}", math::direction_name(facing), describe_pitch(pitch)));
114 }
115 if ctx.pressed(Key::H) {
116 let height = ctx.physics.position(self.drone).z;
117 ctx.speak(format!("{height:.0} meters high"));
118 }
119
120 if let Some(bird) = ctx.physics.entered_tag(self.drone, "bird") {
121 ctx.physics.remove(bird);
122 self.birds -= 1;
123 ctx.play("found");
124 if self.birds == 0 {
125 ctx.ui.message("done", "You found every bird!");
126 } else {
127 ctx.speak(format!("Bird found! {} left.", self.birds));
128 }
129 }
130 if ctx.ui.closed("done") {
131 ctx.quit();
132 }
133 }
134}
135
136fn describe_pitch(pitch: f32) -> String {
137 match pitch.round() as i32 {
138 0 => "looking straight ahead".to_string(),
139 90 => "looking straight up".to_string(),
140 -90 => "looking straight down".to_string(),
141 p if p > 0 => format!("looking {p} degrees up"),
142 p => format!("looking {} degrees down", -p),
143 }
144}
145
146fn main() -> audio_code::Result<()> {
147 Engine::new("Flight").echo_speech(true).run_with(Flight::new)
148}