본문 바로가기

블로그 / 기술

공룡 발이 BGM 박자에 떨어지게: AvoidNow 100 BPM 박자 맞추기

AvoidNow에서 거대 공룡의 발이 음악 박자에 맞춰 찍히도록 만든 과정을 정리했다. BGM의 BPM을 재는 법, 패턴 묶음을 통째로 미는 방법, 커지는 타악기 레이어까지.

Salmonholic ·· 4분 읽기

거대 초식공룡의 다리가 여러 개 내려와 발자국을 남기는 AvoidNow 화면

AvoidNow를 하다 보면 “쿵” 소리가 음악과 같이 떨어진다. 우연이 아니라 그렇게 맞춘 것이다. 모든 장판 묶음은 첫 판정 시각이 BGM의 박에 떨어지도록 밀리고, 걸어가는 공룡은 한 박에 한 걸음씩 걷는다. 9월 26일 밤 커밋 a69870f에 들어간 기능이다. 이 글은 어떻게 박자를 재고, 어떻게 패턴을 박에 붙였는지 적은 것이다.

먼저 BGM의 박자를 쟀다

첫 버전의 소리는 파이썬으로 합성한 임시음이었다. 같은 날 오후 효과음과 BGM을 모두 CC0 에셋으로 바꿨고, BGM은 OpenGameArt에 올라온 “Bamboo Blitz”를 골랐다. 파일은 다시 인코딩하지 않고 그대로 쓴다. 이음매 없이 반복되는 76.8초짜리 곡이다.

박자를 코드에 넣으려면 BPM과 첫 박의 위치를 알아야 한다. 오디오 빌드 스크립트에 측정 결과가 주석으로 남아 있다. 소리가 시작되는 지점(온셋)을 찾아 자기상관을 구하고, 후보 박 간격으로 곡을 접어 봤다. 결과는 100 BPM, 한 박 0.6초, 한 마디 2.4초, 32마디였다. 곡 길이 76.8초를 2.4초로 나누면 정확히 32마디가 나온다. 125 BPM(한 박 0.48초)도 후보였지만 자기상관이 음수가 나와서 버렸다. 첫 다운비트는 파일의 0초였고, 타격음이 격자보다 약 4ms 늦게 울린다는 것까지 적어 뒀다.

BGM 파형과 100 BPM 박 격자 BGM 앞부분 7.2초 파형 위에 0.6초 간격 세로선을 그었다. 큰 타격이 굵은 선(마디 시작)에 떨어진다.

패턴 묶음을 통째로 민다

박자 값이 정해지면 규칙은 단순하다. 규칙 파일에 BPM = 100, BEAT = 0.6을 두고, 어떤 시각 뒤의 가장 가까운 박을 돌려주는 함수를 하나 만들었다.

next_beat(t) = 첫 박 + ceil((t - 첫 박) / 0.6) × 0.6

패턴은 하나가 아니라 묶음으로 나온다. 걸어가는 공룡은 몸통 그림자와 발자국 여러 개, 낙석은 돌 한두 개, 돌비는 돌 여러 개가 한 묶음이다. 묶음을 만든 뒤 그 안에서 가장 먼저 판정이 시작되는 시각을 찾고, 그 시각이 다음 박에 오도록 묶음 전체를 같은 만큼 뒤로 민다. 경고 시작, 판정 시작, 판정 끝, 추적 고정 시각, 알의 등장과 사라짐까지 모두 같이 민다. 묶음 안의 간격은 그대로라서 패턴 모양이 바뀌지 않는다. 몸통 그림자와 아이템은 판정이 없으니 기준에서 뺀다.

밀리는 양은 최대 한 박, 0.6초다. 묶음 전체가 조금 늦게 시작할 뿐 경고 길이는 그대로라서, 박자를 맞춘다고 피하기가 어려워지지는 않는다.

걸어가는 공룡은 한 박에 한 걸음

발 패턴은 한 번 더 손을 봤다. 원래 걸어가는 공룡은 시간이 지날수록 빨라졌다. 같은 날 저녁 난이도를 올린 커밋 6de0cdb에서 걷는 속도를 320px/s에서 440px/s로 올려 두기도 했다. 그런데 속도가 바뀌면 발자국 사이 시간도 바뀌어서 박자와 어긋난다.

그래서 걸음 간격을 한 박으로 고정했다. 한 걸음이 190px이니 속도는 190 ÷ 0.6, 약 317px/s로 정해진다. 첫 발이 박에 떨어지도록 묶음을 밀면 나머지 발도 모두 박에 떨어진다. 공룡이 여러 마리면 두 박씩 엇갈려 출발한다. 솔직히 적어 두면, 이 변경 뒤로 걷는 속도 곡선 함수는 규칙 파일에 남아 있지만 어디서도 부르지 않는다. 난이도는 공룡 수와 패턴 간격, 경고 시간으로 올리고 있다.

걸어가는 공룡이 박자마다 한 걸음씩 찍는 장면 봇 데모 녹화에서 0.6초(한 박) 간격으로 뽑은 네 장면. 박마다 새 발자국 경고가 생기고 발이 찍힌다.

30초마다 오는 이벤트도 박 단위로 짰다. 운석우는 반 박마다 돌 하나, 거대 발 연타는 세 박마다 발 하나, 티라노 떼는 다섯 박 간격으로 두 번이다. 자세한 구성은 30초 이벤트 정리에 있다.

음악을 게임 시각 0초에 맞춘다

패턴을 박에 맞춰도 음악이 엉뚱한 곳에서 재생되고 있으면 소용이 없다. 판이 시작될 때 BGM을 처음부터 다시 튼다. 게임 시각 0초와 곡의 0초, 첫 다운비트가 모두 같은 지점이 된다. 멀티플레이 서버도 같은 BPM 값을 공유 규칙에 갖고 있어서, 서버가 만든 패턴도 같은 격자에 떨어진다. 서버 테스트에는 60초 동안 나온 발자국의 판정 시각이 모두 박 격자에서 0.002박 이내인지 세는 항목이 있다. 기대값은 0개다.

판이 격해지면 북소리가 커진다

BGM 위에는 같은 길이(76.8초), 같은 100 BPM 격자로 만든 타악기 레이어가 하나 더 겹쳐 돈다. 손북 루프에서 한 마디를 떼어 32마디에 깔고, 나무와 둔탁한 타격음을 박 위치에 얹어 만들었다. 130 BPM 전쟁북 루프를 늦춰서 까는 것도 시험했는데, 손으로 친 롤이라 격자에서 흔들려서 뺐다.

이 레이어는 처음에는 들리지 않는다(-60dB). 20초부터 90초 사이에 조금씩 커지고, 이벤트 구간 동안은 최대가 된다. 두 트랙이 0.08초 넘게 어긋나면 타악기 쪽을 BGM 위치로 다시 맞춘다. 음량만 바뀌고 박은 그대로라서, 판이 길어질수록 같은 박자가 점점 크게 들린다.

박자를 맞춘 뒤 가장 달라진 건 피하는 감각이다. 경고를 보고 피하던 게, 음악을 듣고 “다음 박에 떨어진다”를 예상하며 피하는 쪽으로 바뀌었다. 소리를 켜고 AvoidNow를 해 보면 차이를 바로 느낄 수 있다.

#AvoidNow#사운드#BGM#Godot#게임 디자인

함께 읽으면 좋은 글

전체 글 →