소년코딩

2016. 7. 24(일) 게임 엔진 스터디 2주차

 

게임 엔진 스터디 2주차 내용은 다음과 같습니다.

    1. 타이머 함수


1. 타이머 함수

setTimeout()이나 setInterval()같은 타이머 함수들을 이용하여 게임의 FPS(Frame Per Second)를 구현할 수 있습니다.

자바스크립트 타이머는 사실 스레드이며, 단일 스레드 환경에서 동작합니다.

타이머는 단순히 코드를 나중에 실행하도록 미뤄두는 것이며, 그 시각에 다른 코드가 자바스크립트 프로세스를 독점하고 있을 수 있기 때문에 정확히 그 시각에 타이머 함수가 실행된다고 보장할 수 없습니다.

페이지를 내려 받으면서 실행을 시작한 코드, 이벤트 핸들러, Ajax 콜백 모두 같은 스레드에서 실행되며, 주어진 시각에 어느 코드를 실행할지 결졍하는 건 모두 브라우저의 몫입니다.

 

1) 자바스크립트 프로세스

페이지를 불러왔을 때 처음 실행되는 코드는 스크립트 태그 요소 안의 코드입니다. 이 시점 이후부터 자바스크립트 프로세스는 더 실행할 코드를 기다립니다. 

프로세스가 사용 중이지 않을 때는 가장 앞에 예약된 코드가 즉시 실행됩니다.

예를 들어 자바스크립트 프로세스가 다른 코드를 실행중이지 않다면 버튼을 클릭하는 즉시 onclick 이벤트 핸들러가 실행됩니다.

그림으로 나타내면 다음과 같습니다.

타이머함수1

또한 자바스크립트 실행 프로세스와는 별도로, 프로세스가 한가할 때 실행하도록 예약하는 가 존재합니다.

페이지가 브라우저에 존재하는 동안 코드는 큐에 추가되고 그 순서대로 실행됩니다.

ex)

  • 버튼을 클릭하면 이벤트 핸들러 코드가 큐에 추가되며 프로세스가 한가해질 때 실행됩니다.
  • Ajax 응답을 받으면 콜백 함수 코드가 큐에 추가됩니다.

자바스크립트에서 즉시 실행되는 코드는 존재하지 않습니다. 모든 코드는 일단 큐에 추가되었다가 프로세스가 한가해질 때 실행됩니다.

 

2) 타이머 함수

타이머 함수는 지정된 시간이 지난 후 큐에 코드를 삽입하는 방식으로 동작합니다.

코드를 큐에 추가해도 즉시 실행되는 것이 아니며, 가능한 순간에 실행됩니다. 

setTimeout() 타이머 함수를 200밀리초로 설정하더라도 코드가 정확히 200밀리초 뒤에 실행되지는 않습니다. 코드는 200밀리초 후에 큐에 추가됩니다.

그 시점에서 프로세스가 비어있다면 타이머 코드가 실행되므로 겉보기에는 명시한 시간에 정확히 실행되는 것처럼 보이는 것 입니다.

다시말하면 타이머 함수에서 가장 중요한 점은 코드가 실행될 시점이 아니라 큐에 추가될 시점을 지정하는 것 입니다.

타이머함수2

 

3) 타이머 반복

setInterval() 타이머 함수를 이용해서 주기적으로 타이머 코드를 큐에 추가할 수 있습니다.

한 가지 문제는 타이머 코드의 실행이 끝나기도 전에 큐에 코드를 다시 추가할 가능성이 있어 타이머 코드가 갭 없이 계속 예약되어 사용자 인터페이스도 멈춘다는 겁니다.

다행히 자바스크립트 엔진은 이런 문제를 예방할 수 있게 setInterval()로 타이머 코드를 큐에 추가하려 하면 타이머 코드의 다른 인스턴스가 큐에 존재하지않을 때만 추가하도록 했습니다.

하지만 타이머 반복을 이런 식으로 통제하면 두 가지 단점이 있습니다.

  • 실행 되지 않는 구간이 생길 수 있습니다.
  • 타이머 코드 사이의 갭이 예상보다 작을 수 있습니다.

예를들어 게임에서 5FPS를 구현하려 1000/5 => 200밀리초 마다 반복하는 setInterval() 타이머를 등록한다고 가정합니다. (편의를 위해 5FPS라고 가정합니다..)

var FPS = 5;

setInterval(play, 1000 / 5);

