Skip to content

BabylonJS - 播放声音与音乐

音频是创建沉浸式游戏和应用的关键组成部分。Babylon.js 集成了浏览器的 Web Audio API,提供了 Sound 类,可以轻松加载、播放和管理音效及背景音乐。你可以根据事件(如碰撞、按钮点击、角色动作)触发声音,或者播放环境背景音轨。

Babylon.js 声音引擎支持常见的音频格式,如 MP3、WAV、Ogg 等(支持情况取决于浏览器)。它提供了循环播放、自动播放、音量控制、播放速率调整以及空间音频(位置声音)等功能。

通过提供名称、音频文件 URL、场景、可选的加载完成并准备播放时执行的回调函数以及一个选项对象来创建 Sound 对象。

import * as BABYLON from '@babylonjs/core';
// Assuming 'scene' is defined
// Define sound options
const soundOptions = {
loop: true, // Repeat the sound indefinitely
autoplay: true, // Start playing as soon as loaded
volume: 0.8 // Volume level (0.0 to 1.0+)
// playbackRate: 1.0 // Speed of playback
};
// Callback function executed when sound is ready
const soundReadyCallback = () => {
console.log("Background music ready and playing.");
// You could potentially call music.play() here if autoplay was false
};
// Create the sound object
const backgroundMusic = new BABYLON.Sound(
"bgMusic", // Name for the sound
"sounds/ambient_track.mp3", // Path to the audio file
scene, // The scene to attach to
soundReadyCallback, // Function to call when ready
soundOptions // Options object
);
  • name:声音的唯一字符串标识符。
  • url:音频文件的路径或 URL。
  • scene:Babylon.js 场景实例。
  • readyToPlayCallback:(可选)声音加载并解码后调用的函数。
  • options:(可选)包含以下属性的对象:
    • loop (boolean):声音是否应该循环播放。
    • autoplay (boolean):声音准备就绪时是否应自动播放。
    • volume (number):音量(通常范围 0.0 到 1.0,可以更高)。
    • playbackRate (number):声音的播放速度(1.0 为正常速度)。
    • spatialSound (boolean):启用 3D 空间化(位置音频)。默认为 false。
    • distanceModel (string):音量随距离衰减的方式(‘linear’、‘inverse’、‘exponential’)。默认为 ‘linear’。与 spatialSound 一起使用。
    • maxDistance (number):声音不再可听或显著衰减的距离(适用于 ‘linear’ 模型)。默认为 100。与 spatialSound 一起使用。
    • rolloffFactor (number):声音对于 ‘inverse’ 和 ‘exponential’ 模型衰减的速度。默认为 1。
    • refDistance (number):用于音量减少计算的参考距离。默认为 1。
    • panningModel (string):立体声声像算法(‘equalpower’ 或 ‘HRTF’)。默认为 ‘equalpower’。
    • useCustomAttenuation (boolean):允许定义自定义衰减函数。
    • customAttenuationFunction (function):(currentVolume, currentDistance, maxDistance, refDistance, rolloffFactor) => newVolume。

