소개
이 글은 제이터 (루오하오) 쉬(Jater (Ruohao) Xu)가 역붕괴: 베이커리 작전(Reverse Collapse: Code Name Bakery)에서 진행한 작업을 공유하는 3부작 기술 블로그 시리즈의 첫번째 글입니다. 이 글에서는 Wwise Time Stretch 플러그인을 중심으로 Wwise를 활용한 인게임 시네마틱을 제어하는 방법에 대해 자세히 설명합니다. 2부와 3부도 많은 기대 부탁드립니다!
제이터 (루오하오) 쉬는 최근 Audiokinetic 블로그에 '역붕괴: 베이커리 작전 | 원격 협업에서 Wwise의 중요한 역할'이라는 글을 올렸습니다. 이 글에서 그는 폴 러스케이(Paul Ruskay)와 함께 캐릭터 및 커스텀 애니매이션 사운드 디자인 관리, 게임을 위한 상호작용 음악 시스템 제작 과정 등 다양한 작업 과정을 공유했습니다.
Wwise Time Stretch 플러그인을 활용한 인게임 시네마틱 제어
기술 블로그 시리즈 | 제 1부
프로그램 창 간에 Alt-Tab을 빠르게 전환하거나 성능 제한으로 인해 프레임 속도가 버벅이는 상황 등에서 발생할 수 있는 오디오/비디오 동기화 문제를 해결하기 위해 '역붕괴'에서는 인게임 클럭에만 의존하지 않고 Wwise 클락(clock)을 활용한 오디오/비디오 동기화 코드를 구현했습니다. 더불어, 게임 시네마틱에서 인상적인 순간을 강조하는 데 필수적인 슬로우 모션 및 패스트 모션 효과를 지원하기 위한 추가 작업도 진행했습니다. 이는 다양한 활용이 가능한 Wwise Time Stretch 플러그인을 적극 활용해 구현했습니다. 이 접근 방식은 앞서 언급한 문제를 효과적으로 완화하는 데 도움이 되었습니다. '시네마틱'이라는 용어는 Unity Timeline, Unreal Sequencer 등 다양한 게임 엔진 기반의 기능을 포괄하지만, '역붕괴'는 Unity 기반으로 제작되었기 때문에 Unity 개발 환경을 중심으로 예시를 소개하겠습니다.
기본 개념
기본 개념부터 살펴보면, 이 방식의 핵심 철학은 놀라울 만큼 단순하며, 여러 게임 엔진에 적용할 수 있습니다.
현재 오디오 재생 시간이 비디오 재생 시간보다 느릴 경우, 비디오를 일시 정지함.
현재 오디오 재생 시간이 비디오 재생 시간보다 빠를 경우, 비디오를 오디오 재생 시간까지 재생함.
두 재생 시간이 동일할 경우, 이미 완벽하게 동기화된 상태이므로 아무 작업도 하지 않음.
게임이 Unity로 개발되었기 때문에, 위의 개념을 C# 기반의 수도 알고리즘(pseudo algorithm)으로 다음과 같이 작성했습니다.
private void AudioVideoSyncLogic()
{
if (audioTime < videoTime)
{
video.Pause();
}
else if (audioTime > videoTime)
{
video.Play();
}
else
{
continue;
}
}
이 초기 코드 조각을 Unity의 기본 Update() 함수에 통합하면, 시네마틱 재생 중 매 프레임마다 이 로직이 실행되도록 할 수 있습니다. 제대로 작동하기 위해서는 Timeline의 Update Method를 기본값인 'Game Time'에서 'Manual'로 변경해야 한다는 점을 꼭 기억하세요.

