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#

PropertyTypeDefaultDescription
spatialui81Spatial audio enabled (0=2D/ambient, 1=3D/positional)
volumef321.0Volume level (0.0 to 1.0)
loopui80Loop mode (0=play once, 1=loop)
playingui80Playback state (0=stopped, 1=playing)
assetIdui320Audio clip asset ID
refDistancef321.0Reference distance for volume rolloff (meters)
maxDistancef32100.0Maximum audible distance (meters)
groupui80Audio group: 0=SFX, 1=Music, 2=Voice
pitchf321.0Pitch multiplier (1.0=normal, 2.0=octave up)
spatialBlendf321.02D/3D blend (0.0=2D, 1.0=3D)
rolloffFactorf321.0Distance 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 spatial
AudioSource.volume[footstepSource] = 0.7;
AudioSource.loop[footstepSource] = 1; // Loop
AudioSource.playing[footstepSource] = 1; // Start playing
AudioSource.refDistance[footstepSource] = 2.0; // Loud within 2m
AudioSource.maxDistance[footstepSource] = 20.0; // Audible up to 20m
AudioSource.group[footstepSource] = 0; // SFX group
// Create 2D ambient music
const musicSource = world.addEntity();
addComponent(world, AudioSource, musicSource);
AudioSource.assetId[musicSource] = musicClipId;
AudioSource.spatial[musicSource] = 0; // 2D ambient
AudioSource.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-shot
AudioSource.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.

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 1m
AudioSource.maxDistance[gunshot] = 150.0; // Audible up to 150m
AudioSource.rolloffFactor[gunshot] = 2.0; // Fast dropoff
// Quiet, ambient sound (wind)
AudioSource.refDistance[wind] = 10.0; // Max volume within 10m
AudioSource.maxDistance[wind] = 50.0; // Audible up to 50m
AudioSource.rolloffFactor[wind] = 0.5; // Slow dropoff

AudioListener#

The AudioListener component marks the entity that receives audio. Typically attached to the camera entity for first-person perspective.

Properties#

PropertyTypeDefaultDescription
activeui81Listener active state (0=inactive, 1=active)

Usage#

import { addComponent, AudioListener } from '@web-engine/core';
// Attach to camera for first-person audio
const 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#

PropertyTypeDefaultDescription
shapeTypeui80Zone shape: 0=sphere, 1=box
innerRadiusf325.0Inner radius/bounds (no effect)
outerRadiusf3210.0Outer radius/bounds (full effect)
reverbPresetui80Reverb preset (0=none, 1+=preset ID)
occlusionFactorf320.0Occlusion amount (0=none, 1=fully muffled)
activeui81Zone 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; // Sphere
AudioZone.innerRadius[caveZone] = 5.0; // No effect within 5m
AudioZone.outerRadius[caveZone] = 15.0; // Full reverb at 15m
AudioZone.reverbPreset[caveZone] = 3; // Cave preset
AudioZone.occlusionFactor[caveZone] = 0.0; // No occlusion
AudioZone.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; // Box
AudioZone.innerRadius[wallZone] = 2.0;
AudioZone.outerRadius[wallZone] = 5.0;
AudioZone.reverbPreset[wallZone] = 0; // No reverb
AudioZone.occlusionFactor[wallZone] = 0.7; // 70% muffled
AudioZone.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 player
const 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 speed
const 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 music
const bgMusic = world.addEntity();
addComponent(world, AudioSource, bgMusic);
AudioSource.assetId[bgMusic] = musicTrackId;
AudioSource.spatial[bgMusic] = 0; // 2D (no position)
AudioSource.volume[bgMusic] = 0.3; // Quiet background
AudioSource.loop[bgMusic] = 1; // Loop forever
AudioSource.playing[bgMusic] = 1;
AudioSource.group[bgMusic] = 1; // Music group
// Crossfade to new track
const fadeOutDuration = 2.0; // seconds
const fadeInDuration = 2.0;
// Gradually decrease old track volume
AudioSource.volume[oldTrack] -= deltaTime / fadeOutDuration;
if (AudioSource.volume[oldTrack] <= 0) {
AudioSource.playing[oldTrack] = 0;
removeEntity(world, oldTrack);
}
// Gradually increase new track volume
AudioSource.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% overall
GameState.musicVolume[gameStateEntity] = 0.6; // 60% music
GameState.sfxVolume[gameStateEntity] = 0.9; // 90% SFX
GameState.voiceVolume[gameStateEntity] = 1.0; // 100% voice
// Final volume calculation for audio sources:
// finalVolume = AudioSource.volume * GroupVolume * masterVolume

Best 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
Components | Web Engine Docs | Web Engine Docs