Жизненный цикл узла: _ready и _process

Движок сам зовёт специальные функции узла в строго определённые моменты — это его жизненный цикл.

Суть: _ready срабатывает раз при появлении, _process — каждый кадр, _physics_process — каждый шаг физики, и всё это вызывает движок, а не ты.

Ты уже видел _ready(). Это лишь одна из специальных функций, которые движок вызывает за тебя. Вместе они образуют жизненный цикл узла — расписание, по которому движок дёргает твой код. Понять это расписание критически важно: от него зависит, куда писать какую логику.

Жизненный цикл узла:

  узел добавлен в сцену
          |
          v
     _ready()              <- один раз, всё готово
          |
          v
  +---> _process(delta)         <- каждый кадр (отрисовка)
  |        |
  |        v
  +--- _physics_process(delta)  <- каждый шаг физики (стабильно)
          |
          v
   узел удалён из сцены

_ready() вызывается один раз, когда узел и все его потомки готовы. Сюда кладут начальную настройку: задать здоровье, найти ссылки на другие узлы, спрятать меню. _process(delta) вызывается каждый кадр — туда идёт всё, что связано с отрисовкой и обычной логикой: обновить таймер, мигнуть надписью. _physics_process(delta) вызывается на каждом шаге физики со стабильной частотой — туда идёт движение и столкновения, чтобы физика не зависела от того, тормозит компьютер или нет.

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

Полезно понять, зачем вообще разделять _process и _physics_process, а не свалить всё в одну функцию. Отрисовка может идти неравномерно: на сложной сцене кадры рисуются реже, на простой — чаще. Если бы физику считали вместе с отрисовкой, движение бы дёргалось вслед за FPS, а столкновения порой проскакивали бы сквозь стены на лагах. Поэтому физику вынесли в отдельный, стабильный по частоте поток. Тебе как разработчику достаточно запомнить правило: всё, что про движение и столкновения, — в _physics_process; всё, что про картинку, текст и таймеры, — в _process.

Как работает под капотом

Движок ведёт два таймера. Один отрисовочный — он дёргает _process так часто, как может (отсюда нестабильный FPS). Второй физический — он дёргает _physics_process строго фиксированное число раз в секунду (обычно 60), даже если кадры рисуются реже. Поэтому движение в _physics_process получается ровным.

Смоделируем разницу на Python: одно и то же расстояние, но кадры разной длительности. Умножение на delta даёт одинаковый итог.

speed = 100.0  # пикселей в секунду

# Быстрый компьютер: маленький delta, много кадров
pos_fast = 0.0
for _ in range(10):
    delta = 0.1
    pos_fast += speed * delta

# Медленный компьютер: большой delta, мало кадров
pos_slow = 0.0
for _ in range(2):
    delta = 0.5
    pos_slow += speed * delta

print("Быстрый ПК прошёл:", pos_fast)
print("Медленный ПК прошёл:", pos_slow)
print("Одинаково? ", pos_fast == pos_slow)

Та же логика на Python ▶. Несмотря на разное число кадров, итоговое расстояние совпадает — благодаря умножению на delta. Это и есть причина, почему его нельзя забывать.

Частые ошибки

Главная ошибка — двигать персонажа в _process вместо _physics_process: движение начинает дёргаться и зависеть от FPS. Физику и движение — всегда в _physics_process. Вторая ошибка — забыть умножить на delta: тогда скорость становится «пикселей за кадр» вместо «пикселей за секунду» и зависит от железа. Третья — обращаться к другому узлу в _ready, когда тот ещё не готов: лучше брать ссылки на узлы через @onready, а не в самом теле.

Best practices

Распределяй логику по правилам: настройка — в _ready, движение и физика — в _physics_process, остальное (таймеры, анимация интерфейса) — в _process. Всегда умножай скорости на delta. Не делай тяжёлых вычислений каждый кадр, если можно посчитать один раз в _ready. И помни: эти функции вызывает движок — тебе не нужно звать их вручную.

Итоги: жизненный цикл — это расписание вызовов: _ready один раз при появлении, _process каждый кадр, _physics_process каждый шаг физики. Движение пиши в _physics_process. delta — время с прошлого вызова; умножение на него делает движение независимым от FPS.

Проверьте себя
1. Куда правильнее всего писать код движения персонажа?
AВ _ready
BВ _process
CВ _physics_process
DВ обычную переменную
2. Зачем умножать скорость на delta?
AЧтобы код был длиннее
BЧтобы движение было одинаковым на быстром и медленном компьютере
CЧтобы персонаж двигался быстрее
DЭто требование синтаксиса GDScript