menu
 

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)

작곡가 겸 사운드 디자이너

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

댓글

댓글 달기

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

다른 글

머더러스 퍼수츠(Murderous Pursuits)의 대사와 대화 설계 - 제 2부

안녕하세요, 머더러스 퍼수츠의 대화와 대사 시스템의 제 2부로 다시 돌아온 제이미입니다. 제 1부를 아직 보지 않으셨다면 여기를 클릭하여 읽어주세요! 제 1부에서는 게임에서의...

20.5.2020 - 작성자: 제이미 크로스(JAIME CROSS)

동적 음악 설계에 관하여 - 제 1부: 설계 분류하기

설계 계기 저는 2015년에 오디오 게임 엔지니어로서 처음 일을 하게 되면서 그 당시 저의 아트 디렉터를 통해 Wwise를 접하게 되었습니다. 그전에 저는 게임 음악을 작곡하는...

7.10.2020 - 작성자: 천종 호우 (Chenzhong Hou)

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

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

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

Strata, Wwise, Unreal을 결합해 몰입형 게임 환경 만들기

이 블로그에서는 Wwise가 통합된 Unreal Engine 5 프로젝트의 멀티트랙 컬렉션 중 하나를 사용하여 Strata를 이용한 상호작용 디자인 과정을 살펴보겠습니다.이...

16.5.2023 - 작성자: 체이스 스틸(Chase Steele)

Strata 작업 과정 파워업하기 | 2부 - "연결된 REAPER 프로젝트 열기"

Wwise를 REAPER와 함께 사용하는 사운드 디자이너라면 Wwise에서 작업하는 동안 REAPER에서 사운드를 다시 렌더링하고 싶은 경우가 종종 있습니다. 원래대로라면 관련...

15.8.2023 - 작성자: Audiokinetic (오디오키네틱)

임펄스 응답 리버브로 현실감 있는 사운드 만들기

임펄스 응답은 실제 공간을 초현실적으로 재구현하는 것으로 유명합니다. 최고 품질의 임펄스 응답을 녹음하는 것은 다소 기술적이며 고급 장비가 필요한 일입니다. 공간적인 품질을 갖춘...

8.9.2023 - 작성자: BOOM Library

다른 글

머더러스 퍼수츠(Murderous Pursuits)의 대사와 대화 설계 - 제 2부

안녕하세요, 머더러스 퍼수츠의 대화와 대사 시스템의 제 2부로 다시 돌아온 제이미입니다. 제 1부를 아직 보지 않으셨다면 여기를 클릭하여 읽어주세요! 제 1부에서는 게임에서의...

동적 음악 설계에 관하여 - 제 1부: 설계 분류하기

설계 계기 저는 2015년에 오디오 게임 엔지니어로서 처음 일을 하게 되면서 그 당시 저의 아트 디렉터를 통해 Wwise를 접하게 되었습니다. 그전에 저는 게임 음악을 작곡하는...

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

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