Audio Systems
Build centralized audio managers with channels, pooling, and 3D sound.
Introduction
Audio systems manage all game sound. This guide covers audio managers, channel grouping, source pooling, 3D spatial audio, and dynamic music transitions.
Guide 25 of 25
Screenshot
Step-by-Step Guide
Create an audio manager
Build a centralized AudioManager that handles all playback. Scripts call audioManager.play() instead of audio.play() directly.
Set up audio channels
Group sounds into channels: Master, Music, SFX, Voice, Ambient. Each has independent volume control.
Pool audio sources
Pre-create 15-20 audio sources. When playing, find an idle source and assign the clip. Prevents creating/destroying per sound.
Configure 3D audio
For positional sounds (footsteps, gunfire), enable 3D spatial mode. Set min distance and max distance for falloff.
Add dynamic music
Transition between tracks based on game state (explore, combat, boss). Crossfade over 2-3 seconds.
Implement ducking
When voice audio plays, duck music volume by 30% for 2 seconds. Ensures dialogue is audible over music.
FluxScript Example
Here is a complete FluxScript implementation for this guide:
const MAX_SOURCES = 20
on start {
sources = []
for i = 0 to MAX_SOURCES {
src = audio.createSource()
sources[i] = { source: src, inUse: false, channel: null }
}
channels = { master: 1.0, music: 0.7, sfx: 0.9, voice: 0.85, ambient: 0.5 }
currentMusic = null
musicState = "explore"
}
func play(soundId, channel, options) {
options = options || {}
src = null
for i = 0 to MAX_SOURCES {
if !sources[i].inUse { src = sources[i]; break }
}
if src == null return
src.inUse = true
src.channel = channel
src.source.setClip(soundId)
vol = channels[channel] * (options.volume || 1.0)
src.source.setVolume(vol)
if options.position != null {
src.source.set3D(true)
src.source.setPosition(options.position)
src.source.setMinDistance(options.minDist || 3)
src.source.setMaxDistance(options.maxDist || 30)
}
pitch = options.pitch || 1.0
if options.randomPitch pitch = 0.9 + math.random(0.2)
src.source.setPitch(pitch)
src.source.setLoop(options.loop || false)
src.source.play()
if !options.loop {
duration = audio.getClipDuration(soundId)
timer.start("release_" + i, duration, 1)
}
return src
}
func playMusic(soundId, crossfadeTime) {
crossfadeTime = crossfadeTime || 2.0
if currentMusic != null fadeOut(currentMusic, crossfadeTime)
currentMusic = play(soundId, "music", { loop: true, volume: 0 })
fadeIn(currentMusic, channels.music, crossfadeTime)
}
func setMusicState(state) {
if musicState == state return
musicState = state
tracks = { explore: "music/explore", combat: "music/combat", boss: "music/boss" }
playMusic(tracks[state], 2.0)
}
func setChannelVolume(channel, volume) {
channels[channel] = volume
for i = 0 to MAX_SOURCES {
if sources[i].inUse && sources[i].channel == channel {
sources[i].source.setVolume(volume)
}
}
}
on event("combatStart") { setMusicState("combat") }
on event("combatEnd") { setMusicState("explore") }
on event("bossStart") { setMusicState("boss") }Node Graph Example
Prefer visual programming? Here is the equivalent Node Graph:
[On Start] --> [Create: 20 audio sources (pool)]
--> [Init: channel volumes]
[Function: play] --> [Find: idle source from pool]
--> [Set: clip, volume (channel x sound), 3D position]
--> [Set: pitch (with random option), loop]
--> [Play] --> [Timer: auto-release when done]
[Function: playMusic] --> [Fade Out: current music]
--> [Play: new music, loop, volume=0]
--> [Fade In: to music channel volume]
[Function: setMusicState] --> [Branch: state changed]
--> [playMusic: corresponding track, 2s crossfade]
[Function: setChannelVolume] --> [Update: all sources on channel]
[On Event "combatStart"] --> [setMusicState: "combat"]
[On Event "combatEnd"] --> [setMusicState: "explore"]Optimization Tips
Keep your implementation fast and efficient with these optimization tips:
- Pool audio sources. Creating and destroying sources per sound causes GC spikes. Pre-create 15-20 sources and recycle.
- Use channels for volume control. Group sounds into Music, SFX, Voice, Ambient. Lets players adjust independently in settings.
- Stream long music tracks. Do not load entire music files into memory — stream them. Saves 50-100MB per track.
- Use pitch randomization for repeated SFX. Footsteps and gunshots at identical pitch sound monotonous. Randomize by 10%.
Common Mistakes
Creating audio sources per sound
Pool sources. Creating/destroying per footstep causes GC spikes and limits concurrent sounds.
Music not ducking during dialogue
Implement ducking. When voice plays, temporarily lower music so dialogue is audible.
