Web Engine Docs
Preparing documentation
Use the search bar to quickly find any topic
Preparing documentation
Use the search bar to quickly find any topic
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.
The AudioSource component represents a sound emitter in 3D space. Supports both 2D ambient sounds and 3D positional audio with distance-based rolloff.
| 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) |
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 pitch
Audio Groups
Audio groups (SFX, Music, Voice) allow independent volume control via the GameState component. Players can adjust music volume without affecting sound effects.
For 3D spatial audio, the key properties are refDistance and maxDistance:
// 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 dropoff
The AudioListener component marks the entity that receives audio. Typically attached to the camera entity for first-person perspective.
| Property | Type | Default | Description |
|---|---|---|---|
| active | ui8 | 1 | Listener active state (0=inactive, 1=active) |
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 cameraAudioListener.active[camera] = 1;
Single Listener
Only one AudioListener should be active at a time. Multiple listeners can cause audio artifacts and incorrect spatial positioning.
The AudioZone component defines a spatial region that affects audio sources within it, applying reverb effects and occlusion for environmental audio.
| 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) |
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 entranceAudioZone.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.
// 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 footstepsAudioSource.pitch[player] = 0.8 + speed * 0.4; // Faster = higher pitch} else {AudioSource.playing[player] = 0; // Stop footsteps}
// 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 click sound (2D, one-shot)function playUIClick() {const clickSound = world.addEntity();addComponent(world, AudioSource, clickSound);AudioSource.assetId[clickSound] = uiClickClipId;AudioSource.spatial[clickSound] = 0; // 2DAudioSource.volume[clickSound] = 0.5;AudioSource.loop[clickSound] = 0; // One-shotAudioSource.playing[clickSound] = 1;AudioSource.group[clickSound] = 0; // SFX// Auto-cleanup after sound finishessetTimeout(() => {removeEntity(world, clickSound);}, 500); // Assuming 500ms clip duration}
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 * masterVolume