我有以下基于画布的游戏的 JS 代码。
var EXPLOSION = "sounds/explosion.wav";
function playSound(str, vol) {
var snd = new Audio();
snd.src = str;
snd.volume = vol;
snd.play();
}
function createExplosion() {
playSound(EXPLOSION, 0.5);
}
这是可行的,但是每次调用时它都会发送服务器请求来下载声音文件。或者,如果我事先声明 Audio 对象:
var snd = new Audio();
snd.src = EXPLOSION;
snd.volume = 0.5;
function createExplosion() {
snd.play();
}
这是可行的,但是如果在声音播放完毕之前调用 createExplosion 函数,则它根本不会播放声音。这意味着一次只允许播放一次声音文件 - 在发生多次爆炸的情况下它根本不起作用。
有什么方法可以正确播放音频文件并与其自身重叠多次?
我在我正在构建的俄罗斯方块游戏中一直在寻找这个解决方案,我认为这个解决方案是最好的。
function playSoundMove() {
var sound = document.getElementById("move");
sound.load();
sound.play();
}
只需将其加载并准备好即可。
您可以使用
cloneNode()
和 play()
复制节点。
我的音频元素如下所示:
<audio id="knight-audio" src="knight.ogg" preload="auto"></audio>
我有一个
onClick
监听器可以做到这一点:
function click() {
const origAudio = document.getElementById("knight-audio");
const newAudio = origAudio.cloneNode()
newAudio.play()
}
并且由于
audio
元素不会显示,因此您实际上不必将节点附加到任何东西。
我验证了客户端和服务器端,Chrome 仅尝试下载音频文件一次。
注意事项:我不确定对性能的影响,因为在我的网站上,该剪辑的播放次数不会超过页面的最大播放量 40 倍。如果您要做的事情比这大得多,您可能必须清理音频节点?
试试这个:
(function() {
var snds = {};
window.playSound(str,vol) {
if( !snds[str]) (snds[str] = new Audio()).src = str;
snds[str].volume = vol;
snds[str].play();
}
})();
然后,第一次调用它时,它会获取声音,但之后每次调用它时,它都会重用相同的声音对象。
编辑:您还可以预加载重复项,以允许声音一次播放多次:
(function() {
var snds = {}
window.playSound = function(str,vol) {
if( !snds[str]) {
snds[str] = [new Audio()];
snds[str][0].src = str;
}
var snd = snds[str], pointer = 0;
while( snd[pointer].playing) {
pointer++;
if( pointer >= snd.length) {
snd.push(new Audio());
snd[pointer].src = str;
}
}
snd[pointer].volume = vol;
snd[pointer].play();
};
})();
请注意,如果您播放的声音重叠太多,这将发送多个请求,但它应该很快返回“未修改”,并且只有当您播放的次数比以前多时才会这样做。
更多地依赖内存而不是处理时间,我们可以创建音频的多个克隆的数组,然后按顺序播放它们:
function gameSnd() {
tick_wav = new Audio('sounds/tick.wav');
victory_wav = new Audio('sounds/victory.wav');
counter = 0;
ticks = [];
for (var i = 0; i<10;i++)
ticks.push(tick_wav.cloneNode());
tick = function(){
counter = (counter + 1)%10;
ticks[counter].play();
}
victory = function(){
victory_wav.play();
}
}
在我的游戏中,我正在使用预加载,但在声音启动后(根本不预加载或在页面加载时预加载所有内容并不明智,有些声音在某些游戏中根本没有播放,为什么要加载它们)
const audio {};
audio.dataload = {'entity':false,'entityes':[],'n':0};
audio.dataload.ordernum = function() {
audio.dataload.n = (audio.dataload.n + 1)%10;
return audio.dataload.n;
}
audio.dataload.play = function() {
audio.dataload.entity = new Audio('/some.mp3');
for (let i = 0; i<10;i++) {
audio.dataload.entityes.push(audio.dataload.entity.cloneNode());
}
audio.dataload.entityes[audio.dataload.ordernum()].play();
}
audio.dataload.play() // plays sound and preload sounds to memory when it isn't
我创建了一个允许分层音频的类。这与其他答案非常相似,它创建具有相同 src 的另一个节点,但此类只会在必要时执行此操作。如果它已经创建了一个已完成的节点,它将重播该现有节点。 对此的另一个调整是最初获取音频并使用 blob 的 URL。我这样做是为了效率;因此每次创建新节点时都不必从外部获取 src。
class LayeredAudio {
url;
samples = [];
constructor(src){
fetch(src)
.then(response => response.blob())
.then((blob) => {
this.url = URL.createObjectURL(blob);
this.samples[0] = new Audio(this.url);
});
}
play(){
if(!this.samples.find(e => e.paused)?.play()){
this.samples.push(new Audio(this.url))
this.samples[this.samples.length - 1].play()
}
}
}
const aud = new LayeredAudio("URL");
aud.play()
var audio = new Array(10);
// The length of the audio array is how many times
// the audio can overlap
for (var i = 0; i < audio.length; i++) {
audio[i] = new Audio("your audio");
}
function PlayAudio() {
// Whenever you want to play it call this function
audio[audioIndex].play();
audioIndex++;
if(audioIndex > audio.length - 1) {
audioIndex = 0;
}
}
<button id="btn" onclick="clickMe()">ding</button>
<script>
function clickMe() {
const newAudio = new Audio("./ding.mp3");
newAudio.play()
}
</script>