spatial_audio_3d/
spatial_audio_3d.rs

1//! This example illustrates how to load and play an audio file, and control where the sounds seems to come from.
2use 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    // 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)]
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}