Понимание параметра delta в Godot

Проблема

Параметр delta или «delta time» часто недопонимается в разработке игр. В этом руководстве мы расскажем, как он используется, важность независимого от частоты кадров движения и практические примеры его использования в Godot.

Решение

Чтобы проиллюстрировать проблему, давайте рассмотрим узел Sprite, перемещающийся по экрану. Если наш экран имеет ширину 600 пикселей, и мы хотим, чтобы спрайт пересек экран за 5 секунд, мы можем использовать следующий расчет для определения необходимой скорости:

600 pixels / 5 seconds = 120 pixels/second

Мы будем перемещать спрайт на каждом кадре, используя функцию _process(). Если игра работает со скоростью 60 кадров в секунду, мы можем найти перемещение на каждом кадре следующим образом:

120 pixels/second * 1/60 second/frame = 2 pixels/frame

Обратите внимание, что единицы измерения согласованы во всех вышеперечисленных расчетах. Всегда обращайте внимание на единицы измерения в ваших расчетах — это сэкономит вас от ошибок.

Вот необходимый код:

extends Node2D

# Desired movement in pixels/frame
var movement = Vector2(2, 0)

func _process(delta):
    $Sprite.position += movement

Запустите этот код и вы увидите, что у спрайта занимает 5 секунд, чтобы пересечь экран.

Проблема начинается, если что-то еще загружает компьютер. Это называется лагом и может происходить из различных источников — причиной может быть ваш код или даже другие запущенные приложения на вашем компьютере. Если это происходит, то продолжительность кадра может увеличиться. В крайнем случае, представьте себе, что частота кадров уменьшилась вдвое — каждый кадр занимает 1/30 вместо 1/60 секунды. Перемещаясь на 2 пикселя/кадр, теперь спрайту потребуется вдвое больше времени, чтобы достигнуть края.

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

Исправление проблемы с частотой кадров

При использовании функции _process() в нее автоматически включается параметр delta, который передается из движка (то же самое происходит с _physics_process(), используемой для физического кода). Это значение с плавающей запятой, представляющее собой длительность времени с предыдущего кадра. Обычно это будет примерно 1/60 или 0,0167 секунды.

Используя эту информацию, мы можем перестать думать о том, сколько нужно переместить на каждом кадре, и просто учесть нашу желаемую скорость в пикселях в секунду (в данном случае, 120 по расчетам выше).

Умножив значение delta движка на это число, мы получим, сколько пикселей перемещать на каждом кадре. Это число автоматически корректируется, если время кадра колеблется.

# 60 frames/second
120 pixels/second * 1/60 second/frame = 2 pixels/frame

# 30 frames/second
120 pixels/second * 1/30 second/frame = 4 pixels/frame

Обратите внимание, что если частота кадров уменьшится вдвое (то есть время на кадр удвоится), то наше перемещение на каждом кадре также должно удвоиться, чтобы сохранить желаемую скорость.

Давайте измените код, чтобы использовать этот расчет:

extends Node2D

# Desired movement in pixels/second.
var movement = Vector2(120, 0)

func _process(delta):
    $Sprite.position += movement * delta

Теперь, при выполнении на 30 кадрах в секунду, время перемещения остается постоянным:

Если частота кадров становится очень низкой, движение перестает быть плавным, но время остается неизменным.

Использование дельты с уравнениями движения

Что если ваше движение более сложное? Концепция остается той же. Держите ваши единицы измерения в секундах, а не в кадрах, и умножайте на дельту на каждом кадре.

Работать с пикселями и секундами гораздо легче, так как это связано с тем, как мы измеряем эти величины в реальном мире. «Гравитация составляет 100 pixels/second/second, поэтому после того, как мяч падает 2 секунды, его скорость составляет 200 pixels/second.» Если вы работаете с кадрами, то вам придется думать об ускорении в единицах pixels/frame/frame. Попробуйте сами — это не очень удобно.

Например, если вы применяете гравитацию, это ускорение — каждый кадр оно увеличит скорость на некоторую величину. Как в предыдущем примере, скорость затем изменяет положение узла.

Попробуйте изменить значение delta и target_fps в следующем коде, чтобы увидеть эффект:

extends Node2D

# Acceleration in pixels/sec/sec.
var gravity = Vector2(0, 120)
# Acceleration in pixels/frame/frame.
var gravity_frame = Vector2(0, .033)

# Velocity in pixels/sec or pixels/frame.
var velocity = Vector2.ZERO

var use_delta = false
var target_fps = 60

func _ready():
    Engine.target_fps = target_fps

func _process(delta):
    if use_delta:
        velocity += gravity * delta
        $Sprite.position += velocity * delta
    else:
        velocity += gravity_frame
        $Sprite.position += velocity

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

Использование кинематических функций

В приведенных выше примерах мы использовали Sprite, чтобы упростить понимание, обновляя положение его на каждом кадре. Если вы используете кинематическое тело (в 2D или 3D), вы вместо этого будете использовать один из его методов движения. В частности, в случае move_and_slide(), часто возникает некоторая путаница, потому что он использует вектор скорости, а не позицию. Это означает, что вам не нужно умножать свою скорость на дельту для определения расстояния — эта функция делает это за вас. Однако вам все равно нужно применять ее ко всем остальным расчетам, таким как ускорение. Например:

# Sprite movement code:
velocity += gravity * delta
position += velocity * delta

# Kinematic body movement code:
velocity += gravity * delta
move_and_slide()

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

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

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

четыре + 20 =

Прокрутить вверх