Я экспериментирую с созданием игрового движка с нуля на Java, и у меня есть пара вопросов. Мой основной игровой цикл выглядит так:
int FPS = 60;
while(isRunning){
/* Current time, before frame update */
long time = System.currentTimeMillis();
update();
draw();
/* How long each frame should last - time it took for one frame */
long delay = (1000 / FPS) - (System.currentTimeMillis() - time);
if(delay > 0){
try{
Thread.sleep(delay);
}catch(Exception e){};
}
}
Как вы можете видеть, я установил частоту кадров 60FPS, которая используется в delay
расчете. Задержка гарантирует, что каждый кадр занимает одинаковое количество времени перед рендерингом следующего. В своей update()
функции я делаю, x++
что увеличивает горизонтальное значение графического объекта, который я рисую, с помощью следующего:
bbg.drawOval(x,40,20,20);
Что меня смущает, так это скорость. Когда я установил FPS
значение 150, рендеринг круга действительно быстро пересекает скорость, в то время как установка FPS
30 перемещается по экрану на половине скорости. Разве частота кадров не влияет только на «плавность» рендеринга, а не на скорость рендеринга объектов? Я думаю, что я пропускаю большую часть, я хотел бы получить некоторые разъяснения.
источник
1000 / FPS
деление может быть выполнено, а результат назначен переменной перед вашимwhile(isRunning)
циклом. Это помогает сохранить пару инструкций процессора для выполнения чего-то более одного раза без необходимости.Ответы:
Вы перемещаете круг на один пиксель за кадр. Не должно быть большим сюрпризом, что, если ваш цикл рендеринга работает со скоростью 30 кадров в секунду, ваш круг будет двигаться со скоростью 30 пикселей в секунду.
У вас есть три возможных способа решения этой проблемы:
Просто выберите одну частоту кадров и придерживайтесь ее. Это то, что делали многие игры старой школы - они работали с фиксированной частотой 50 или 60 кадров в секунду, обычно синхронизировались с частотой обновления экрана и просто разрабатывали игровую логику, чтобы делать все необходимое в течение этого фиксированного интервала времени. Если по какой-то причине этого не произошло, игра просто пропустит кадр (или, возможно, потерпит крах), эффективно замедляя как рисование, так и игровую физику до половины скорости.
В частности, игры, используемые функции , такие как обнаружение столкновений аппаратных спрайтов в значительной степени были работа , как это, потому что их игра логика была неразрывно связана с оказанием, что было сделано в аппаратных средств по фиксированной ставке.
Используйте переменный временной шаг для вашей игровой физики. По сути, это означает переписать игровой цикл так, чтобы он выглядел примерно так:
и, внутри
update()
, корректируя физические формулы для учета переменного временного шага, например, так:Одна из проблем этого метода заключается в том, что сложно сохранить физику (в основном) независимой от временного шага ; Вы действительно не хотите, чтобы расстояние, на которое игроки могли прыгать, зависело от их частоты кадров. Формула, которую я показал выше, прекрасно работает для постоянного ускорения, например, под действием силы тяжести (а та, что в связанном посте, работает довольно хорошо, даже если ускорение меняется со временем), но даже с самыми совершенными физическими формулами, работа с плавающими производит немного «числового шума», который, в частности, может сделать невозможным точное воспроизведение. Если это то, что вы думаете, вы можете захотеть, вы можете предпочесть другие методы.
Разъедините обновление и нарисуйте шаги. Идея заключается в том, что вы обновляете игровое состояние с использованием фиксированного временного шага, но запускаете различное количество обновлений между каждым кадром. То есть ваш игровой цикл может выглядеть примерно так:
Чтобы сделать воспринимаемое движение более плавным, вы, возможно, захотите, чтобы ваш
draw()
метод плавно интерполировал такие вещи, как положение объектов между предыдущим и следующим игровыми состояниями. Это означает, что вам нужно передать корректное интерполяционное смещение вdraw()
метод, например, так:Вам также необходимо, чтобы ваш
update()
метод фактически вычислял игровое состояние на один шаг вперед (или, возможно, несколько, если вы хотите выполнить сплайн-интерполяцию более высокого порядка), и сохранял предыдущие позиции объектов перед их обновлением, чтобыdraw()
метод мог интерполировать между ними. (Также возможно просто экстраполировать прогнозируемые позиции на основе скоростей и ускорений объектов, но это может выглядеть отрывисто, особенно если объекты движутся сложными способами, что часто приводит к сбоям прогнозов.)Одним из преимуществ интерполяции является то, что для некоторых типов игр она позволяет значительно снизить частоту обновления игровой логики, сохраняя при этом иллюзию плавного движения. Например, вы можете обновлять игровое состояние только, скажем, 5 раз в секунду, в то же время рисуя от 30 до 60 интерполированных кадров в секунду. При этом вы можете также рассмотреть возможность чередования игровой логики с чертежом (т. Е. Иметь параметр для вашего
update()
метода, который указывает ему запускать только x % от полного обновления перед возвратом), и / или запускать физику игры / логика и код рендеринга в отдельных потоках (остерегайтесь глюков синхронизации!).Конечно, также возможно комбинировать эти методы различными способами. Например, в многопользовательской игре клиент-сервер у вас может быть сервер (который не должен ничего рисовать) запускать свои обновления с фиксированным временным интервалом (для согласованной физики и точного воспроизведения), в то время как клиент выполняет прогнозирующие обновления (для быть переопределенным сервером, в случае каких-либо разногласий) с переменным временным шагом для лучшей производительности. Также возможно полезное сочетание интерполяции и обновлений с переменным временным шагом; например, в только что описанном сценарии клиент-сервер на самом деле нет особого смысла в том, чтобы клиент использовал более короткие временные шаги обновления, чем сервер, поэтому можно установить более низкий предел для временного шага клиента и выполнить интерполяцию на этапе рисования, чтобы обеспечить более высокий FPS.
(Редактировать: добавлен код, чтобы избежать абсурдных интервалов обновления / подсчета, если, скажем, компьютер временно приостановлен или иным образом заморожен более чем на секунду, пока запущен игровой цикл. Спасибо Mooing Duck за напоминание о необходимости этого .)
источник
updateInterval
это просто количество миллисекунд, которое вы хотите между обновлениями состояния игры. Например, 10 обновлений в секундуupdateInterval = (1000 / 10) = 100
.currentTimeMillis
это не монотонные часы. ИспользуйтеnanoTime
вместо этого, если только вы не хотите, чтобы синхронизация времени в сети мешала скорости вашей игры.while(lastTime+=updateInterval <= time)
. Это просто мысль, а не исправление.Ваш код в настоящее время выполняется каждый раз, когда отображается кадр. Если частота кадров выше или ниже заданной частоты кадров, ваши результаты будут меняться, поскольку обновления не имеют одинакового времени.
Чтобы решить эту проблему, вы должны обратиться к Delta Timing .
Сделать это:
Затем вам нужно будет умножить дельта-время на значение, которое вы хотите изменить по времени. Например:
источник
time()
возвращает одно и то же дважды, вам не нужны ошибки div / 0 и бесполезная обработка.Это потому, что вы ограничиваете частоту кадров, но вы делаете только одно обновление на кадр. Итак, давайте предположим, что игра работает на скорости 60 кадров в секунду, вы получаете 60 обновлений логики в секунду. Если частота кадров упадет до 15 кадров в секунду, у вас будет только 15 обновлений логики в секунду.
Вместо этого попробуйте накапливать прошедшее до сих пор время кадра, а затем обновлять игровую логику один раз для каждого заданного временного промежутка, например, чтобы запустить свою логику со скоростью 100 кадров в секунду, вы будете запускать обновление один раз за каждые накопленные 10 мс (и вычитать их из счетчик).
Добавьте альтернативное (лучше для визуального) обновление вашей логики на основе прошедшего времени.
источник