function play() {
    if( running ) {
        
        update();        // 게임 업데이트 함수
        display();       // 게임 렌더링 함수
    }
}

위 예제에서 만약 update() 함수와 display() 함수가 실행되고 종료되는데 까지 300밀리초 조금넘게 실행된다면 어떻게 될까요?

타이머함수3

위에서 말한 두가지 문제점이 생겼습니다.

  • (실행되지 않는 구간) => 605 밀리초 지점에서는 첫번째 타이머 코드가 아직 실행중이며, 큐에는 타이머 코드가 존재해버려 타이머 코드가 큐에 추가되지 않고 누락되어버립니다.
  • (타이머 코드 사이의 갭이) => 405밀리초 지점에 추가한 타이머 코드는 첫 번째 타이머 코드가 실행을 마치는 즉시 실행되는데, 이는 200밀리초 인터벌을 가지려 한 의도와 다르게됩니다.

 

setInterval() 타이머 함수의 위와 같은 문제를 피하러면 setTimeout() 타이머 함수를 재귀호출하는 패턴을 사용해야 합니다.

var FPS = 5;

function play() {
    if( running ) {
        
        update();        // 게임 업데이트 함수
        display();       // 게임 렌더링 함수
    }

    setTimeout(play, 1000/5);
}

play();

이 재귀 패턴은 함수 실행이 끝날 때마다 setTimeout()으로 새 타이머를 생성합니다. 이렇게 하면 이전 타이머 코드가 실행을 마친 시점에서만 새 타이머 코드를 큐에 삽입하므로 setInterval()의 단점을 모두 막을 수 잇습니다.

 

 

자바스크립트에서 게임을 만들 때 일반적으로 setInterval(), setTimeout()과 같은 타이머 함수를 사용합니다.

위에서 배운대로 타이머 함수는 모두 정밀한 타이밍을 제공하도록 만들어진 함수가 아닙니다. 두 번째 매개변수로 넘기는 숫자는 코드를 큐에 언제 넣을지 나타낼 뿐입니다. 큐에 이미 다른 작업이 있다면 대기해야 합니다.

 

4) requsetAnimationFrame

타이머 함수를 이용하여 루프를 구현할 때 가장 어려운 부분은 FPS를 얼마나 주느냐입니다.

FPS는 게임을 부드럽게 표현할 수 있을 만큼 짧은 동시에 브라우저에서 실제로 렌더링할 수 있을 만큼 길어야 합니다.

대부분의 모니터는 리프레시율이 60Hz이므로 초당 60번씩 화면을 다시그립니다. 따라서 브라우저에서 이보다 빨리 화면을 갱신한다 한들 사용자는 아무 차이도 느낄 수 없읍니다.

따라서 가장 부드러운 애니메이션을 표현하는 딜레이는 1초를 60으로 나눈(1000/60) 17밀리초 입니다. 이 FPS를 쓰면 브라우저의 한계에 가장 가까우므로 가장 애니메이션이 부드럽습니다.
 

requestAnimationFrame() 함수를 사용하면 브라우저에서 자바스크립트 애니메이션이 실행 중임을 알고 최적의 인터벌을 적용합니다.(가변적입니다.)

첫 번재 매개변수로 함수를 받는데, 이 함수는 화면을 갱신하기 전에 호출할 함수입니다.

requestAnimationFrame() 함수는 주어진 함수를 단 한번만 실행하므로 애니메이션을 만드려면 다시 호출해야합니다.

그렇다면 가변적이므로 코드가 실제로 실행되는 시점을 정확히 알 수 없는 문제는 어떻게 해결할까요? 다음과 같은 패턴을 사용하면됩니다.

var startTime = Date.now();

function play() {
    // 마지막 갱신에서 시간이 얼마나 지났는지 계산
    var diff = Date.now() - startTime;
    
    // diff를 사용해 다음 단계 진행
    update(diff);  // 게임 업데이트 함수
    display();     // 게임 렌더링 함수
    
    requestAnimationFrame(play);
}

requestAnimationFrame(play);

 

game

by 소년코딩

추천은 글쓴이에게 큰 도움이 됩니다.

악플보다 무서운 무플, 댓글은 블로그 운영에 큰 힘이됩니다.

댓글 로드 중…

블로그 정보

소년코딩 - 소년코딩

소년코딩, 자바스크립트, C++, 물리, 게임 코딩 이야기

최근에 게시된 이야기