请确保你的项目目录中有一个 sounds 文件夹,其中包含指定的音频文件(例如,ambient_track.mp3、explosion.wav)。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Babylon.js - Background Music</title>
<script src="https://cdn.babylonjs.com/babylon.js"></script>
<style>
html, body { overflow: hidden; width: 100%; height: 100%; margin: 0; padding: 0; }
#renderCanvas { width: 100%; height: 100%; touch-action: none; }
#info { position: absolute; top: 10px; left: 10px; color: white; background-color: rgba(0,0,0,0.5); padding: 5px; }
</style>
</head>
<body>
<canvas id="renderCanvas"></canvas>
<div id="info">Loading Scene and Sound...</div>
<script type="text/javascript">
const canvas = document.getElementById("renderCanvas");
const engine = new BABYLON.Engine(canvas, true);
const infoDiv = document.getElementById("info");
const createScene = function () {
const scene = new BABYLON.Scene(engine);
scene.clearColor = new BABYLON.Color4(0, 0.1, 0.2, 1.0);
const camera = new BABYLON.ArcRotateCamera("Camera", -Math.PI / 2, Math.PI / 2.5, 5, BABYLON.Vector3.Zero(), scene);
camera.attachControl(canvas, true);
const light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene);
// Simple box to look at
const box = BABYLON.MeshBuilder.CreateBox("box", {}, scene);
// --- Sound Setup ---
const soundOptions = {
loop: true,
autoplay: true,
volume: 0.5
};
infoDiv.textContent = "Loading sound: sounds/ambient_track.mp3";
let backgroundMusic = null;
try {
backgroundMusic = new BABYLON.Sound("bgMusic", "sounds/ambient_track.mp3", scene,
() => {
// Sound is ready to play (or already playing due to autoplay)
infoDiv.textContent = "Scene Ready. Background Music Playing.";
console.log("Music loaded and ready.");
},
soundOptions
);
// Handle potential loading/decoding errors after creation
backgroundMusic.onErrorObservable.add((error) => {
console.error("Error loading/playing sound:", error);
infoDiv.textContent = "Error loading sound. Check console.";
});
} catch (error) {
console.error("Failed to initialize sound:", error);
infoDiv.textContent = "Failed to initialize sound. Check console and file path.";
}
// ---------------------
return scene;
};
const scene = createScene();
engine.runRenderLoop(function () {
scene.render();
});
window.addEventListener("resize", function () {
engine.resize();
});
</script>
</body>
</html>

此演示显示一个简单的方块。页面加载时,它尝试从 sounds 文件夹中加载 ambient_track.mp3 文件。如果成功,音乐将以 50% 的音量自动循环播放,并且左上角的文本将更新为“Scene Ready. Background Music Playing.”(场景准备就绪。背景音乐播放中)。如果声音文件无法加载或解码,则会在文本叠加层和浏览器控制台中出现错误消息。

通常,你希望响应特定事件播放声音,而不是自动播放。将 autoplay 设置为 false,并在需要时调用 Sound 对象的 play() 方法。你可能希望在调用 play 之前确保声音已加载,这通常在 readyToPlayCallback 内部完成,或者通过检查 isReady() 状态来完成。

// Example: Play sound on click
let gunshotSound = null;
gunshotSound = new BABYLON.Sound("gunshot", "sounds/explosion.wav", scene, () => {
// Sound is ready, enable playing it
window.addEventListener("mousedown", (evt) => {
if (evt.button === 0 && gunshotSound) {
gunshotSound.play();
}
});
console.log("Gunshot sound ready.");
}, { autoplay: false });
// Example: Play sound on key press
window.addEventListener("keydown", (evt) => {
// Example: Play on space bar (keyCode 32)
if (evt.keyCode === 32 && gunshotSound && gunshotSound.isReady()) {
gunshotSound.play();
}
});

Sound 对象提供了控制播放的方法:

  • sound.play(time?: number):开始播放,可选地从指定时间(秒)开始。
  • sound.pause():暂停播放。
  • sound.stop():停止播放并重置位置到开头。
  • sound.setVolume(volume: number, time?: number):设置音量,可选地在一定时长内淡入淡出。
  • sound.setPlaybackRate(rate: number):改变播放速度。
  • sound.currentTime:获取当前播放位置(秒)。

你可以使用 onended 事件处理程序或 onEndedObservable 在声音播放结束时做出反应:

// Using the observable (recommended)
sound.onEndedObservable.add(() => {
console.log("Sound finished playing.");
// Maybe trigger another sound or game event
});
// Using the older onended property
/*
sound.onended = () => {
console.log("Sound finished playing.");
};
*/

空间声音使音频听起来源自 3D 场景中的特定位置。当听众(摄像机)相对于声源移动时,音量和声像(左/右平衡)会发生变化。要使用空间声音,在选项中设置 spatialSound: true。

const spatialSoundOptions = {
loop: false,
autoplay: false,
spatialSound: true,
distanceModel: "exponential", // Sound fades more naturally over distance
maxDistance: 50, // Becomes very quiet beyond 50 units
rolloffFactor: 2
};
const explosionSound = new BABYLON.Sound("explosion", "sounds/explosion.wav", scene, null, spatialSoundOptions);

对于空间声音,你通常希望声音的位置跟随移动的对象。使用 attachToMesh() 方法将声音的位置链接到网格的位置。

