시간 다루기

_

_

Win32 환경에서의 타이머 종류

Ansi C의 time(), _ftime()

Ansi 표준 C 라이브러리의 함수들로, time()은 최소 1초 단위의 값을 돌려준다. 메인 루프 제어용으로는 부적합하나 게임 저장 날짜/시간의 기록 등에는 유용할 것임. _ftime()은 밀리초(1000분의 1초) 단위의 값을 돌려준다.

Win32 API의 timeGetTime() 함수

흔히 멀티미디어 타이머라고 하는 것으로, 윈도우즈가 시작된 이후 흐른 시스템 시간을 돌려준다. 단위는 밀리초. 기본 정밀도는 Win9x의 경우 1 밀리초, NT 패밀리의 경우 5 밀리초 이상이라고 함. 이정도면 _ftime()과 함께 게임에서 써먹을 수 있을 만한 함수.

타이머의 해상도는 timeGetDevCaps()와 timeBeginPeriod()로 조정할 수 있다.

Win32 API의 WM_TIMER 메시지

일정한 시간 주기로 발생하는 메시지. 주기의 최소 단위는 역시 밀리초 단위. 메시지 펌프에서 잡거나 SetTimer()를 이용해서 콜백 함수를 호출하게 만들 수 있음. 그러나 메시지의 우선 순위가 낮기 때문에 정확하지 못하다.

Win32 API의 GetTickCount() 함수

시스템 틱 카운트를 돌려주며, 단위는 밀리초. Win9x의 경우 기본 정밀도는 약 55 밀리초, NT 급은 10에서 16 정도... timeGetTime()보다 못함...

고해상도 타이머

하드웨어가 고해상도 타이머를 지원하는 경우라면 가장 좋은 선택으로, 매우 정밀한 시간을 얻을 수 있다. 얻을 수 있는 시간의 해상도는 하드웨어마다 다르나, 어쨌든 적어도 위에 나온 것들보다는 정밀하다고 함.

관련 함수(자세한 사항은 MSDN 참고)

둘 다 windows.h만 포함시키면 됨...

BOOL QueryPerformanceCounter(
  LARGE_INTEGER *lpPerformanceCount   // pointer to counter value
);

BOOL QueryPerformanceFrequency(
  LARGE_INTEGER *lpFrequency   // address of current frequency
);

고해상도 타이머의 해상도 및 지원 여부 알아내기

QueryPerformanceFrequency(&ticksPerSecond) 의 반환값이 0이 아니면 고해상도 타이머를 지원하는 것이다. 이 때 tickPerSecond에는 초 당 틱 수가 설정된다.

예:

  LARGE_INTEGER ticksPerSecond;
  if (!QueryPerformanceFrequency(&ticksPerSecond))
  {
    // 지원하지 않음
    return false;
  }

현재 시간 얻기

QueryPerformanceCounter(&ticks)를 호출하면 ticks에 성능 카운터 수(음.. 컴퓨터가 켜진 후 지나간 틱 수)가 설정된다.

이를 위에서 얻은 ticksPerSecond로 나누면 초 단위의 시간을 얻을 수 있다.

예:

  LARGE_INTEGER ticks;
  QueryPerformanceCounter(&ticks);

  float seconds =  ((float)ticks.QuadPart  / (float)ticksPerSecond.QuadPart;

지나간 시간 얻기

게임에서 실제로 필요한 것은 이전 프레임으로부터 흐른 시간인데, 그냥 현재 시간에서 이전 시간을 빼면 됨...

예:

float GetElapsedSeconds(unsigned long elapsedFrames = 1)
{
  static LARGE_INTEGER s_lastTime = m_startTime;
  LARGE_INTEGER currentTime;

  QueryPerformanceCounter(&currentTime);

  float seconds =  ((float)currentTime.QuadPart -
           (float)s_lastTime.QuadPart) / (float)m_ticksPerSecond.QuadPart;

  // reset the timer
  s_lastTime = currentTime;

  return seconds;
} // end GetElapsedSeconds()

QueryPerformanceCounter의 문제점

일부 메인보드/칩셋에서 시간이 건너뛴다는 보고가 있음.

RDTSC

인텔 펜티엄 계열 CPU에서 제공하는 어셈블리 명령이다. 펜티엄은 내부적으로 TSC(Time Stamp Counter)라는 64비트 카운터를 유지하는데 이 카운터의 값은 클럭 사이클마다 증가한다. RDTSC 명령은 내부 TSC 카운터의 값을 EDX와 EAX 레지스터에 복사하는 명령이다. 이 명령은 6~11 클럭을 소요한다. 고해상도 타이머가 이 명령을 이용해 구현되었다고 한다.

참고:

참고 자료

시간에 기반한 게임 갱신

고정 시간 간격

한 프레임에 걸리는 시간을 고정시키는 것. 간단히 말하면, 한 프레임에 걸리는 표준 목표 시간을 설정하고, 한 프레임에 걸린 시간이 그보다 작으면 남는 부분만큼 아무 일도 하지 않고 기다리는 것이다.

의사코드:

   게임 메인 루프  {
           현재시각을 얻는다.

       한 프레임 처리;

       흐른시간 = 현재시각 - 아까 얻은 현재시각

       만일 ( 흐른시간 < 목표시간) {
            (목표시간 - 흐른시간)만큼 쉰다. //예: Sleep(...);
    }

가변 시간 간격

게임의 기능 및 객체들이 이전 프레임 이후 흐른 시간(이하 프레임 시간)에 기반해서 갱신되게 하는 것. 예를 들어 어떤 객체가 1초당 10미터를 수평 이동한다고 하면, 객체의 x 좌표는:

x = x + 10 * (프레임 시간)

이렇게 하면 프레임 시간에 상관없이(즉 CPU 속도나 기타 다른 요인과 독립적으로) 객체는 항상 초 당 10 미터의 속력을 가지게 된다.

더 읽을거리