Wwise를 사용하여 UE 게임에 두 개의 오디오 장치 구현하기

게임 오디오 / 사운드 디자인 / VR 체험

1

먼저 제 소개를 해드릴게요. 저는 에드 카신스키(Ed Kashinsky)이며 러시아 상트페테르부르크 출신 사운드 디자이너 겸 음악가입니다. 현재 저는 아주 흥미롭고 독특한 사운드를 가진 프로젝트를 작업하고 있죠. 바로 프리스트 vs. 폴터가이스트 VR (Priest vs. Poltergeist VR)라는 로컬 멀티플레이어 VR 게임입니다. 이 프로젝트에 사운드를 작업하는 것이 굉장히 까다로웠습니다. 사운드가 아주 흥미롭고 독특하다고 느껴지는 이유가 바로 이 때문이지 않을까 하네요.

프리스트 vs. 폴터가이스트는 폴터가이스트 (시끄러운 소리를 내는 유령)의 역할을 하는 VR 플레이어와 성직자의 역할을 하며 마우스와 키보드로 캐릭터를 제어하는 PC 플레이어 간의 결투 게임입니다. 여기서 까다로운 점은 두 플레이어 모두 한 PC에서 플레이한다는 점입니다. 두 플레이어가 다른 출력 장치 (VR 헤드폰과 PC 헤드폰/스피커)를 통해 소리를 들어야 할 뿐만 아니라, 각 사운드가 별도의 위치 지정, 감쇠, 차단, 타이밍 등을 가져야 하죠.

레벨의 어딘가에서 의자가 땅바닥으로 떨어진다고 상상해보세요. 한 플레이어가 불과 몇 미터 떨어진 곳에 있었다면 소리가 아주 명확하게 들리겠죠. 하지만 다른 플레이어가 같은 거리만큼 떨어져 있지만 벽을 사이에 두고 있을 경우 충돌음이 더욱 건조하고 낮게 들릴 겁니다. 그렇기 때문에 두 플레이어 모두 각자의 출력 장치에서 충돌음을 들어야 하지만 각자 현재 위치와 설정에 따라 소리가 변경되어야 합니다.

Unreal의 네이티브 오디오 라이브러리를 포함한 몇 가지 해결책을 시험해본 후 저희는 Wwise를 사용하기로 했습니다. Wwise는 별도의 응용 프로그램 안에서 원하는 대로 사운드를 작업할 수 있게 해주는 게임을 위한 독립형 오디오 엔진입니다. 심지어 저희 게임의 궁극적인 목표인 사운드를 서로 다른 오디오 장치로 전송하는 기능도 가지고 있죠.

이 엔진의 기능을 Unreal에 통합하기 위해서 Wwise는 플러그인을 제공합니다. 이론적으로 이 플러그인은 Wwise 엔진의 모든 기능을 게임 엔진으로 전달하여 게임 엔진 안에서 직접 사용할 수 있게 해주어야 합니다. 여기서 바로 문제점이 생겼습니다. Wwise의 Unreal 플러그인이 저희가 보조 오디오 장치 출력으로 사운드를 재생하는 데에 필요한 기능을 포함하지 않더라고요.

그래서 직접 C++를 사용하여 이 기능을 추가해야 했죠.

목표

플러그인의 주요 컴포넌트인 AkComponent는 레벨 안에 존재하며 리스너에미터 두 가지 모두로서 작동합니다. 에미터는 레벨 안에 있는 어느 위치에서나 사운드를 재생합니다. 리스너는 한 쌍의 귀와 같이 모든 사운드를 듣고 캐릭터의 위치에 따라 사운드를 오디오 장치로 출력합니다 (보통 카메라 컴포넌트에 연결되어 있죠).

저희 게임에는 두 캐릭터가 있습니다. 각 캐릭터는 동시에 두 오디오 장치로 출력하는 리스너의 역할을 하는 AkComponent를 가지고 있습니다. 일반적으로 다음과 같죠:

2

  • 에미터는 Post Ak Event 블루프린트 함수를 실행하며 이로 인해 Wwise에서 이벤트가 실행됩니다.
  • 이 이벤트는 두 오디오 버스로 전송되는 사운드를 재생하죠.
  • 오디오 버스는 각자의 Wwise 오디오 장치로 사운드를 전송합니다. 저희 게임의 경우 System 혹은 System_VR로 사운드를 전송하죠.
  • 각 리스너는 각자의 출력으로 연결되어 있으며 (Output Device 1, Output Device 2) Wwise로부터 사운드를 듣습니다. 사운드가 오디오 버스에 도달하면 각자의 출력에서 사운드를 재생합니다.

