Audio Components
3D spatial audio with AudioSource, AudioListener, and AudioZone components using Web Audio API.
Audio components enable realistic 3D positional audio in your game. Web Engine uses the Web Audio API for spatial audio with distance attenuation, doppler effects, and environmental audio zones for reverb and occlusion.
AudioSource#
The AudioSource component represents a sound emitter in 3D space. Supports both 2D ambient sounds and 3D positional audio with distance-based rolloff.
Properties#
| Property | Type | Default | Description |
|---|---|---|---|
| spatial | ui8 | 1 | Spatial audio enabled (0=2D/ambient, 1=3D/positional) |
| volume | f32 | 1.0 | Volume level (0.0 to 1.0) |
| loop | ui8 | 0 | Loop mode (0=play once, 1=loop) |
| playing | ui8 | 0 | Playback state (0=stopped, 1=playing) |
| assetId | ui32 | 0 | Audio clip asset ID |
| refDistance | f32 | 1.0 | Reference distance for volume rolloff (meters) |
| maxDistance | f32 | 100.0 | Maximum audible distance (meters) |
| group | ui8 | 0 | Audio group: 0=SFX, 1=Music, 2=Voice |
| pitch | f32 | 1.0 | Pitch multiplier (1.0=normal, 2.0=octave up) |
| spatialBlend | f32 | 1.0 | 2D/3D blend (0.0=2D, 1.0=3D) |
| rolloffFactor | f32 | 1.0 | Distance rolloff factor (higher=faster dropoff) |
Usage#
import { addComponent, AudioSource } from '@web-engine/core'; // Create 3D positional sound (footsteps)const footstepSource = world.addEntity();addComponent(world, Transform, footstepSource);addComponent(world, AudioSource, footstepSource); AudioSource.assetId[footstepSource] = footstepClipId;AudioSource.spatial[footstepSource] = 1; // 3D spatialAudioSource.volume[footstepSource] = 0.7;AudioSource.loop[footstepSource] = 1; // LoopAudioSource.playing[footstepSource] = 1; // Start playingAudioSource.refDistance[footstepSource] = 2.0; // Loud within 2mAudioSource.maxDistance[footstepSource] = 20.0; // Audible up to 20mAudioSource.group[footstepSource] = 0; // SFX group // Create 2D ambient musicconst musicSource = world.addEntity();addComponent(world, AudioSource, musicSource); AudioSource.assetId[musicSource] = musicClipId;AudioSource.spatial[musicSource] = 0; // 2D ambientAudioSource.volume[musicSource] = 0.5;AudioSource.loop[musicSource] = 1;AudioSource.playing[musicSource] = 1;AudioSource.group[musicSource] = 1; // Music group // Create one-shot sound effect (explosion)const explosionSource = world.addEntity();addComponent(world, Transform, explosionSource);addComponent(world, AudioSource, explosionSource); AudioSource.assetId[explosionSource] = explosionClipId;AudioSource.spatial[explosionSource] = 1;AudioSource.volume[explosionSource] = 1.0;AudioSource.loop[explosionSource] = 0; // One-shotAudioSource.playing[explosionSource] = 1;AudioSource.refDistance[explosionSource] = 5.0;AudioSource.maxDistance[explosionSource] = 100.0;AudioSource.pitch[explosionSource] = 0.9; // Slightly lower pitchAudio Groups
Audio groups (SFX, Music, Voice) allow independent volume control via the GameState component. Players can adjust music volume without affecting sound effects.
Spatial Audio Settings#
For 3D spatial audio, the key properties are refDistance and maxDistance:
- refDistance: Distance where volume is at maximum (closer = louder, but no gain increase)
- maxDistance: Distance where sound becomes inaudible (beyond this = silent)
- rolloffFactor: How quickly volume decreases with distance (1.0 = linear, 2.0 = faster)
// Loud, close-range sound (gunshot)AudioSource.refDistance[gunshot] = 1.0; // Max volume within 1mAudioSource.maxDistance[gunshot] = 150.0; // Audible up to 150mAudioSource.rolloffFactor[gunshot] = 2.0; // Fast dropoff // Quiet, ambient sound (wind)AudioSource.refDistance[wind] = 10.0; // Max volume within 10mAudioSource.maxDistance[wind] = 50.0; // Audible up to 50mAudioSource.rolloffFactor[wind] = 0.5; // Slow dropoffAudioListener#
The AudioListener component marks the entity that receives audio. Typically attached to the camera entity for first-person perspective.
Properties#
| Property | Type | Default | Description |
|---|---|---|---|
| active | ui8 | 1 | Listener active state (0=inactive, 1=active) |
Usage#
import { addComponent, AudioListener } from '@web-engine/core'; // Attach to camera for first-person audioconst camera = world.addEntity();addComponent(world, Transform, camera);addComponent(world, Camera, camera);addComponent(world, CameraTag, camera);addComponent(world, AudioListener, camera); // Audio follows camera AudioListener.active[camera] = 1;Single Listener
Only one AudioListener should be active at a time. Multiple listeners can cause audio artifacts and incorrect spatial positioning.
AudioZone#
The AudioZone component defines a spatial region that affects audio sources within it, applying reverb effects and occlusion for environmental audio.
Properties#
| Property | Type | Default | Description |
|---|---|---|---|
| shapeType | ui8 | 0 | Zone shape: 0=sphere, 1=box |
| innerRadius | f32 | 5.0 | Inner radius/bounds (no effect) |
| outerRadius | f32 | 10.0 | Outer radius/bounds (full effect) |
| reverbPreset | ui8 | 0 | Reverb preset (0=none, 1+=preset ID) |
| occlusionFactor | f32 | 0.0 | Occlusion amount (0=none, 1=fully muffled) |
| active | ui8 | 1 | Zone active state (0=inactive, 1=active) |
Usage#
import { addComponent, AudioZone } from '@web-engine/core'; // Create reverb zone (cave)const caveZone = world.addEntity();addComponent(world, Transform, caveZone);addComponent(world, AudioZone, caveZone); Transform.position[caveZone] = [50, 0, 50]; // Cave entrance AudioZone.shapeType[caveZone] = 0; // SphereAudioZone.innerRadius[caveZone] = 5.0; // No effect within 5mAudioZone.outerRadius[caveZone] = 15.0; // Full reverb at 15mAudioZone.reverbPreset[caveZone] = 3; // Cave presetAudioZone.occlusionFactor[caveZone] = 0.0; // No occlusionAudioZone.active[caveZone] = 1; // Create occlusion zone (behind wall)const wallZone = world.addEntity();addComponent(world, Transform, wallZone);addComponent(world, AudioZone, wallZone); Transform.position[wallZone] = [0, 0, 10]; AudioZone.shapeType[wallZone] = 1; // BoxAudioZone.innerRadius[wallZone] = 2.0;AudioZone.outerRadius[wallZone] = 5.0;AudioZone.reverbPreset[wallZone] = 0; // No reverbAudioZone.occlusionFactor[wallZone] = 0.7; // 70% muffledAudioZone.active[wallZone] = 1;Zone Blending
Audio zones use falloff between innerRadius and outerRadius for smooth transitions. At innerRadius, no effect. At outerRadius, full effect. In between, linear interpolation.
Common Use Cases#
Footstep Sounds#
// Attach audio source to playerconst player = world.addEntity();addComponent(world, Transform, player);addComponent(world, CharacterController, player);addComponent(world, AudioSource, player); AudioSource.assetId[player] = footstepClipId;AudioSource.spatial[player] = 1;AudioSource.volume[player] = 0.6;AudioSource.loop[player] = 1;AudioSource.refDistance[player] = 2.0;AudioSource.maxDistance[player] = 15.0; // Control playback based on movement speedconst speed = Math.sqrt( CharacterController.movement[player][0] ** 2 + CharacterController.movement[player][2] ** 2); if (speed > 0.1 && CharacterController.grounded[player]) { AudioSource.playing[player] = 1; // Play footsteps AudioSource.pitch[player] = 0.8 + speed * 0.4; // Faster = higher pitch} else { AudioSource.playing[player] = 0; // Stop footsteps}Background Music#
// 2D ambient background musicconst bgMusic = world.addEntity();addComponent(world, AudioSource, bgMusic); AudioSource.assetId[bgMusic] = musicTrackId;AudioSource.spatial[bgMusic] = 0; // 2D (no position)AudioSource.volume[bgMusic] = 0.3; // Quiet backgroundAudioSource.loop[bgMusic] = 1; // Loop foreverAudioSource.playing[bgMusic] = 1;AudioSource.group[bgMusic] = 1; // Music group // Crossfade to new trackconst fadeOutDuration = 2.0; // secondsconst fadeInDuration = 2.0; // Gradually decrease old track volumeAudioSource.volume[oldTrack] -= deltaTime / fadeOutDuration;if (AudioSource.volume[oldTrack] <= 0) { AudioSource.playing[oldTrack] = 0; removeEntity(world, oldTrack);} // Gradually increase new track volumeAudioSource.volume[newTrack] += deltaTime / fadeInDuration;AudioSource.volume[newTrack] = Math.min(AudioSource.volume[newTrack], 0.3);UI Sounds#
// UI click sound (2D, one-shot)function playUIClick() { const clickSound = world.addEntity(); addComponent(world, AudioSource, clickSound); AudioSource.assetId[clickSound] = uiClickClipId; AudioSource.spatial[clickSound] = 0; // 2D AudioSource.volume[clickSound] = 0.5; AudioSource.loop[clickSound] = 0; // One-shot AudioSource.playing[clickSound] = 1; AudioSource.group[clickSound] = 0; // SFX // Auto-cleanup after sound finishes setTimeout(() => { removeEntity(world, clickSound); }, 500); // Assuming 500ms clip duration}Volume Control#
Audio groups allow independent volume control. Use the GameState component to control master, music, SFX, and voice volumes:
import { GameState } from '@web-engine/core'; // Set volume levels (0.0 to 1.0)GameState.masterVolume[gameStateEntity] = 0.8; // 80% overallGameState.musicVolume[gameStateEntity] = 0.6; // 60% musicGameState.sfxVolume[gameStateEntity] = 0.9; // 90% SFXGameState.voiceVolume[gameStateEntity] = 1.0; // 100% voice // Final volume calculation for audio sources:// finalVolume = AudioSource.volume * GroupVolume * masterVolumeBest Practices#
- Use 2D audio (spatial=0) for UI, music, and ambient sounds
- Use 3D audio (spatial=1) for positioned sounds (footsteps, gunshots, explosions)
- Set refDistance based on sound type: 1m for loud sounds, 5-10m for ambient
- Use audio groups to allow players to control volume independently
- Limit concurrent sounds to 8-16 for performance (browser audio contexts have limits)
- Use AudioZone for environmental effects like caves, tunnels, and buildings
- Implement sound pooling to avoid creating/destroying entities for frequent sounds
- Use pitch variation (0.9-1.1) for repeated sounds to avoid monotony