使用 Web Audio API 开发游戏音频

Boris Smus
Boris Smus

简介

音频是打造多媒体体验的重要因素 极具吸引力。如果您尝试过在静音模式下观看电影, 您可能已经注意到了

游戏也不例外!我最喜欢的视频游戏记忆是音乐 和音效现在,很多情况下 但我还是收不到近藤浩司的《Zelda》 构图Matt Uelmen 笔下的 Diablo 我脑海中的音乐。同样适用于音效 例如,用户提交的可立即辨认的单位点击响应 魔兽世界,以及任天堂经典游戏的样品。

游戏音频会带来一些有趣的挑战。为了打造令人信服的 游戏音乐,设计人员需要适应可能不可预测的游戏 玩家所处的状态在实践中,游戏的某些部分 持续时间未知,声音会与环境相互影响 以复杂的方式进行混音,例如房间效果和相对声音 定位。最后,这个设备上可以播放大量的声音 所有这一切都需要达到一定的音质,才能正常呈现 性能下降。

网页版游戏音频

对于简单的游戏,使用 <audio> 标记可能就足够了。不过,许多 浏览器提供较差的实现,这会导致音频故障 且延迟时间较长这可能是暂时性的问题 正在努力改进各自的实施方式。对于 <audio> 标记的状态,有一个很棒的测试套件 areweplayingyet.org 网站。

不过,深入了解 <audio> 标记规范后, 但很多事情是无法实现的 这并不奇怪,因为它是专为媒体播放而设计的。部分 限制包括:

  • 无法对声音信号应用过滤器
  • 无法访问原始 PCM 数据
  • 没有来源和监听器的位置和方向的概念
  • 没有精细的时间设置。

在本文的其余部分,我将在 使用 Web Audio API 编写的游戏音频的上下文。简要 请参阅使用入门 教程

背景音乐

游戏通常会循环播放背景音乐。

如果您的循环很短且可预测,可能会非常令人厌烦。如果玩家 卡在某个区域或关卡时,连续播放同一选段 这时有必要让曲目逐渐淡出 以免继续感到沮丧另一种策略是 逐渐淡入淡出, 游戏的情境

例如,如果玩家所在的区域有史诗级 boss 战, 可能就会有几种不同的合辑,在气氛、 预示着要剧烈。音乐合成软件通常可以 通过选取 要在导出中使用的轨道集。这样你就可以有 保持一致,并避免在从头至尾的淡入淡出 复制到另一个曲目

车库带

