Как вы отделяете игровую логику от дисплея? - PullRequest
19 голосов
/ 20 августа 2008

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

Ответы [ 8 ]

30 голосов
/ 20 августа 2008

Я думаю, что вопрос показывает немного недопонимания того, как игровые движки должны быть разработаны. Что совершенно нормально, потому что это чертовски сложные вещи, которые трудно понять правильно;)

У вас правильное впечатление, что вы хотите то, что называется независимостью от частоты кадров. Но это касается не только рендеринга кадров.

Фрейм в однопоточных игровых движках обычно называется тик. Каждый тик вы обрабатываете ввод, обрабатываете игровую логику и визуализируете фрейм, основанный на результатах обработки.

То, что вы хотите сделать, это иметь возможность обрабатывать игровую логику на любом FPS (кадрах в секунду) и иметь детерминированный результат.

Это становится проблемой в следующем случае:

Проверьте ввод: - Ввод ключа: «W», что означает, что мы двигаем персонажа игрока вперед на 10 единиц:

playerPosition + = 10;

Теперь, так как вы делаете это каждый кадр, если вы работаете со скоростью 30 кадров в секунду, вы будете перемещать 300 единиц в секунду.

Но если вместо этого вы работаете со скоростью 10 FPS, вы будете перемещать только 100 единиц в секунду. И поэтому ваша игровая логика не Частота кадров не зависит.

К счастью, решить эту проблему и сделать игровую логику независимой от частоты кадров является довольно простой задачей.

Во-первых, вам нужен таймер, который будет отсчитывать время рендеринга каждого кадра. Это число в секундах (т. Е. 0,001 секунды для завершения тика) умножается на то, что вы хотите быть независимым от частоты кадров. Так что в этом случае:

Удерживая 'W'

playerPosition + = 10 * frameTimeDelta;

(Delta - причудливое слово для «Измени что-то»)

Таким образом, ваш игрок будет перемещать некоторую долю 10 за один тик, и после полной секунды тиков вы переместите полные 10 единиц.

Однако, это будет падать, когда речь идет о свойствах, где скорость изменения также меняется со временем, например, ускорение транспортного средства. Эту проблему можно решить с помощью более продвинутого интегратора, такого как «Verlet».

Многопоточный подход

Если вы все еще заинтересованы в ответе на ваш вопрос (поскольку я не ответил на него, но представил альтернативу), вот он. Разделение игровой логики и рендеринга на разные темы. У него есть свои недостатки, хотя. Достаточно, чтобы подавляющее большинство игровых движков оставалось однопоточным.

Это не значит, что в так называемых однопоточных двигателях работает только один поток. Но все значимые задачи обычно находятся в одном центральном потоке. Некоторые вещи, такие как Collision Detection, могут быть многопоточными, но обычно фаза Collision блока Tick блокируется до тех пор, пока все потоки не вернутся, и механизм не вернется к одному потоку выполнения.

Многопоточность представляет целый, очень большой класс проблем, даже некоторые из которых касаются производительности, поскольку все, даже контейнеры, должны быть поточно-ориентированными. И игровые движки - это очень сложные программы, поэтому они редко стоят того, чтобы усложнить их многопоточность.

Подход с фиксированным временным шагом

Наконец, как заметил другой комментатор, наличие временного шага фиксированного размера и контроль того, как часто вы «шагаете» по игровой логике, также может быть очень эффективным способом решения этой проблемы со многими преимуществами.

Здесь приведена ссылка для полноты, но другой комментатор также ссылается на нее: Fix Your Time Step

8 голосов
/ 20 августа 2008

Koen Witters имеет очень подробную статью о различных настройках игрового цикла.

Он покрывает:

  • FPS зависит от постоянной скорости игры
  • Скорость игры зависит от переменной FPS
  • Постоянная скорость игры с максимальным FPS
  • Постоянная скорость игры не зависит от переменной FPS

(Это заголовки, извлеченные из статьи в порядке желательности.)

4 голосов
/ 20 августа 2008

Вы можете сделать свой игровой цикл похожим на:

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 каждый кадр.

2 голосов
/ 20 августа 2008

В тот день была отличная статья по флипкоду об этом. Я хотел бы выкопать его и представить вам.

http://www.flipcode.com/archives/Main_Loop_with_Fixed_Time_Steps.shtml

Это хорошо продуманный цикл запуска игры:

  1. Однопоточная
  2. При фиксированных игровых часах
  3. С графикой как можно быстрее, используя интерполированные часы

Ну, по крайней мере, я так думаю. :-) Жаль, что обсуждение, которое продолжалось после этой публикации, труднее найти. Возможно, машина обратного пути может помочь там.

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);
0 голосов
/ 29 января 2010

Это не распространяется на абстракцию программ более высокого уровня, то есть конечные автоматы и т. Д.

Хорошо управлять движением и ускорением, регулируя их с помощью промежутка времени. Но как насчет того, чтобы вызвать звук через 2,55 секунды после того или иного или изменить игровой уровень спустя 18,25 с и т. д.

Это может быть привязано к накопителю истекшего времени (счетчику), НО эти тайминги могут облажаться, если ваша частота кадров падает ниже разрешения сценария состояния Т.е. если вашей более высокой логике требуется зернистость 0,05 с, и вы падаете ниже 20 кадров в секунду.

Детерминизм можно сохранить, если игровая логика запускается в отдельном «потоке» (на уровне программного обеспечения, который я бы предпочел для этого или на уровне ОС) с фиксированным интервалом времени, не зависящим от fps.

Наказанием может быть то, что вы можете тратить время процессора между кадрами, если ничего не происходит, но я думаю, что оно того стоит.

0 голосов
/ 15 сентября 2008

Однопоточные решения с задержками перед отображением графики - это хорошо, но я думаю, что прогрессивный способ - запускать игровую логику в одном потоке и отображение в другом.

Но вы должны правильно синхронизировать потоки;) Реализация займет много времени, поэтому, если ваша игра не слишком большая, однопоточное решение подойдет.

Кроме того, извлечение GUI в отдельный поток кажется отличным подходом. Вы когда-нибудь видели всплывающее сообщение «Миссия завершена», когда юниты перемещаются в играх RTS? Об этом я и говорю:)

0 голосов
/ 21 августа 2008

Enginuity имеет немного другой, но интересный подход: пул задач.

0 голосов
/ 20 августа 2008

Исходя из моего опыта (не очень), ответы Джесси и Адама должны поставить вас на правильный путь.

Если вам нужна дополнительная информация и понимание того, как это работает, я обнаружил, что примеры приложений для TrueVision 3D были очень полезны.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...