当前位置: 首页 > Web前端 > HTML5

【教你做小游戏】只需几行原生JS,写一个函数,播放音效,播放BGM,切换BGM

时间:2023-04-05 01:06:48 HTML5

我是公众号线下派对游戏的作者HullQin(欢迎来到关注公众号,发送加微信,交朋友),转载本文需作者HullQin授权。我独立开发了《联机桌游合集》,这是一个网页,在这里你可以轻松地和朋友一起玩网络游戏,五子棋等游戏,不收费,也没有广告。还为GameJam2022开发了《Dice Crush》,喜欢的话可以关注我HullQin哦~有空我会分享制作游戏的相关技术。问题描述做一个小游戏,需要播放音效和BGM。如何?首先,我们区分两个概念:背景音乐(简称BGM)和音效(SEforSoundEffect)。背景音乐需要循环播放。很长的音乐,中途可能会要求暂停或切歌。通常只有一个BGM同时播放。音效在需要的时候播放一次,比较短的声音通常和动画、用户操作一起触发。许多SE可以同时叠加。一玩就完了。所以,这两个需求是不同的,我们最好分开实现。必备知识浏览器如何播放声音目前前端可以通过audio标签来播放声音,引入几个重要的属性:src:声音资源的URL。type:声音资源的类型,将以这种方式进行解码。例如,.mp3应使用audio/mpeg,.ogg应使用audio/ogg,而.wav应使用audio/wav。loop:是否循环播放,如果有这个属性(不用赋值),表示循环播放。否则玩一次就结束。另外,audio对应的元素还有一个属性volume,可以通过JS设置修改。0表示没有声音,1表示100%,也就是音乐的真实音量。浏览器播放声音的限制浏览器有一个限制:只有当用户与网页进行交互时(按下键盘和鼠标都算作交互),才允许播放声音。所以当你打开视频网站或者打开直播间时,网页往往会提示“点击此处取消静音”。如果在用户交互之前调用APIaudio.play()播放音乐,会报错:Uncaught(inpromise)DOMException:play()failedbecausetheuserdidn'tfirstinteractwiththedocument。https://goo.gl/xX8pDD播放BGM定义audio标签因为同时全局只有一个BGM在播放,我们可以在html文件中定义这个BGM的audio标签:后,可以得到这个dom节点:constbgmEl=document.getElementById('bgm');当然你也可以用JS生成这个html:constbgmEl=document.createElement('audio');bgmEl.setAttribute('loop','');bgmEl.setAttribute('type','musictype');bgmEl.setAttribute('src','你的音乐地址');document.body.appendChild(bgmEl);设置开始播放的时机letbgmStarted=false;conststartPlayBGM=()=>{如果(bgmStarted)返回;bgmStarted=真;bgmEl.play();document.body.removeEventListener('click',startPlayBGM);window.removeEventListener('keydown',startPlayBGM);};document.body.addEventListener('click',startPlayBGM);window.addEventListener('keydown',startPlayBGM);如您所见,我们已经监听了鼠标事件和键盘事件,只要用户与开始玩了~为了实现切换BGM我在《我们用48h,合作创造了一款Web游戏:Dice Crush,参加国际赛事》游戏中做了这个效果:当用户主动切换游戏速度(Slow,Normal,Fast)时,点击切换时BGM也会立即切换。另外,为了避免每次切换后BGM从头开始,玩家会听腻。于是我直接设置了3个audio标签,每个audio标签循环播放1首BGM(共3首歌曲)。那么你只需要这样切换BGM功能:设置其他2个音频的音量=0,要播放的BGM的音量=1。这样可以确保每个开关对应歌曲的不同播放位置,让玩家不会感到无聊。让当前=0;constchangeBGM=(num)=>{if(!startPlayBGM){current=num;返回;}//注意:音频是一个包含3个音频节点的数组。audios.forEach((audio,index)=>{if(num===index){audio.volume=1;audio.play();//可选。根据你想要达到的效果,可以删掉或者保留它。}else{audio.volume=0;}});};changeBGM函数有一个小细节:如果当前没有发生交互,则当前的音乐编号将存储在current变量中。当然,startPlayBGM函数也有一些变化:初始化时,所有音频的音量都为0。用户交互后,将当前对应的BGM音量设置为1,并调用其audio.play()。想一想,为什么会这么实现?因为可能有调用changeBGM的时候,没有发生交互。当前BGM需要保存。然后当交互发生时,就播放电流。播放音效定义音效常量由于音效和文件较多,建议在项目中使用一个配置文件来定义所有音效:例如:constSE={drop:{path:'audio/se/Shot1.ogg',类型:'audio/ogg',持续时间:1500,音量:0.75,},Roll:{路径:'audio/se/roll.wav',类型:'audio/wav',持续时间:1500,volume:0.75,},Crush:{path:'audio/se/crush.wav',type:'audio/wav',duration:1500,volume:0.75,},丢失:{path:'audio/se/lose.mp3',类型:'音频/mpeg',持续时间:5500,音量:1,},};SE对象的key是音效的名称,value中的路径会赋值给src。duration表示本次SE的时长(毫秒),建议大于等于音效时长,但不要太大。定义播放音效的容器因为音效可能是并发的,所以我们预先定义了16个audio标签,最多可以支持16个音效同时播放。并且允许重复使用这些音频。constseList=[];for(leti=0;i<16;i++){constaudioElement=document.createElement('audio');document.body.appendChild(audioElement);seList.push({dom:audioElement,finishTime:0,});}当某个SE播放完duration毫秒后,表示音频任务完成,处于“idle”状态。你会如何实现这个逻辑?使用16个setTimeouts?不要频繁使用setTimeout,我们可以通过finishTime记录它的播放完成时间。每次播放时,只要计算它是否空闲即可。此外,一些动作游戏可能会密集播放音效。如果太密集的话,我们16个并发的音频就撑不住了,所以最好加个“防抖”把80ms以内重复播放的音效合并,但是如果合并的话,我们就加大音量的声音效果。合并的越多,音效越大。constbroadcastSe=(se)=>{//获取当前时间constnow=newDate().getTime();//判断是否需要防抖处理(同类型音效,播放时差小于80ms)constsameItem=seList.find((item)=>item.dom.getAttribute('src')===se.path&&Math.abs(item.finishTime-现在-se.duration)<80);//同样的音效,只是增加音量,最大值为1,结束函数。if(sameItem){sameItem.dom.volume=Math.min(sameItem.dom.volume+0.1,1);返回;}//不同音效,寻找空闲音频,要求dom的finishTime小于当前时间戳,表示空闲constpotentialDom=seList.find((item)=>item.finishTimepotentialDom.dom.play(),0);}else{console.log('资源不足,无法播放',se.path);}};玩多了发个SE,效果如下:我在最后写到我是公众号线下派对游戏的作者HullQin(欢迎关注公众号,发加微信,交个朋友),并且转载本文需征得作者HullQin的授权。我独立开发了《联机桌游合集》,这是一个网页,在这里你可以轻松地和朋友一起玩网络游戏,五子棋等游戏,不收费,也没有广告。还为GameJam2022开发了《Dice Crush》,喜欢的话可以关注我HullQin哦~有空我会分享制作游戏的相关技术。