利用 Wwise 将两个 Audio Device 整合到 UE 游戏中

游戏音频 / 声音设计 / VR 体验

1

大家好,我叫埃德·卡辛斯基,是一名来自俄罗斯圣彼得堡的声音设计师和音乐家,目前在做一个非常有趣且重视声音表现的项目:这是一个名为《牧师与鬼驱人(Priest vs. Poltergeist)》的线下多人VR游戏。通常重视声音意味着少不了声音方面的挑战,但这也让我学到了很多。借写博客的机会来跟各位分享一下。

简单介绍一下,《牧师与鬼驱人是一款以决斗为主题的VR 游戏。其中,VR 玩家扮演 鬼驱人PC 玩家则扮演 牧师,并通过鼠标和键盘来控制角色。这里的难点在于它是在同一台 PC 上运行的。不仅两名玩家要能听到来自不同输出设备(VR 耳机和 PC 耳机/扬声器)的声音,而且各种声音还需要有自己独立的定位、衰减、声笼、时机等设置。

试想一下在关卡中某个地方有把椅子掉到了地上。如果一名玩家离它只有几米,声音会听得很清楚。倘若另一玩家距离同样远,但跟椅子之间隔了一堵墙,那么落地声听起来就会比较干,音量也会低一些。因此,不但两名玩家要能从不同的输出设备听到落地声,而且还得根据其各自的当前位置和设置相应地加以调节。

当时我们试了好几种解决方案(包括 Unreal 的原生音频库),最终决定利用 Wwise 来加以实现。借助这款独立的游戏音频引擎,我们可以在设计工具中随心所欲地处理声音。甚至将声音发送到不同的 Audio Device,而这正是我们想要的。

为了将音频引擎的功能集成到 Unreal 中,Wwise 提供了相应的插件。理论上来说,它应当将 Wwise 引擎的所有功能传给游戏引擎以便在后者当中使用。然而,我们很快便碰了壁:Wwise Unreal 插件并没有涵盖我们所需的那种“指定在不同设备上播放声音”的功能。

最后我们别无选择,只好亲自动手在 C++ 中添加这项功能。

目的

插件的主组件 AkComponent 被添加在关卡中,既可用作 Listener,也可用作 EmitterEmitter 可在关卡中的任何位置播放声音。Listener 则像双耳一样监听所有声音,并根据角色所在位置将声音输出到 Audio DeviceListener 通常与 Camera 组件绑定)。

这里有两个角色。每个角色都有一个 AkComponent 作为 Listener,来将声音同时输出到两个 Audio Device。总的来说,就像这样:

2

  • Emitter 触发 Blueprint 函数 Post Ak Event,继而在 Wwise 中触发 Event
  • Event 播放输出到两条 Audio Bus 的声音。
  • Audio Bus 将声音发送到与之对应的 Wwise Audio Device(本例中为 System System_VR)。
  • 各个 Listener 关联与之对应的输出(Output Device 1 Output Device 2),并监听来自 Wwise 的声音。在声音抵达 Audio Bus 时,便在对应输出上进行播放。

我们要做的便是在 AkComponent 设置中添加两个文本字段,以便可根据需要填入声音输出的目标音频设备名称。

3

1 步:设置 Wwise

首先,我们要在 Wwise 创作工具中创建音频设备和音频总线。详细信息,请参阅原始文档(“总线通路部分)。

4

  • Wwise 中为 VR 玩家添加 Audio Device。将类型设为 System,并命名为 System_VR。这个便是最终要在 AkComponent 设置下 Wwise Device Name 字段中键入的 Wwise Audio Device 的名称。
  • VR 玩家创建 Audio BusMaster_VR_Aux_Bus),并将 System_VR 设为 Audio Device

5

  • Wwise 中,声音只能发送到一条 Output Bus。但是,我们要针对两个角色播放声音。为此,需要使用 Auxilary Bus

6

为了在两个 Audio Device 上播放同一声音,我们需要为 Master VR Audio Bus 创建一条嵌套的 Auxiliary Bus。结果应如下图所示:

7

这样我们便可将声音单独或一并发送给两个角色了。

***

创作工具部分准备就绪,接下来只需按照下列规则为各种声音设置总线即可:

  • 牧师的声音 (PC):将 Master Audio Bus设为Output Bus,并将 Auxiliary Bus 保留为空。
  • 鬼驱人的声音 (VR):将Master VR Audio Bus设为Output Bus,并将 Auxiliary Bus 保留为空。
  • 通用声音:将Master Audio Bus设为Output Bus,并在 Auxiliary Bus 列表中添加 Master_VR_Aux_Bus

8

如此, Wwise创作工具部分就准备就绪了。

