Жизненный цикл узла: _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.