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
Learn how to play sounds, control playback, and handle audio events from your game scripts.
Web Engine provides multiple ways to play and control audio from scripts. Use the SoundManager API for one-shot sound effects, or control AudioSource components directly for persistent, looping sounds.
For one-shot sound effects (explosions, gunshots, UI clicks), use theSoundManager singleton:
import { SoundManager, AudioChannel } from '@web-engine-dev/core';export default (api) => {const soundManager = SoundManager.getInstance();return {onStart() {// Initialize audio systemsoundManager.init();},onUpdate(dt) {// Play sound effect when player shootsif (api.input.fireDown) {const handle = soundManager.play(gunShotAssetId, {volume: 0.8,pitch: 0.9 + Math.random() * 0.2, // Random pitch variationspatial: false, // 2D soundgroup: AudioChannel.SFX,priority: 150, // Medium priorityfadeIn: 0.01, // Quick fade in});// Store handle if you need to stop it laterapi.log('Playing sound, handle:', handle);}}};};
Plays a one-shot sound and returns a handle for controlling it:
const handle = soundManager.play(assetId, {volume: 1.0, // Volume (0.0 - 1.0)pitch: 1.0, // Playback speed (0.01 - 4.0)loop: false, // Loop the soundspatial: true, // Enable 3D spatial audiorefDistance: 1.0, // Distance for volume rolloffmaxDistance: 100.0, // Maximum audible distancerolloffFactor: 1.0, // Distance attenuation factorgroup: AudioChannel.SFX, // Audio channel (SFX, Music, Voice)priority: 128, // Voice stealing priority (0-255, higher = less likely to steal)fadeIn: 0.0, // Fade in duration in secondsfadeOut: 0.0, // Fade out duration in seconds});// Returns -1 if failed, or handle >= 0 on success
Stops a playing sound by handle:
// Stop with configured fade outsoundManager.stop(handle);// Stop immediately (no fade)soundManager.stop(handle, true);
Updates the 3D position of a spatial sound:
export default (api) => {let bulletSoundHandle = -1;return (dt) => {// Play bullet whiz sound at bullet positionif (api.input.fireDown) {bulletSoundHandle = soundManager.play(whizAssetId, {spatial: true,refDistance: 2.0,maxDistance: 50.0,});}// Update sound position as bullet movesif (bulletSoundHandle >= 0) {const bulletPos = getBulletPosition();soundManager.updatePosition(bulletSoundHandle,bulletPos.x,bulletPos.y,bulletPos.z);// Check if sound is still playingif (!soundManager.isPlaying(bulletSoundHandle)) {bulletSoundHandle = -1; // Sound finished}}};};
Check if a sound is still playing:
if (soundManager.isPlaying(handle)) {api.log('Sound still playing');} else {api.log('Sound finished or stopped');}
For persistent, looping sounds, control AudioSource components directly:
import { AudioSource, AudioChannel } from '@web-engine-dev/core';export default (api) => {return {onStart() {// Setup looping engine soundAudioSource.assetId[api.entity] = engineSoundAssetId;AudioSource.volume[api.entity] = 0.5;AudioSource.pitch[api.entity] = 0.8;AudioSource.loop[api.entity] = 1; // Enable loopingAudioSource.spatial[api.entity] = 1;AudioSource.refDistance[api.entity] = 5.0;AudioSource.maxDistance[api.entity] = 100.0;AudioSource.group[api.entity] = AudioChannel.SFX;// Start playingAudioSource.playing[api.entity] = 1;},onUpdate(dt) {// Adjust pitch based on speedconst speed = api.velocity.length();const maxSpeed = 100;const enginePitch = 0.5 + (speed / maxSpeed) * 1.5;AudioSource.pitch[api.entity] = enginePitch;// Adjust volume based on throttleconst throttle = api.input.move.y; // Forward/back inputconst engineVolume = 0.3 + Math.abs(throttle) * 0.7;AudioSource.volume[api.entity] = engineVolume;},onDestroy() {// Stop playing when entity is destroyedAudioSource.playing[api.entity] = 0;}};};
Web Engine provides logic nodes for visual scripting with audio:
// audio/play_sound logic node// Inputs:// - exec: trigger// - assetId: asset ID// - volume: number (0.0-1.0)// - pitch: number (0.01-4.0)// - loop: boolean// - spatial: boolean// - priority: number (0-255)// - fadeIn: number (seconds)// - fadeOut: number (seconds)//// Outputs:// - exec: trigger (fires after sound starts)// - handle: number (sound handle for stop node)// For one-shot sounds, plays via SoundManager// For looping sounds, uses AudioSource component
// audio/stop_sound logic node// Inputs:// - exec: trigger// - handle: number (from Play Sound output)// - immediate: boolean (skip fade out if true)//// Outputs:// - exec: trigger (fires after stop command)// Stops sound by handle (works with both SoundManager and AudioSource)
// audio/set_volume logic node// Inputs:// - exec: trigger// - volume: number (0.0-1.0)//// Outputs:// - exec: trigger//// Sets volume on the entity's AudioSource component
Play 3D positional sounds at world positions:
export default (api) => {const soundManager = SoundManager.getInstance();return {// Play explosion at impact pointonCollision(other) {const impactPos = api.position;const handle = soundManager.play(explosionAssetId, {volume: 1.0,spatial: true,refDistance: 10.0, // Loud explosionmaxDistance: 200.0, // Audible from far awayrolloffFactor: 0.8, // Slow falloff for large explosiongroup: AudioChannel.SFX,priority: 200, // High priority, don't stealfadeIn: 0.02,fadeOut: 0.5,});// Position is locked at creation for one-shots// To move a sound, you must update it every framesoundManager.updatePosition(handle,impactPos.x,impactPos.y,impactPos.z);}};};
export default (api) => {const soundManager = SoundManager.getInstance();let stepTimer = 0;const stepInterval = 0.4; // Steps every 0.4 secondslet isLeftFoot = true;// Different surfaces have different soundsconst footstepSounds = {grass: grassStepAssetId,concrete: concreteStepAssetId,metal: metalStepAssetId,};return (dt) => {const speed = api.velocity.length();const isMoving = speed > 0.1;if (isMoving) {stepTimer += dt;// Adjust step rate based on speedconst currentInterval = stepInterval / (speed / 5.0);if (stepTimer >= currentInterval) {stepTimer = 0;isLeftFoot = !isLeftFoot;// Detect surface type (simplified)const surfaceType = api.groundMaterial || 'grass';const assetId = footstepSounds[surfaceType] || footstepSounds.grass;// Play footstep with variationsoundManager.play(assetId, {volume: 0.6,pitch: 0.95 + Math.random() * 0.1, // Slight pitch variationspatial: true,refDistance: 2.0,maxDistance: 30.0,group: AudioChannel.SFX,priority: 100,});}} else {stepTimer = 0; // Reset when stopped}};};
export default (api) => {let currentMusicEntity = null;let fadeTimer = 0;const fadeDuration = 2.0; // 2 second crossfadeconst musicTracks = {explore: exploreMusicAssetId,combat: combatMusicAssetId,victory: victoryMusicAssetId,};function playMusic(trackName, fadeIn = true) {// Stop current music with fadeif (currentMusicEntity) {AudioSource.playing[currentMusicEntity] = 0;}// Create new music entityconst entity = api.world.createEntity();currentMusicEntity = entity;AudioSource.assetId[entity] = musicTracks[trackName];AudioSource.volume[entity] = fadeIn ? 0.0 : 0.7;AudioSource.loop[entity] = 1;AudioSource.spatial[entity] = 0; // 2D musicAudioSource.group[entity] = AudioChannel.Music;AudioSource.playing[entity] = 1;if (fadeIn) {fadeTimer = 0; // Start fade in}}return (dt) => {// Fade in musicif (fadeTimer < fadeDuration && currentMusicEntity) {fadeTimer += dt;const progress = Math.min(1.0, fadeTimer / fadeDuration);AudioSource.volume[currentMusicEntity] = progress * 0.7;}// Check game state and switch musicif (api.isInCombat && currentTrack !== 'combat') {playMusic('combat');} else if (!api.isInCombat && currentTrack === 'combat') {playMusic('explore');}};};
export default (api) => {const soundManager = SoundManager.getInstance();const ambientSounds = [{ id: birdChirpAssetId, interval: 5.0, timer: 0 },{ id: windGustAssetId, interval: 8.0, timer: 0 },{ id: distantThunderAssetId, interval: 15.0, timer: 0 },];return (dt) => {// Play random ambient sounds at intervalsambientSounds.forEach(sound => {sound.timer += dt;if (sound.timer >= sound.interval) {sound.timer = 0;// Random position around playerconst angle = Math.random() * Math.PI * 2;const distance = 20 + Math.random() * 30;const x = api.position.x + Math.cos(angle) * distance;const z = api.position.z + Math.sin(angle) * distance;const y = api.position.y;const handle = soundManager.play(sound.id, {volume: 0.3 + Math.random() * 0.2,pitch: 0.9 + Math.random() * 0.2,spatial: true,refDistance: 5.0,maxDistance: 100.0,group: AudioChannel.SFX,priority: 50, // Low priority});soundManager.updatePosition(handle, x, y, z);// Randomize next intervalsound.interval = (5.0 + Math.random() * 10.0);}});};};