2 步:设置 AkComponent

接下来我们要对付编译器,以便我们对插件所作的修改能够生效。如果不清楚如何将 Blueprint 工程转换为 C++ 工程并对其进行编译,请参阅以下文档:

https://docs.unrealengine.com/en-US/Programming/Development/CompilingProjects/index.html

https://docs.unrealengine.com/en-US/Programming/Introduction/index.html

在以下位置您可以找到我们将要处理的文件:

{Project Folder}/Plugins/Wwise/Source/AkAudio

AkAudioDevice.h

打开 /Public/AkAudioDevice.h,并在 public: 之后添加以下代码:

// connecting audio device with device from wwise 
AkOutputDeviceID AddCustomOutput(FString AudioDevice, FString WwiseDevice, UAkComponent* in_pComponent);
// removing connection
AKRESULT RemoveCustomOutput(AkOutputDeviceID deviceId);
//  searching audio device by substring
TTuple <AkUInt32, FString> SearchAudioDeviceIdByName(FString deviceName);

9

AKComponent.h 

接着转到 /Classes/AkComponent.h,并在 public: 之后添加以下代码:

TTuple <AkUInt32, FString> FAkAudioDevice::SearchAudioDeviceIdByName(FString deviceName)
{
TTuple <AkUInt32, FString> result;
AkUInt32 deviceId = AK_INVALID_DEVICE_ID;

if (deviceName.Len() == 0) {
// getting default device
AK::GetWindowsDevice(-1, deviceId, NULL, AkDeviceState_Active);
auto deviceNameWstr = AK::GetWindowsDeviceName(-1, deviceId, AkDeviceState_Active);
result.Key = deviceId;
result.Value = FString(deviceNameWstr);
} else {
AkUInt32 immDeviceCount = AK::GetWindowsDeviceCount(AkDeviceState_Active);
for (AkUInt32 i = 0; i < immDeviceCount; ++i) {
AK::GetWindowsDevice(i, deviceId, NULL, AkDeviceState_Active);
auto deviceNameWstr = AK::GetWindowsDeviceName(i, deviceId, AkDeviceState_Active);
if (FString(deviceNameWstr).Contains(deviceName)) {
result.Key = deviceId;
result.Value = FString(deviceNameWstr);
break;
}
}
}
return result;
}
AkOutputDeviceID FAkAudioDevice::AddCustomOutput(FString AudioDevice, FString WwiseDevice, UAkComponent* in_pComponent)
{
TTuple <AkUInt32, FString> Device;
AkOutputDeviceID deviceId = AK_INVALID_DEVICE_ID;
FString WwiseDeviceName = "System";
AKRESULT res = AK_Fail;
if (AudioDevice.Len() == 0 && WwiseDevice.Len() == 0) {
return deviceId;
}
if (WwiseDevice.Len() > 0) {
WwiseDeviceName = WwiseDevice;
}
Device = SearchAudioDeviceIdByName(*AudioDevice);
if (Device.Key) {
AkOutputSettings outputSettings(*WwiseDeviceName, Device.Key);
auto gameObjID = in_pComponent->GetAkGameObjectID();
res = AK::SoundEngine::AddOutput(outputSettings, &deviceId, &gameObjID, 1);
}
FString componentName = in_pComponent->GetName();
if (res != AK_Success) {
UE_LOG(LogAkAudio, Error, TEXT("Error attaching of AkComponent \"%s\" to \"%s\" <-> \"%s\". Error \"%d"), *componentName, *AudioDevice, *WwiseDeviceName, res);
} else {
UE_LOG(LogAkAudio, Warning, TEXT("AkComponent \"%s\" attached to \"%s\" <-> \"%s\" "), *componentName, *Device.Value, *WwiseDeviceName);
}
return deviceId;
}
AKRESULT FAkAudioDevice::RemoveCustomOutput(AkOutputDeviceID deviceId)
{
return AK::SoundEngine::RemoveOutput(deviceId);
}

AKComponent.h 

接着转到 /Classes/AkComponent.h,并在 public: 之后添加以下代码:

