Spatial Audio¶
Spatial audio simulates sound positioning in 2D space, making your game world feel more immersive with distance-based volume attenuation, stereo panning, and Doppler effects.
What is Spatial Audio?¶
Spatial audio adjusts sound based on the distance and direction between a sound source and an audio listener. As entities move in your game world, sounds automatically fade, pan left/right, and shift pitch to match their spatial relationships.
Key Features:
- Distance attenuation — Sounds fade as they move away from the listener
- Stereo panning — Sounds pan left/right based on horizontal position
- Doppler effect — Pitch shifts based on relative velocity
- Real-time updates — Audio adjusts every frame as entities move
- Configurable curves — Linear, quadratic, or custom falloff
- ECS integration — Component-based via
SoundEffectSourceComponentandAudioListenerComponent - Pitch and volume variation — Randomized per-play for natural-sounding repetition
- Fade in/out — Smooth volume transitions on start and stop
- Max concurrent instances — Limit how many copies of the same sound can overlap
Basic Concepts¶
Audio Listener¶
The listener represents the "ears" in your game world — typically attached to the player or camera.
Properties:
| Property | Description | Default |
|---|---|---|
GlobalSpatialVolume |
Master volume for all spatial audio (0.0–1.0) | 1.0 |
SpeedOfSound |
World units per second for Doppler calculations | 343 |
Sound Effect Source¶
A positioned sound emitter. Uses SoundEffectSourceComponent (extends AudioSourceComponent).
Inherited from AudioSourceComponent:
| Property | Description | Default |
|---|---|---|
Volume |
Base volume (0.0–1.0) | 1.0 |
Pitch |
Playback speed (0.25–4.0) | 1.0 |
Priority |
Eviction priority (higher = less likely evicted) | 0 |
Bus |
Bus name for group operations | "sfx" |
PlayOnEnable |
Auto-play when enabled | false |
LoopCount |
-1 = infinite, 0 = once | 0 |
TriggerPlay |
Set true to play next frame |
false |
TriggerStop |
Set true to stop next frame |
false |
TriggerPause |
Set true to pause next frame |
false |
TriggerResume |
Set true to resume next frame |
false |
IsPlaying |
Read-only, managed by AudioSystem | — |
IsPaused |
Read-only, managed by AudioSystem | — |
PlaybackEnded |
Set by system when playback ends naturally | — |
Spatial properties on SoundEffectSourceComponent:
| Property | Description | Default |
|---|---|---|
SoundEffect |
The ISoundEffect to play |
null |
EnableSpatialAudio |
Toggle spatial processing | false |
MinDistance |
Full volume radius | 50 |
MaxDistance |
Silent beyond this | 500 |
RolloffFactor |
Attenuation curve (0 = flat, 1 = linear, 2 = quadratic) | 1.0 |
SpatialBlend |
Stereo strength (0.0 = mono, 1.0 = full) | 1.0 |
DopplerFactor |
Doppler intensity (0 = off, 1 = realistic, max 5) | 0 |
MaxConcurrentInstances |
Global cap on simultaneous instances of same sound (0 = unlimited) | 0 |
PitchVariation |
Random pitch offset per play (0–1) | 0 |
VolumeVariation |
Random volume reduction per play (0–1) | 0 |
FadeInDuration |
Seconds to fade in on play | 0 |
FadeOutDuration |
Seconds to fade out on stop | 0 |
TriggerStopOldest |
Stop oldest overlapping track on this entity | false |
Music Source¶
For positional music triggers, use MusicSourceComponent (extends AudioSourceComponent). Spatial audio does not apply to music.
| Property | Description | Default |
|---|---|---|
Music |
The IMusic to play |
null |
CrossfadeDuration |
Crossfade when replacing another music entity | 0 |
FadeOutDuration |
Fade when stopped | 0 |
LoopStartMs |
Loop point offset in ms (allows intro) | 0 |
TriggerSeek / SeekPositionMs |
Seek to position | — |
Bus |
Default "music" |
"music" |
Distance Attenuation¶
Volume = BaseVolume x AttenuationFactor
- Within MinDistance: factor = 1.0
- Beyond MaxDistance: factor = 0.0
- Between: curve depends on RolloffFactor
Stereo Panning¶
Pan = -1.0 (full left) to +1.0 (full right)
Calculated from direction between listener and source.
SpatialBlend controls strength.
Listener rotation affects panning direction.
Setup¶
AudioSystem¶
AudioSystem is a default system — it's added automatically to every scene. No registration needed.
Create Audio Listener¶
Typically attached to the player or camera:
var player = World.CreateEntity("Player");
player.AddComponent<TransformComponent>(t => t.Position = new Vector2(640, 360));
player.AddComponent<AudioListenerComponent>(l =>
{
l.GlobalSpatialVolume = 1.0f;
l.SpeedOfSound = 343f; // for Doppler
});
Create Sound Effect Source¶
var enemy = World.CreateEntity("Enemy");
enemy.AddComponent<TransformComponent>(t => t.Position = new Vector2(200, 300));
enemy.AddComponent<SoundEffectSourceComponent>(src =>
{
src.SoundEffect = _enemyGrowlSound;
src.EnableSpatialAudio = true;
src.MinDistance = 100f;
src.MaxDistance = 500f;
src.RolloffFactor = 1.0f;
src.SpatialBlend = 1.0f;
src.Volume = 0.7f;
src.LoopCount = -1;
src.PlayOnEnable = true;
});
Distance Attenuation¶
Linear Falloff¶
src.RolloffFactor = 1.0f;
// At 50% distance: volume is ~50%
Quadratic Falloff¶
src.RolloffFactor = 2.0f;
// At 50% distance: volume is ~25%
No Falloff¶
src.RolloffFactor = 0f;
// Full volume from MinDistance to MaxDistance, then silent
Distance Guidelines¶
// Small sounds (coins, footsteps)
src.MinDistance = 50f;
src.MaxDistance = 200f;
// Medium sounds (weapon fire, attacks)
src.MinDistance = 100f;
src.MaxDistance = 500f;
// Large sounds (explosions, boss roars)
src.MinDistance = 150f;
src.MaxDistance = 800f;
// Environmental ambient (waterfalls, wind)
src.MinDistance = 200f;
src.MaxDistance = 1000f;
Stereo Panning¶
src.SpatialBlend = 1.0f; // Full stereo
src.SpatialBlend = 0.5f; // Partial stereo (good for background sounds)
src.SpatialBlend = 0.0f; // Mono (center, no panning)
Doppler Effect¶
src.DopplerFactor = 1.0f; // Realistic pitch shift from relative motion
src.DopplerFactor = 2.0f; // Exaggerated
// Tune the listener's speed of sound to match your world scale
listener.SpeedOfSound = 343f;
Requires EnableSpatialAudio = true. The Doppler pitch is multiplied with the component's Pitch.
Pitch and Volume Variation¶
Add natural randomness to repeated sounds:
src.PitchVariation = 0.1f; // +/- 10% pitch each play
src.VolumeVariation = 0.05f; // Up to 5% quieter each play
Fade In/Out¶
src.FadeInDuration = 0.5f; // 0.5s fade-in on play
src.FadeOutDuration = 1.0f; // 1s fade-out on stop
// Setting TriggerStop a second time during an active fade-out forces immediate stop
Max Concurrent Instances¶
Limit overlapping copies of the same sound globally:
src.MaxConcurrentInstances = 3; // At most 3 instances of this sound across all entities
Complete Examples¶
Enemy Audio¶
var enemy = World.CreateEntity("Enemy");
enemy.AddComponent<TransformComponent>(t => t.Position = position);
enemy.AddComponent<SoundEffectSourceComponent>(src =>
{
src.SoundEffect = _growlSound;
src.EnableSpatialAudio = true;
src.MinDistance = 80f;
src.MaxDistance = 400f;
src.RolloffFactor = 1.5f;
src.SpatialBlend = 0.9f;
src.Volume = 0.6f;
src.LoopCount = -1;
src.PlayOnEnable = true;
src.DopplerFactor = 0.5f;
});
Collectible Coin¶
var coin = World.CreateEntity("Coin");
coin.AddComponent<TransformComponent>(t => t.Position = position);
coin.AddComponent<SoundEffectSourceComponent>(src =>
{
src.SoundEffect = _jingleSound;
src.EnableSpatialAudio = true;
src.MinDistance = 30f;
src.MaxDistance = 150f;
src.RolloffFactor = 1.0f;
src.SpatialBlend = 0.7f;
src.Volume = 0.5f;
src.LoopCount = -1;
src.PlayOnEnable = true;
src.PitchVariation = 0.05f;
});
Ambient Zone with Music¶
var zone = World.CreateEntity("BossZone");
zone.AddComponent<TransformComponent>(t => t.Position = zoneCenter);
zone.AddComponent<MusicSourceComponent>(m =>
{
m.Music = _bossMusic;
m.LoopCount = -1;
m.CrossfadeDuration = 2.0f;
m.PlayOnEnable = true;
});
One-Shot with Fade-Out¶
var alarm = World.CreateEntity("Alarm");
alarm.AddComponent<TransformComponent>(t => t.Position = alarmPos);
alarm.AddComponent<SoundEffectSourceComponent>(src =>
{
src.SoundEffect = _alarmSound;
src.EnableSpatialAudio = true;
src.MinDistance = 100f;
src.MaxDistance = 600f;
src.LoopCount = -1;
src.PlayOnEnable = true;
src.FadeInDuration = 0.3f;
src.FadeOutDuration = 1.0f;
});
// Later, stop with fade-out
alarm.GetComponent<SoundEffectSourceComponent>()!.TriggerStop = true;
Triggering Playback¶
Control playback via trigger flags (consumed each frame by AudioSystem):
var src = entity.GetComponent<SoundEffectSourceComponent>()!;
// Play
src.TriggerPlay = true;
// Stop
src.TriggerStop = true;
// Pause / Resume
src.TriggerPause = true;
src.TriggerResume = true;
// Check state
if (src.IsPlaying) { }
if (src.IsPaused) { }
if (src.PlaybackEnded) { }
// Stop oldest overlapping track on this entity
src.TriggerStopOldest = true;
If both TriggerPlay and TriggerStop are set in the same frame, both are cleared and no action is taken. Same for TriggerPause/TriggerResume.
Best Practices¶
Do¶
- Use spatial audio for diegetic sounds — In-world sounds (enemies, pickups, effects)
- Disable for non-diegetic sounds — UI, narration
- Match distances to game scale — Larger worlds need larger distances
- Test with headphones — Stereo panning is clearest with headphones
- Use
PitchVariation— Avoids repetitive-sounding effects - Use
FadeOutDuration— Prevents clicks on stop
Don't¶
- Don't enable spatial audio globally — Only for positioned sounds
- Don't use tiny MinDistance — Causes abrupt volume changes
- Don't use huge MaxDistance — Wastes processing on inaudible sounds
- Don't overlap too many sources — Use
MaxConcurrentInstancesto limit
Troubleshooting¶
No Spatial Audio¶
- Ensure
EnableSpatialAudio = trueon theSoundEffectSourceComponent - Verify an
AudioListenerComponentexists and is enabled - Check that both entities have a
TransformComponent - Confirm
SpatialBlend > 0for panning
Sounds Cut Off Abruptly¶
Increase MaxDistance or add FadeOutDuration:
src.MaxDistance = 400f;
src.FadeOutDuration = 0.5f;
Sounds Too Quiet¶
Check GlobalSpatialVolume on the listener and Volume on the source.
Panning Feels Wrong¶
Brine2D uses X+ = Right, Y+ = Down. Listener rotation affects panning direction.
Too Many Audio Sources¶
Use MaxConcurrentInstances and disable sources beyond hearing range:
src.MaxConcurrentInstances = 3;
// Or disable far-away entities in a behavior/system
See Also¶
- Sound Effects — Basic sound playback
- Music Playback — Background music
- Getting Started — Audio basics