모든 화면에서 같은 배

일일 순위표는 오래된 작은 휴대폰, 144Hz 태블릿, 데스크톱 브라우저에서 같은 동굴 구간을 나는 것이 똑같이 어려울 때만 공정합니다. 여기에 무엇이 필요했는지, 그리고 무엇을 똑같이 만들 수 없었는지 소개합니다.

개발 일지 · 2026년 9월 20일 · 모든 글

일일 동굴 글은 모든 플레이어에게 세계를 동일하게 만드는 것에 관한 것이었습니다. 이번 글은 공정성의 나머지 절반, 즉 배를 동일하게 동작시키는 것에 관한 것입니다. 기기가 게임 플레이 방식을 바꿀 수 있는 방법에는 두 가지 별개의 방식이 있고, 이들은 각각 별개의 수정이 필요합니다. 하나는 화면이 얼마나 자주 갱신되는가이고, 다른 하나는 화면이 얼마나 큰가입니다.

문제 하나: 프레임 속도

배는 위치와 속도를 가집니다. 매 프레임마다 중력이나 추력이 속도를 바꾸고, 속도가 위치를 바꿉니다. 이를 작성하는 명백한 방법은 다음과 같습니다.

vy += accel * dt;     // update velocity
py += vy * dt;        // then move by the NEW velocity

무해해 보이지만 미묘하게 틀렸습니다. 두 번째 줄은 배가 이미 프레임 전체를 프레임 끝 속도로 이동한 것처럼 취급합니다. 배가 가속 중이라면, 이는 매 프레임마다 accel × dt²의 절반만큼 초과합니다. 문제는 프레임 길이인 dt가 기기마다 다르기 때문에 초과량도 다르다는 것이고, 그래서 배는 갱신 빈도마다 조금씩 다른 경로로 날았습니다.

0.5초 동안 추력을 유지한 상태로 측정했습니다. 정확한 답은 225픽셀입니다. 예전 적분기는 144Hz에서 228, 60Hz에서 232, 30Hz에서 240을 냈습니다. 세 기기, 같은 입력에서 세 개의 다른 거리입니다.

해결책은 프레임 전후 속도의 평균을 사용하는 사다리꼴 규칙입니다.

const vyPrev = vy;
vy += accel * dt;
py += (vyPrev + vy) * 0.5 * dt;   // average speed over the frame

일정한 가속도에 대해서는 프레임 길이와 관계없이 이것이 정확합니다. 동일한 입력 스케줄을 재생하면 이제 12Hz에서 144Hz까지 0.000픽셀의 편차가 나옵니다. 이전에는 4.1픽셀이었습니다. 테스트는 두 가지를 모두 확인합니다. 새 적분기가 모든 갱신 빈도에서 일치한다는 것과, 예전 것이 실제로 어긋났다는 것입니다. 그래서 누군가 이것을 되돌리면 순위표가 조용히 흐트러지는 대신 테스트가 실패합니다.

두 가지 솔직한 유보 사항

이것이 갱신 빈도 간의 점수 격차를 없애지는 못했습니다. 예상했던 일입니다. 시뮬레이션된 조종사는 그 후에도 60Hz보다 144Hz에서 여전히 약 3분의 1 더 높은 점수를 냈습니다. 그 격차는 물리가 아닙니다. 입력 해상도입니다. 엄지손가락은 프레임 경계에서만 누름과 떼기를 전환할 수 있으므로, 더 빠른 화면은 실제로 더 세밀한 제어를 제공합니다. 어떤 적분기도 이를 고칠 수 없으며, 매 프레임마다 입력을 뒤집는 봇은 사람에 비해 이를 과장합니다. 물리 쪽 절반은 이제 정확하고, 나머지는 하드웨어의 속성입니다.

60Hz에서는 아주 약간의 너프입니다. 0.5초 상승이 이제 232픽셀 대신 225픽셀을 커버합니다. 이것은 보상하지 않은 채로 두었습니다. 실제 기기 테스트에서 배가 둔하게 느껴진다면, 지렛대는 추력 상수이며 약 3% 더 늘리면 예전 0.5초 동작이 복원됩니다. 마침 추력이 플레이어 피드백으로 이미 두 번 바뀐 적이 있다는 이야기를 할 좋은 순간인데, 그 이야기가 제가 이 값을 건드리는 데 신중한 이유입니다.

문제 둘: 화면 높이

초당 픽셀 제곱 단위의 중력은 높이 375포인트의 휴대폰과 높이 520포인트의 태블릿에서 매우 다른 의미를 가집니다. 상수가 같다면 같은 상승이 태블릿에서는 화면의 훨씬 작은 비율만 가로지르게 되어 게임이 더 무겁게 느껴질 것입니다.

