05-添加声音
9.1.3 添加声音
第7章介绍了如何向画布应用程序中添加一个强健的声音管理器以及相关的知识。如果读者对相关概念还不熟悉,请先阅读第 7 章。本章只介绍向游戏中添加声音所必需的代码。
街机游戏需要同时播放许多声音。有些时候还会非常快速地连续播放这些声音。在第7章的介绍中,使用HTML5的
提示
在写作本书时,Windows版本的Opera浏览器为播放声音提供了最好的支持。如果读者在这个游戏或本书其他示例,以及读者自己的创建的游戏中遇到了与声音相关的问题,请在Opera浏览器中进行测试。
1.游戏中的声音
向游戏中添加以下3种声音。
- 玩家发射导弹时的声音(shoot1.mp3、shootl.ogg、shootl.wav)。
- 爆炸的声音(explode1.mp3、explode1.ogg、explode1.wav)。
- 飞碟发射导弹的声音(saucershoot.mp3、saucershoot.ogg、saucershoot.wav)。
在为本章准备的下载文件中,为每个声音提供了3种不同格式的文件:.wav、.ogg和.mp3。
2.向游戏中添加声音对象实例变量和管理器变量
在游戏代码的变量定义段落中,创建一些变量,使其能与在第 7 章中创建的声音管理器代码协同工作。为每个声音创建3个对象实例,并将它们放入对象池中。
var explodeSound;
var explodeSound2;
var explodeSound3;
var shootSound;
var shootSound2;
var shootSound3;
var saucershootSound;
var saucershootSound2;
var saucershootSound3;
另外,还需要创建一个数组保存对象池中的声音。
var soundPool = new Array();
为了指定要播放哪个声音,分别为每个声音指定一个常量。当播放声音时,只需要使用该常量就可以了。这种方法便于更换声音的名字。如果以后需要修改声音,那么在重构的代码中进行会更容易。
var SOUND_EXPLODE = "explode1";
var SOUND_SHOOT = "shoot1";
var SOUND_SAUCER_SHOOT = "saucershoot"
最后,需要定义一个名为audioType的变量。在声音管理器中用这个变量代表当前的文件类型。
3.加载声音和图片表资源
第 7 章曾使用了一个函数,该函数在游戏状态机处于空闲等待状态的时候会加载所有游戏的资源。将这些代码添加到这个游戏中一个名为gamestateInit()的函数中。
提示
目前,声音还不能在所有的浏览器以相同的方式工作。在这个示例游戏中,我们对所有的图片和声音进行了预加载。但是对于IE9和IE10来说,预加载有时可能会出问题。如果在IE中测试游戏时出现预加载问题,则需要将预加载的项目数量从16改为7,即不预加载声音。
function gameStateInit(){
loadCount = 0;
itemsToLoad = 16; //在IE中改为7
explodeSound = document.createElement("audio");
document.body.appendChild(explodeSound);
audioType = supportedAudioFormat(explodeSound);
explodeSound.setAttribute("src", "explode1." + audioType);
explodeSound.addEventListener("canplaythrough",itemLoaded,false);
explodeSound2 = document.createElement("audio");
document.body.appendChild(explodeSound2);
explodeSound2.setAttribute("src", "explode1." + audioType);
explodeSound2.addEventListener("canplaythrough",itemLoaded,false);
explodeSound3 = document.createElement("audio");
document.body.appendChild(explodeSound3);
explodeSound3.setAttribute("src", "explode1." + audioType);
explodeSound3.addEventListener("canplaythrough",itemLoaded,false);
shootSound = document.createElement("audio");
audioType = supportedAudioFormat(shootSound);
document.body.appendChild(shootSound);
shootSound.setAttribute("src", "shoot1." + audioType);
shootSound.addEventListener("canplaythrough",itemLoaded,false);
shootSound2 = document.createElement("audio");
document.body.appendChild(shootSound2);
shootSound2.setAttribute("src", "shoot1." + audioType);
shootSound2.addEventListener("canplaythrough",itemLoaded,false);
shootSound3 = document.createElement("audio");
document.body.appendChild(shootSound3);
shootSound3.setAttribute("src", "shoot1." + audioType);
shootSound3.addEventListener("canplaythrough",itemLoaded,false);
saucershootSound = document.createElement("audio");
audioType = supportedAudioFormat(saucershootSound);
document.body.appendChild(saucershootSound);
saucershootSound.setAttribute("src", "saucershoot." + audioType);
saucershootSound.addEventListener("canplaythrough",itemLoaded,false);
saucershootSound2 = document.createElement("audio");
document.body.appendChild(saucershootSound2);
saucershootSound2.setAttribute("src", "saucershoot." + audioType);
saucershootSound2.addEventListener("canplaythrough",itemLoaded,false);
saucershootSound3 = document.createElement("audio");
document.body.appendChild(saucershootSound3);
saucershootSound3.setAttribute("src", "saucershoot." + audioType);
saucershootSound3.addEventListener("canplaythrough",itemLoaded,false);
shipTiles = new Image();
shipTiles.src = "ship_tiles.png";
shipTiles.onload = itemLoaded;
shipTiles2 = new Image();
shipTiles2.src = "ship_tiles2.png";
shipTiles2.onload = itemLoaded;
saucerTiles= new Image();
saucerTiles.src = "saucer.png";
saucerTiles.onload = itemLoaded;
largeRockTiles = new Image();
largeRockTiles.src = "largerocks.png";
largeRockTiles.onload = itemLoaded;
mediumRockTiles = new Image();
mediumRockTiles.src = "mediumrocks.png";
mediumRockTiles.onload = itemLoaded;
smallRockTiles = new Image();
smallRockTiles.src = "smallrocks.png";
smallRockTiles.onload = itemLoaded;
particleTiles = new Image();
particleTiles.src = "parts.png";
particleTiles.onload = itemLoaded;
switchGameState(GAME_STATE_WAIT_FOR_LOAD);
}
注意,为每个声音创建3个实例对象。尽管它们都使用了同一个声音文件,但必须分别对它们进行预加载。在这个函数中,还要加载图片表。itemToLoaded和loadCount都是应用程序作用域范围的变量。在事件回调函数itemLoaded()中,使用itemToLoaded变量与loadCount变量进行检测。每个资源加载后都会调用该函数。这种做法使得在所有资源加载完毕之后,可以比较容易地改变应用的状态并开始运行游戏。现在,简要地看一下itemLoaded()函数。
function itemLoaded(event){
loadCount++;
//console.log("loading:" + loadCount)
if (loadCount >= itemsToLoad){
shootSound.removeEventListener("canplaythrough",itemLoaded, false);
shootSound2.removeEventListener("canplaythrough",itemLoaded, false);
shootSound3.removeEventListener("canplaythrough",itemLoaded, false);
explodeSound.removeEventListener("canplaythrough",itemLoaded,false);
explodeSound2.removeEventListener("canplaythrough",itemLoaded,false);
explodeSound3.removeEventListener("canplaythrough",itemLoaded,false);
saucershootSound.removeEventListener("canplaythrough",itemLoaded,false);
saucershootSound2.removeEventListener("canplaythrough",itemLoaded,false);
saucershootSound3.removeEventListener("canplaythrough",itemLoaded,false);
soundPool.push({name:"explode1", element:explodeSound, played:false});
soundPool.push({name:"explode1", element:explodeSound2, played:false});
soundPool.push({name:"explode1", element:explodeSound3, played:false});
soundPool.push({name:"shoot1", element:shootSound, played:false});
soundPool.push({name:"shoot1", element:shootSound2, played:false});
soundPool.push({name:"shoot1", element:shootSound3, played:false});
soundPool.push({name:"saucershoot", element:saucershootSound,
played:false});
soundPool.push({name:"saucershoot", element:saucershootSound2,
played:false});
soundPool.push({name:"saucershoot", element:saucershootSound3,
played:false});
switchGameState(GAME_STATE_TITLE)
}
}
在这个函数中,首先移除每个加载项的事件监听器,然后将声音添加到声音池中。最后,调用switchGameState()函数,将游戏状态转到标题屏。
4.播放声音
播放声音将使用第7章的playSound()函数。在此处不会重复给出函数代码。例A-2将给出游戏的全部代码,其中也包括了这个函数。在代码中许多需要播放声音的地方调用playSound()函数。例如,本章之前的createExplode()函数中包含了以下代码。
playSound(SOUND_EXPLODE,.5);
当需要播放声音池中的一个实例时,可以调用playSound()函数,并传递代表声音的常量名以及音量错位参数。如果在声音池中有可用的实例,则使用该实例播放声音。
现在学习应用池的另一种类型——对象池。