最初在《Saints Row (2022)》的前期制作当中设计系统时,音频团队便决定让现实世界来主导诸多系统的运行方式。这主要是由整个团队的构成决定的。我们有具备实践经验的开发人员将现实中的技能运用到自研引擎中。我们的物理程序员做过飞行模拟器,车辆设计师曾是这个行业的工程师…所以他们会根据自己的专业知识来构建各个系统。所以,我们决定充分利用这一点;事实证明,所有这些系统都切实可行。既然如此,我们也就这样坚持了下来。
比如,我们的车辆发动机设置跟真实车辆没什么两样;我们可以获取实际的 RPM 值,还要考虑传动比、油门、悬挂甚至轮胎滑移,而不仅仅是使用 "Velocity" RTPC 来发动/关闭发动机(尽管确实有这些功能)。除此之外,我们甚至还有一个 RTPC 用来检测船只的螺旋桨是否浸没在水中。
而且,每辆车都会根据碰撞类型来记录车辆的碰撞情况。如果我驾车全速行驶并撞上横向驶来的 NPC 车辆,我的车会发出迎面相撞的声音,NPC 则会发出 T-bone 的声音。凭借有限的素材,我们最终将一般车辆碰撞声编排成了数千万种。这还没算可能脱落或喷射液体的车辆部件所对应的调和音分层。
我们将同样的理论运用到了道具撞击当中;如果有个大的空木桶撞到金属杆,听起来跟现实中会是完全一样的。此外,还有我喜欢的 Initial Delay 功能(估计都没人真正注意到)。不过正因如此,在子弹撞击和爆炸时声音才会以声速传播。
在整个项目中,我们都是这样做的。在尝试为任何音频系统做复杂精妙的设计之前,都会考虑现实生活中可以想到的各种实际应用。不过在设计电台系统时,情况开始变得棘手起来。
《Saints Row》电台 – 简介
我做电台系统的设计其实非常巧合。在加入 Volition 之前,我一直在电台行业的激烈竞争中打拼,先是做广告宣传,后来又做广告设计,在加入放送团队之前还做过工程设计。毫不夸张地说,我对广播电台的运作可谓一清二楚。就音频系统而言,为《Saints Row》设计电台是我全权负责的第一个项目。
之前的《Saints Row》游戏有个由背景时钟、计时器和寻址表构成的复杂网络,每一帧都有成百上千个表格文件相互通信。这就像是一张非常脆弱的蜘蛛网,只要蜘蛛打个喷嚏蛛网就会散掉。对玩家来说,在不同的车载收音机播放同一电台时会出现交叉干扰、同步错误等问题。不过,基本的功能算是有了。
但是,我觉得我们可以做得更好。考虑到由现实世界主导诸多系统的设计理念,我把音频程序员迈克•瑙莫夫 (Mike Naumov) 邀请到了我的办公室。
我的想法很简单:电台就是电台。现实世界中到处都有发射器和接收器。我们所要做的就是将两者联系起来,并设置电台元素可依循的播放逻辑。
发射器
在让大家收听之前,我们必须先播放一些音乐素材。刚开始,我们在游戏世界的原点下放置了一个对象,并播放包含若干传统乡村 APM 曲目的 Random Container。最终,我们把它做成了 Tumbleweed 电台。
我的首要任务是确保使用虚声部设置来保持同步。与此同时,迈克开始着手开发电台调谐功能。为此,我们在另一容器中添加了另一个对象,以便对电台之间的切换进行测试。
接收器
接下来,我们只需要弄清楚如何在打开收音机时真正听到电台的声音。起初,我们尝试了简单地设置一个 RTPC 来在选择某个电台时将其变成 2D,同时使用 Speaker Panning/3D Spatialization Mix 功能将所有其他电台保持为 3D。不过,我们担心 NPC 车辆也会调到同一电台而导致音乐互相交叠。最终的解决方案其实很简单:直接为玩家拥有的接收器指派一个 PC/NPC RTPC 来实现基本相同的功能。
现在,我们有了一个功能强大的电台组件。您可以将其放在各个车辆上,然后将玩家角色的电台跟 NPC 的电台区分开,并在不同的电台之间流畅地切换。
借助自研的多点定位发声体工具(类似于 Unreal 中为 AK 设置的 Set Multiple Positions 节点),我们可以在不创建新的游戏对象的情况下为每个电台组件动态地附加发声位置,以允许多个车辆在移动时调到同一电台而又不会导致声部或对象大量增加。
电台
现在,我们搞定了发射和收听的方式。接下来,就要开始构建电台了。根据当年使用 Scott Studio 设置和运行现实电台播放列表的经验,我觉得可以直接在我们的电台系统中再现这一功能。
以下是 2006 年左右 Scott Studios SS32 电台自动化软件界面的 Google 截图。
(来源:https://www.devabroadcast.com/dld.php?r=759)
这样电台节目编排人员就可以预先设定相应的顺序,包括广告时段、新闻时段、DJ 谈话、天气、电台 ID。所有这些都可细分为单个元素,让放送人员运行自动播放列表,或者适时地做出强制性的调整。
为此,我们创建了一个由相同元素系统组成的模块化系统。设计师可以按照预先确定的顺序将这些元素插入到系统中。这样就不会出现连续播放太多广告、一首歌的尾声之后紧接下一首歌的前奏或两段广告之间被插入电台 ID…之类的情况。除此之外,这种模块化设计还为玩家创建自定义播放列表提供了便利。
从表格编辑器的截图中可以看到,所有非语言元素之间都插有新闻广播。这是因为只有在通过完成游戏环节解锁新闻广播时系统才会在下一空档播放新闻广播。而且,新闻广播当中还会探讨玩家的所作所为。在迈克的大力协助下,音频团队很轻松地就完成了对该功能的设置。
歌曲
目前为止,我们已经围绕多项 Interactive Music 功能对整个系统做了规划。接下来,就要用到 Wwise 了。考虑到每个元素都是模块化的,我们只需为其分别创建一个 Play Event 即可。这些都是在单独的表格类中配对的,所以可通过代码来完成所有的选择 – 每首歌曲对应一个 Switch Container,下面嵌套的每种 Flavor 对应一个序列:
在选择 Flavor 后,会为该电台的对象设置 Switch,并为这首歌的 Switch Container 发送 Play Event。然后,便可设置 Exit Cue 来控制过渡,让每位 DJ 都能准确地把控时机(就是在特定节拍上停止讲话,所有主持人都要做到这一点):
该 Exit Cue 会触发歌曲,这样 DJ 就能卡在点上:
同样,我们对尾声也做了处理;在音乐进入尾声时,会触发 Exit Cue,提示 DJ 开始讲话。另外,我们还在 DJ 语音总线上应用了旁链压缩,以便在音乐比较密集时让语音穿透音乐。
除了 Exit Cue,我们还要在元素播完时将之告知于电台系统。为此,我们还放置了带有特定标签的 Custom Cue 以便代码监听 AK 回调;在触发 Custom Cue 时,会告知系统选择并播放下一元素。这大大简化了引擎端的处理过程,使元素之间的交叉淡变非常自然。就跟现实中的电台 DJ 一样,可以手动强制播放下一元素或预先设定交叉淡变时长。所有元素都有同样的自定义 Custom Cue,所以代码每次只需监听一个回调即可。
结语
在把所有这些都结合在一起之后,我感觉这是我迄今为止最逼真的电台体验。在收听同一电台时,两辆经过的汽车会完全同步。您可以劫持一辆正在播放自己喜欢的歌曲的汽车,这时会立即去除歌曲相同位置应用的 LPF 和音量偏置。您可以各个电台之间自由切换,然后回到刚开始在听的那首歌,就像它一直在通过电波播放一样,而无需考虑自己的个人操作…因为它确实一直在播放。
如果没有 Wwise 功能的完美支持、我当时在项目中担任的职务、我在现实世界中的相关从业经验还有迈克的出色工作和全力配合,我们可能还在使用时钟和寻址表来播放歌曲,CPU 预算肯定高得让人难以置信…不仅如此,我们还解决了原有系统中的各种问题,并让电台尽可能地模块化,以便将来游戏粉丝做 Mod 包。总的来说,这是一次非常愉快的经历,我们也对此感到非常自豪。
评论