위 이미지는 RCAudioClockSync.cs 스크립트의 일부 설정을 보여줍니다. 이 경우, 재생하려는 Event나 앰비언스를 문자열 입력값으로 함께 전달할 수도 있습니다.
문제 해결
위 코드를 스크립트에 그대로 통합할 경우 몇 가지 문제가 발생할 수 있으며, 시네마틱이 게임 전반의 다양한 상호작용 상황에서도 정상적으로 재생되도록 하기 위해 코드 수정 및 개선이 필요합니다. 해결이 필요한 주요 문제들은 다음과 같습니다:
- 현재 오디오 재생 시간 가져오기: 스크립트가 오디오의 재생 진행 상황을 정확히 추적할 수 있도록 합니다.
- 시네마틱 정지 시 오디오와 비디오 동기화 처리: 시네마틱 종료 시 오디오와 함께 비디오도 정확히 정지되도록 보장합니다.
- 예외 처리용 안전 장치 구현: 시네마틱이 제대로 설정되지 않았을 경우에도 안정적으로 동작하도록 예외 처리를 적용합니다.
- 오디오 동기화를 위한 QA 기능: 동기화되지 않은 기본 설정과 비교하여 오디오 동기화를 검증할 수 있도록 QA 팀에서 활용할 수 있는 추가 기능을 개발합니다.
이러한 문제들을 해결함으로써 게임 내 시네마틱 시퀀스를 처리하는 스크립트의 안정성과 신뢰성을 향상시킬 수 있습니다.
현재 오디오 재생 시간 가져오기
Wwise는 프로그래머들이 활용할 수 있는 매우 유용한 함수를 제공합니다. 바로 GetSourcePlayPosition() 함수입니다 (링크: GetSourcePlayPosition (audiokinetic.com)).
GetSourcePlayPosition() 함수는 주어진 Playing ID에 대한 현재 오디오 재생 시간을 반환합니다. 편리하게도 Wwise Unity 통합에서는 원래 C++ 호출보다 더 간단하게 접근할 수 있는 형태의 함수가 제공되는데, 이는 다음과 같습니다:
public static AKRESULT GetSourcePlayPosition(uint in_PlayingID, out int out_puPosition, bool in_bExtrapolate)
이 메소드를 사용하기 위해 먼저 Playing ID를 전달하여 호출한 결과를 변수에 할당합니다. Ak_Success가 반환되면, 현재의 오디오 시간이 매개 변수 수정자인 out_puPosition으로 전달됩니다. 어떤 이유로든 Ak_Fail이 반환될 경우 함수는 -1을 출력합니다. 아래의 수도 코드는 이 솔루션을 설명하며, playingID 변수를 가져오는 과정은 예시에서 생략되어 있습니다.
public int GetSourcePlaybackPositionInMilliseconds(uint playingID, bool extrapolated)
{
int returnPos = 0;
AKRESULT returnResult = AkSoundEngine.GetSourcePlayPosition(playingID, out returnPos, extrapolated);
if (returnResult == AKRESULT.AK_Success)
{
return returnPos;
}
else
{
return -1;
}
}
따라서 GetSourcePlaybackPositionInMilliseconds()를 호출하고 그 반환 값을 audioTime 변수에 할당함으로써 현재 오디오 재생 시간을 실시간으로 가져올 수 있습니다. 만약 -1 값이 반환될 경우 예외로 간주하고 오디오/비디오 동기화 로직을 실행하지 않아야 합니다.
시네마틱 정지 시 오디오와 비디오 동기화 처리
앞서 언급한 AudioVideoSyncLogic() 함수를 다시 떠올려 봅시다. 오디오가 끝나면 비디오는 끊김 없이 따라가며 마지막 동작을 그대로 이어받아 현재 상태를 유지해야 합니다. 이상적으로는 오디오와 비디오가 동시에 끝날 경우 둘 다 함께 정지해야 합니다. 예를 들어, 만약 오디오가 끝나는 시점에 비디오가 일시 정지 상태라면 그대로 멈춰 있게 되어 게임이 멈춘 것처럼 보일 수 있습니다. 반대로, 오디오 파일 끝에 몇 초간의 무음 구간이 있을 경우, 오디오가 멈출 때까지 비디오는 계속 재생되어 화면이 부자연스러워질 수 있습니다. 게임에 치명적인 버그가 발생하지 않도록 구현 과정에서 이러한 문제들을 반드시 해결해야 합니다.
시네마틱 게임 오브젝트 자체를 파괴(destroy)함으로써 문제를 해결하는 경우도 있지만, 이러한 방식은 오디오 부서의 통제 범위를 벗어난 여러 외부 요인에 영향을 받게 됩니다. 반면, 오디오가 끝나는 시점에 비디오를 명시적으로 정지시키면 별도의 설명이 필요 없는 범용적인 솔루션을 구현할 수 있습니다. 이를 위해 AudioVideoSyncLogic() 함수의 마지막에 아래의 수도 코드를 추가할 수 있습니다. 참고로 videoDuration 변수를 가져오는 부분은 생략되어 있습니다.
if (audioTime > videoDuration)
{
video.Stop();
}
예외 처리용 안전 장치 구현
개발 과정에서는 시네마틱에 임시 오디오가 사용되거나 아예 오디오가 없는 상황이 자주 발생합니다. 게임플레이 측면에서는 메소드가 실행되지 않아 비디오가 멈추고 게임플레이가 중단되는 간헐적인 문제도 발생할 수 있습니다. 따라서 소프트웨어의 안정성을 위해 앞서 말한 메소드만으로는 충분하지 않습니다.. 위 메소드들이 실패할 경우를 대비해, 시네마틱을 개발하는 에디터 환경이든 게임플레이 런타임이든 관계없이 항상 작동할 수 있는 예외 처리용 안전 장치를 구현해야 합니다. 이 방식은 단순히 Unity의 기본 Update Method를 사용하는 것과는 다릅니다. 특히 런타임 중에는 플레이어가 사용중인 Update Method를 제어할 수 없어야 하기 때문입니다. 시네마틱 재생 중에 기본 Update Method로 전환하게 되면 예상보다 더 많은 알려진 문제들이 발생할 수 있습니다.
아래 함수는 시네마틱의 수동 업데이트 방식, 즉 폴백(fallback) 방식의 예를 보여줍니다. videoTime 변수를 가져오는 과정은 예시에서 생략되어 있습니다.
private void AudioVideoSyncFallbackLogic()
if (videoTime < videoDuration)
{
videoTime += deltaTime
video.Play();
}
else
{
video.Stop();
}
}
AudioVideoSyncLogic()는 유효한 Playing ID와 오디오 시간이 모두 확보되었을 때 실행되며, 그 외의 모든 상황에서는 AudioVideoSyncFallbackLogic()가 유용하게 사용됩니다.
요약하면, Update 또는 Tick 메소드 내에는 아래의 코드 블록과 함수 호출이 포함되어야 합니다. 다음은 수도 코드를 활용한 예시입니다.
private void LateUpdate()
{
int audioTime = GetSourcePlaybackPositionInMilliseconds((uint)playingID, true);
if (playingID != -1 && audioTime != -1)
{
AudioVideoSyncLogic();
}
else
{
AudioVideoSyncFallbackLogic();
}
}
여기서는 LateUpdate()를 사용하는데, 이는 Unity Timeline과 같은 시네마틱 내 모든 애니메이션 및 요소들이 완전히 업데이트된 후에 오디오와 비디오를 동기화하기 위함입니다. 이 방식은 간헐적으로 일부 요소가 제대로 업데이트되지 않는 경우에도 안정적인 결과를 제공합니다. 일반적인 Update() 함수를 사용할 수도 있지만, 커스터마이징된 스크립트와 시각적 요소들이 많이 포함된 Timeline에서는 LateUpdate() 사용을 권장합니다. 구현 방식은 프로젝트마다 다를 수 있으며, Timeline Playable이나 다른 애셋을 참조하기 위해 함수에 커스텀 입력 매개 변수를 추가하면 유용할 수 있습니다.
오디오 동기화를 위한 QA 기능
QA 팀이 시네마틱 관련 버그를 A/B 비교 영상으로 확인할 수 있도록 토글 기능을 추가했습니다. 이 토글은 오디오-비디오 동기화 및 폴백 메소드를 간단히 바이패스할 수 있게 해주며, 영상 녹화 전에 기본 Update Method인 Game Time으로 쉽게 되돌릴 수 있도록 합니다. 이를 통해 버그의 원인이 새로 추가된 오디오-비디오 동기화 기능 때문인지, Timeline 내의 애셋이나 스크립트 때문인지 쉽게 판별할 수 있습니다.
Wwise Time Stretch 플러그인을 활용한 슬로우 모션과 패스트 모션 구현
아래에 정리한 모든 단계를 따르면 많은 게임에서 효과적으로 동작하는 오디오-비디오 동기화 시스템을 구축할 수 있습니다. 하지만 각 프로젝트마다 추가적인 커스터마이징 기능이 필요한 특수한 경우가 있을 수 있습니다. 예를 들어, 역붕괴에서는 시네마틱에서의 인상적인 순간을 강조하기 위해 이 동기화 시스템이 슬로우 모션과 패스트 모션 기능을 추가로 지원해야 했습니다.
기존 오디오-비디오 동기화 시스템이 정상적으로 작동하면서도 원하는 목표를 달성하려면, 새로운 기능들을 기존 기반 시스템에 통합해야 했습니다. 이 때 Wwise Time Stretch 플러그인(링크: Time Stretch Plug-in (Time Stretch (audiokinetic.com)))이 활용됩니다. 이 플러그인은 Wwise에서 음높이를 변경하지 않고도 음성의 재생 속도를 조절할 수 있어 이번 사례에 최적의 솔루션입니다.
Time Stretch를 설정하려면, Project Explorer 계층 구조에서 Mixer, Container 또는 Audio Source의 Effect 탭으로 이동합니다. 이번 경우 시네마틱 SFX Actor Mixer에서 설정했습니다. 이 믹서는 시네마틱 SFX의 전반적인 재생 트랙을 관리하며, Time Stretch에서 설정한 모든 사항은 이 믹서 하위에 포함된 모든 시네마틱 SFX에 적용됩니다. 이 설정은 게임 내 모든 시네마틱 Timeline을 관리하며, 각 Timeline은 한 번에 하나씩 재생됩니다. (이 트랙은 Unity Timeline을 제어하는 데 사용됩니다.)