이렇게 하기 위해서 저희는 AkComponent 설정에서 두 개의 텍스트 입력란을 얻어서 사운드를 라우팅할 오디오 장치의 이름을 입력할 수 있게 해야 했습니다.

3

1 단계. Wwise 구성하기

먼저 Wwise 오디오 장치오디오 버스를 만들어봅시다. 더 자세한 정보는 원본 설명서 (버스 라우팅 섹션)를 참고해 주세요.

4

  • Wwise에서 VR 플레이어에 사용할 장치를 추가합니다. 타입을 'System'으로 설정하고 이름을 'System_VR'로 지정합니다. 바로 이 이름이 나중에 AkComponent 설정의 'Wwise Device Name' 입력란에 입력해야 할 Wwise 오디오 장치의 이름입니다.
  • VR에 사용할 Audio Bus를 만들고 System_VR을 Audio Device로 설정합니다.

5

  • Wwise에서는 사운드가 단 하나의 출력 버스로만 전송될 수 있습니다. 하지만 저희는 사운드가 두 캐릭터에게 재생되어야 하기 때문에 Auxilary Bus를 만들어서 사용해봅시다.

6

한 사운드를 두 장치에서 재생하기 위해서 VR 오디오 버스에 사용할 네스팅된 Auxiliary Bus를 만들어야 합니다. 바로 다음과 같이 말이죠:

7

이제 사운드를 각 캐릭터로 혹은 두 캐릭터로 전송할 수 있습니다.

***

이제 주요 작업이 끝났으니 모든 사운드에 버스를 설정하기만 하면 됩니다. 예를 들면 다음과 같습니다:

  • 성직자 사운드 (PC): Output Bus를 Master Audio Bus로 설정하고 Auxiliary Bus는 비워둡니다
  • 폴터가이스트 사운드 (VR): Output Bus를 Master Audio VR Bus로 설정하고 Auxiliary Bus는 비워둡니다
  • 두 캐릭터에 사용할 사운드: Output Bus를 Master Audio Bus로 설정하고 Auxiliary Buses 목록에서 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:’ 뒤에 다음 내용을 추가합니다

//wwise에서의 장치로 오디오 장치 연결
AkOutputDeviceID AddCustomOutput(FString AudioDevice, FString WwiseDevice, UAkComponent* in_pComponent);
//연결 제거
AKRESULT RemoveCustomOutput(AkOutputDeviceID deviceId);
// 하위 문자열로 오디오 장치 검색
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로 가서 PostRegisterGameObjectPostUnregisterGameObject라는 두 개의 빈 함수를 찾아서 다음 내용으로 교체합니다:

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 에디터를 실행하기만 하면 됩니다.

모든 작업을 올바르게 실행했다면 Unreal Engine 에디터의 AkComponent 설정에 두 개의 텍스트 입력란이 나타납니다. 바로 이 입력란이 오디오 출력 장치를 Wwise 오디오 장치와 연결해 줍니다.

  • Wwise Device Name - 저희의 경우 'System' 혹은 'System_VR'
  • Audio Device Name - 예를 들어 'Sony Headphones'과 같은 하드웨어 장치 이름. 'Headphones' 만으로도 충분히 실제 장치를 찾을 수 있을 겁니다.

장치를 찾았는지의 여부는 Unreal Editor의 Output Log에서 확인할 수 있습니다:

12

3 단계. 마무리 작업

마지막 단계는 캐릭터 리스너를 설정하는 것입니다. 각 폰/캐릭터에 사용할 AkComponent를 만듭니다. 그런 다음 AkComponent의 설정에서 각 컴포넌트에 맞게 오디오 장치를 입력합니다.

13

이때 '모든' 에미터에 리스너를 설정하는 것이 아주 중요합니다. 그렇지 않으면 이때까지 작업한 내용이 작동하지 않게 됩니다. 리스너를 설정하기 위해서는 'Set Listeners'라는 블루프린트 함수를 사용할 수 있습니다.

Begin Play에서 다음 예시와 같이 함수를 사용하세요.

14

이제 작업이 끝났습니다. 에디터에서 게임을 실행하고 두 오디오 장치를 확인해보세요. Output Log에 다음 내용이 표시되며 두 출력 장치에서 사운드를 들을 수 있을 겁니다.

