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

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

FormatExtensionNotes
WAV.wavUncompressed, fast loading
OGG.oggCompressed, good for music
MP3.mp3Compressed, widely supported
FLAC.flacLossless compression