Подтвердить что ты не робот

Как вы отделяете логику игры от отображения?

Как вы можете сделать кадры отображения в секунду независимыми от логики игры? Это значит, что логика игры работает на той же скорости независимо от того, насколько быстро видеокарта может отобразиться.

4b9b3361

Ответ 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 были очень полезны.