然后,借助 Web Audio API,您可以使用 类似于通过 XHR 的 BufferLoader 类(这是 介绍 Web Audio API 的文章中有详细介绍。 加载声音需要时间,因此游戏中使用的资源应该 在网页加载时、在关卡开始时加载,或者可能是逐渐加载 。

接下来,为每个节点创建一个来源,并为每个节点创建一个增益节点 来源并连接图。

完成后,您可以同时播放所有这些来源 由于它们的长度相同,因此 Web Audio API 可以保证它们保持一致随着角色 与最终 Boss 战斗距离较远或较远,游戏获得的 计算链中各个节点的值,并使用增益 金额算法,如下所示:

// Assume gains is an array of AudioGainNode, normVal is the intensity
// between 0 and 1.
var value = normVal - (gains.length - 1);
// First reset gains on all nodes.
for (var i = 0; i < gains.length; i++) {
    gains[i].gain.value = 0;
}
// Decide which two nodes we are currently between, and do an equal
// power crossfade between them.
var leftNode = Math.floor(value);
// Normalize the value between 0 and 1.
var x = value - leftNode;
var gain1 = Math.cos(x - 0.5*Math.PI);
var gain2 = Math.cos((1.0 - x) - 0.5*Math.PI);
// Set the two gains accordingly.
gains[leftNode].gain.value = gain1;
// Check to make sure that there's a right node.
if (leftNode < gains.length - 1) {
    // If there is, adjust its gain.
    gains[leftNode + 1].gain.value = gain2;
}

在上述方法中,两个源同时播放,然后我们淡入淡出 使用等功率曲线(如 intro)。

目前,许多游戏开发者都使用 <audio> 标记作为背景 音乐,因为它非常适合流式传输内容。现在,你可以 从 <audio> 标记提取到网络音频上下文。

由于 <audio> 标记适用于 在线播放内容 - 让您可以立即播放背景音乐 而不必等待下载完成通过引入 流式传输到 Web Audio API 中,您可以操控或分析音频流。通过 以下示例将低通滤波器应用于 <audio> 标记:

var audioElement = document.querySelector('audio');
var mediaSourceNode = context.createMediaElementSource(audioElement);
// Create the filter
var filter = context.createBiquadFilter();
// Create the audio graph.
mediaSourceNode.connect(filter);
filter.connect(context.destination);

有关将 <audio> 标记与 Web Audio API,请参阅这篇简短的文章

音效

游戏通常会播放音效以响应用户输入或变化 处于游戏状态时但就像背景音乐一样 非常烦人。为避免这种情况,建立一个池通常很有用 类似但又不同的声音。这可能有轻微变化 从脚步样本的变化到急剧变化,如 《魔兽系列》:响应点击单位。

在游戏中,音效的另一个主要特点是 想象一下,您正与 多个演员在开机枪。每把机枪都发动很多次 这将导致同时播放数十种音效。 同时回放来自多个精确定时源的声音 是 Web Audio API 真正出没的一个地方

下面的这个例子是通过 创建单个项目符号样本 交错播放。

var time = context.currentTime;
for (var i = 0; i < rounds; i++) {
    var source = this.makeSource(this.buffers[M4A1]);
    source.noteOn(time + i - interval);
}

现在,如果你游戏中的所有机关枪都听起来像这样 会很无聊当然,它们会根据不同的声音 距离目标的距离和相对位置(稍后会对此进行详细介绍), 即使这还不够幸运的是,Web Audio API 提供了一种 ,以两种方式轻松调整上面的示例:

  1. 子弹发射之间会略微切换
  2. 通过将每个样本的播放速率(同时更改音高)更改为 更好地模拟现实世界的随机性。

有关这些技术的实际应用示例,请参阅 台球桌演示,该演示使用随机抽样,并且随 playRate 来播放更有趣的碰球声音。

3D 定位音效

游戏的背景通常具有某种几何属性, 2D 或 3D。在这种情况下,立体声定位音频在 提升体验的沉浸感幸运的是,Web Audio API 内置硬件加速定位音频功能 使用起来非常简单顺便说一下,你应该确保 有立体声扬声器(最好是头戴式耳机),适合以下设备: 才有意义。

在上面的示例中, 画布,鼠标则会影响音频源(扬声器 图标)。以上简单示例展示了如何使用 AudioPannerNode 实现 达到这种效果。上述示例的基本思路是 通过设置音频来源的位置来响应鼠标移动, 如下所示:

PositionSample.prototype.changePosition = function(position) {
    // Position coordinates are in normalized canvas coordinates
    // with -0.5 < x, y < 0.5
    if (position) {
    if (!this.isPlaying) {
        this.play();
    }
    var mul = 2;
    var x = position.x / this.size.width;
    var y = -position.y / this.size.height;
    this.panner.setPosition(x - mul, y - mul, -0.5);
    } else {
    this.stop();
    }
};

Web Audio 处理空间化的须知事项:

  • 默认情况下,监听器位于原点 (0, 0, 0)。
  • Web Audio 位置 API 是无单位的,因此我引入了多重维度 来改善演示效果
  • Web Audio 使用 y 向上的笛卡尔坐标(与大多数 计算机图形系统)。所以我要把 Y 轴换成 上方的代码段

高级:音锥

位置模型非常强大,相当先进,很大程度上基于 OpenAL。有关详情,请参见 链接的规范。

排名模型

Web Audio API 附加了一个 AudioListener 它在空间中可通过位置和 屏幕方向。每个来源均可通过 AudioPannerNode 传递, 将输入音频空间化。平移节点包含位置 以及距离和方向模型。

距离模型根据距离指定增益量 而方向模型可通过 指定内外锥,用于确定 负)增益(如果监听器在内锥内) 或位于外圆锥外。

var panner = context.createPanner();
panner.coneOuterGain = 0.5;
panner.coneOuterAngle = 180;
panner.coneInnerAngle = 0;

虽然我的示例是 2D 格式的,但这个模型可以很容易地泛化到 维度。如需查看 3D 空间化声音的示例,请查看此内容 位置示例。除了定位之外, 声音模型还可以选择包含多普勒偏移的速度。这个 更详细地展示了多普勒效应的示例。

有关此主题的详情,请参阅 [混合位置音频和 WebGL][webgl]。

房间特效和滤镜

事实上,感知声音的方式在很大程度上取决于房间内的 同一扇门发出咔哒声,会让听起来非常难听 和大型开放式大厅不同得分最高的游戏 制作值时就要模仿这些效果 为每个环境单独提供一组样本的成本非常高, 进而产生更多资产和更多游戏数据。

简单地说,用来区分原始音频和 声音和声音在现实世界中的反应就是 响应。这些冲动反应可能非常困难 但实际上有很多网站托管着许多 这些预先录制的脉冲响应文件(存储为音频) 。

有关如何产生冲动反应的更多信息 请通读“录制设置”部分 Web Audio API 规范的卷积部分。

更重要的是,Web Audio API 提供了一种简单的 我们可以使用 ConvolverNode 开头。

