Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Spatial Audio

3D positional audio creates immersive soundscapes where sounds have position and direction.

Audio Listener

The listener is the "ear" in the scene, represented as an entity with the AUDIO_LISTENER component. Usually attached to the camera:

#![allow(unused)]
fn main() {
fn initialize(&mut self, world: &mut World) {
    let camera = spawn_camera(world, Vec3::new(0.0, 2.0, 10.0), "Camera".to_string());
    world.resources.active_camera = Some(camera);

    world.core.add_components(camera, AUDIO_LISTENER);
    world.core.set_audio_listener(camera, AudioListener);
}
}

Spatial Audio Source

Attach sounds to entities for positional audio:

#![allow(unused)]
fn main() {
fn spawn_ambient_sound(world: &mut World, position: Vec3, sound_name: &str) -> Entity {
    let entity = world.spawn_entities(
        AUDIO_SOURCE | LOCAL_TRANSFORM | GLOBAL_TRANSFORM,
        1
    )[0];

    world.core.set_local_transform(entity, LocalTransform {
        translation: position,
        ..Default::default()
    });

    world.core.set_audio_source(entity, AudioSource::new(sound_name)
        .with_spatial(true)
        .with_looping(true)
        .playing(),
    );

    entity
}
}

Distance Attenuation

Sounds get quieter with distance:

#![allow(unused)]
fn main() {
world.core.set_audio_source(entity, AudioSource::new("waterfall")
    .with_spatial(true)
    .with_looping(true)
    .playing(),
);
}

Rolloff Modes

ModeDescription
LinearLinear falloff between min/max distance
InverseRealistic 1/distance falloff
ExponentialSteep falloff, good for small sounds

Moving Sound Sources

Sounds automatically track their entity's position:

#![allow(unused)]
fn main() {
fn update_helicopter(world: &mut World, helicopter: Entity, dt: f32) {
    if let Some(transform) = world.core.get_local_transform_mut(helicopter) {
        transform.translation.x += 10.0 * dt;
    }
    mark_local_transform_dirty(world, helicopter);
}
}

Non-Spatial (2D) Audio

UI sounds and music should not be spatial:

#![allow(unused)]
fn main() {
world.core.set_audio_source(entity, AudioSource::new("ui_click").playing());
}

Directional Audio Sources

Some sounds are directional (like a speaker):

#![allow(unused)]
fn main() {
world.core.set_audio_source(entity, AudioSource::new("announcement")
    .with_spatial(true)
    .playing(),
);
}

Manual Volume Control

Adjust volume based on distance to the listener:

#![allow(unused)]
fn main() {
fn update_audio_attenuation(world: &mut World, source: Entity, listener: Entity) {
    let source_pos = world.core.get_global_transform(source).map(|t| t.translation());
    let listener_pos = world.core.get_global_transform(listener).map(|t| t.translation());

    if let (Some(src), Some(lst)) = (source_pos, listener_pos) {
        let distance = (lst - src).magnitude();
        let max_distance = 50.0;
        let volume = (1.0 - distance / max_distance).clamp(0.0, 1.0);

        if let Some(audio) = world.core.get_audio_source_mut(source) {
            audio.volume = volume;
        }
    }
}
}

Common Patterns

Ambient Soundscape

#![allow(unused)]
fn main() {
fn setup_forest_ambience(world: &mut World) {
    spawn_ambient_sound(world, Vec3::zeros(), "forest_ambient");

    spawn_ambient_sound(world, Vec3::new(10.0, 5.0, 0.0), "bird_chirp");
    spawn_ambient_sound(world, Vec3::new(-8.0, 4.0, 5.0), "bird_song");

    spawn_ambient_sound(world, Vec3::new(0.0, 0.0, 20.0), "stream");
}
}

Footstep System

#![allow(unused)]
fn main() {
fn play_footstep_at_position(world: &mut World, position: Vec3) {
    let entity = world.spawn_entities(AUDIO_SOURCE | LOCAL_TRANSFORM | GLOBAL_TRANSFORM, 1)[0];

    world.core.set_local_transform(entity, LocalTransform {
        translation: position,
        ..Default::default()
    });

    world.core.set_audio_source(entity, AudioSource::new("footstep")
        .with_spatial(true)
        .playing(),
    );
}
}