이 글은 그 문제를 분석하고, Unity 환경에서 오디오와 입력 지연을 최소화하여 정확도를 높이는 방법을 공유합니다.
독자적인 연구 부분이 많아 잘못된 내용이 있을 수도 있습니다.
예제태고갤에 소개한 태고의 달인 연습 프로그램 TaikoPractice
리듬 게임이 어떻게 구현되는지도 궁금했지만 사실 태고의 달인 연습하는 프로그램을 만들고 싶었습니다.
매번 틀리는 구간만 틀려서 특정 패턴만 연습하고 싶었거든요. 자세한 내용은 해당 글 참고 바랍니다.
1. 문제의 본질: 세 가지 지연리듬 게임에서 타격감과 싱크를 해치는 주요 원인은 크게 세 가지입니다.
- 오디오 지연 (30~100ms, 가장 큼)
- 입력 지연 (~8ms, 코어 게이머 아니면 못 느끼는 정도)
- 모니터 디스플레이 지연 (오래된 모니터 아니면 미미함)
입력·화면 지연은 보정으로 일정 수준까지 맞출 수 있습니다. 하지만 "키음이 있어 키를 누르면 바로 소리가 나야 하는 게임의 경우" 오디오 지연은 게임 플레이에 치명적입니다.
2. 오디오 지연 줄이기그래픽에 DirectX/OpenGL/Metal이 있듯, 오디오에도 백엔드가 있습니다.
- ASIO
- 음악 제작용으로 널리 쓰이는 저지연 드라이버.
- Windows 오디오 스택을 거치지 않고 장치로 직접 출력 → 딜레이가 거의 없음.
- 단점: 다른 앱에서 소리를 캡처하기 어렵고 (녹화 프로그램을 못 씀), 일부 메인보드 사운드 칩셋은 지원하지 않음.
- WASAPI
- Windows 기본 드라이버. 호환성은 좋지만 지연이 큼.
- 독점 모드로 약간 줄일 수 있으나 Unity 기본 오디오 엔진에서는 지원되지 않음
Unity 기본 오디오 시스템은 WASAPI 기반이라 정밀한 판정을 요구하는 리듬게임에는 부적합합니다. 따라서 ASIO 지원 엔진을 선택해야 합니다.
하지만 모든 기본 메인보드 사운드 칩셋이 ASIO 를 지원하지는 않아 유저 입장에선
- 오디오 인터페이스 구매
- 혹은 가이드 를 참고하면 어느정도 비슷하게 흉내낼 수 있음.
2가지 방법중 하나를 고려해야 합니다.
FMOD 도입대표적인 대안은 외부 사운드 엔진 FMOD입니다.
- 상업 리듬게임(DJMAX, EZ2ON 등)도 FMOD을 사용하는 경우가 많음.
- ASIO를 지원 → 오디오 지연을 크게 줄일 수 있음.
이런 고급 엔진을 쓰는게 오버킬인 것 같기도 해서 다른 오디오 엔진도 찾아서 시도해 보았습니다만 적용이 힘들었습니다.
최근 게임 시장에서 많이 사용되고 있는 추세라서 배워두면 좋을 것 같습니다.
FMOD 설정기본적인 프로젝트 설정은 아래의 글을 참고해보시고.. sicilian-najdorf.tistory.com/49
프로젝트에서
키음을 Assets에 등록하고 간단한 2D Action Event를 생성합니다.
- 오디오 데이터 내보내기전 데이터를 담아주는 공간.
- 버퍼 크기가 크면 클 수록 연산량은 줄어들지만 그만큼 지연.
상단 메뉴 FMOD > Edit Setting 에서 Platform Specific을 눌러보시면 유니티 에디터랑 빌드에서 사용될 설정을 볼 수 있고 Auto 를 풀고 2^k 형태로 값을 적어주시면 됩니다.
64 정도가 적당한 것 같습니다.
런타임 바꾸는 방법도 있는데 FMOD 시스템 전체를 재시작해야 해서 좀 복잡합니다.
Backend의 경우 스크립트로 바꿔줘야 하는데
var coreSystem = RuntimeManager.CoreSystem; var result = coreSystem.setOutput(AudioBackend.ASIO);런타임에도 여러번 바꿀 수 있습니다.
위와 같이 오디오 출력이 가능합니다.
www.fmod.com/docs/2.00/api/core-api-system.html#system_createsound 를 사용하여 URL 로 사운드를 로드하거나 메모리에 올라간 데이터로 사운드를 만들 수 있습니다.
float[] samples; CREATESOUNDEXINFO exinfo = new() { cbsize = Marshal.SizeOf(typeof(CREATESOUNDEXINFO)), length = (uint)samples.Length, numchannels = 2, // 사운드에 따라 변경 필요 defaultfrequency = 44100, // format = SOUND_FORMAT.PCMFLOAT }; RESULT result = RuntimeManager.CoreSystem.createSound(Marshal.UnsafeAddrOfPinnedA rrayElement(samples, 0), MODE.OPENMEMORY | MODE.OPENRAW | MODE.CREATESAMPLE, ref exinfo, out var sound); 3. 입력(Input) 지연 줄이기방법은 입력만 담당하는 입력 스레드를 만드는 겁니다.
다만 유의해야 할 점은
- 다른 스레드에서는 유니티 API에 접근할 수 없음
- 동기화 문제가 발생할 수 있으므로
- C# collection을 사용할 경우 Thread-safe collections 을 사용
- 여러 스레드에서 동시에 실행될 수 있는 코드는 learn.microsoft.com/ko-kr/dotnet/csharp/language-reference/statements/lock 을 사용하여 문제를 해결
이렇게 하면 Update 메소드를 1ms 단위로 실행할 수 있습니다. 스레드를 1ms 만큼 재웠지만 더 걸릴 수 있습니다. 실제로 초당 1000번 실행되는게 아니라 600-700번 정도 실행되는 것 같습니다.
user32.dll에 Key상태를 알 수 있는 API가 있습니다. GetAsyncKeyState(c) & 0x8000 가 0 이 아니면 현재 내려가 있는 상태고 그렇지 않으면 올라가 있는 상태입니다. Dictionary에 사용하는 키 마다 이전 상태를 저장하도록 하고 상태가 바뀌면 키가 눌린 것을 감지할 수 있습니다.
foreach(char c in keys) { var prevState = state[c]; var currState = (GetAsyncKeyState(c) & 0x8000) != 0; if (!prevState && currState) { OnPressed.Invoke(c); } state[c] = currState; }인개갤에도 리듬 게임 만드시는 분들이 계시는것 같은데 도움이 되셨으면 합니다.
GetAsyncKeyState는 해봤는데 가끔 키입력 씹히더라
PC 환경마다 다를 수도 있나보네 위에 예시로 만든거 본인 PC에선 씹히는 경우는 못본거 같음
리겜 만들면서 fmod 최초 적용이 머리 아프더라구요. 특히 기존 모든 처리가 유니티 오디오 타임을 추종하도록 해둬서 적용시 어떻게 처리할지에 대한 부분이랑(오디오 소스를 대체하더라구요) fmod 스튜디오로 뭔가 작업을 해줘야 하는것 같은데 다른 할게 많으니까 메뉴얼 보기 너무 머리 아파서 다시 롤백 해버림ㅠㅠ
저도 진짜 머리 박아가면서 겨우했는데 사실 판정 넉넉하고 키음만 아니면 유니티 오디오로도 충분한거 같긴해요
FMOD 미리 깔아놓고 관계없는 기능부터 개발중인데 잘 참고하겠습니다. 감사합니다. - dc App
아하.. 이거 때문에 빌드 테스트할 때마다 묘하게 싱크가 안 맞았던 건가.. 글자 한글자씩 나올 때 타이핑음 나오는 연출이엇는데
감사합니다.