spatial_audio_3d/
spatial_audio_3d.rs1use bevy::{
3 color::palettes::basic::{BLUE, LIME, RED},
4 prelude::*,
5 time::Stopwatch,
6};
7
8fn main() {
9 App::new()
10 .add_plugins(DefaultPlugins)
11 .add_systems(Startup, setup)
12 .add_systems(Update, update_positions)
13 .add_systems(Update, update_listener)
14 .add_systems(Update, mute)
15 .run();
16}
17
18fn setup(
19 mut commands: Commands,
20 asset_server: Res<AssetServer>,
21 mut meshes: ResMut<Assets<Mesh>>,
22 mut materials: ResMut<Assets<StandardMaterial>>,
23) {
24 let gap = 4.0;
26
27 commands.spawn((
29 Mesh3d(meshes.add(Sphere::new(0.2).mesh().uv(32, 18))),
30 MeshMaterial3d(materials.add(Color::from(BLUE))),
31 Transform::from_xyz(0.0, 0.0, 0.0),
32 Emitter::default(),
33 AudioPlayer::new(asset_server.load("sounds/Windless Slopes.ogg")),
34 PlaybackSettings::LOOP.with_spatial(true),
35 ));
36
37 let listener = SpatialListener::new(gap);
38 commands.spawn((
39 Transform::default(),
40 Visibility::default(),
41 listener.clone(),
42 children![
43 (
45 Mesh3d(meshes.add(Cuboid::new(0.2, 0.2, 0.2))),
46 MeshMaterial3d(materials.add(Color::from(RED))),
47 Transform::from_translation(listener.left_ear_offset),
48 ),
49 (
51 Mesh3d(meshes.add(Cuboid::new(0.2, 0.2, 0.2))),
52 MeshMaterial3d(materials.add(Color::from(LIME))),
53 Transform::from_translation(listener.right_ear_offset),
54 )
55 ],
56 ));
57
58 commands.spawn((
60 DirectionalLight::default(),
61 Transform::from_xyz(4.0, 8.0, 4.0).looking_at(Vec3::ZERO, Vec3::Y),
62 ));
63
64 commands.spawn((
66 Text::new(
67 "Up/Down/Left/Right: Move Listener\nSpace: Toggle Emitter Movement\nM: Toggle Mute",
68 ),
69 Node {
70 position_type: PositionType::Absolute,
71 bottom: px(12),
72 left: px(12),
73 ..default()
74 },
75 ));
76
77 commands.spawn((
79 Camera3d::default(),
80 Transform::from_xyz(0.0, 5.0, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
81 ));
82}
83
84#[derive(Component, Default)]
85struct Emitter {
86 stopwatch: Stopwatch,
87}
88
89fn update_positions(
90 time: Res<Time>,
91 mut emitters: Query<(&mut Transform, &mut Emitter), With<Emitter>>,
92 keyboard: Res<ButtonInput<KeyCode>>,
93) {
94 for (mut emitter_transform, mut emitter) in emitters.iter_mut() {
95 if keyboard.just_pressed(KeyCode::Space) {
96 if emitter.stopwatch.is_paused() {
97 emitter.stopwatch.unpause();
98 } else {
99 emitter.stopwatch.pause();
100 }
101 }
102
103 emitter.stopwatch.tick(time.delta());
104
105 if !emitter.stopwatch.is_paused() {
106 emitter_transform.translation.x = ops::sin(emitter.stopwatch.elapsed_secs()) * 3.0;
107 emitter_transform.translation.z = ops::cos(emitter.stopwatch.elapsed_secs()) * 3.0;
108 }
109 }
110}
111
112fn update_listener(
113 keyboard: Res<ButtonInput<KeyCode>>,
114 time: Res<Time>,
115 mut listeners: Single<&mut Transform, With<SpatialListener>>,
116) {
117 let speed = 2.;
118
119 if keyboard.pressed(KeyCode::ArrowRight) {
120 listeners.translation.x += speed * time.delta_secs();
121 }
122 if keyboard.pressed(KeyCode::ArrowLeft) {
123 listeners.translation.x -= speed * time.delta_secs();
124 }
125 if keyboard.pressed(KeyCode::ArrowDown) {
126 listeners.translation.z += speed * time.delta_secs();
127 }
128 if keyboard.pressed(KeyCode::ArrowUp) {
129 listeners.translation.z -= speed * time.delta_secs();
130 }
131}
132
133fn mute(keyboard_input: Res<ButtonInput<KeyCode>>, mut sinks: Query<&mut SpatialAudioSink>) {
134 if keyboard_input.just_pressed(KeyCode::KeyM) {
135 for mut sink in sinks.iter_mut() {
136 sink.toggle_mute();
137 }
138 }
139}