게임 화면에 fps 를 띄우는 열 줄짜리 코드
지난 글에서 아이패드에서만 느린 게임을 고친 이야기를 적었다. 원인을 찾을 수 있었던 건 화면에 fps 를 찍어 두었기 때문이다. 느리다는 느낌만으로는 고쳐도 나아졌는지 알 수 없고, 기기를 바꿔 비교할 수도 없다. 이 글에서는 그 표시를 어떻게 만들었는지, 그리고 아이패드에서 숫자를 읽기까지 겪은 일을 적는다.
fps 는 1초에 화면을 몇 번 새로 그렸는지다. 브라우저에서 게임 루프를 돌릴 때는 보통 requestAnimationFrame 에 그리기 함수를 등록한다. 브라우저는 화면을 갱신하기 직전에 이 함수를 부르면서 현재 시각을 인자로 넘겨 준다. 그러니 이 함수가 불린 횟수를 세고, 1초가 지날 때마다 그 횟수를 시간으로 나누면 된다.
게임 루프를 이미 쓰고 있다면 루프 안에서 한 줄을 호출하는 것으로 끝난다. 설명을 위해 줄인 예시는 이렇다.
let frames = 0;
let since = performance.now();
let label = null;
function countFrame(now) {
frames++;
if (now - since < 1000) return;
const fps = (frames * 1000) / (now - since);
if (!label) {
label = document.createElement('div');
label.style.cssText = 'position:absolute;top:6px;left:50%;translate:-50% 0;' +
'padding:2px 6px;border-radius:6px;background:rgba(0,0,0,.6);color:#fff;font:700 13px system-ui;pointer-events:none';
document.body.append(label);
}
label.textContent = fps.toFixed(1) + 'fps';
frames = 0;
since = now;
}
function loop(now) {
update();
draw();
countFrame(now);
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
1초를 기다렸다가 평균을 내는 이유는 프레임마다 값이 출렁이기 때문이다. 한 프레임의 간격으로 계산하면 숫자가 계속 바뀌어 읽을 수 없다. 표시 칸은 처음 한 번만 만들고 이후에는 글자만 바꾼다. pointer-events: none 을 주어 이 표시가 게임 조작을 가로채지 않게 한다.
처음에는 구름 점프에만 이 코드를 넣었다. 그런데 같은 도구를 쓰는 다른 게임에도 같은 증상이 있었다. 게임마다 코드를 복사해 넣는 대신 모든 게임이 쓰는 공통 루프로 옮겼다. 켜 두지 않은 게임은 이 코드를 한 줄도 실행하지 않는다.
켜는 방법은 주소 뒤에 ?debug=fps 를 붙이는 것이다. 이 방식은 실제 사이트에서도 쓸 수 있다는 점이 컸다. 아이패드나 아이폰에서 주소만 바꿔 열면 되므로 별도 빌드가 필요 없고, 이 표시는 기록이나 점수에 영향을 주지 않도록 보기만 하게 만들었다.
fps 옆에 캔버스의 픽셀 크기(width x height)도 같이 찍는다. 같은 화면이라도 기기마다 캔버스의 실제 픽셀 수가 다르고, 고해상도 화면에서는 그리는 픽셀이 훨씬 많다. 대시 런이 느렸던 실제 아이패드의 캔버스는 2732×1784 픽셀이었다. 이 숫자가 같이 보여야 느린 이유가 그리는 양 때문인지 그리는 방식 때문인지 구분할 수 있다. 캔버스 크기를 줄여서 비교하는 lowres 값도 같은 주소 값에 붙여 쓸 수 있게 했다.
시뮬레이터를 준비하는 데 든 것
실제 아이패드가 늘 손에 있는 것은 아니라서 Xcode 의 iOS 시뮬레이터를 쓰기로 했다. 시뮬레이터는 맥에서 아이패드를 흉내 내는 프로그램이다. 안에서 돌아가는 사파리는 실제 기기와 같은 엔진이다. 처음 준비할 때 걸린 것은 iOS 용 시뮬레이터 플랫폼을 내려받는 일이었다. 용량이 약 8GB 였다. 받고 나서는 아이패드 기기를 하나 만들고 켜는 명령을 차례로 실행한다.
xcodebuild -downloadPlatform iOS
xcrun simctl create "iPad A16 test" com.apple.CoreSimulator.SimDeviceType.iPad-A16 com.apple.CoreSimulator.SimRuntime.iOS-26-1
xcrun simctl boot {device id}
xcrun simctl 은 터미널에서 시뮬레이터를 조작하는 도구다. 주소를 열 때는 xcrun simctl openurl 을 쓰는데, 처음 실행하면 시간 초과 오류가 나는데도 사파리는 열린다. 오류 메시지만 보면 실패한 것처럼 보여서 헷갈리기 쉽다. 맥에서 띄운 개발 서버를 시뮬레이터가 그대로 볼 수 있어서, 코드를 고치고 새로고침만 하면 된다.
시뮬레이터 화면의 숫자는 스크린샷을 찍어서 읽는다. 게임이 열아홉 개가 되니 하나씩 열어 보는 일이 번거로워서, 스크린샷의 글자를 읽는 스크립트를 만들었다. macOS 에는 이미지 속 글자를 읽어 주는 문자 인식 기능(Vision)이 있어서 그것을 불러 쓴다. 게임마다 주소를 열고 화면을 찍고 숫자를 읽어 표로 정리한다.
시뮬레이터의 스크린샷은 기기를 가로로 돌려도 항상 세로 픽셀로 나온다. 그래서 글자가 옆으로 누운 채 찍힐 수 있다. 사진을 0, 90, 180, 270도로 돌려 가며 읽도록 했다. 게임이 세로나 가로 전용이라 기기를 돌려 달라는 화면이 뜨면 시뮬레이터를 직접 돌려서 다시 읽게 했다. 이렇게 처음 잰 결과는 열아홉 개 중 열여덟 개가 53~60fps 였다.
지난 글에서 적은 것처럼 시뮬레이터는 맥의 그래픽 칩을 쓴다. 시뮬레이터에서 60fps 가 나와도 실제 아이패드에서는 11fps 였다. 그래서 시뮬레이터 숫자는 방향을 잡는 데 쓰고, 마지막 확인은 실제 기기에서 같은 주소로 한다. 실제 사이트에서도 이 표시가 켜지니, 배포한 뒤 아이패드 주소창에 ?debug=fps 만 붙이면 같은 숫자를 볼 수 있다.
fps 표시를 하나 만들어 두면 성능 문제가 감각에서 숫자로 바뀐다. 코드는 짧지만 이것이 없었다면 아이패드에서 느린 이유를 찾는 데 훨씬 오래 걸렸을 것이다.
참고한 문서
- MDN, window.requestAnimationFrame: https://developer.mozilla.org/en-US/docs/Web/API/Window/requestAnimationFrame
- MDN, performance.now: https://developer.mozilla.org/en-US/docs/Web/API/Performance/now
- Apple, 시뮬레이터에서 앱 실행하기: https://developer.apple.com/documentation/xcode/running-your-app-in-simulator-or-on-a-device
- Apple, Vision 프레임워크(문자 인식): https://developer.apple.com/documentation/vision
이 글과 관련된 게임
다른 글
- 배포했는데 한 시간 동안 예전 페이지가 보였다 2026년 10월 25일
- 구름이 닿지 않는 판은 왜 나오지 않을까 2026년 10월 25일
- 밟으면 사라지는 구름은 길에 놓지 않았다 2026년 10월 25일
- 바탕을 지웠는데 분홍 띠가 남았다 2026년 10월 25일
- 하루에 세 번 바꾼 배포 제외 규칙 2026년 10월 25일
