33 个音效,一个音频文件都没有
· 阅读约 4 分钟
Strandbreak Online 是个很吵的游戏: 八把主武器各有自己的枪声,十几把副武器借用和它们手感最接近的那把枪的声音, 还有命中和暴击、死亡和精英怪死亡、拾取、升级、爆炸,外加一个自带五种音效的 Boss。 加起来一共三十三个音效。而游戏的下载包里没有任何音频文件。
这是怎么做到的
每个声音都是一份写成数据的简短配方:用哪些振荡器、音量包络是什么形状、 一个滤波器、再加一点噪声。游戏启动时,这些配方会被渲染成原始采样缓冲区, 也就是音频文件本来会被解码成的那种东西,然后直接从那里播放。
代价在浏览器里实测过:以每秒 48,000 个采样计, 渲染那十一个需要在浏览器允许出声的第一时间就准备好的声音,花了 37 毫秒。 其余的声音在第一次播放时才渲染。下载的代价是零字节。
没人想到的好处:可以测试
下载包更小当然不错,但那不是重点。重点在于,把配方变成采样的那段代码是纯函数: 它不碰浏览器的音频系统,也不碰页面。 这意味着它能在普通的测试里运行,在一台没有扬声器的机器上运行, 而测试可以对游戏里的每一个声音下断言:
- 听得见。 响度高于一个下限。配方里打错一个字、渲染出一片寂静,构建就会失败。
- 从不削波。 没有任何一个采样超过最大值。
- 里面没有坏数据:没有非法数值。
- 没有直流偏移,也就是那种把波形持续推向一侧、让扬声器发出闷响的偏移。
- 以静音开始,以静音结束,所以不会有爆音。
- 结果是确定的:同一份配方每次都产出同样的采样。
- 和其他所有声音都不一样。 两个提示音不小心共用了同一份配方,测试就会失败。
- 每把主武器都有自己的枪声,而且游戏里的每把武器,包括副武器,都指向一个确实存在的声音。
这些都不可能靠耳朵听出来。最要紧的那种故障,「事件触发了,却什么声音都没出来」, 在截图里看不见,在试玩中也很容易漏掉,因为玩家正忙着呢。 如果用音频文件,这一切就只能靠一个人戴着耳机把声音挨个点一遍,点一次,然后再也不会点第二次。
割草类游戏的混音器必须做到的三件事
一个屏幕上可能同时有几百个敌人的游戏,请求播放声音的速度比任何人能听清的都快。 没有规则的话,混音要么糊成一团,要么变成噪音。
每个声音都有最短间隔和发声数预算。 一帧里可能有五十次命中事件。 当成五十个叠在一起的声音来播,效果只是很吵。 在浏览器里测过:现在五十个同时到达的命中音效请求,只会产生两个发声。 耳朵分辨不出差别,混音里的其他声音也保住了。
每份配方的峰值都做了归一化。 一个声音相对另一个声音有多响,由配方表决定, 而不是由每一层的音量决定。这样一来,调整一个声音的构成, 就不会悄悄改变它播放时的响度。
主输出上有一个限幅器。 四十个峰值为 0.3 的发声叠在一起,远远超过最大值, 而浏览器在超过最大值时的处理方式,就是听得见的失真。 一个温和的限幅器能把总和压在范围之内。
静音和闪避是两回事
还有一条规则,是在之前一个游戏上吃了亏才学到的。 当页面需要游戏安静下来,比如要播广告时,游戏会闪避(duck): 声音淡出,广告结束后再淡回来。 当玩家按下静音键时,游戏就是静音。这是两个独立的开关。 如果它们是同一个开关,广告一结束,就会给一个明明要求安静的玩家重新打开声音。 这一点同样在浏览器里测过:闪避期间设置的静音,在闪避结束后依然保持。
说到底
用代码生成素材,通常是从体积的角度来讨论的。 更有力的理由是:凡是由纯函数生成的东西,都可以像其他代码一样测试, 而声音恰恰是大多数游戏里测试最少的部分之一。 一个机器检查不了的声音,迟早会在没人注意的时候坏掉。
来玩 Strandbreak Online,记得开着声音。
