Введение
Физические тики и отрендеренные кадры
Одна из ключевых концепций, которую необходимо понимать в Godot, — это различие между физическими тиками (иногда называемыми итерациями или физическими кадрами) и отрендеренными кадрами. Физика выполняется с фиксированной частотой тиков (задаётся в Project Settings > Physics > Common > Physics Tick per Second), которая по умолчанию равна 60 тикам в секунду.
Однако движок не обязательно рендерит с одинаковой частотой. Хотя многие мониторы обновляются с частотой 60 Гц (циклов в секунду), многие — с совершенно другой (например, 75 Гц, 144 Гц, 240 Гц и более). Даже если монитор может отображать новый кадр, например, 60 раз в секунду, нет гарантии, что центральный и графический процессоры смогут выдавать кадры с такой частотой. Например, при работе с вертикальной синхронизацией компьютер может быть слишком медленным для 60 кадров и достигать предельных значений только при 30 кадрах в секунду, в этом случае кадры, которые вы видите, будут меняться с частотой 30 кадров в секунду (что приведет к подтормаживанию).
Но здесь есть проблема. Что произойдёт, если тики физики не совпадают с кадрами? Что произойдёт, если частота тиков физики не совпадает по фазе с частотой кадров? Или, что ещё хуже, что произойдёт, если частота тиков физики ниже частоты кадров рендеринга?
Эту проблему легче понять, если рассмотреть экстремальный сценарий. Если установить частоту физических тиков 10 в секунду в простой игре с частотой рендеринга 60 кадров в секунду, то можно увидеть, что положение объекта в зависимости от отрендеренных кадров будет "прыгать" каждую десятую секунды, а не обеспечивать плавное движение. Когда физические тиконы рассчитывают новое положение для нового объекта, он отображается в этом положении не один раз, а в течение 6 кадров.
Этот скачок можно увидеть в других сочетаниях тиков/частоты кадров как сбои или дрожание, вызванные этим лестничным эффектом из-за несоответствия между физическим временем тика и временем отрисовки кадра.
Что можно сделать, если кадры и такты не синхронизированы?
Заблокировать тиковую/кадровую частоту вместе?
Самое очевидное решение — избавиться от этой проблемы, обеспечив физический тик, совпадающий с каждым кадром. Этот подход использовался на старых консолях и компьютерах с фиксированным железом. Если вы знаете, что все игроки будут использовать одно и то же оборудование, вы можете убедиться, что оно достаточно быстрое для расчёта тактов и кадров, например, 50 FPS, и можете быть уверены, что всё будет работать отлично для всех.
Однако современные игры часто не рассчитаны на стационарное оборудование. Вы часто планируете выпускать игры на настольных компьютерах, мобильных устройствах и других платформах. Производительность каждого из них сильно различается, как и частота обновления экрана. Нам нужно найти более эффективное решение этой проблемы.
Адаптировать тиковую скорость?
Вместо того, чтобы проектировать игру с фиксированной частотой физических тиков, мы могли бы масштабировать её в зависимости от оборудования конечного пользователя. Например, мы могли бы использовать фиксированную частоту физических тиков, подходящую для этого оборудования, или даже варьировать длительность каждого физического тика в соответствии с длительностью конкретного кадра.
Это работает, но есть проблема. Физика (и игровая логика, которая часто также выполняется в _physics_process) работают лучше всего и стабильнее всего при запуске с фиксированной, предопределённой частотой тиков. Если вы попытаетесь запустить гоночную игру с физикой, рассчитанной на 60 TPS (тиков в секунду), например, с 10 TPS, физика будет вести себя совершенно иначе. Управление может быть менее отзывчивым, столкновения и траектории могут быть совершенно иными. Вы можете тщательно протестировать свою игру при 60 TPS, а затем обнаружить, что она зависает на компьютерах конечных пользователей при запуске с другой частотой тиков.
Это может затруднить контроль качества из-за трудновоспроизводимых ошибок, особенно в играх класса AAA, где подобные проблемы могут обойтись очень дорого. Это также может быть проблематично для честности многопользовательских игр, поскольку запуск игры на определённых частотах тиков может быть более выгодным, чем на других.
Заблокируйте частоту тиков, но используйте интерполяцию для сглаживания кадров между физическими тиками
Это стало одним из самых популярных подходов к решению проблемы, хотя он необязателен и по умолчанию отключен.
Мы установили, что наиболее желательная конфигурация физики/игровой логики для обеспечения согласованности и предсказуемости — это фиксированная на этапе разработки частота тиков физики. Проблема заключается в несоответствии между зафиксированным положением физического объекта и тем, где мы "хотим" отображать физический объект в кадре для обеспечения плавности движения.
Ответ оказывается простым, но поначалу его может быть немного сложно осознать.
Вместо того, чтобы отслеживать только текущее положение физического объекта в движке, мы отслеживаем как текущее положение объекта, так и предыдущее положение на предыдущем такте физики.
Зачем нам нужна предыдущая позиция (фактически, всё преобразование, включая поворот и масштабирование)? Применив немного математической магии, мы можем с помощью интерполяции вычислить, каким будет преобразование объекта между этими двумя точками в нашем идеальном мире плавного непрерывного движения.
Линейная интерполяция
Самый простой способ добиться этого — линейная интерполяция, или лерпинг, который вы, возможно, уже использовали ранее.
Рассмотрим только положение и ситуацию, когда мы знаем, что предыдущая координата X физического такта составляла 10 единиц, а текущая координата X физического такта составляет 30 единиц.
Примечание
Хотя математические вычисления здесь объяснены, вам не нужно беспокоиться о деталях, так как этот шаг будет выполнен автоматически. Godot может использовать более сложные формы интерполяции, но линейная интерполяция — самый простой способ объяснения.
Фракция интерполяции физики
Если наши физические тики происходят 10 раз в секунду (в данном примере), что произойдёт, если отрендеренный кадр будет создан в момент времени 0,12 секунды? Мы можем выполнить некоторые математические вычисления, чтобы определить, где будет находиться объект, чтобы добиться плавного движения между двумя тиками.
Прежде всего, нам нужно рассчитать, на каком расстоянии от физического тика должен находиться объект. Если последний физический тик был в 0,1 секунды, то мы находимся на 0,02 секунды (0,12 - 0,1) от тика, который, как мы знаем, будет длиться 0,1 секунды (10 тиков в секунду). Доля от тика равна:
fraction = 0.02 / 0.10
fraction = 0.2
Это называется дробью интерполяции физики, и Godot легко рассчитывает её для вас. Её можно получить в любом кадре, вызвав Engine.get_physics_interpolation_fraction.
Расчет интерполированного положения
Получив интерполяционную дробь, мы можем подставить её в стандартное линейное интерполяционное уравнение. Таким образом, координата X будет равна:
x_interpolated = x_prev + ((x_curr - x_prev) * 0.2)
Итак, подставим x_prev на 10, а x_curr на 30:
x_interpolated = 10 + ((30 - 10) * 0.2)
x_interpolated = 10 + 4
x_interpolated = 14
Давайте разберем это подробнее:
Мы знаем, что X начинается с координаты на предыдущем тике (
x_prev), что составляет 10 единиц.Мы знаем, что после полного тика будет добавлена разница между текущим тиком и предыдущим тиком (
x_curr - x_prev) (что составляет 20 единиц).Единственное, что нам нужно изменить, — это пропорцию этой разницы, которую мы добавляем, в зависимости от того, насколько далеко мы продвинулись в физическом тике.
Примечание
Хотя в этом примере интерполируется положение, то же самое можно сделать с поворотом и масштабом объектов. Знать детали не обязательно, Godot сделает всё за вас.
Сглаженные преобразования между физическими тиками?
Сопоставление всего этого показывает, что должна быть возможность получить хорошую и плавную оценку преобразования объектов между текущим и предыдущим физическим тиком.
Но подождите, вы, возможно, заметили кое-что. При интерполяции между текущим и предыдущим тиками мы оцениваем не положение объекта сейчас, а положение объекта в прошлом. Точнее, мы оцениваем положение объекта между 1 и 2 тиками в прошлом.
В прошлом
Что это значит? Эта схема действительно работает, но это означает, что мы фактически создаём задержку между тем, что видим на экране, и тем, где объекты должны находиться.
На практике большинство людей не заметят эту задержку, или, скорее, она, как правило, не нежелательна. В играх и так присутствуют значительные задержки, просто мы их обычно не замечаем. Наиболее существенный эффект заключается в небольшой задержке ввода, что может быть важным фактором в играх с быстрым вводом. В некоторых ситуациях с быстрым вводом может потребоваться отключить интерполяцию физики и использовать другую схему или использовать высокую частоту тиков, что смягчит эти задержки.
Зачем заглядывать в прошлое? Почему бы не предсказать будущее?
Существует альтернатива этой схеме: вместо интерполяции между предыдущим и текущим тиком мы используем математические вычисления для экстраполяции в будущее. Мы пытаемся предсказать, где объект будет находиться, а не показывать, где он был. Это возможно и, возможно, будет предложено в будущем, но есть несколько существенных недостатков:
Прогноз может быть неверным, особенно если объект сталкивается с другим объектом во время физического такта.
Если прогноз оказался неверным, объект может переместиться в "невозможное" положение, например, внутрь стены.
Если скорость движения низкая, то эти неверные прогнозы могут не представлять большой проблемы.
Если прогноз оказался неверным, объекту может потребоваться подпрыгнуть или резко вернуться на скорректированную траекторию. Это может вызывать визуальный дискомфорт.
Фиксированная интерполяция временного шага
В Godot вся эта система называется физической интерполяцией, но её также можно встретить под названием "интерполяция с фиксированным временным шагом", поскольку она интерполирует объекты, перемещаемые с фиксированным временным шагом (физические такты в секунду). В некотором смысле второй термин точнее, поскольку его можно использовать и для интерполяции объектов, не подчиняющихся законам физики.
Совет
Хотя интерполяция физики обычно является хорошим выбором, существуют исключения, когда вы можете отказаться от встроенной интерполяции физики Godot (или использовать её ограниченно). Примером могут служить многопользовательские интернет-игры. Многопользовательские игры часто получают информацию о тиках или времени от других игроков или сервера, и эта информация может не совпадать с локальными тиками физики, поэтому пользовательский метод интерполяции часто может оказаться более подходящим.