플러그인은 대부분 기본 설정을 유지하였으며, 코드에서 구현할 RTPC를 통해 Time Stretch 속성을 변경하는 것에 중점을 두었습니다.


Wwise 공식 문서에 따르면, Time Stretch 기능은 Y축을 기준으로 25부터 1600까지의 범위를 지원합니다. 이 값은 원본 사운드 지속 시간에 대한 백분율을 나타내며, 25%는 원래 속도의 4배 빠른 재생, 1600%는 16배 느린 재생을 의미합니다. 계산을 단순화하기 위해, X축 범위를 0.25부터 16까지 갖는 RTPC를 생성했습니다. 이는 원본 오디오 실제 재생 속도의 역 배수를 나타냅니다. 1을 분자로 두고 이 수치를 분모로 나누면, 실제로 사용할 수 있는 배율을 얻을 수 있습니다. 예를 들어, 1을 1/4로 나누면 4가 되어 재생 속도가 4배 빨라지고, 1을 16으로 나누면 0.0625가 되어 재생 속도가 16배 느려집니다.
이 설정의 유일한 단점은 Time Stretch 배율이 0.25에서 16까지로 제한된다는 점입니다. 상한이나 하한에 도달하면, 제한으로 인해 재생 속도를 더 높이거나 낮출 수 없습니다. 하지만 다른 많은 게임이 아닌 ‘역붕괴’ 전용 사례에서는, 이 범위가 슬로우 모션과 패스트 모션의 모든 상황을 포괄하기에 충분했습니다. 역붕괴에서는 게임 기획 및 애니메이션 팀의 요청에 따라 최대 배율을 0.25에서 4까지로 제한하여 적용하고 있습니다.
이번 예제에서는 매개 변수 수정자의 출력 값을 추출하는 작은 래퍼(wrapper) 함수를 만들고, 이 기능을 사용할 곳에서 필요에 따라 적용하는 방식을 소개합니다.
public float GetGlobalRTPC(string rtpcName)
{
int rtpcType = 1;
float acquiredRtpcValue = float.MaxValue;
AkSoundEngine.GetRTPCValue(rtpcName, null, 0, out acquiredRtpcValue, ref rtpcType);
if(acquiredRtpcValue >= 0.25f && acquiredRtpcValue <= 16.0f)
{
return acquiredRtpcValue;
}
else
{
return 1.0f;
}
}
RTPC를 전역으로 설정하는 것 외에도, 위 함수는 잘못된 값이 감지될 경우 RTPC 설정을 무시하고 기본값 1.0f로 재설정합니다.
마지막으로, 이 함수를 오디오-비디오 동기화 로직이 실행되는 코드 블록에 추가해보겠습니다. 시네마틱 재생 로직과 비디오가 끝난 후 시네마틱을 정지하는 로직 사이에 아래의 코드를 삽입하고, 계산된 값을 Unity의 timeScale 변수에 할당합니다.
Time.timeScale = 1.0f / GetGlobalRTPC(“TimelineTimeDilation”);
위에서 소개한 모든 단계를 거쳐 Wwise를 활용한 인게임 시네마틱 Timeline을 제어하는 작업을 성공적으로 구현했습니다. 또한, 원하는 지속 시간과 프레임 시간에 맞춰 최대 16배 느린 슬로우 모션부터 최대 4배 빠른 패스트 모션까지 자유롭게 활용할 수 있게 되었습니다. 이제 Unity Timeline 창에서 RTPC를 자유롭게 설정할 수 있습니다.

