Skip to content

Audio

Doodle Engine supports four audio channels: background music, ambient sounds, voice lines, and one-shot sound effects.

Each location has music and ambient fields:

content/locations/tavern.yaml
id: tavern
name: '@location.tavern.name'
description: '@location.tavern.description'
banner: tavern.png
music: tavern_ambience.ogg
ambient: fire_crackling.ogg

Music automatically crossfades when the player travels to a new location.

Add voice to dialogue nodes:

NODE emotional_scene
VOICE bartender_sad.ogg
BARTENDER: @bartender.sad_line

Play one-shot sounds from dialogue effects:

SOUND door_slam.ogg

Override the current music track from within dialogue:

MUSIC tension_theme.ogg

The override clears when the player travels to a new location and the destination’s location music resumes. To reset immediately to the current location’s music, pass an empty string:

MUSIC

The useAudioManager hook manages all audio playback automatically based on the snapshot. Volume values are reactive: pass current values each render and the hook applies them to the audio elements.

The hook does not own volume state. Use AudioSettingsContext (or your own state) as the single source of truth for volumes.

import { useAudioManager, useAudioSettings } from '@doodle-engine/react';
function MyGame() {
const { snapshot } = useGame();
const audioSettings = useAudioSettings();
useAudioManager(snapshot, {
masterVolume: audioSettings.masterVolume,
musicVolume: audioSettings.musicVolume,
soundVolume: audioSettings.soundVolume,
voiceVolume: audioSettings.voiceVolume,
crossfadeDuration: 1000,
});
}

If you’re using GameShell, audio management is built in. You don’t need to call useAudioManager yourself.

OptionTypeDefaultDescription
masterVolumenumber1.0Master volume (0-1)
musicVolumenumber0.7Music channel volume (0-1)
soundVolumenumber0.8Sound effects volume (0-1)
voiceVolumenumber1.0Voice channel volume (0-1)
crossfadeDurationnumber1000Crossfade time in ms
interface AudioManagerControls {
stopAll: () => void;
}

The hook reacts to snapshot changes:

  • Music: When snapshot.music changes, crossfades to the new track
  • Voice: When snapshot.dialogue?.voice changes, plays the voice file
  • Sounds: Plays all entries in snapshot.pendingSounds (cleared after each snapshot)

Sound effects and pending sounds are transient. They appear in one snapshot and are automatically cleared.

Place audio files in the matching assets/audio/ subdirectory:

assets/
audio/
music/
tavern_ambience.ogg
market_bustle.ogg
tension_theme.ogg
sfx/
door_slam.ogg
voice/
bartender_greeting.ogg

The engine resolves bare filenames to full paths at snapshot time. A music field set to tavern_ambience.ogg becomes /assets/audio/music/tavern_ambience.ogg in the snapshot. See Assets & Media for the full convention table.

UI sounds (button clicks, menu open/close) are handled by a separate useUISounds hook. These are renderer chrome sounds, not game content audio.

import { useUISounds } from '@doodle-engine/react'
const uiSounds = useUISounds({
basePath: '/audio/ui',
volume: 0.5,
sounds: {
click: 'click.ogg',
menuOpen: 'menu_open.ogg',
menuClose: 'menu_close.ogg',
},
})
// Play sounds on UI interactions
<button onClick={() => { uiSounds.playClick(); handleAction() }}>
Do Something
</button>

If you’re using GameShell, UI sounds are built in. Configure them via the uiSounds prop:

<GameShell registry={registry} config={config} uiSounds={{ volume: 0.5 }} />

Place UI sound files separately from game audio:

assets/
audio/
ui/
click.ogg
menu_open.ogg
menu_close.ogg
music/
tavern_ambience.ogg
sfx/
door_slam.ogg
voice/
bartender_greeting.ogg