// Make a source node for the sample.
var source = context.createBufferSource();
source.buffer = this.buffer;
// Make a convolver node for the impulse response.
var convolver = context.createConvolver();
convolver.buffer = this.impulseResponseBuffer;
// Connect the graph.
source.connect(convolver);
convolver.connect(context.destination);

另请参阅 Web Audio API 规范中的这个房间效果演示 页面,还有此示例,可让您控制 (原始)和(通过卷积处理)混合的出色爵士乐标准。

最后的倒计时

构建了一款游戏,配置了定位音频 图中有大量 AudioNode 都会回放 。太棒了,但还有一件事需要考虑:

由于多种声音只是堆叠在一起, 因此您可能会发现自己处于 超出了音箱的能力阈值。为图片点赞 超出画布边界,如果 波形超出其最大阈值,从而产生明显的失真。 波形如下所示:

截短

这里展示了剪辑的实际应用示例。波形看起来有问题:

截短

一定要聆听类似上面的强烈的失真 反之,过于柔和的混音会迫使听众兴奋 和音量。如果您遇到了这种情况,您真的需要解决问题!

检测剪辑

从技术角度来看,当 信号超过有效范围,即 -1 和 1 之间。 一旦检测到这种情况,提供视觉反馈非常有用, 情况。要以可靠的方式执行此操作,请将 JavaScriptAudioNode 放入 您的图表。音频图表的设置如下:

// Assume entire sound output is being piped through the mix node.
var meter = context.createJavaScriptNode(2048, 1, 1);
meter.onaudioprocess = processAudio;
mix.connect(meter);
meter.connect(context.destination);

可在以下 processAudio 处理程序中检测到裁剪:

function processAudio(e) {
    var buffer = e.inputBuffer.getChannelData(0);

    var isClipping = false;
    // Iterate through buffer to check if any of the |values| exceeds 1.
    for (var i = 0; i < buffer.length; i++) {
    var absValue = Math.abs(buffer[i]);
    if (absValue >= 1) {
        isClipping = true;
        break;
    }
    }
}

一般而言,请注意不要过度使用 JavaScriptAudioNode 性能原因。在这种情况下, 计量供给可能会轮询音频图中的 RealtimeAnalyserNode,以获取 getByteFrequencyData,呈现时间,由 requestAnimationFrame。这种方法更高效, 大部分信号(包括可能被截断的位置), 每秒最多渲染 60 次,而音频信号 能够更快地做出响应

由于片段检测非常重要,因此我们很可能会 内置 MeterNode Web Audio API 节点。

防止剪辑

通过调整主 AudioGainNode 上的增益, 以阻止剪辑。然而,实际上,由于 游戏中的音效可能取决于多种因素, 可能很难确定阻止 针对所有州进行裁剪。一般来说,您应该将增益调整为 但这更像是一门艺术,而不是一门科学。

加一点糖

压缩器常用于音乐和游戏制作 并控制整体信号中的峰值。这个 通过 DynamicsCompressorNode,可插入到您的音频图表中, 发出更响亮、更丰富和更饱满的音效,而且也有助于剪辑。 直接引用规范,此节点

通常,使用动态压缩是一种不错的选择,尤其是在 如前所述,您并不确切地知道 播放声音以及播放时间DinahMoe 实验室的 Plink 是 这是个很好的例子 这取决于您和其他参与者。压缩机在大多数情况下都很有用, 但一些罕见的情况除外, 在这些情况下,您需要极大地 经过调优、音质恰到好处的母带曲目。

只需添加 音频图中的 DynamicsCompressorNode,通常作为最后一个节点 :

// Assume the output is all going through the mix node.
var compressor = context.createDynamicsCompressor();
mix.connect(compressor);
compressor.connect(context.destination);

有关动态压缩的更多详情,请参阅此维基百科 文章提供了丰富的信息。

总结一下,请仔细倾听是否存在剪辑内容, 主增益节点。然后,运用力度调整整个组合 压缩器节点。您的音频图表可能如下所示:

最终结果

总结

以上就是我认为对游戏音频最重要的方面 使用 Web Audio API 进行开发。利用这些方法,您可以 直接在浏览器中打造真正引人入胜的音频体验。在我之前 结束了,下面我来为大家介绍一个针对具体浏览器的提示: 如果标签页转到后台,则停止播放声音 可见性 API,否则您可能需要创建一个可能的 导致用户体验不佳

有关网络音频的更多信息,请参阅 更多介绍性的使用入门文章。如果您有 ,查看网络音频常见问题解答中是否已有解答。 最后,如果您有其他问题,请在堆栈 溢出使用 web-audio 标记。

在结束之前,我先向大家介绍一些非常棒的网络用法 Audio API 在现今游戏中的运用: