НАЙКРАЙНО плутати через ігровий цикл «Постійна швидкість гри Максимальна FPS»


12

Нещодавно я прочитав цю статтю про ігри Петлі: http://www.koonsolo.com/news/dewitters-gameloop/

І рекомендована остання реалізація мене глибоко бентежить. Я не розумію, як це працює, і це виглядає як повний безлад.

Я розумію принцип: оновлюйте гру з постійною швидкістю, і все, що залишиться, надайте гру якомога більше разів.

Я вважаю, що ви не можете використовувати:

  • Отримайте внесок за 25 кліщів
  • Візуалізація гри на 975 кліщів

Підхід, оскільки ви отримаєте вклад для першої частини другої, і це буде дивно? Або це те, що відбувається в статті?


По суті:

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

Як це навіть справедливо?

Припустимо його значення.

MAX_FRAMESKIP = 5

Припустимо next_game_tick, якому було призначено моменти після ініціалізації, перед тим, як сказати основний цикл гри ... 500.

Нарешті, оскільки я використовую 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, тому що минув час з моменту next_game_tickпризначення. loopsдорівнює нулю, як ви бачите у статті.

Отже, ми ввели цикл while, давайте зробимо певну логіку і приймемо деякий вклад.

Ядаядаяда.

Наприкінці циклу while, який, нагадаю, знаходиться в нашому головному циклі гри:

next_game_tick += SKIP_TICKS;
loops++;

Давайте оновимо, як виглядає наступна ітерація коду while

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

1000, тому що минув час, щоб отримати введення та робити речі, перш ніж ми дійшли до наступного зменшення циклу, де GetTickCount () відкликається.

540 тому, що в коді 1000/25 = 40, отже, 500 + 40 = 540

1 тому, що наш цикл повторився один раз

5 , ви знаєте чому.


Отже, оскільки цей цикл Хоча ЧИСТО залежно від цього, MAX_FRAMESKIPа не призначений, TICKS_PER_SECOND = 25;як гра повинна працювати навіть правильно?

Мене не здивувало, що коли я реалізував це у своєму коді, я міг би правильно додати, оскільки я просто перейменував свої функції на обробку вводу користувача та малював гру на те, що автор статті має у своєму прикладі коду, гра нічого не робила .

Я помістив fprintf( stderr, "Test\n" );всередину цикл while, який не надрукується, поки гра не закінчиться.

Як цей ігровий цикл працює 25 разів на секунду, гарантовано, при цьому відображається так швидко, як це можливо?

Для мене, якщо я не пропускаю чогось ВЕЛИЧЕГО, це виглядає як ... нічого.

І хіба не ця структура цього циклу, хоча він працює 25 разів на секунду, а потім оновлює гру саме те, про що я згадував на початку статті?

Якщо це так, чому ми не могли б зробити щось просте на кшталт:

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

    loops++;
}

drawGame();

І розраховуйте на інтерполяцію іншим способом.

Пробачте за моє надзвичайно довге запитання, але ця стаття принесла мені більше шкоди, ніж користі. Зараз я сильно розгублений - і не знаю, як реалізувати правильний цикл гри через усі ці виникаючі питання.


1
Штани краще спрямовані на автора статті. Яка частина вашого об’єктивного питання ?
Анко

3
Чи справді цей цикл гри дійсний, хтось пояснить. З моїх тестів, вона не має правильної структури для запуску 25 разів на секунду. Поясніть мені, чому це так. Також це не зграя, це низка питань. Чи повинен я використовувати смайлики, я здаюся розлюченим?
цуйп

2
Оскільки ваше запитання зводиться до "Чого я не розумію щодо цього циклу гри", і у вас є багато слів жирного шрифту, воно вимикається як мінімум змучене.
Кірбінатор

@Kirbinator Я можу це оцінити, але я намагався обґрунтувати все те, що мені здається незвичним у цій статті, тому це не дрібне і порожнє питання. Я не думаю, що це вкрай важливо, що все-таки я хотів би подумати, що у мене є певні моменти - адже це стаття, яка намагається навчити, але не робить такої хорошої роботи.
цуйп

7
Не зрозумійте мене неправильно, це гарне питання, це може бути на 80% коротше.
Кірбінатор

Відповіді:


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 виконувати непотрібну роботу, і якщо візуалізація триватиме більше часу, ніж очікувалося, це може змусити вас почати працювати над наступним кадром пізніше, ніж було призначено, тож краще перейти до операційної системи та почекати.


+ Голосувати. Мммм. Це справді залишає мене спантеличеним, що робити тоді. Я дякую вам за ваше розуміння. Можливо, я, мабуть, зіграю, проблема справді полягає в тому, щоб обмежити FPS або мати динамічно встановлений FPS, і як це зробити. На мій погляд, потрібна фіксована обробка вводу, тому гра працює в однаковому темпі для всіх. Це лише простий 2D платформер MMO (дуже довго)
tsujp