// Assuming 'movingBox' is a mesh in your scene
// and 'explosionSound' is a spatial sound
explosionSound.attachToMesh(movingBox);
// Now, if movingBox moves, the perceived source of explosionSound moves with it.
// You can play the sound when needed:
// movingBox.actionManager = new BABYLON.ActionManager(scene);
// movingBox.actionManager.registerAction(
// new BABYLON.ExecuteCodeAction(BABYLON.ActionManager.OnPickTrigger, () => {
// explosionSound.play();
// })
// );

你可以控制 Babylon.js 音频引擎管理的所有声音的主音量:

// Set global volume to 70%
BABYLON.Engine.audioEngine.setGlobalVolume(0.7);
// Get the global volume
const currentGlobalVolume = BABYLON.Engine.audioEngine.getGlobalVolume();
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Babylon.js - Spatial Sound</title>
<script src="https://cdn.babylonjs.com/babylon.js"></script>
<style>
html, body { overflow: hidden; width: 100%; height: 100%; margin: 0; padding: 0; }
#renderCanvas { width: 100%; height: 100%; touch-action: none; }
#info { position: absolute; bottom: 10px; left: 10px; color: white; background-color: rgba(0,0,0,0.5); padding: 5px; }
</style>
</head>
<body>
<canvas id="renderCanvas"></canvas>
<div id="info">Click the box to play spatial sound</div>
<script type="text/javascript">
const canvas = document.getElementById("renderCanvas");
const engine = new BABYLON.Engine(canvas, true);
const infoDiv = document.getElementById("info");
const createScene = function () {
const scene = new BABYLON.Scene(engine);
scene.clearColor = new BABYLON.Color4(0.2, 0.1, 0.1, 1.0);
// Listener position is camera position
scene.audioEnabled = true; // Ensure audio is enabled
const camera = new BABYLON.ArcRotateCamera("Camera", Math.PI / 4, Math.PI / 3, 20, BABYLON.Vector3.Zero(), scene);
camera.attachControl(canvas, true);
const light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene);
const box = BABYLON.MeshBuilder.CreateBox("box", { size: 2 }, scene);
const boxMat = new BABYLON.StandardMaterial("boxMat", scene);
boxMat.diffuseColor = new BABYLON.Color3(1, 0.5, 0.2); // Orange
box.material = boxMat;
// --- Spatial Sound Setup ---
let explosionSound = null;
const spatialSoundOptions = {
loop: false,
autoplay: false,
spatialSound: true,
distanceModel: "exponential",
maxDistance: 30,
rolloffFactor: 1.5
};
try {
explosionSound = new BABYLON.Sound("explosion", "sounds/explosion.wav", scene,
() => { console.log("Explosion sound ready."); infoDiv.textContent = "Click the box."; },
spatialSoundOptions);
// Attach sound to the box
explosionSound.attachToMesh(box);
// Play sound on click using Action Manager
box.actionManager = new BABYLON.ActionManager(scene);
box.actionManager.registerAction(
new BABYLON.ExecuteCodeAction(BABYLON.ActionManager.OnPickTrigger, () => {
if (explosionSound && explosionSound.isReady()) {
explosionSound.play();
infoDiv.textContent = "Playing sound...";
explosionSound.onEndedObservable.addOnce(() => { infoDiv.textContent = "Click the box."; });
}
})
);
} catch (error) {
console.error("Failed to initialize sound:", error);
infoDiv.textContent = "Error loading sound. Check console.";
}
// --------------------------
// Animate box movement
let angle = 0;
scene.onBeforeRenderObservable.add(() => {
angle += 0.01;
box.position.x = 5 * Math.sin(angle);
box.position.z = 5 * Math.cos(angle);
});
return scene;
};
const scene = createScene();
engine.runRenderLoop(function () { scene.render(); });
window.addEventListener("resize", function () { engine.resize(); });
</script>
</body>
</html>

此演示显示一个沿着圆形路径移动的橙色方块。从 sounds 文件夹加载的爆炸声(explosion.wav)被作为一个空间声音并附加到方块上。当你点击方块时,爆炸声会播放。随着方块的移动和你旋转摄像机,你应该会注意到声音的感知位置和音量会根据其相对于摄像机(听众)的位置发生变化。底部左侧的文本提示你点击方块,并在声音播放时更新文本。