[教你做小游戏] 只用几行原生 JS,写一个函数,播放音效、播放 BGM、切换 BGM
我是 HullQin,公众号线下聚会游戏的作者(欢迎关注公众号,发送加微信,交个朋友),转发本文前需获得作者 HullQin 授权。我独立开发了《联机桌游合集》,是个网页,可以很方便的跟朋友联机玩斗地主、五子棋等游戏,不收费没广告。还独立开发了《合成大西瓜重制版》。还开发了《Dice Crush》参加 Game Jam 2022。喜欢可以关注我 HullQin 噢~我有空了会分享做游戏的相关技术。
问题描述
要做小游戏,播放音效、BGM 是必须的。如何实现呢?
首先我们区分 2 个概念:背景音乐(Background Music 简称 BGM)和音效(Sound Effect 简称 SE)。
背景音乐是需要循环播放的,是很长的音乐,可能中途有暂停、切歌的诉求。同一时间一般只有 1 首 BGM 在播放。
音效是在需要时单次播放,比较短的声音,一般随着动画、用户操作一同触发。同一时间可能叠加很多个 SE。播放完,就结束了。
所以,二者诉求不同,我们最好分别实现。
前提知识
浏览器如何播放声音
目前,前端可以通过audio
这个标签,来播放声音,介绍几个重要的属性:
src
:声音资源的 URL。type
:声音资源的类型,会用该方式解码。例如.mp3 应该用audio/mpeg
,而.ogg 则用audio/ogg
,而.wav 是audio/wav
。loop
:是否循环播放,若有该属性(不需要赋值),则表示循环播放。否则播放一次后就结束了。
此外,audio 对应的 element 还有属性是volume
,可以通过 JS 设置和修改,0 表示没声音,1 表示 100%,即音乐真实音量。
浏览器播放声音的限制
浏览器有个限制:只有用户跟网页发生了交互(按键盘、鼠标都算交互),才允许播放声音。所以当你打开视频网站时、或者打开某个直播间时,网页上往往会提示「点此取消静音」,其实是网页开发者对该限制做的妥协,也是相关协议制定者期望的表现。
如果你在用户发生交互前,调用 APIaudio.play()
播放了音乐,会有报错:
Uncaught (in promise) DOMException: play() failed because the user didn't interact with the document first. https://goo.gl/xX8pDD
播放 BGM
定义 audio 标签
因为全局同时只有 1 个 BGM 在播放,我们可以在 html 文件中定义这个 BGM 的 audio 标签:
之后可以获取这个 dom 节点:
当然,你也可以用 JS 生成这个 html:
设置开始播放的时机
可以看到,我们监听了鼠标事件和键盘事件,只要用户发生了交互,就可以开始播放了~
实现切换 BGM
我在《我们用 48h,合作创造了一款 Web 游戏:Dice Crush,参加国际赛事》游戏中,做了这种效果:
用户主动切换游戏速度时(Slow、Normal、Fast),BGM 也会随着切换。是点击时立马切换的。此外,为了避免每次切换后,BGM 都从头开始,让玩家听腻。所以我直接设置了 3 个audio
标签,每个 audio 标签各自循环播放 1 首 BGM(一共 3 首)。那么切换 BGM 函数只需要做这件事:设置其它 2 个audio
音量=0,要播放的 BGM 的audio
音量=1。这就保证了每次切换,都是对应歌曲的不同播放位置,让玩家没有厌烦感。
changeBGM
函数有个小细节:如果当前还没发生交互,那么会把当前的音乐编号存到current
变量。当然startPlayBGM
函数也有一些变化:初始化时,所有 audio 的 volume 都是 0,用户发生交互后,把current
对应的 BGM 的 volume 设置为 1,并且调用它的audio.play()
。你思考下,为什么这么实现?
因为可能有时候 changeBGM 调用时,还没发生交互。需要把当前的 BGM 存下来。然后发生交互时,播放current
即可。
播放音效
定义音效常量
因为音效很多,文件比较多,建议用一个配置文件,定义项目中所有的音效:
例如:
其中 SE 对象的 key 是音效的名字,值中 path 会赋值给src
。duration 表示这个 SE 的时长(毫秒),建议大于等于音效的时长,但不要太大。
定义播放音效的容器
因为音效可能会并发,我们提前定义 16 个audio
标签,最多可支持 16 个音效同时播放。而这些audio
是允许重复利用的。
当某个 SE 播放开始过了duration
毫秒后,表明这个audio
任务完成了,处于「闲置」状态了。
这种逻辑你会怎么实现呢?使用 16 个 setTimeout 吗?
不要频繁使用setTimeout
,我们完全可以通过finishTime
记录它的播放完成时间。每次播放时,计算是否空闲即可。
此外,有些动作类游戏,可能会密集的播放音效,如果太密集,我们 16 个并发的audio
也无法支撑住了,所以最好加个「防抖」,将 80ms 内重复播放的音效合并,但是如果合并了,我们给音效音量加大。合并的越多,音效越响亮。
播放多个 SE 时,效果如下:
写在最后
我是 HullQin,公众号线下聚会游戏的作者(欢迎关注公众号,发送加微信,交个朋友),转发本文前需获得作者 HullQin 授权。我独立开发了《联机桌游合集》,是个网页,可以很方便的跟朋友联机玩斗地主、五子棋等游戏,不收费没广告。还独立开发了《合成大西瓜重制版》。还开发了《Dice Crush》参加 Game Jam 2022。喜欢可以关注我 HullQin 噢~我有空了会分享做游戏的相关技术。
版权声明: 本文为 InfoQ 作者【HullQin】的原创文章。
原文链接:【http://xie.infoq.cn/article/0bf14bc65d657f139c76274c9】。文章转载请联系作者。
评论