🎮⏱ Как и когда использовать (или не использовать) Delta Time в Unity
Delta Time – незаменимый инструмент в разработке игр на Unity. Он позволяет сделать игру независимой от частоты кадров, но его неправильное использование может сломать весь геймплей. Разберем, когда и как его применять.
Delta Time — это встроенное в Unity значение, которое возвращает количество времени в секундах, прошедшее с момента последнего кадра.
И это все, что он делает.
Очень просто и очень конкретно.
Но даже если вы новичок в Unity, вы, вероятно, уже использовали конструкцию Time.deltaTime
раньше и будете часто использовать ее в будущем, поскольку этот параметр влияет практически на любой аспект игры, который работает в течение определенного времени.
Это означает, что каждый раз, когда вы измеряете количество прошедшего времени, меняете какое-то значение в течение нескольких кадров или перемещаете что-либо с постоянной скоростью, вам, вероятно, понадобится использовать deltaTime
для этих операций.
Но не всегда...
Далее мы разберём, как же на самом деле работает Delta Time в Unity и когда вам следует его использовать.
Что такое Delta Time в Unity и как его использовать
Delta Time — это статическое значение с плавающей точкой, которое возвращает длительность в секундах между последним кадром и текущим. К нему можно получить доступ с помощью класса Time
.
Например:
Типичное использование дельты времени — это измерение временных интервалов, где добавление длительности последнего кадра к текущему итогу может быть использовано для создания базового таймера:
Это может быть также полезно для создания секундомера, который можно использовать для измерения интервалов и вызова логики с задержкой.
Однако дельта времени также используется как значение масштабирования. Это связано с тем, что частота кадров в играх обычно не постоянна. Игра может работать с частотой 60 кадров в секунду, или с частотой 30, или, как это часто бывает, частота кадров может постоянно меняться, что означает, что каждый кадр будет обрабатываться разное количество времени.
Для задач, которые должны быть выполнены в кадре, в котором они были начаты, это не является проблемой. И, по сути, именно так работает большая часть кода, он запускается и завершается одновременно в течение одного кадра как можно быстрее. Это означает, что длительность этого кадра на самом деле не имеет значения.
Однако если вы пытаетесь сделать что-то в нескольких кадрах в течение определенного периода времени (например, переместить объект или изменить значение), то вам нужно будет контролировать, как быстро это происходит, что означает масштабирование либо по скорости движения, либо по количеству времени, которое движение должно занять.
Например, если вы хотите постоянно перемещать объект в определенном направлении, один из способов сделать это — изменять значение позиции объекта в каждом кадре:
Что работает, в некотором роде...
Проблема с перемещением объекта таким образом заключается в том, что он будет перемещаться на одну единицу в каждом кадре. Но поскольку для обработки каждого кадра требуется разное количество времени, объект будет двигаться непоследовательно. Удвойте частоту кадров, и объект будет двигаться в два раза быстрее, уменьшите ее вдвое, и он будет двигаться с половинной скоростью. Или, что более вероятно, он будет двигаться с переменной скоростью в зависимости от текущей частоты кадров.
Это происходит потому, что единицей масштаба является кадр, когда на самом деле это должно быть время. Умножение значения перемещения на дельту времени преобразует скорость изменения из единиц на кадр в единицы в секунду:
Это означает, что за одну секунду фактического времени объект переместится вперед на одну единицу — и неважно, сколько на это потребуется кадров.
Если вы хотите изменить скорость движения объекта, то вы можете это сделать, изменив значение переменной, которая масштабируется по дельте времени.
Например, если вы хотите, чтобы объект двигался со скоростью 2 единицы в секунду, просто измените 1 на 2. А еще лучше — создайте переменную для хранения значения скорости, чтобы можно было легко изменять скорость движения из инспектора, не изменяя сам скрипт:
В качестве альтернативы можно масштабировать скорость изменения в зависимости от прошедшего времени. Обычно, когда вы хотите контролировать, сколько времени займет что-то, вы можете использовать метод Lerp
из класса Mathf
:
Lerp
измеряет количество прошедшего времени относительно предполагаемой продолжительности. Затем он возвращает значение между минимальным и максимальным диапазоном на основе весового значения, которое представляет собой количество прошедшего времени, деленное на целевую продолжительность.
Это означает, что фактическая скорость изменения может быть быстрее или медленнее в зависимости от того, сколько времени это должно занять.
В любом случае, независимо от того, изменяете ли вы значение по скорости или по продолжительности, масштабирование скорости изменения по дельте времени чрезвычайно важно по двум причинам.
- Это означает, что скорость изменения постоянна, даже если частота кадров не является таковой.
- Но это также означает, что то, насколько быстро может двигаться объект или как быстро изменится значение, будет зависеть от изменений масштабирования времени.
Что такое масштабирование времени в Unity
Масштабирование времени (параметр timeScale
) — это статическое значение с плавающей точкой в классе времени, которое представляет, как быстро проходит время.
Параметр имеет значение по умолчанию равное единице, что означает, что одна секунда в вашей игре эквивалентна одной секунде в реальной жизни.
Однако можно изменить шкалу времени, сдвинув ее вверх или вниз, что означает, что время будет проходить быстрее или медленнее.
Например, вы можете установить масштабирование времени на 0.5 следующим образом:
Тогда время будет идти с половиной своей обычной скорости.
В вашей игре это означает, что объект, которому обычно требуется одна секунда, чтобы переместиться на одну единицу, теперь будет тратить две секунды, чтобы преодолеть то же расстояние.
Но только если его движение масштабируется с помощью deltaTime
.
Это происходит потому, что deltaTime
сама по себе масштабируется с помощью этого параметра.
Таким образом, когда масштабирование времени не равно единице, deltaTime
также масштабируется вслед за этим параметром.
Если вы уменьшите масштабирование времени до нуля, скорость изменения значений также будет равна нулю, а игра фактически будет приостановлена.
Вот почему обычно рекомендуется всегда масштабировать изменения значений, основанные на времени, с помощью значения deltaTime
, особенно если вы используете параметр timeScale
для приостановки игры.
Но что, если вы хотите что-то изменить, пока игра приостановлена?
Не масштабируемое время в Unity
Не масштабируемое время — это время, на которое не влияют изменения параметра timeScale
в Unity.
Это означает, что если вы замедлите или остановите игру с помощью масштабирования времени, не масштабируемые значения времени не будут затронуты.
Это полезно, когда вам нужно использовать дельту времени, но вы не хотите, чтобы то, что вы изменяете, зависело от изменения скорости игры.
Например, вы можете захотеть создать эффект замедленного движения в своей игре или создать функцию, которая изменяет скорость течения времени, например, в игре-симуляторе, что можно сделать, изменив масштабирование времени. Но вы можете не хотеть, чтобы не связанная с этим логика, такая как пользовательский интерфейс, зависела от того, насколько быстро движется игра.
В этом случае использование unscaledDeltaTime
вместо обычного параметра deltaTime
позволит вам масштабировать изменения значений на фактическое количество времени, которое потребовалось кадру для обработки.
Но это не значит, что вам следует использовать unscaledDeltaTime
для всего, если вы не планируете менять шкалу времени.
Обычно лучше использовать unscaledDeltaTime
только тогда, когда вам нужно измерить фактическое количество времени, прошедшего в кадре, независимо от шкалы времени, и использовать deltaTime
для всего остального, особенно для игровой логики.
Это справедливо потому, что deltaTime
, в отличие от unscaledDeltaTime
, включает встроенный предел, Maximum Delta Time, на случай, если кадр будет слишком длинным.
Максимальная дельта времени (Maximum Allowed Timestep)
Значение deltaTime
ограничено максимальным количеством времени для каждого кадра, то есть если кадр очень длинный, его длительность ограничивается значением по умолчанию 0.3333333 секунды.
Это значение Maximum Allowed Timestep во вкладке Project Settings.
Это значение можно изменить, однако важно не устанавливать его слишком высоким.
Цель максимального времени дельты — предотвратить экстремальный скачок вперед, когда происходит очень длинный кадр, например, если игра на мгновение замирает.
В такой ситуации обычно лучше продолжить с того места, где вы остановились, когда игра снова возобновится, вместо того, чтобы изменять значения на основе того, сколько времени прошло.
Например, если игрок движется вперед в новую область и запускается операция загрузки новой локации, которая занимает несколько секунд, вы не ожидаете, что он переместится вперед на эквивалентную величину, когда игра наконец возобновится.
Однако это не единственная причина, по которой дельта времени ограничена.
Другая причина, по которой следует избегать чрезмерно длинных deltaTime
, — предотвратить увеличение вызовов FixedUpdate
, поскольку физическая система пытается «догнать» длинный кадр.
Это связано с тем, что физическая система может вызывать метод FixedUpdate для обработки физики несколько раз в следующем кадре в зависимости от того, сколько времени заняла обработка последнего кадра.
Это означает, что если бы он не был ограничен, один длинный кадр мог бы привести к тому, что последующие кадры будут все длиннее и длиннее, поскольку физическая система будет делать все больше и больше вызовов, чтобы компенсировать потерянное время, что в экстремальных обстоятельствах в конечном итоге просто сломает игру.
В результате не масштабируемая дельта времени идеально подходит для измерения того, сколько времени на самом деле занял кадр, но в большинстве других случаев лучше использовать стандартный deltaTime
.
Однако бывают случаи, когда, даже если вы делаете что-то в нескольких кадрах, вам вообще не нужно использовать дельта-время.
Например, при работе с физикой.
Фиксированная дельта времени в Unity
В отличие от времени кадра, которое является переменным, физическая система Unity работает с фиксированным временным шагом, эквивалентным примерно 50 кадрам в секунду по умолчанию.
В отличие от дельты времени, которое показывает, сколько времени занял последний кадр, fixedDeltaTime
определяет, сколько раз физическая система должна обновляться в каждом кадре.
Это может быть несколько раз в течение кадра или не обновляться вообще, в зависимости от того, сколько времени занял предыдущий кадр.
Это означает, что если вы хотите последовательно изменять значение внутри метода FixedUpdate
, вам нужно будет умножать его на fixedDeltaTime
.
Например, вот так:
Однако если вы перемещаете объект в FixedUpdate
, то, как правило, вы будете перемещать его с помощью одной из встроенных физических функций Unity, что может означать, что вам вообще не нужно масштабировать величину перемещения по дельте времени.
Это связано с тем, что большинство физических функций перемещения Unity, таких как AddForce
, обычно принимают значение Vector3
, которое представляет собой величину силы, которую необходимо применить, и направление.
Однако, в отличие от функций перемещения на основе преобразования, таких как Translate, которые обычно требуют масштабирования по дельте времени, большинство физических функций будут автоматически масштабировать перемещение объекта при обновлении физической системы.
В результате вам следует всегда вызывать физические функции, такие как AddForce
или AddTorque
, из FixedUpdate
и не масштабировать их по дельте времени.
Однако есть некоторые исключения.
Например, MovePosition
— это метод из класса Rigidbody
, который перемещает физический объект в новое положение, что означает, что принимаемое ею значение — это положение, а не сила.
В этом случае, если вы используете его для перемещения объекта, вам обычно нужно умножать величину перемещения на fixedDeltaTime
, чтобы ее можно было контролировать в единицах в секунду и чтобы скорость перемещения всегда оставалась одинаковой, даже если фиксированный временной шаг изменяется:
Есть некоторые физические функции, которые не только не нужно масштабировать, их вообще не нужно помещать в FixedUpdate
.
Например, одноразовые физические функции, такие как AddForce
в режиме Impulse
или метод бросания лучей Raycast
.
Поскольку эти функции обычно будут запускаться только один раз, не имеет значения, помещены ли они в FixedUpdate
или нет, и вместо этого их можно вызывать в обычном методе Update
вместе с другой игровой логикой.
Это может быть полезно, особенно если вы хотите связать физическую функцию с пользовательским вводом, который обычно не считывается в FixedUpdate
.
Какие еще темы по оптимизации игр на Unity вы хотели бы увидеть в следующих статьях?