15

궁금한 것이 있으시다면 제 Soundcloud를 둘러보시거나 저에게 연락해 주세요.

 

에드 카신스키(ED KASHINSKY)

작곡가 겸 사운드 디자이너

에드 카신스키(ED KASHINSKY)

작곡가 겸 사운드 디자이너

에드는 상트페테르부르크 출신 사운드 디자이너 겸 음악가이며 사운드 제작과 소프트웨어 개발에 대한 지식도 겸비하고 있습니다. 상호작용 음악과 사운드 엔진에 관심이 많습니다.

댓글

댓글 달기

이메일 주소는 공개되지 않습니다.

다른 글

왜 Wwise를 쓰는 걸까요?

게임 음향 커뮤니티에서 가장 흔한 질문 중 하나는 바로 '미들웨어 사용의 장점을 상사나 고객에게 어떻게 설명해야 하는가'입니다. 우리 음향 전문가의 입장에서 이 질문은 너무나 쉽게...

12.10.2018 - 작성자: CIARAN WALSH (키어런 왈쉬)

하이브리드 상호작용 음악의 시대가 올 것인가? 제 1부 -상호작용 음악의 R&D 플랫폼으로 Get Even 사용하기

저는 게임 음악을 작곡할 때 어떻게 하면 플레이어에게 의미있게 다가갈 수 있을까 항상 고민합니다. 작곡가는 보통 크리에이티브 디렉터, 오디오 디렉터와 함께 이야기, 감정, 주제,...

22.10.2019 - 작성자: 올리비에 더리비에르 (OLIVIER DERIVIÈRE)

라우드니스에 대한 몇가지 사항

안녕하세요 여러분. 저는 중국 YooZoo Games의 오디오팀 멤버인 유장(Yu Zhang)이라고 합니다. 2006년에 ITU-R BS 1770 기준이 도입된 지 13년이...

20.4.2020 - 작성자: 유장 (YU ZHANG)

게임 사운드 보관 | 제 2부: '컨커 최악의 날'과 미스터리한 MP3

오늘 소개할 이야기는 뜻밖의 결과, 다시 말해 우연한 발견에 관한 이야기입니다. 연구 프로젝트가 의도한 대로 끝나지 않았기 때문에 복잡하게 느껴지실 수도 있지만 끝까지...

29.9.2021 - 작성자: 파니 러비야르 (Fanny REBILLARD)

텔 미 와이(Tell Me Why) | 오디오 다이어리 제 2부: 음악

Tell Me Why의 음악은 본질적으로 캐릭터의 서사와 감정을 뒷받침하도록 설계되었습니다. 게임의 이야기는 두 주인공에게 아주 자세하게 집중되어 있으며 생각에 잠기기 쉬운 느린...

23.6.2022 - 작성자: 루이 마르탱 (Louis Martin)

어쌔신 크리드 발할라(Assassin’s Creed Valhalla) | 샌드박스 음악 시스템

어쌔신 크리드 발할라(Assassin’s Creed Valhalla)는 규모가 거대한 프로젝트였습니다. 어쌔신 크리드 프랜차이즈 중 가장 큰 맵 중 하나였고, 오픈 월드 장르에서도...

27.11.2024 - 작성자: 알렉상드르 푸아리에(Alexandre Poirier)

다른 글

왜 Wwise를 쓰는 걸까요?

게임 음향 커뮤니티에서 가장 흔한 질문 중 하나는 바로 '미들웨어 사용의 장점을 상사나 고객에게 어떻게 설명해야 하는가'입니다. 우리 음향 전문가의 입장에서 이 질문은 너무나 쉽게...

하이브리드 상호작용 음악의 시대가 올 것인가? 제 1부 -상호작용 음악의 R&D 플랫폼으로 Get Even 사용하기

저는 게임 음악을 작곡할 때 어떻게 하면 플레이어에게 의미있게 다가갈 수 있을까 항상 고민합니다. 작곡가는 보통 크리에이티브 디렉터, 오디오 디렉터와 함께 이야기, 감정, 주제,...

라우드니스에 대한 몇가지 사항

안녕하세요 여러분. 저는 중국 YooZoo Games의 오디오팀 멤버인 유장(Yu Zhang)이라고 합니다. 2006년에 ITU-R BS 1770 기준이 도입된 지 13년이...