Dev_logcomma

모니터 주사율에 따른 게임 속도 문제 해결 – COMMA #3

2025-03-27
ProjectTrouble

📌 Delta Time 적용


💡 문제 분석

  • 게임 오브젝트 이동이 FPS(초당 프레임 수) 에 의존하고 있는 구조였다. - 60Hz 모니터는 1초에 60번, 144Hz 모니터는 144번 업데이트가 호출된다. - 그 결과 동일한 이동량을 프레임마다 적용하면 고주사율 환경에서 더 많이 이동하는 구조가 된다.

🔎 관련 개념과 용어들

📍 주사율(Hz)

모니터가 1초에 화면을 몇 번 갱신하는지를 나타낸다.

  • 60Hz → 1초에 60번
  • 144Hz → 1초에 144번
  • 240Hz → 1초에 240번

➡️ 주사율이 높을수록 더 부드러운 화면

📍 프레임 / FPS

  • 프레임: 화면 한 장
  • FPS: 1초 동안 몇 개의 프레임을 보여주는가

예를 들어:

  • 60FPS → 1초에 60장
  • 144FPS → 1초에 144장

➡️ FPS가 높을수록 부드러운 움직임


js
1
function update() {
  character.x += 5; // 1프레임마다 5px 이동
}

비교해보면:

  • 60FPS → 5 * 60 = 300px/s
  • 144FPS → 5 * 144 = 720px/s

➡️ 고주사율 = 더 빠른 게임 진행

동일한 게임임에도 장비 성능 차이로 난이도 차이가 발생하게 됨


✅ 해결 방법: Delta Time 적용


💬 Delta Time 적용 예시

js
1
let lastFrameTime = 0;
 
function update(timestamp) {
  if (!lastFrameTime) lastFrameTime = timestamp;
 
  const deltaTime = (timestamp - lastFrameTime) / 1000; // ms → s
  lastFrameTime = timestamp;
 
  character.x += 100 * deltaTime; // 1초에 100px 이동
}

🛠 적용한 코드

requestAnimationFrame 의 timestamp 기반 Delta Time 계산

js
1
let lastFrameTime = 0;
 
function main(timestamp) {
if (!lastFrameTime) lastFrameTime = timestamp;
 
const deltaTime = (timestamp - lastFrameTime) / 1000;
lastFrameTime = timestamp;
 
if (!Enemy.isGameOver) {
update(deltaTime);
render();
requestId.value = requestAnimationFrame(main);
} else {
stop();
stopAllMusic();
emits("open-game-over", score.value, currentTime.value);
cancelAnimationFrame(requestId.value);
}
}
 

🔗 Kaplay 적용 방식

Kaplay의 경우 requestAnimationFrame 직접 사용 대신 onUpdate()에서 처리

js
1
let lastUpdateTime = performance.now();
 
k.onUpdate(() => {
  if (!isGameStarted.value) return;
 
  const currentTime = performance.now();
  const deltaTime = (currentTime - lastUpdateTime) / 1000;
  lastUpdateTime = currentTime;
 
  for (let i = 0; i < obstaclesLayer.parts.length; i++) {
    const currentPart = obstaclesLayer.parts[i];
    const nextPart = obstaclesLayer.parts[(i + 1) % obstaclesLayer.parts.length];
 
    if (currentPart.pos.x < -IMAGE_WIDTH) {
      currentPart.pos.x = nextPart.pos.x + IMAGE_WIDTH;
    }
 
    currentPart.move(obstaclesLayer.speed * deltaTime * 60, 0);
  }
 
  obstaclesLayer.speed -= 5 * deltaTime;
});