게임 시간 진행을 유저 입력, 프로세서 속도와 디커플링 해야 한다.

게임은 유저 입력이 없어도 계속 돌아간다. (루프가 존재한다.)

게임 루프의 코드는 프로그램 실행 시간의 대부분을 차지하기 때문에 최적화를 고려하여 깐깐하게 만들어야 한다.

d3dApp.cpp의 Run()에 존재하는 루프문

게임 루프의 핵심 업무는 어떤 하드웨어에서라도 일정한 속도로 실행될 수 있도록 하는 것이다.

좋은 하드웨어에서의 환경과 좋지 않은 환경에서의 while문 내부 코드 소요 시간은 차이가 있을 수 있기 때문에 프레임을

60FPS로 고정하면 (좋지 않은 환경에서도 60FPS는 보장 된다는 가정 하에) 게임이 느려지는 문제는 회피할 수 있다.

 

Sleep(100); 코드는 사실 sleep(while문 시작 시점 시간 - while문 끝 시점 시간 + 100) 으로 써야 정확하다.

 

가변 시간 간격, 유동 시간 간격

위 예제에서 mTimer.Tick() 함수는 현재 시각을 기록하고 mTimer 객체는 현재 시각과 이전 시각을 갖고 있다.

Update와 Draw 함수에 mTimer 객체를 넘겨줌으로써 시간 간격 차이를 이용해 게임 월드 상태를 진행할 수 있다.

 

예를 들면 총알의 위치를 속도 * 시간 간격으로 계산 할 수 있다.

 

가변 시간 간격 방식을 사용하면 게임이 비결정적이게 된다.

네트워크 게임에서 위의 총알 위치를 계산하는 방법을 예시로 들자면

1초에 총알을 위치를 50번 계산하는 PC에서와 5번 계산하는 PC에서의 총알의 위치는 다를 수 있다.

ㄴ 기존 위치 + 변위 값을 통해 위치를 계산하게 될텐데 변위값은 float이기 때문에 숫자를 누적할수록 반올림 오차가 커질 수 있기 때문이다.

 

물리 계산은 고정 시간 간격을 사용하되 렌더링되는 간격은 유연하게 하는 방법

렌더링되는 간격을 60FPS로 고정시킨다면 프로세서의 낭비이다. (더 자주 렌더링 할 수 있는데도 막는 것이므로)

 

double previous = getCurrentTime();
double lag = 0.0;
while (true) {
  double current = getCurrentTime();
  double elapsed = current - previous;
  previous = current;
  lag += elapsed;
  processInput();
  
  while (lag >= MS_PER_UPDATE) {
  	update();
    lag -= MS_PER_UPDATE;
  }
  render(lag / MS_PER_UPDATE);
}

render 함수의 인자는 위치 보간에 쓰인다.

update()와 render()가 동일한 시각에 호출된다는 보장이 없기 때문에 물체의 움직임이 튀어 보일 수 있다.

 

렌더러가 게임 객체들과 각각의 속도를 안다고 가정할 때, 아마도 정점 셰이더에서 이 인자를 이용해 위치 보간을 하지 않을까 생각한다. (확실하지 않음..) 실제로 물리 계산에 필요한 정보를 수정하는 것은 의미가 없기 때문이다. 

 

60fps 고정 시간 간격 환경에서의 함수 호출 빈도(16ms = 1/60초 라고 가정)

9.7 디자인 결정

1. 게임 루프를 직접 관리하는가, 플랫폼이 관리하는가?

ㄴ 플랫폼 이벤트 루프 사용

ㄴ 게임 엔진 루프 사용

ㄴ 직접 만든 루프 사용

 

2. 전력 소모 문제

ㄴ 프레임율 제한

ㄴ 프레임율 제한 해제

 

3. 게임 플레이 속도는 어떻게 제어할 것인가?

ㄴ 동기화 없는 고정 시간 간격 방식 - 게임 속도가 하드웨어와 게임 복잡도에 영향을 받는다.

ㄴ 동기화하는 고정 시간 간격 방식 - 고정 시간 간격으로 게임을 실행하되, 루프 마지막에 지연(sleep)이나 동기화 지점을 넣어서 게임이 너무 빨리 실행되는 것을 막는다.

ㄴ 가변 시간 간격 방식

ㄴ 업데이트는 고정 시간 간격으로, 렌더링은 가변 시간 간격으로

+ Recent posts