/**
* Name of Audio Device in Wwise. If empty, "System" is using
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, AdvancedDisplay, Category = "AkComponent")
FString WwiseDeviceName;
/**
* Name of Audio Device in OS. If empty, default device is using
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, AdvancedDisplay, Category = "AkComponent")
FString AudioDeviceName;
AkOutputDeviceID OutputID;

10

AkComponent.cpp

最后转到 /Private/AkComponent.cpp,搜索两个空函数 PostRegisterGameObject PostUnregisterGameObject,并将其替换为:

void UAkComponent::PostRegisterGameObject()
{
FAkAudioDevice* AkAudioDevice = FAkAudioDevice::Get();
if (AudioDeviceName.Len() > 0 || WwiseDeviceName.Len() > 0) {
OutputID = AkAudioDevice->AddCustomOutput(AudioDeviceName, WwiseDeviceName, this);
}
}
void UAkComponent::PostUnregisterGameObject()
{
FAkAudioDevice* AkAudioDevice = FAkAudioDevice::Get();
if (AkAudioDevice && OutputID != AK_INVALID_DEVICE_ID) {
AkAudioDevice->RemoveCustomOutput(OutputID);
}
}

11

恭喜你到达这里!接下来还需要在 Visual Studio 中编译工程并运行 Unreal Editor ,就可以验证成果了。

如果前面全部操作无误的话,Unreal Editor AkComponent 设置中会出现两个新的文本字段。利用这两个字段,我们可以将音频输出设备与 Wwise音频设备互连。

  • Wwise Device Name - 本例中为 System System_VR
  • Audio Device Name - 硬件设备名称,比如 Sony Headphones。其实只要输入 Headphones 应该就足以找到实际设备了。

无论有没有找到设备,Unreal Editor Output Log 中都会显示相应的结果:

12

3 步:收尾工作

最后一步是设置与角色对应的 Listener。首先,为每个 Pawn/Character 创建一个 AkComponent。然后,在各自的 AkComponent 设置中键入 Audio Device 的名称。

13

注意(非常重要):每个 Emitter 都需要设置 Listener,否则整套设置便会不起作用。对此,有专门的 Blueprint 函数 Set Listeners

比方说,可以利用BeginPlay事件来执行这个操作:

14

就这么简单!最后可以在 Unreal Editor 中测试一下播放效果,看看两个 Audio Device 是否都能正常运行。这时应当会在 Output log 中看到以下内容,并同时听到来自两个输出设备的声音。

15

有兴趣的话,不妨看下我的 Soundcloud。如果有什么问题,可以发消息给我。

 

埃德·卡辛斯基 (Ed Kashinsky)

作曲家兼声音设计师

埃德·卡辛斯基 (Ed Kashinsky)

作曲家兼声音设计师

埃德·卡辛斯基 (Ed Kashinsky) 是一名来自圣彼得堡的声音设计师、音乐家,拥有声音制作及软件开发背景。他热衷于对互动音乐及声音引擎应用的研究。

评论

留下回复

您的电子邮件地址将不会被公布。

更多文章

《11-11: Memories Retold》背后的声音设计

《11-11: Memories Retold》是一款叙事游戏,其背景设在第一次世界大战期间,讲述了两个角色在西部前线发生的故事。其中,Harry...

28.5.2019 - 作者:约安.莫尔文(YOANN MORVAN)

从 UI 设计角度探讨如何制作 UI 音频 - Part 1

在有些情况下,游戏的用户界面最终只会对玩家的整体体验产生很小的影响。在其他情况下,比如对很多手游来说,几乎整个游戏都是围绕 UI...

14.10.2019 - 作者:约瑟夫·马尔丘克 (Joseph Marchuk)

为何要选用 Wwise 来打造 3D 互动音乐体验

Dirty Laundry by Blake Ruby 移动 VR 应用:音频设计详解。

26.10.2021 - 作者:朱利安•梅西纳 (Julian Messina)

使用Wwise作为合成器实现一个音乐创作App

项目介绍...

30.9.2022 - 作者:视感科技

利用 Wwise 为游戏制作母带 | 第 1 部分:游戏的音频母带制作

无论是使用带有 3D Audio 的耳机、立体声 TV 扬声器还是豪华的 7.1.4 Atmos...

14.5.2024 - 作者:Loïc Couthier & Danjeli Schembri

如何使用 Wwise 和 Unity 创建可对音频作出反应的对象

在此,我想向大家展示如何使用 RTPC 在 Unity 中移动游戏对象,并创建由音频驱动/可对音频作出反应的对象。本文要求读者具备 Wwise-101...

28.5.2024 - 作者:Tomokazu Hiroki

更多文章

《11-11: Memories Retold》背后的声音设计

《11-11: Memories Retold》是一款叙事游戏,其背景设在第一次世界大战期间,讲述了两个角色在西部前线发生的故事。其中,Harry...

从 UI 设计角度探讨如何制作 UI 音频 - Part 1

在有些情况下,游戏的用户界面最终只会对玩家的整体体验产生很小的影响。在其他情况下,比如对很多手游来说,几乎整个游戏都是围绕 UI...

为何要选用 Wwise 来打造 3D 互动音乐体验

Dirty Laundry by Blake Ruby 移动 VR 应用:音频设计详解。