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
| Mode | Description |
|---|---|
Linear | Linear falloff between min/max distance |
Inverse | Realistic 1/distance falloff |
Exponential | Steep 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(), ); } }