"일정한 게임 속도 최대 FPS"게임 루프와 매우 혼동


12

최근에 Game Loops에서이 기사를 읽었습니다 : http://www.koonsolo.com/news/dewitters-gameloop/

그리고 권장되는 마지막 구현은 나를 깊이 혼란스럽게합니다. 나는 그것이 어떻게 작동하는지 이해하지 못하며 완전한 혼란처럼 보입니다.

본인은 원칙을 이해합니다. 게임을 일정 속도로 업데이트하고 남은 것은 가능한 한 여러 번 게임을 렌더링합니다.

나는 당신이 사용할 수 없다고 생각합니다 :

  • 25 틱에 대한 입력 받기
  • 975 틱을위한 게임 렌더링

두 번째 부분의 첫 번째 부분에 대한 입력을 받고 접근하는 것이 어떻습니까? 아니면 기사에서 무슨 일이 일어나고 있습니까?


본질적으로 :

while( GetTickCount() > next_game_tick && loops < MAX_FRAMESKIP)

그게 어떻게 유효합니까?

그의 가치를 가정합시다.

MAX_FRAMESKIP = 5

메인 게임 루프가 500이되기 전에 초기화 후 순간이 할당 된 next_game_tick를 가정 해 봅시다.

마지막으로, 게임에 SDL과 OpenGL을 사용하고 있으며 렌더링에만 OpenGL을 사용하는 경우 GetTickCount()SDL_Init가 호출 된 이후의 시간 을 반환 한다고 가정 해 봅시다 .

SDL_GetTicks -- Get the number of milliseconds since the SDL library initialization.

출처 : http://www.libsdl.org/docs/html/sdlgetticks.html

저자는 또한 이것을 가정합니다 :

DWORD next_game_tick = GetTickCount();
// GetTickCount() returns the current number of milliseconds
// that have elapsed since the system was started

while진술 을 확장하면 다음과 같은 결과를 얻습니다.

while( ( 750 > 500 ) && ( 0 < 5 ) )

750 이후 시간이 지났으므로 750 next_game_tick이 지정되었습니다. loops기사에서 볼 수 있듯이 0입니다.

while 루프에 들어갔습니다. 논리를 수행하고 입력을 받아들입니다.

야다 야다 야다.

while 루프가 끝나면 메인 게임 루프 안에 있음을 상기시킵니다.

next_game_tick += SKIP_TICKS;
loops++;

while 코드의 다음 반복이 어떻게 보이는지 업데이트합시다

while( ( 1000 > 540 ) && ( 1 < 5 ) )

루프의 다음 ineteration에 도달하기 전에 시간이 입력을 받고 작업을 수행했기 때문에 1000 입니다. 여기서 GetTickCount ()가 호출됩니다.

540 인해 코드 25분의 1,000 = 40, 따라서, 500 + 40 = 540

1 루프가 한 번 반복 되었기 때문에

5 , 당신은 이유를 알고 있습니다.


그래서이 While 루프는 명확하게 작동 MAX_FRAMESKIP하고 의도 된 TICKS_PER_SECOND = 25;게임이 아니기 때문에 게임이 올바르게 실행되는 방법은 무엇입니까?

이것을 코드에 구현했을 때 사용자 입력을 처리하고 기사 작성자가 예제 코드에 가지고있는 것에 게임을 그리는 기능을 단순히 이름을 바꿀 때 정확하게 추가 할 수 있다는 것은 놀라운 일이 아닙니다. .

fprintf( stderr, "Test\n" );게임이 끝날 때까지 인쇄되지 않는 while 루프 내부를 배치했습니다 .

이 게임 루프는 가능한 한 빨리 렌더링하면서 어떻게 초당 25 회 실행되고 보장됩니까?

나에게, 거대한 무언가를 놓치지 않으면, 그것은 ... 아무것도 보이지 않습니다.

그리고이 while 루프의 구조가 초 당 25 번 실행되고 기사 시작 부분에서 미리 언급 한 것과 정확히 일치하는 게임이 아닌가?

그렇다면 왜 우리는 다음과 같은 간단한 일을 할 수 없었습니까?

while( loops < 25 )
{
    getInput();
    performLogic();

    loops++;
}

drawGame();

그리고 다른 방법으로 보간을 계산합니다.

나의 긴 질문을 용서하지만이 기사는 나에게 좋은 것보다 더 큰 해를 끼쳤다. 나는 지금 매우 혼란스러워하며 이러한 모든 질문으로 인해 적절한 게임 루프를 구현하는 방법을 모릅니다.


1
Rants는 기사의 저자에게 더 잘 지시됩니다. 객관적인 질문 은 어느 부분 입니까?
Anko

3
이 게임 루프가 유효합니까? 내 테스트에서 초당 25 번 실행하는 올바른 구조가 없습니다. 왜 그런지 설명해주세요. 또한 이것은 비열한 것이 아니며 일련의 질문입니다. 이모티콘을 사용해야합니까, 화났습니까?
tsujp