Хоча цикл здається правильним і збільшується next_game_tick для цього. Саме там можна тримати моделювання, що працюють з постійною швидкістю на більш повільному та швидкому апаратному забезпеченні. Запуск коду візуалізації "якнайшвидше" (або швидше, ніж фізика в будь-якому випадку) має сенс лише тоді, коли є інтерполяція в коді візуалізації, щоб зробити його більш гладким для швидких об'єктів тощо (кінець статті), але це просто витрачена витрата енергії, якщо це рендерінг більше, ніж те, що може показати пристрій виводу (екран).
Dotti

Тож його код є дійсною реалізацією, тепер це так. І нам просто треба жити з цими відходами? Або є методи, на які я можу знайти це.
цуйп

Поступитися ОС і чекати - це лише гарна ідея, якщо ви маєте гарне уявлення про те, коли ОС поверне вам контроль - що може бути не як тільки вам захочеться.
Килотан

Я не можу проголосувати за цю відповідь. Йому повністю не вистачає інтерполяційного матеріалу, що, скоріше, робить всю другу половину відповіді недійсною.
AlbeyAmakiir

4

Можливо, найкраще, якщо я її трохи спрощую:

while( game_is_running ) {

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

        next_game_tick += SKIP_TICKS;
    }
    display_game();
}

whileцикл всередині mainloop використовується для запуску кроків моделювання від місця, де він був, до місця, де він повинен бути зараз. update_game()Функція завжди повинна припускати, що SKIP_TICKSминуло лише минулий час з моменту останнього дзвінка. Це дозволить фізиці ігор працювати з постійною швидкістю на повільному та швидкому апаратному забезпеченні.

Збільшення next_game_tickза кількістю SKIP_TICKSрухається це ближче до поточного часу. Коли це стає більшим, ніж поточний час, воно current > next_game_tickперерветься ( ) і mainloop продовжує виводити поточний кадр.

Після надання, наступний дзвінок до GetTickCount()буде повертати новий поточний час. Якщо цей час вищий, ніж next_game_tickце означає, що ми вже відстаємо від 1-Н кроків у моделюванні і нам слід наздогнати, виконуючи кожен крок симуляції з однаковою постійною швидкістю. У цьому випадку, якщо вона нижча, вона просто повторить той же кадр знову (якщо не буде інтерполяції).

Оригінальний код обмежив кількість циклів, якщо ми залишилися занадто далеко ( MAX_FRAMESKIP). Це змушує насправді щось показувати і не виглядати як заблокований, якщо, наприклад, відновлення після призупинення чи гра, призупинена у відладці протягом тривалого часу (припускаючи, GetTickCount()що не зупиняється протягом цього часу), поки вона не наздогнала часу.

Щоб позбутися від марного ж візуалізації кадру, якщо ви не використовуєте інтерполяцію всередині display_game(), ви можете загорнути його всередину, якщо заява типу:

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" за ідеальним кадром. Потім ви ще раз перевіряєте свій час. Якщо ваш поточний час зараз менший, ніж коли слід оновити наступний_кадр, тоді ви пропускаєте оновлення та малюєте те, що у вас є.

Якщо ваш current_time більший (уявіть, що останній процес малювання зайняв дуже багато часу, тому що десь була якась гикавка, чи купа сміття на керованій мові, або реалізація керованої пам’яті в C ++ або будь-якому іншому), то нічия пропускається, і next_frameоновляється ще один додатковий кадр, який варто тривати часу, поки або оновлення не дотягнуть до того, де ми повинні бути на годиннику, або ми пропустили малюнок достатньої кількості кадрів, які ми ОБОВ'ЯЗКОВО намалювати, щоб гравець міг бачити що вони роблять.

Якщо ваша машина надшвидка або ваша гра надзвичайно проста, то це current_timeможе бути рідше next_frame, а це означає, що ви не оновлюєтесь протягом цих моментів.

Ось звідки надходить інтерполяція. Так само, ви могли б окремим bool, оголошеним поза петлями, оголосити "брудним" простором.

Всередині циклу оновлення ви встановите dirty = true, що означає, що ви фактично виконали оновлення.

Тоді, замість того, щоб просто дзвонити draw(), ви скажете:

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

Тоді ви пропускаєте будь-яку інтерполяцію для плавного руху, але ви гарантуєте, що ви оновлюєтесь лише тоді, коли у вас є фактичні оновлення (а не інтерполяції між державами).

Якщо ви сміливі, є стаття під назвою "Виправте свій крок!" автор GafferOnGames.
Він підходить до проблеми дещо інакше, але я вважаю це гарнішим рішенням, яке робить здебільшого те саме (залежно від особливостей вашої мови та того, наскільки ви дбаєте про фізичні розрахунки вашої гри).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.