Как вы отделяете логику игры от отображения?
Как вы можете сделать кадры отображения в секунду независимыми от логики игры? Это значит, что логика игры работает на той же скорости независимо от того, насколько быстро видеокарта может отобразиться.
Ответы
Ответ 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 были очень полезны.