2
귀하의 질문은 "이 게임 루프에 대해 이해하지 못하는 내용"으로 귀결되고 굵은 글씨로 된 단어가 많으므로 적어도 분노로 빠져 나옵니다.
Kirbinator

@ Kirirator 나는 그것을 이해할 수 있지만, 나는이 기사에서 비정상적으로 발견 된 모든 것을 접지하려고 노력했기 때문에 얕은 질문이 아닙니다. 어쨌든, 나는 그것이 내가 가르치려고하는 기사이지만 그렇게 좋은 일을하지 않는 것은 유효한 포인트가 있다고 생각하고 싶습니다.
tsujp

7
잘못 이해하지 마십시오. 좋은 질문입니다. 80 % 더 짧을 수 있습니다.
Kirbinator

답변:


8

저자는 작은 오류가 있다고 생각합니다.

while( GetTickCount() > next_game_tick && loops < MAX_FRAMESKIP)

해야한다

while( GetTickCount() < next_game_tick && loops < MAX_FRAMESKIP)

즉, 아직 다음 프레임을 그릴 시간이 아니라면 MAX_FRAMESKIP만큼 많은 프레임을 건너 뛰지 않은 상태에서 기다려야합니다.

또한 next_game_tick루프에서 왜 업데이트되는지 이해하지 못하고 또 다른 오류라고 생각합니다. 프레임이 시작될 때 다음 프레임의 시점을 결정할 수 있습니다 (고정 프레임 속도를 사용하는 경우). 은 next game tick우리가 업데이트 및 렌더링 후 남은 얼마나 많은 시간에 의존하지 않습니다.

저자는 또 다른 일반적인 오류를 만듭니다

남은 것이 있으면 게임을 가능한 한 많이 렌더링합니다.

이는 동일한 프레임을 여러 번 렌더링하는 것을 의미합니다. 저자는 그것을 알고 있습니다 :

게임은 초당 50 회 꾸준히 업데이트되며 렌더링은 가능한 한 빨리 이루어집니다. 렌더링이 초당 50 회 이상 수행되면 일부 후속 프레임이 동일하므로 실제 비주얼 프레임은 초당 최대 50 프레임으로 표시됩니다.

이로 인해 GPU가 불필요한 작업을 수행하게되며 렌더링이 예상보다 오래 걸리면 의도 한 것보다 늦게 다음 프레임에서 작업을 시작할 수 있으므로 OS에 양보하고 기다리는 것이 좋습니다.


+ 투표. 음 이것은 실제로 어떻게해야할지에 대해 의아해합니다. 당신의 통찰력에 감사드립니다. 아마 놀 것입니다. 문제는 실제로 FPS를 제한하거나 FPS를 동적으로 설정하는 방법에 대한 지식과 그 방법입니다. 내 마음에는 고정 된 입력 처리가 필요 하므로 게임은 모든 사람에게 동일한 속도로 실행됩니다. 이것은 단순한 2D
플랫

루프가 정확하고 next_game_tick가 증가하는 것처럼 보입니다. 더 느리고 더 빠른 하드웨어에서 시뮬레이션을 일정한 속도로 계속 실행해야합니다. 렌더링 코드에 "가능한 빨리"(또는 물리보다 빠름)를 실행하는 것은 렌더링 코드에 보간법이있어 빠른 객체 등 (기사 끝)에 대해보다 매끄럽게 만들 때만 의미가 있지만 에너지가 낭비되는 경우 출력 장치 (화면)가 표시 할 수있는 것보다 더 많이 렌더링합니다.
Dotti

따라서 그의 코드는 유효한 구현입니다. 그리고 우리는이 쓰레기와 함께 살아야합니까? 아니면 내가 찾을 수있는 방법이 있습니까?
tsujp

OS에 항복하고 기다리는 것은 OS가 언제 당신에게 제어권을 돌려 줄 것인지에 대한 좋은 아이디어가 있다면 좋은 아이디어 일뿐입니다.
Kylotan

이 답변에 투표 할 수 없습니다. 그는 보간을 완전히 잃어 버렸습니다. 대답의 전체 절반을 무효로 만듭니다.
AlbeyAmakiir

4

조금 단순화하면 가장 좋습니다.

while( game_is_running ) {

    current = GetTickCount();
    while(current > next_game_tick) {
        update_game();

        next_game_tick += SKIP_TICKS;
    }
    display_game();
}

whilemainloop 내부의 루프는 시뮬레이션 단계를 어디에서든지, 현재 위치까지 실행하는 데 사용됩니다. update_game()함수는 항상 SKIP_TICKS마지막 호출 이후에 시간이 경과 했다고 가정해야합니다 . 이렇게하면 게임 물리가 느리고 빠른 하드웨어에서 일정한 속도로 실행됩니다.

