Есть десятки статей, книг и дискуссий по игровым циклам. Тем не менее, я довольно часто сталкиваюсь с чем-то вроде этого:
while(running)
{
processInput();
while(isTimeForUpdate)
{
update();
}
render();
}
Что в основном беспокоит меня об этом подходе, так это «независимый от обновления» рендеринг, например, рендеринг кадра, когда вообще нет изменений. Поэтому мой вопрос: почему этому подходу часто учат?
while (isTimeForUpdate)
, а неif (isTimeForUpdate)
. Основная цель не в том,render()
когда не былоupdate()
, ноupdate()
многократно междуrender()
s. Независимо от того, обе ситуации имеют действительное использование. Первый будет действителен, если состояние может измениться вне вашейupdate
функции, например, изменить то, что отображается на основе неявного состояния, такого как текущее время. Последнее действительно, потому что оно дает вашему физическому движку возможность выполнять множество небольших точных обновлений, что, например, снижает вероятность «коробления» через препятствия.Ответы:
Существует долгая история того, как мы пришли к этому общему соглашению, с множеством захватывающих задач на этом пути, поэтому я постараюсь мотивировать его поэтапно:
1. Проблема: устройства работают на разных скоростях
Вы когда-нибудь пытались сыграть в старую игру для DOS на современном ПК, и она работает неиграбельно быстро - просто размытие?
У многих старых игр был очень наивный цикл обновления - они собирали входные данные, обновляли состояние игры и рендерили так быстро, как позволяло оборудование, без учета того, сколько времени прошло. Это означает, что как только меняется оборудование, меняется и игровой процесс.
Как правило, мы хотим, чтобы наши игроки имели постоянный опыт и ощущения от игры на различных устройствах (при условии, что они соответствуют минимальным требованиям), независимо от того, используют ли они прошлогодний телефон или новейшую модель, игровой настольный компьютер высшего класса или ноутбук среднего уровня.
В частности, для игр, которые являются конкурентоспособными (многопользовательские или через таблицы лидеров), мы не хотим, чтобы игроки, работающие на одном устройстве, имели преимущество перед другими, потому что они могут работать быстрее или иметь больше времени для реакции.
Верным решением здесь является блокировка скорости, с которой мы делаем обновления состояния геймплея. Таким образом, мы можем гарантировать, что результаты всегда будут одинаковыми.
2. Так почему бы просто не заблокировать частоту кадров (например, с помощью VSync) и по-прежнему запускать обновления и рендеринг состояния игрового процесса в режиме lockstep?
Это может работать, но не всегда приемлемо для аудитории. Было долгое время, когда работа на скорости 30 кадров в секунду считалась золотым стандартом для игр. Теперь игроки обычно ожидают минимальную полосу 60 кадров в секунду, особенно в многопользовательских экшен-играх, а некоторые старые игры теперь выглядят заметно изменчиво, поскольку наши ожидания изменились. Также есть вокальная группа игроков на ПК, которые вообще возражают против блокировок кадров. Они платят много за свое новейшее оборудование и хотят использовать эту вычислительную мощь для максимально плавного и высокоточного рендеринга, на который он способен.
В VR, в частности, частота кадров является королем, и стандарт продолжает набирать обороты. В начале недавнего возрождения виртуальной реальности игры часто работали со скоростью около 60 кадров в секунду. Теперь 90 более стандартен, и аппаратное обеспечение, подобное PSVR, начинает поддерживать 120. Это может продолжаться и дальше. Таким образом, если игра VR ограничивает частоту кадров тем, что выполнимо и принято сегодня, она может остаться позади, так как аппаратное обеспечение и ожидания будут развиваться дальше.
(Как правило, будьте осторожны, когда говорят, что «игроки не могут воспринимать что-либо быстрее, чем XXX», поскольку это обычно основано на определенном типе «восприятия», например, на распознавании кадра в последовательности. Восприятие непрерывности движения обычно гораздо более чувствительный.)
Последняя проблема заключается в том, что игра, использующая заблокированную частоту кадров, также должна быть консервативной - если вы когда-нибудь попадете в момент, когда вы обновляете и отображаете необычно большое количество объектов, вы не хотите пропустить свой кадр. срок и вызвать заметное заикание или заминку. Поэтому вам нужно либо установить достаточно низкий бюджет для содержания, чтобы оставить запас, или инвестировать в более сложные функции динамической настройки качества, чтобы избежать привязки всего игрового процесса к худшей производительности на минимальном оборудовании.
Это может быть особенно проблематично, если проблемы с производительностью обнаруживаются на поздних стадиях разработки, когда все ваши существующие системы собраны и настроены с учетом частоты кадров при рендеринге, которую вы не всегда можете решить. Разъединение скорости обновления и рендеринга дает больше гибкости при работе с изменчивостью производительности.
3. Не имеет ли обновление с фиксированным временным шагом те же проблемы, что и (2)?
Я думаю, что это основа первоначального вопроса: если мы отделяем наши обновления и иногда визуализируем два кадра без обновлений между состояниями игры, то разве это не то же самое, что рендеринг в режиме lockstep при более низкой частоте кадров, поскольку видимых изменений на экран?
На самом деле есть несколько разных способов, которыми игры могут эффективно использовать развязку этих обновлений:
а) частота обновления может быть выше, чем частота кадров
Как отмечает Tyjkenn в другом ответе, физика, в частности, часто имеет более высокую частоту, чем рендеринг, что помогает минимизировать ошибки интеграции и дает более точные столкновения. Таким образом, вместо 0 или 1 обновлений между визуализированными кадрами у вас может быть 5, 10 или 50.
Теперь игрок, выполняющий рендеринг со скоростью 120 кадров в секунду, может получать 2 обновления за кадр, в то время как игрок с более низкой спецификацией аппаратного рендеринга со скоростью 30 кадров в секунду получает 8 обновлений за кадр, и обе их игры работают с одинаковой скоростью «тики в секунду в реальном времени». Более качественное оборудование делает его более гладким, но не меняет радикально, как работает геймплей.
Здесь есть риск, что, если частота обновления не соответствует частоте кадров, вы можете получить «частоту ударов» между ними . Например. у большинства фреймов у нас достаточно времени для 4 обновлений состояния игры и небольшого остатка, затем время от времени у нас достаточно накопленных ресурсов, чтобы сделать 5 обновлений в фрейме, делая небольшой скачок или заикание в движении. Это можно решить с помощью ...
б) интерполяция (или экстраполяция) игрового состояния между обновлениями
Здесь мы часто будем позволять игровому состоянию жить в одном фиксированном временном шаге в будущем и хранить достаточно информации из двух самых последних состояний, чтобы мы могли отобразить произвольную точку между ними. Затем, когда мы готовы показать новый кадр на экране, мы смешиваемся с соответствующим моментом только для отображения (т. Е. Здесь мы не изменяем основное игровое состояние)
Если все сделано правильно, это делает движение более плавным и даже помогает скрыть некоторые колебания частоты кадров, пока мы не опустимся слишком низко.
c) Добавление плавности к изменениям не игрового состояния
Даже без интерполяции состояния геймплея мы все же можем получить некоторые плавные выигрыши.
Чисто визуальные изменения, такие как анимация персонажей, системы частиц или VFX, и элементы пользовательского интерфейса, такие как HUD, часто обновляются отдельно от фиксированного временного шага состояния игрового процесса. Это означает, что если мы помечаем наше игровое состояние несколько раз за кадр, мы не платим их стоимость с каждым тиком - только на последнем проходе рендеринга. Вместо этого мы масштабируем скорость воспроизведения этих эффектов, чтобы соответствовать длине кадра, чтобы они воспроизводились настолько плавно, насколько позволяет частота кадров рендеринга, не влияя на скорость или честность игры, как обсуждалось в (1).
Движение камеры может сделать это тоже - особенно в VR, мы иногда показываем один и тот же кадр более одного раза, но перепроектируем его, чтобы учесть движение головы игрока между ними , чтобы мы могли улучшить воспринимаемую задержку и комфорт, даже если мы можем не рендерит все так быстро. Некоторые системы потоковой передачи игр (где игра запускается на сервере, а игрок запускает только тонкий клиент) также используют эту версию.
4. Почему бы просто не использовать этот стиль (с) для всего? Если это работает для анимации и пользовательского интерфейса, разве мы не можем просто масштабировать наши обновления состояния игрового процесса в соответствии с текущей частотой кадров?
Да * это возможно, но нет, это не просто.
Этот ответ уже немного длинный, поэтому я не буду вдаваться в подробности, просто краткое резюме:
Умножение на
deltaTime
работает, чтобы приспособиться к обновлениям переменной длины для линейного изменения (например, движение с постоянной скоростью, обратный отсчет таймера или прогресс по временной шкале анимации)К сожалению, многие аспекты игр являются нелинейными . Даже такая простая вещь, как гравитация, требует более сложных методов интеграции или подшагов с более высоким разрешением, чтобы избежать расхождения результатов при различных частотах кадров. Ввод и управление плеером само по себе является огромным источником нелинейности.
В частности, результаты дискретного обнаружения и разрешения коллизий зависят от частоты обновления, что приводит к ошибкам туннелирования и дрожания, если кадры становятся слишком длинными. Таким образом, переменная частота кадров заставляет нас использовать более сложные / дорогие методы непрерывного обнаружения столкновений для большей части нашего контента или терпеть изменчивость нашей физики. Даже при непрерывном обнаружении столкновений возникают проблемы, когда объекты движутся по дугам, что требует более коротких временных шагов ...
Таким образом, в общем случае для игры средней сложности, поддержание согласованного поведения и справедливости полностью за счет
deltaTime
масштабирования находится где-то между очень сложным и интенсивным обслуживанием до невозможного.Стандартизация частоты обновления позволяет нам гарантировать более согласованное поведение в различных условиях , часто с более простым кодом.
Сохранение этой частоты обновлений в отрыве от рендеринга дает нам гибкость в управлении плавностью и производительностью опыта без изменения логики игрового процесса .
Даже тогда мы никогда не получим действительно «идеальную» независимость от частоты кадров, но, как и многие другие подходы в играх, она дает нам управляемый метод, позволяющий набрать «достаточно хороший» для потребностей данной игры. Вот почему это обычно преподносится как полезная отправная точка.
источник
read-update-render
наша задержка в худшем случае составляет 17 мс (без учета графического конвейера и задержки отображения). При(read-update)x(n>1)-render
разомкнутом цикле с той же частотой кадров задержка в худшем случае может быть только одинаковой или лучшей, потому что мы проверяем и действуем на входе так часто или чаще. :)Другие ответы хороши и говорят о том, почему игровой цикл существует и должен быть отделен от цикла рендеринга. Тем не менее, что касается конкретного примера «Зачем визуализировать кадр, если не было никаких изменений?» Это действительно сводится к аппаратному обеспечению и сложности.
Видеокарты - это конечные автоматы, и они действительно способны делать одно и то же снова и снова. Если вы визуализируете только то, что изменилось, это на самом деле дороже, а не меньше. В большинстве сценариев нет ничего статичного: если вы немного переместитесь влево в FPS-игре, вы изменили пиксельные данные 98% материала на экране, вы также можете визуализировать весь кадр.
Но в основном сложность. Отслеживание всего, что изменилось во время обновления, намного дороже, потому что вы должны либо все переделывать, либо отслеживать старый результат какого-либо алгоритма, сравнивать его с новым результатом и отображать этот пиксель только в том случае, если изменение отличается. Это зависит от системы.
Конструкция аппаратного обеспечения и т. Д. В значительной степени оптимизирована для существующих соглашений, и конечный автомат был хорошей моделью для начала.
источник
Рендеринг обычно самый медленный процесс в игровом цикле. Люди не могут легко заметить разницу в частоте кадров быстрее 60, поэтому зачастую менее важно тратить время на рендеринг быстрее, чем это. Тем не менее, существуют и другие процессы, которые выиграли бы от более высокой скорости. Физика одна. Слишком большое изменение в одной петле может привести к тому, что объекты будут пролетать мимо стен. Могут быть способы обойти простые ошибки столкновения на больших приращениях, но для многих сложных физических взаимодействий вы просто не получите ту же точность. Однако, если физический цикл выполняется чаще, вероятность глюков меньше, поскольку объекты можно перемещать с меньшими приращениями без визуализации каждый раз. Больше ресурсов уходит на чувствительный физический движок и меньше тратится на рисование большего количества кадров, которые пользователь не видит.
Это особенно важно в играх с большим количеством графики. Если для каждого игрового цикла был один рендер, и у игрока не было самой мощной машины, в игре могут быть точки, в которых число кадров в секунду падает до 30 или 40. Хотя это все равно будет не совсем ужасная частота кадров, игра начала бы работать довольно медленно, если бы мы старались, чтобы каждое физическое изменение было достаточно небольшим, чтобы избежать сбоев. Игрок будет раздражен тем, что его персонаж ходит только вдвое медленнее. Однако, если скорость рендеринга не зависит от остальной части цикла, игрок сможет оставаться на фиксированной скорости ходьбы, несмотря на снижение частоты кадров.
источник
Конструкция, подобная той, что в вашем вопросе, может иметь смысл, если подсистема рендеринга имеет некоторое представление о «прошедшем времени с момента последнего рендеринга» .
Рассмотрим, например, подход, при котором положение объекта в игровом мире представляется через фиксированные
(x,y,z)
координаты с подходом, который дополнительно сохраняет текущий вектор движения(dx,dy,dz)
. Теперь вы можете написать свой игровой цикл так, чтобы изменение позиции происходило вupdate
методе, но вы также можете спроектировать его так, чтобы изменение движения происходило во времяupdate
. С последним подходом, даже если ваше игровое состояние на самом деле не изменится до следующегоupdate
,render
-функция, которая вызывается на более высокой частоте, может уже нарисовать объект в слегка обновленном положении. Хотя это технически приводит к несоответствию между тем, что вы видите, и тем, что представлено внутри, разница достаточно мала, чтобы не иметь значения для большинства практических аспектов, но позволяет анимации выглядеть намного более плавно.Прогнозирование «будущего» состояния вашей игры (несмотря на риск ошибочности) может быть хорошей идеей, если принять во внимание, например, задержки сетевого ввода.
источник
В дополнение к другим ответам ...
Проверка на изменение состояния требует значительной обработки. Если для проверки изменений требуется одинаковое (или больше!) Время обработки, по сравнению с фактическим выполнением обработки, вы действительно не улучшили ситуацию. В случае рендеринга изображения, как говорит @Waddles, видеокарта действительно хороша в том, чтобы снова и снова делать одну и ту же глупую вещь, и проверять каждый кусок данных на предмет изменений дороже, чем просто передавать их через на видеокарту для обработки. Кроме того, если рендеринг является игровым процессом, то вряд ли экран не изменился бы в последнем тике.
Вы также предполагаете, что рендеринг занимает значительное процессорное время. Это очень сильно зависит от вашего процессора и видеокарты. В течение многих лет основное внимание уделялось постепенной передаче более сложной работы по рендерингу на видеокарту и уменьшению входных данных, необходимых для процессора. В идеале
render()
вызов процессора должен просто установить передачу DMA и все. Получение данных на графическую карту затем делегируется контроллеру памяти, а создание изображения делегируется на графическую карту. Они могут делать это в свое время, в то время как процессор параллельнопродолжает физику, игровой движок и все остальное, что процессор делает лучше. Очевидно, что реальность намного сложнее, но возможность перекладывать работу на другие части системы также является существенным фактором.источник