Я недавно прочитал эту статью об игровых циклах: 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, давайте сделаем некоторую логику и примем ввод.
Yadayadayada.
В конце цикла 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 раз в секунду, и рендерится так быстро, как может?
Для меня, если я не пропустил что-то ОГРОМНОЕ, это выглядит ... ничего.
И разве эта структура, этого цикла while, якобы запускается 25 раз в секунду, а затем обновляет игру именно так, как я упоминал ранее в начале статьи?
Если это так, почему мы не можем сделать что-то простое, как:
while( loops < 25 )
{
getInput();
performLogic();
loops++;
}
drawGame();
И рассчитывать на интерполяцию другим способом.
Простите за мой чрезвычайно длинный вопрос, но эта статья принесла мне больше вреда, чем пользы. Сейчас я сильно запутался - и не знаю, как реализовать правильный игровой цикл из-за всех этих возникших вопросов.
источник
Ответы:
Я думаю, что автор допустил небольшую ошибку:
while( GetTickCount() > next_game_tick && loops < MAX_FRAMESKIP)
должно быть
while( GetTickCount() < next_game_tick && loops < MAX_FRAMESKIP)
То есть: пока еще не пришло время рисовать наш следующий кадр, и пока мы не пропустили столько кадров, сколько MAX_FRAMESKIP, нам следует подождать.
Я также не понимаю, почему он обновляется
next_game_tick
в цикле, и я предполагаю, что это еще одна ошибка. Поскольку в начале кадра вы можете определить, когда должен быть следующий кадр (при использовании фиксированной частоты кадров). Значениеnext game tick
не зависит от того, сколько времени у нас осталось после обновления и рендеринга.Автор также делает еще одну распространенную ошибку
Это означает рендеринг одного и того же кадра несколько раз. Автор даже знает об этом:
Это только приводит к тому, что графический процессор выполняет ненужную работу, и если рендеринг занимает больше времени, чем ожидалось, это может привести к тому, что вы начнете работать над следующим кадром позже, чем предполагалось, поэтому лучше уступить ОС и ждать.
источник
Может быть, лучше, если я немного упросту это:
while
Цикл внутри mainloop используется для запуска шагов моделирования от того, где он был, до того места, где он должен быть сейчас.update_game()
Функция всегда должна предполагать, чтоSKIP_TICKS
с момента последнего вызова прошло только время. Это позволит поддерживать физику игры на постоянной скорости на медленном и быстром оборудовании.Увеличение
next_game_tick
на количествоSKIP_TICKS
перемещений приближает его к текущему времени. Когда это становится больше, чем текущее время, оно ломается (current > next_game_tick
) и mainloop продолжает отображать текущий кадр.После рендеринга следующий вызов
GetTickCount()
возвратит новое текущее время. Если это время больше, чемnext_game_tick
это означает, что мы уже отстаем от 1-N шагов в симуляции, и мы должны наверстать упущенное, выполняя каждый шаг в симуляции с той же постоянной скоростью. В этом случае, если он ниже, он просто снова визуализирует тот же кадр (если нет интерполяции).Оригинальный код ограничил количество циклов, если мы остались слишком далеко (
MAX_FRAMESKIP
). Это только заставляет его фактически показывать что-то и не похоже на то, что оно заблокировано, если, например, возобновить приостановку или приостановку игры в отладчике на долгое время (при условии, чтоGetTickCount()
он не останавливается в течение этого времени), пока это не застало время.Чтобы избавиться от бесполезного рендеринга того же кадра, если вы не используете интерполяцию внутри
display_game()
, вы можете обернуть его внутри, если выражение вродеЭто также хорошая статья об этом: http://gafferongames.com/game-physics/fix-your-timestep/
Также, возможно, причина, по которой ваши
fprintf
результаты, когда ваша игра заканчивается, может быть просто в том, что она не сбрасывается.Извините за мой английский.
источник
Его код выглядит полностью действительным.
Рассмотрим
while
цикл последнего набора:У меня есть система, которая говорит: «пока игра запущена, проверьте текущее время - если оно больше, чем наша подсчет частоты кадров, и мы пропустили рисование менее 5 кадров, затем пропустите рисование и просто обновите ввод и физика: еще нарисовать сцену и начать следующую итерацию обновления "
Когда происходит каждое обновление, вы увеличиваете время «next_frame» на свою идеальную частоту кадров. Затем вы снова проверяете свое время. Если ваше текущее время сейчас меньше, чем когда должен быть обновлен next_frame, тогда вы пропустите обновление и нарисуете то, что у вас есть.
Если ваш current_time больше (представьте, что последний процесс рисования занял очень много времени, потому что где-то был какой-то сбой, или сборка мусора в управляемом языке, или реализация управляемой памяти в C ++ или что-то еще), тогда рисовать получает пропущено , и
next_frame
обновляются с другой дополнительной рамкой стоит времени, пока либо обновления не догонять , где мы должны быть на часах, или мы пропустили рисунок достаточно кадров , что мы обязательно должны сделать один, так что игрок может видеть что они делаютЕсли ваша машина очень быстрая или ваша игра очень простая, то это
current_time
может быть не такnext_frame
часто, что означает, что вы не обновляетесь в течение этих пунктов.Вот где начинается интерполяция. Аналогично, вы можете иметь отдельный тип bool, объявленный вне циклов, чтобы объявить «грязное» пространство.
Внутри цикла обновления вы бы установили
dirty = true
, что означает, что вы действительно выполнили обновление.Тогда вместо того, чтобы просто позвонить
draw()
, вы скажете:Тогда вы упускаете любую интерполяцию для плавного движения, но вы гарантируете, что обновляетесь только тогда, когда у вас действительно происходят обновления (а не интерполяции между состояниями).
Если вы смелы, есть статья под названием «Исправьте свой временной шаг!» GafferOnGames.
Он немного по-другому подходит к этой проблеме, но я считаю, что это более симпатичное решение, которое делает в основном то же самое (в зависимости от особенностей вашего языка и от того, насколько вы заботитесь о физических вычислениях в вашей игре).
источник