증가 next_game_tick의 양만큼 SKIP_TICKS그 현재 시간 가까이 이동한다. 이것이 현재 시간보다 current > next_game_tick커지면 ( ) 이 끊어 지고 메인 루프는 현재 프레임을 계속 렌더링합니다.

렌더링 후 다음에 호출하면 GetTickCount()새로운 현재 시간이 반환됩니다. 이 시간이 그보다 높으면 next_game_tick시뮬레이션에서 이미 1-N 단계 뒤에 있으며 시뮬레이션의 모든 단계를 동일한 일정 속도로 실행해야합니다. 이 경우 더 낮은 경우 보간이없는 한 동일한 프레임을 다시 렌더링합니다.

우리가 너무 멀리 떨어져 있으면 원래 코드는 루프 수를 제한했습니다 ( MAX_FRAMESKIP). 이것은 실제로 무언가를 보여주고 예를 들어 일시 중단이나 게임이 디버거에서 일시 중지 된 경우 ( GetTickCount()시간 동안 멈추지 않는다고 가정) 일시 중단에서 다시 시작하는 경우 잠긴 것처럼 보이지 않습니다.

inside 보간을 사용하지 않는 경우 쓸모없는 동일한 프레임 렌더링을 없애려면 display_game()if 문을 다음과 같이 감싸십시오.

while (game_is_running) {
    current = GetTickCount();
    if (current > next_game_tick) {
        while(current > next_game_tick) {
            update_game();

            next_game_tick += SKIP_TICKS;
        }
    display_game();
    }
    else {
    // could even sleep here
    }
}

이것에 대해서도 좋은 기사입니다 : http://gafferongames.com/game-physics/fix-your-timestep/

또한 fprintf게임이 끝날 때 출력이 플러시되지 않았기 때문일 수 있습니다.

내 영어에 대해 죄송합니다.


4

그의 코드는 완전히 유효 해 보입니다.

while마지막 세트 의 루프를 고려하십시오 .

// JS / pseudocode
var current_time = function () { return Date.now(); }, // in ms
    framerate = 1000/30, // 30fps
    next_frame = current_time(),

    max_updates_per_draw = 5,

    iterations;

while (game_running) {

    iterations = 0;

    while (current_time() > next_frame && iterations < max_updates_per_draw) {
        update_game(); // input, physics, audio, etc

        next_frame += framerate;
        iterations += 1;
    }

    draw();
}

"게임이 실행되는 동안 현재 시간을 확인하십시오. 실행중인 프레임 속도 집계보다 큰 경우 5 프레임 미만으로 그리기를 건너 뛴 다음 그리기를 건너 뛰고 입력 만 업데이트하는 시스템이 있습니다. 물리학 : 그렇지 않으면 장면을 그리고 다음 업데이트 반복을 시작합니다. "

각 업데이트가 발생할 때 이상적인 프레임 속도로 "next_frame"시간을 늘리십시오. 그런 다음 시간을 다시 확인하십시오. 현재 시간이 next_frame을 업데이트해야하는 시간보다 짧으면 업데이트를 건너 뛰고 얻은 것을 그립니다.

current_time이 더 큰 경우 (어딘가에 딸꾹질이 있거나 관리 언어로 된 가비지 수집이 있거나 C ++ 또는 기타로 관리되는 메모리 구현으로 인해 마지막 그리기 프로세스가 실제로 오랜 시간이 걸린다고 상상해보십시오) 생략됩니다와는 그릴 next_frame과 함께 업데이트되는 또 다른 하나 업데이트 우리가 시계에 있어야 할 곳에 따라 잡을 때까지, 시간의 추가 프레임의 가치, 또는 우리는 플레이어가 볼 수 있도록 우리가 절대적으로, 하나를 그리는해야한다는 충분한 프레임을 그리기 생략했습니다 그들이하고있는 일.

컴퓨터가 초고속이거나 게임이 매우 단순한 current_time경우 next_frame빈도 가 낮을 수 있습니다 . 이는 해당 시점 동안 업데이트하지 않는 것을 의미합니다.

여기에서 보간이 시작됩니다. 마찬가지로, "더러운"공간을 선언하기 위해 루프 외부에서 선언 된 별도의 부울을 가질 수 있습니다.

업데이트 루프 내에서 dirty = true실제로 업데이트를 수행했음을 나타내는을 설정 합니다.

그런 다음을 호출하는 대신 다음과 draw()같이 말합니다.

if (is_dirty) {
    draw(); 
    is_dirty = false;
}

그런 다음 부드러운 움직임을 위해 보간법을 놓치지 만 실제로 상태가 보간되지 않고 실제로 업데이트가있을 때만 업데이트되도록합니다.

당신이 용감하다면, "당신의 타임 스텝을 고치세요!"라는 기사가 있습니다. GafferOnGames에 의해.
문제에 약간 다르게 접근하지만 언어 기능과 게임의 물리 계산에 얼마나 관심이 있는지에 따라 거의 같은 일을하는 더 예쁜 솔루션이라고 생각합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.