위 이미지는 프레임 1071부터 1078까지 0.1배속 슬로우 모션을 구현한 예시입니다. 이 구현에서는 슬로우 모션 종료 프레임(1079) 다음에 또 다른 RTPC를 생성하여 배율을 수동으로 1로 재설정해야 합니다.
최종 결과물에서는 Wwise에 의해 사운드와 영상이 모두 정확하게 느려져 오디오와 비디오 동기화에 대한 걱정이 해소되었습니다. 이 방식은 슬로우 모션과 패스트 모션 프레임용 오디오 애셋 제작이 따로 필요 없어, 사운드 디자이너의 개발 시간을 단축시켜 줍니다. 애니메이터 입장에서는 슬로우 모션과 패스트 모션 곡선에 대한 미세 조정이 더 이상 필요 없어졌죠.
최종 결과를 보여주는 영상 클립입니다. 기능 작동에 대한 이해를 돕기 위해 의도적으로 여러 번의 프레임 끊김을 만들었습니다. Wwise Time Stretch로 구현한 슬로우 모션은 영상 클립의 0:23초 지점에서 들을 수 있습니다.
알림: 이 글에서 사용된 코드 예제들은 설명을 위해 일반화하여 재구성된 버전입니다. 기본 로직은 정상적으로 동작하는 것이 검증되었으며, 특정 프로젝트에 특화된 API 호출과 함수들은 저작권 문제로 인해 예제에서 생략되었습니다.

댓글