docs/bevy/examples/audio/spatial_audio_3d.rs 4 K · 139 lines · raw · history

1 //! This example illustrates how to load and play an audio file, and control where the sounds seems to come from.
2 use bevy::{
3 color::palettes::basic::{BLUE, LIME, RED},
4 prelude::*,
5 time::Stopwatch,
6 };
7
8 fn 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
18 fn setup(
19 mut commands: Commands,
20 asset_server: Res<AssetServer>,
21 mut meshes: ResMut<Assets<Mesh>>,
22 mut materials: ResMut<Assets<StandardMaterial>>,
23 ) {
24 // Space between the two ears
25 let gap = 4.0;
26
27 // sound emitter
28 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 // left ear indicator
44 (
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 // right ear indicator
50 (
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 // light
59 commands.spawn((
60 DirectionalLight::default(),
61 Transform::from_xyz(4.0, 8.0, 4.0).looking_at(Vec3::ZERO, Vec3::Y),
62 ));
63
64 // example instructions
65 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 // camera
78 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)]
85 struct Emitter {
86 stopwatch: Stopwatch,
87 }
88
89 fn 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
112 fn 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
133 fn 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 }