그래서 상수들은 게임의 느낌이 조정되고 테스트된 가장 큰 아이폰의 가로 방향 높이인 440포인트라는 기준 높이를 기준으로 정의됩니다. 다른 어떤 화면에서든 중력, 추력, 종단 속도는 모두 H / 440으로 곱해집니다. 통로의 절반 간격도 높이에 비례해 조정되므로, 모든 궤적은 기준 궤적과 기하학적으로 닮은꼴이 됩니다. 초당 통과하는 통로의 비율이 같고, 한쪽 벽에서 다른 쪽 벽까지 가는 시간이 같으며, 체감되는 반응성이 같습니다. 더 큰 화면은 픽셀 단위로 더 가파른 가속을 얻고, 더 작은 화면은 의도적으로 더 완만한 가속을 얻습니다.

이는 앞으로의 모든 코드에 대한 하나의 규칙을 동반합니다. 수직 움직임을 다루는 모든 것은 비율 기반이어야 한다는 것입니다. 종단 속도의 일부, 높이의 일부처럼요. 배의 속도와 비교되는 원시 초당 픽셀 상수는 절대 사용하지 않습니다. 그 숫자는 한 기기에서만 맞기 때문입니다. 리터럴이 불가피하다면 같은 배율을 곱합니다.

웹 버전도 같은 규칙을 따릅니다. 브라우저 빌드는 캔버스를 그 기준 휴대폰과 같은 크기인 956x440으로 고정하므로, 브라우저에서의 게임은 픽셀과 물리 면에서 정확한 복제본입니다. 이전 웹 빌드에는 독자적인 느낌 배율이 있었지만, 이는 다른 한계로 인해 생긴 흐물흐물함에 맞서기 위해서만 존재했고 한계가 일치하자 삭제되었습니다.

문제 셋: 화면 너비

높이는 수직 축입니다. 너비는 수평 축이며, 더 미묘합니다. 장애물은 세계 안에서 고정된 거리로 배치됩니다. 동굴은 화면 너비에 대한 계수를 포함한 속도로 스크롤됩니다. 그래서 화면이 더 넓으면 같은 장애물이 화면 기준으로 더 빨리 지나가고, 각각에 반응할 시간은 너비에 비례해 줄어듭니다.

이것은 실제로 존재하는 허점이었습니다. 너비 약 1280포인트인 대형 안드로이드 태블릿은 작은 휴대폰보다 약 1.75배 빠르게 공유 동굴을 마주했습니다. 그래서 너비는 모든 플랫폼에서 가로 방향 가장 넓은 아이폰의 너비인 956포인트로 제한됩니다. 그보다 넓은 기기는 좌우에 검은 여백이 생깁니다. 아이폰에서는 아무 효과가 없고 대형 안드로이드 기기와 데스크톱 브라우저만 제한합니다.

이 상한에는 아직 고치지 못한 잔여물이 있습니다. 플레이어의 충돌 반경은 너비에 비례해 조정되지만 통로는 높이에 비례해 조정되므로, 더 정사각형에 가까운 태블릿에서는 배가 휴대폰보다 간격에 비해 약간 더 작아져 약 20% 더 관대해집니다. 이를 고치려면 히트박스를 바꿔야 하는데, 이는 별도의 플레이테스트가 필요한 느낌 변경이며 관대한 방향으로 치우쳐 있으므로 미뤄도 됩니다.

공정성을 주장하는 대신 테스트하기

이 중 어느 것도 플레이하면서 눈에 보이지 않습니다. 몇 픽셀의 오차를 느낄 수는 없고, 아무도 여섯 대의 기기에서 동시에 같은 동굴을 플레이하지 않습니다. 그래서 각 주장에는 그것을 재현하는 테스트가 있습니다. 물리 테스트는 12Hz에서 144Hz까지의 프레임 속도에서 고정된 입력을 적분하고, 동굴 테스트는 실제 생성기를 여섯 가지 기기 크기에서 재생해 결과를 바이트 단위로 비교합니다. 제 경험상 테스트하지 않은 공정성 속성은 이미 조용히 깨져 있었습니다.

요약하면, TUNL은 세 가지 별개의 방식(프레임 속도, 높이, 너비)으로 화면 간에 공정하며, 각각 원인도 수정 방법도 잔여물도 다릅니다. 가지고 있는 어떤 기기에서든 오늘의 동굴에서 직접 결과를 확인할 수 있습니다.

오늘의 동굴 플레이