Studio X Labs有幸为《逆向坍塌:面包房行动》提供全面的声音设计、音频编程、游戏内混音和回放系统。这是我们首次与中国开发商进行长期合作。为了提供最佳音频解决方案,我们选用了Wwise作为我们的游戏音频引擎。
为游戏创建音频的挑战和乐趣往往在于解决问题,并开发必要的流程、系统和结构,以实现所有音频元素的最佳融合。对于《逆向坍塌》来说,整个开发进程跨越了四年,其中在2022年因游戏剧情和任务设计的大幅扩展而中断了八个月。
尽管存在不同时区和语言带来的挑战,我们建立的流程仍然在《少女前线》系列粉丝中达到了满意的评价。
评价:
“《逆向坍塌:面包房行动》玩起来非常有趣,视觉效果很吸引人。除了视觉外,游戏的音频设计也非常出色。优美的音乐给游戏奠定了基调,而音效设计则给予了游戏所需的冲击力。即使没有英文配音,游戏中的配音也很扎实,枪声有着期待的冲击力,爆炸则真正凸显出了影响力,提供了恰到好处的沉浸式体验,非常符合游戏的设定和类型。” 4/5 - Hardcore Gamer
“您可以在优质的音响设备下充分享受游戏出色的音效。游戏中冲击力十足的枪声和爆炸声带来了令人难以置信的神秘沉浸感。此外,每个敌人士兵在战场上重重地、有节奏地移动,几乎带有威胁性的脚步声,为每个任务增添了应有的紧迫感。” 92/100 - Game8
创建内容实现流程:
在项目初期,我们需要开发一个有着高度扩展性与兼容性的流程,以有效地在多个关键领域交付音频成果:
- 声音设计:为角色、武器和环境打造音频,以配合成千上万的定制动画。
- 本地化:确保游戏日文版语音的无缝播放。
- 互动音乐系统:搭建动态音乐系统,实时适应游戏玩法上的变化。
角色动画:
《逆向坍塌》是一款角色和自定义动画数量庞大的游戏。为了有效管理需要音效设计的大量动画,我们实施了基于嵌套Switch Containers的文件夹系统。这种方法使我们能够大幅减少从Unity调用的AKEvents数量,显著简化流程,并且为每个基础角色提供了音效设计的灵活性。通过使用嵌套Switch Containers,我们能够有效地将角色音频事件定向到它们指定的文件夹。考虑到我们处理的动画数量,这一系统至关重要。Wwise中有27个可玩角色和72个敌人角色(有部分角色共享同一Switch Containers),它们都共享同样的嵌套Switch文件夹结构。因此,看看主角蒙德的Switch文件夹:
蒙德文件夹中的每个Switch Container都附有特定事件类型的Switch Containers,如跑步(Run)、抓取(Grab)和放回(Putback)。对于“跑步”动作,我们包含了所有环境表面的变化。对于“死亡”动画,每个Blend Container都包含了按照定制角色动画的变化和时序组织的SFX资源。而“抓取”动作则包括了52个可用物品的动画(部分道具共享同一动画)。
这种结构的扩展性显著;例如,处理每个角色52个物品,27个角色总共产生1,404个抓取动画和额外的1,404个放回物品动画。使用嵌套Switch Ccontainers使这些事件调用非常可控。
由于许多角色共享类似的动画类型(如奔跑、摔倒、死亡和抓取物品),我们通过在所有角色类型中重复使用大量SFX资源来优化内存使用。一旦创建了基础素材,例如脚步声、布料音效和物品使用声音,它们就能高效地在各个角色之间复制。
耗时的部分在于在角色测试关卡调试和单独调整每个角色和敌人动画,根据定制动画需要进行精确的SFX时序调整。由于成千上万的角色动画,我们采用Switch Containers管理和分发角色和敌人事件的方法被证明是有效的解决方案。
武器:
对于武器,我们同样使用了Switch Containers;但由于玩家角色可以使用任意一种52个物品,我们将嵌套文件夹反向组织,并基于角色进行了分类。因此,在每个物品的Switch Containers中,都有角色的Blend Containers,提供了相同的灵活性,以便每次物品使用都可以是特定于角色的。由于游戏中有如此多的定制动画,我们再次能够减少调用的技能事件数量,并让Wwise通过引用的游戏物件解析事件的目标。
互动音乐系统
《逆向坍塌》以其精心打造的背景音乐而闻名,这些音乐不仅增强了游戏的沉浸感,还提升了游戏整体的艺术感。在本项目中,我们与以在《少女前线》和《边境》上的工作而闻名的中国作曲家G.K.密切合作。
音乐系统设计的初衷是交互式和可扩展的,能够适应游戏中大量的关卡。每个关卡支持最多3个阶段,在敌方回合期间,音乐混音会经历特殊修改以反映玩法变化,并增强等待敌方单位结束回合时的游戏体验。此外,如Boss战音乐和在重制版中复刻2013年原版等游戏关卡的音乐也需要特殊处理。
为了实现这一宏大的目标,我们充分利用了Wwise中可用的各种工具。这些工具包括灵活的Interactive Music Hierarchy,如嵌套的音乐Switch Containers和States,用于控制应该播放哪些音乐。我们还实现了Custom Music Cues,以支持除States外的Boss战音乐,并且无缝连接到音乐轨道的RTPC中,并将这些元素与游戏代码高度集成。
音乐Switch Containers与States
《逆向坍塌》提供超过60小时的内容和超过60个不同的关卡。为了管理如此大量的数据,我们选择将音乐数据名称与每个关卡中使用的名称匹配,如下图所示:
上面的图片展示了游戏第一章的交互式音乐设置范例。类似的设置在后续的文件夹中也有应用。最初,我们考虑用不同的曲目作为参考,以创建更少的音乐播放列表容器。然而,在权衡利弊之后,我们决定为每个关卡使用三个音乐播放列表容器。以下是这一决定的一些原因:
优势:
- 这是一个通用解决方案。每个关卡最多可以有3个阶段;有些关卡可能只有1个或2个阶段。在这些情况下,我们可以简单地重复使用那些不需要的阶段的音乐片段。这种方法既有效又灵活。
- 这种方法与游戏中的数据表匹配,允许我们将已在整个游戏中使用的levelId变量用于音乐系统。这确保了整合的一致性。
- 每个关卡都有自己的Sound Bank,用于音乐和与游戏内实时运算的影片相关的文件,这使得我们更容易与我们的Sound Bank管理解决方案集成。这简化了流程并确保了高效的架构组织。
- 这种配置提供了灵活性,可以指定任何一个关卡进行特殊处理,而不影响其他关卡或可能引起的连锁反应。
- 考虑到我们与上海团队的远程合作,由于时区差异,高效的沟通和迭代速度至关重要。与日常办公室工作相比,沟通可能会比较慢。为了简化这一过程,我们列出了游戏中的每个关卡,确保任何使用交互式音乐系统的人都能快速理解。这种方法有助于作曲家和声音设计师更轻松地管理和编辑文件。
- 由于不需要交互式地选择音乐曲目,所以所需的代码显著减少。通过这种配置,加载关卡时,我们可以明确知道对应的音乐将自动播放。这简化了开发过程,并减少了需要管理的代码量。
缺点:
- 在Wwise的初始设置过程可能非常繁琐,但一旦配置完成,系统就变得非常简单易用。
- 由于命名和结构的相似性,过程中存在出现错误风险,这使得很难快速发现和测试。
- 在Wwise中对音乐系统修改的编辑速度较慢,因为你需要在不同的容器中跟踪相同的音乐轨道。
切换敌方回合的RTPC
敌方回合在游戏中频繁出现,尤其是在后期关卡中,玩家会花费大量时间观察地图上的敌方对战情况。尽管可以通过按下跳过按钮加快此过程,但我们选择通过提供不同的音乐混音版本来增强玩家体验,为那些选择花费大量时间观察敌方行动的玩家提供另一种体验。
整个过程始于对原始音乐轨道的修改。作曲家G.K.在此方面表现出色,迅速为我们提供了再混音版本的敌方回合音乐轨道,与正常战斗回合的音乐长度相同。
我们通过镜像结构和实施一个带有插值时间的RTPC,对玩家/盟友回合音乐和敌方回合之间进行交叉淡入淡出的特殊处理。随着敌方回合的开始,这个RTPC逐渐从0过渡到1,反之亦然,逐渐切入另一个混音版本的音乐。
上面的截图展示了RTPC将如何进行调整。在代码中,我们将这个RTPC调用与敌方回合开始时的音乐切换相关联,并在玩家和盟友回合开始时调用它。
在“事件”选项卡中,我们的主要音乐事件同时触发两个Switch Containers,每个都具有不同的音乐混音属性。我们可以使用之前提到的RTPC决定在任何时刻播放哪一个。由于每个音乐混音版本的持续时间相同,同时触发它们并利用RTPC在敌方回合混音和玩家/盟友回合混音之间切换,使我们能够在任何时候实现不同混音版本音乐的无缝过渡。
Boss音乐段过渡
为了支持Boss回合的特殊情况,我们在现有的三阶段互动音乐系统之上添加了一个RTPC。此设置使我们能够在游戏过程中随时无缝地从正常战斗音乐段过渡到Boss音乐段。
我们需要解决的一个挑战是Boss音乐段可能与正常战斗音乐段长度不同。如果目标音乐段比当前音乐段短,在当前音乐段结尾处切换到另一首音乐可能会导致音乐中出现一段时间的空白。
上图说明了在两个音乐段之间过渡时出现的问题。由于在过渡期间两个音乐段同时播放,如果我们尝试在RC_Boss3_preV2结束时过渡到RC_RW_B10_pre_Upver,将会出现短暂的静音,这在我们的情况下是不可取的。
为了解决这个问题,我们调用了 Seek() (Seek (audiokinetic.com)) 函数,并在我们的音乐回调函数中使用了由Wwise提供的两个Custom Music Cues:NormalExitCue表示正常音轨的持续时间是两个中最短的,而BossExitCue表示Boss音轨是最短的。我们添加了一段代码来检测我们是从正常音轨还是Boss音轨退出提示进行切换。然后,我们通过快速交叉渐变将目标音轨从开头播放。
在这个例子中,我们将创建一个小的包装函数来提取参数修饰符的输出值,并在我们打算使用此功能的区域按需应用它。
public float GetGlobalRTPC(string rtpcName)
{
int rtpcType = 1;
float acquiredRtpcValue = float.MaxValue;
AkSoundEngine.GetRTPCValue(rtpcName, null, 0, out acquiredRtpcValue, ref rtpcType);
if(acquiredRtpcValue >= 0.25 && acquiredRtpcValue <= 16).
{
return acquiredRtpcValue;
}
else
{
return 1.0f;
}
}
除了全局设置RTPC外,上述函数还会确保如果检测到不正确的值,它将忽略要设置的RTPC,并将该值重置为默认值1.0f。
public void MusicSegmentSwitch(object in_cookie, AkCallbackType in_type, object in_info)
{
if (in_type == AkCallbackType.AK_MusicSyncUserCue)
{
var musicInfo = in_info as AkMusicSyncCallbackInfo;
if(musicInfo != null)
{
if (musicInfo.userCueName == “BossExitCue”)
{
if (GetGlobalRTPC(“BossBattleMusic”) > 0.5f)
{
AkSoundEngine.Seek(musicPlayingID, 0, false);
}
}
if (musicInfo.userCueName == “NormalExitCue”)
{
if (GetGlobalRTPC(“BossBattleMusic”) <= 0.5f)
{
AkSoundEngine.Seek(musicPlayingID, 0, false);
}
}
}
}
}
上面的代码段展示了如何利用BossBattleMusic RTPC,在范围0到1之间进行交互地切换音乐段;在切换时,音乐段将回滚到开头,并通过交叉淡入重新开始播放。
免责声明:本文中使用的代码片段是重构的通用版本,仅用于说明目的。底层逻辑已经验证其正确性。由于潜在的版权限制,特定项目特定的API调用和函数在示例中已被省略。
评论