Audio System
Live Demo: Audio
Nightshade uses Kira for audio playback, supporting both sound effects and music.
Enabling Audio
Audio requires the audio feature:
[dependencies]
nightshade = { git = "...", features = ["engine", "audio"] }
Loading Sounds
Load sounds at initialization using load_sound_from_bytes and world.resources.audio.load_sound:
#![allow(unused)] fn main() { use nightshade::ecs::audio::*; const EXPLOSION_WAV: &[u8] = include_bytes!("../assets/sounds/explosion.wav"); const MUSIC_OGG: &[u8] = include_bytes!("../assets/sounds/music.ogg"); fn initialize(&mut self, world: &mut World) { if let Ok(data) = load_sound_from_bytes(EXPLOSION_WAV) { world.resources.audio.load_sound("explosion", data); } if let Ok(data) = load_sound_from_bytes(MUSIC_OGG) { world.resources.audio.load_sound("music", data); } } }
load_sound_from_bytes decodes raw audio bytes into StaticSoundData, and world.resources.audio.load_sound caches it by name for later reference.
Playing Sounds
Entity-Based Playback
Sounds are played through the AudioSource component, which references a cached sound by name:
#![allow(unused)] fn main() { let entity = world.spawn_entities(AUDIO_SOURCE | LOCAL_TRANSFORM | GLOBAL_TRANSFORM, 1)[0]; world.core.set_audio_source(entity, AudioSource::new("explosion").playing()); }
Looping Music
#![allow(unused)] fn main() { let music = world.spawn_entities(AUDIO_SOURCE, 1)[0]; world.core.set_audio_source(music, AudioSource::new("music") .with_looping(true) .playing(), ); }
Audio Source Component
The AudioSource component controls playback:
#![allow(unused)] fn main() { pub struct AudioSource { pub audio_ref: Option<String>, pub volume: f32, pub looping: bool, pub playing: bool, pub spatial: bool, pub reverb: bool, } }
Builder methods:
#![allow(unused)] fn main() { AudioSource::new("name") .with_volume(0.8) .with_looping(true) .with_spatial(true) .with_reverb(true) .playing() }
Audio Listener
Mark the entity that "hears" sounds (usually the camera):
#![allow(unused)] fn main() { world.core.add_components(camera_entity, AUDIO_LISTENER); world.core.set_audio_listener(camera_entity, AudioListener); }
Spatial Audio Sources
Attach sounds to positioned entities for 3D audio:
#![allow(unused)] fn main() { const ENGINE_LOOP: &[u8] = include_bytes!("../assets/sounds/engine_loop.wav"); fn initialize(&mut self, world: &mut World) { if let Ok(data) = load_sound_from_bytes(ENGINE_LOOP) { world.resources.audio.load_sound("engine_loop", data); } let entity = world.spawn_entities( AUDIO_SOURCE | LOCAL_TRANSFORM | GLOBAL_TRANSFORM, 1, )[0]; world.core.set_audio_source(entity, AudioSource::new("engine_loop") .with_spatial(true) .with_looping(true) .playing(), ); } }
Sound Variations
Play random variations by switching the audio_ref:
#![allow(unused)] fn main() { const FOOTSTEP_1: &[u8] = include_bytes!("../assets/sounds/footstep_1.wav"); const FOOTSTEP_2: &[u8] = include_bytes!("../assets/sounds/footstep_2.wav"); const FOOTSTEP_3: &[u8] = include_bytes!("../assets/sounds/footstep_3.wav"); const FOOTSTEP_4: &[u8] = include_bytes!("../assets/sounds/footstep_4.wav"); fn initialize(&mut self, world: &mut World) { for (name, bytes) in [ ("footstep_1", FOOTSTEP_1), ("footstep_2", FOOTSTEP_2), ("footstep_3", FOOTSTEP_3), ("footstep_4", FOOTSTEP_4), ] { if let Ok(data) = load_sound_from_bytes(bytes) { world.resources.audio.load_sound(name, data); } } } fn play_footstep(world: &mut World, source_entity: Entity) { let sounds = ["footstep_1", "footstep_2", "footstep_3", "footstep_4"]; let index = rand::random::<usize>() % sounds.len(); if let Some(audio) = world.core.get_audio_source_mut(source_entity) { audio.audio_ref = Some(sounds[index].to_string()); audio.playing = true; } } }
Triggering Sounds on Events
#![allow(unused)] fn main() { fn run_systems(&mut self, world: &mut World) { for event in world.resources.physics.collision_events() { if matches!(event.kind, CollisionEventKind::Started) { if let Some(audio) = world.core.get_audio_source_mut(self.impact_source) { audio.audio_ref = Some("impact".to_string()); audio.playing = true; } } } } }
Stopping Sounds
Stop a sound attached to an entity:
#![allow(unused)] fn main() { world.resources.audio.stop_sound(entity); }
Or set playing to false:
#![allow(unused)] fn main() { if let Some(audio) = world.core.get_audio_source_mut(entity) { audio.playing = false; } }
Supported Formats
| Format | Extension | Notes |
|---|---|---|
| WAV | .wav | Uncompressed, fast loading |
| OGG | .ogg | Compressed, good for music |
| MP3 | .mp3 | Compressed, widely supported |
| FLAC | .flac | Lossless compression |