Как вы можете сделать кадры отображения в секунду независимыми от логики игры? Это значит, что логика игры работает на той же скорости независимо от того, насколько быстро видеокарта может отобразиться.
Как вы отделяете логику игры от отображения?
Ответ 1
Я думаю, что этот вопрос показывает немного непонимание того, как должны быть разработаны игровые движки. Что совершенно нормально, потому что они проклятые сложные вещи, которые трудно получить правильно;)
Вы находитесь под правильным впечатлением, что хотите, чтобы получилась так называемая независимость от частоты кадров. Но это относится не только к рамкам рендеринга.
Рамка в однопоточных игровых движках обычно называется Tick. Каждый тик обрабатывает входные данные, обрабатывает логику игры и создает кадр, основанный на результатах обработки.
То, что вы хотите сделать, - это обработать логику игры на любом FPS (Frames Per Second) и иметь детерминированный результат.
Это становится проблемой в следующем случае:
Проверить ввод: - Входный ключ: "W", что означает, что мы перемещаем персонаж игрока вперед на 10 единиц:
playerPosition + = 10;
Теперь, когда вы делаете это каждый кадр, если вы используете 30 FPS, вы будете перемещать 300 единиц в секунду.
Но если вы вместо этого используете 10 FPS, вы будете перемещать только 100 единиц в секунду. И поэтому ваша игровая логика не является независимой от частоты кадров.
К счастью, для решения этой проблемы и создания вашей логики игры Frame Rate Independent - довольно простая задача.
Сначала вам нужен таймер, который будет считать время, которое каждый кадр принимает для рендеринга. Это число в секундах (так что 0,001 секунд для завершения Tick) затем умножается на то, что вы хотите быть независимым от частоты кадров. Итак, в этом случае:
При удерживании 'W'
playerPosition + = 10 * frameTimeDelta;
(Delta - причудливое слово для "Change In Something" )
Таким образом, ваш игрок переместит некоторую долю 10 в один тик, а после полной секунды Ticks вы переместили бы все 10 единиц.
Однако это будет падать, когда дело доходит до свойств, где скорость изменения также изменяется с течением времени, например ускоряющее транспортное средство. Это можно решить, используя более продвинутый интегратор, например "Verlet".
Многопоточный подход
Если вас по-прежнему интересует ответ на ваш вопрос (так как я не ответил на него, а представил альтернативу), вот он. Разделение игровой логики и рендеринга на разные темы. Тем не менее, у него есть спина. Достаточно, чтобы подавляющее большинство игровых движков осталось однопоточным.
Нельзя сказать, что в так называемых однопоточных двигателях существует только один поток. Но все важные задачи обычно находятся в одном центральном потоке. Некоторые вещи, такие как обнаружение конфликтов, могут быть многопоточными, но в целом фаза столкновений блоков Tick блокируется до тех пор, пока все потоки не вернутся, и двигатель возвращается к одному потоку выполнения.
Многопоточность представляет собой целый, очень большой класс проблем, даже некоторые из них, поскольку все, даже контейнеры, должны быть потокобезопасными. И Game Engines - это очень сложные программы для начала, поэтому редко стоит дополнительное усложнение многопоточности.
Пошаговый шаг с фиксированным шагом
Наконец, как заметил еще один комментатор, имея шаг фиксированного размера и контролируя, как часто вы "шагаете", логика игры также может быть очень эффективным способом обработки этого со многими преимуществами.
Ссылка на полноту, но другой комментатор также ссылается на нее: Исправить свой временной шаг
Ответ 2
Koen Witters имеет очень подробную статью о различных настройках игрового цикла.
Он охватывает:
- FPS зависит от постоянной скорости игры.
- Скорость игры зависит от переменной FPS
- Постоянная скорость игры с максимальным FPS
- Постоянная скорость игры независимо от переменной FPS
(Это заголовки, извлеченные из статьи, в порядке желательности.)
Ответ 3
Вы можете сделать свой игровой цикл похожим:
int lastTime = GetCurrentTime();
while(1) {
// how long is it since we last updated?
int currentTime = GetCurrentTime();
int dt = currentTime - lastTime;
lastTime = currentTime;
// now do the game logic
Update(dt);
// and you can render
Draw();
}
Тогда вам просто нужно написать свою функцию Update()
, чтобы учесть временной дифференциал; например, если у вас есть объект, движущийся с некоторой скоростью v
, обновите его положение на v * dt
каждый кадр.
Ответ 4
В этот день была отличная статья о флип-кодах. Я хотел бы выкопать его и представить его вам.
http://www.flipcode.com/archives/Main_Loop_with_Fixed_Time_Steps.shtml
Это хорошо продуманный цикл для запуска игры:
- Однопоточный
- При фиксированных часах игры
- С графикой как можно быстрее, используя интерполированные часы
Ну, по крайней мере, это то, что я думаю.:-) Слишком плохо, что дискуссия, которая преследовалась после этой публикации, сложнее найти. Возможно, эта машина может помочь.
time0 = getTickCount();
do
{
time1 = getTickCount();
frameTime = 0;
int numLoops = 0;
while ((time1 - time0) TICK_TIME && numLoops < MAX_LOOPS)
{
GameTickRun();
time0 += TICK_TIME;
frameTime += TICK_TIME;
numLoops++;
// Could this be a good idea? We're not doing it, anyway.
// time1 = getTickCount();
}
IndependentTickRun(frameTime);
// If playing solo and game logic takes way too long, discard pending
time.
if (!bNetworkGame && (time1 - time0) TICK_TIME)
time0 = time1 - TICK_TIME;
if (canRender)
{
// Account for numLoops overflow causing percent 1.
float percentWithinTick = Min(1.f, float(time1 - time0)/TICK_TIME);
GameDrawWithInterpolation(percentWithinTick);
}
}
while (!bGameDone);
Ответ 5
Enginuity имеет несколько иной, но интересный подход: пул задач.
Ответ 6
Однопоточные решения с временными задержками перед отображением графики прекрасны, но я думаю, что прогрессивным способом является запуск игровой логики в одном потоке и отображение в другом потоке.
Но вам нужно правильно синхронизировать потоки;) Это займет много времени, поэтому, если ваша игра не слишком велика, однопоточное решение будет в порядке.
Кроме того, извлечение GUI в отдельный поток, кажется, отличный подход. Вы когда-нибудь видели всплывающее сообщение "Миссия завершена", когда юниты перемещаются в играх RTS? Это то, о чем я говорю:)
Ответ 7
Это не охватывает более высокий уровень абстракции программы, т.е. состояния машин и т.д.
Это прекрасно, чтобы управлять движением и ускорением, настраивая те, у кого время вашего кадра. Но как насчет таких вещей, как запуск звука через 2,55 секунды после того или иного или изменение игровой уровень 18.25 сек позже и т.д.
Это может быть привязано к счетчику отсчета прошедшего времени (счетчик), но эти тайминги могут закручивайтесь, если ваша частота кадров падает ниже вашего разрешения script Если ваша высшая логика требует 0,05 сек детализации, и вы опускаетесь ниже 20 кадров в секунду.
Детерминизм может сохраняться, если логика игры запускается на отдельной "нить", (на уровне программного обеспечения, который я бы предпочел для этого, или уровня ОС) с фиксированным временным фрагментом, независимо от fps.
Казнь может заключаться в том, что вы можете тратить время между промежуточными кадрами, если не так много, но я думаю, что это того стоит.
Ответ 8
Из моего опыта (не так много) ответы Джесси и Адама должны поставить вас на правильный путь.
Если вам нужна дополнительная информация и понять, как это работает, я обнаружил, что примеры приложений для TrueVision 3D были очень полезны.