힘들면 쉬었다 가자.

[게임 프로그래밍 - 게임 루프]The Game Loop [2] - Koen Witters 본문

프로그래밍/기타

[게임 프로그래밍 - 게임 루프]The Game Loop [2] - Koen Witters

오클라호마호 2011. 3. 1. 16:42

원문 보기 : http://dewitters.koonsolo.com/gameloop.html

 

 

이전글 보기 : 2011/03/01 - [프로그래밍/기타] - the game loop [1] - Koen Witters

 

 

 

 

가변적인(variable) FPS에 의존적인 게임 스피드

 

 

구현(Implementation)

  게임 루프의 또다른 구현은 가능한한 빠르게 실행되도록 하고, FPS가 게임 스피드를 결정(dictate)하도록 하는 것이다. 게임은 이전 프레임의 시간차로 갱신된다.

 

 

    DWORD prev_frame_tick;
    DWORD curr_frame_tick = GetTickCount();

 

    bool game_is_running = true;

    while( game_is_running ) {
        prev_frame_tick = curr_frame_tick;
        curr_frame_tick = GetTickCount();

 

        update_game( curr_frame_tick - prev_frame_tick );
        display_game();
    }

 

 

  우리는 update_game() 함수에서 시간차를 고려해야 하기 때문에 게임 코드가 아주 약간 더 복잡해졌다. 그러나 아직까지 그렇게 어렵지는 않다.

 

  처음 보기에는 우리의 문제에 대한 이상적인 솔루션으로 보인다. 나는 지금까지 많은 똑똑한 프로그래머들이 이런 종류의 게임 루프를 구현하는 것을 보았다. 그들중 일부는 아마도 그들의 게임 루프를 구현하기 이전에 이 글을 읽기를 바랬을 것이다. 나는 1분뒤에 이 루프가 느린 하드웨어와 빠른 하드웨어 모두에서 중대한 문제점들을 가질 수 있다는 것을 보여줄 것이다.

 


느린 하드웨어(Slow hardware)

  느린 하드웨어는 게임이 무거워(heavy)지는 곳 같은 몇몇 지점에서 때때로 확연한 딜레이를 야기한다. 이것은 3D 게임에서 매우 많은 폴리곤들이 보여주는 순간에는 틀림없이 발생한다. 이러한 프레임 레이트에서의 드롭(drop)은 입력에 대한 반응 시간에 영향을 주고, 따라서 플레이어의 반응시간에도 역시 영향을 준다. 게임의 갱신은 역시 지연을 느끼고 게임 상태는 큰 시간-청크(time-chunks) 후에 갱신될 것이다. 결과적으로 플레이어의 반응시간과 AI는 느려지고 단순한 전술?조작?행동?(maneuver)의 실패를 야기하거나 아예 불가능할 수도 있다. 예를들면, 정상적인 FPS에서는 피할 수 있는 장애물도 느린 FPS에서는 피하는게 불가능해질수도 있다. A more serious problem with slow hardware is that when using physics, your simulation can even explode!  <- 무슨뜻인지 잘 모르겠음-_-;; 어거지로 짜 맞추면.. 대략 "느린 하드웨어에서의 더 심각한 문제는 물리학??(physics)을 이용할 때이다. 당신의 시뮬레이션은 폭발??(explode)할 수도 있다."

 

 

빠른 하드웨어(Fast hardware)

  당신은 어떻게 위에 언급한 게임루프가 빠른 하드웨어에서 잘못 돌아갈 수 있는지 궁금해 할 것이다. 불행하게도, 그것은 가능하다. 그리고 당신에게 그걸 보여주기위해 우선 먼저 컴퓨터에서의 수학에 관해서 약간 설명하도록 하겠다.

 

  단정도부동소수점(float)이나 배정도부동소수점(double)값의 메모리 공간은 제한되있다. 따라서 몇몇 값들은 표현이 불가능하다. 예를들면, 0.1은 2진수(binary)로 표현될 수 없고, 따라서 double로 저장될 때 반올림되어진다. 파이썬(python)을 이용하여 보여주겠다.

 

>>> 0.1
0.10000000000000001

 

  이것 자체로는 별로 대단할게 없지만, 결과는 그렇지 않다. 당신이 밀리초(millisecond)당 0.001 유닛(units)의 스피드를 가진 경주용 자동차를 가지고 있다고 가정해보자. 10초가 지난후 당신의 자동차는 10만큼의 거리를 가게 될 것이다. 만약 당신이 이 계산을 게임에서 하는 것처럼 분할한다면, 당신은 초당 프레임을 입력으로 이용하는 다음의 함수를 이용할 것이다.

 

 

>>> def get_distance( fps ):
...     skip_ticks = 1000 / fps
...     total_ticks = 0
...     distance = 0.0
...     speed_per_tick = 0.001
...     while total_ticks < 10000:
...             distance += speed_per_tick * skip_ticks
...             total_ticks += skip_ticks
...     return distance

 

 

이제 우리는 40FPS 일 때의 거리를 계산할 수 있다.

 

>>> get_distance( 40 )
10.000000000000075

 

  잠깐만.. 10.0이 아니지 않은가??? 무슨 일이 일어난 것일까?? 글쎄, 우리가 계산을 400번으로 분할 했기 때문에, 라운딩 오차(rounding error)는 커졌다. 나는 100FPS에서는 무슨일이 일어날지 궁금하다...

 

>>> get_distance( 100 )
9.9999999999998312

 

  뭐야?? 에러는 더욱 커졌다!! 글쎄, 우리는 100FPS에서 더 많은 연산을 하기 때문에, 라운딩 오차는 더 커질 수 있었다. 그래서 게임은 40FPS에서 돌아가느냐, 100FPS에서 돌아가느냐에 따라 달라질 수 있다.

 

>>> get_distance( 40 ) - get_distance( 100 )
2.4336088699783431e-13

 

  당신은 이 차이가 게임 자체에서 보여질 때는 매우 작은 것이라고 생각할 것이다. 그러나 진짜 문제는 당신이 이 부정확한 값을 다른 계산들에서 사용하려고 할 때 시작될 것이다. 이렇게 하면 작은 에러도 커질 수 있고 높은 프레임레이트에서 당신의 게임이 바보(fuck-up)가 될 것이다. 그러한 가능성은?? 고려해봐야 할만큼 매우 충분하다!! 나는 이러한 종류의 게임 루프를 사용하는 게임을 봐왔다. 그리고 그것은 높은 프레임 레이트에서 실제로 곤란에 처했다. 문제가 게임의 코어(core)에 숨어있다는 것을 프로그래머가 발견한 후에는, 수정을 위해 매우 많은 코드가 재작성 된다.

 

 

결론(Conclusion)

  이러한 종류의 게임 루프는 처음보기에는 매우 좋은 것처럼 보인다. 그러나 멍청해지지 말자. 당신의 게임은 느린 하드웨어, 빠른 하드웨어 양쪽 모두에서 문제를 일으킬 수 있다. 게다가, 게임 갱신 함수의 구현은 고정 프레임 레이트를 사용한 경우보다 더 어렵다. 이것을 사용할 이유가 있겠는가??




Comments