~2 мин чтения

GDScript, lifecycle и сигналы

Как Godot вызывает ваш код — _ready, _process, _physics_process, signals, await.

Иллюстрация: GDScript, lifecycle и сигналы

Скрипт на узле

GDScript-файл (.gd) обычно начинается с extends, указывая, от какого узла наследуется:

extends Node3D

# Опционально: глобальное имя класса (виден в редакторе и других скриптах)
class_name Enemy

# Экспорт в Inspector — аналог [SerializeField]
@export var max_hp: int = 100
@export_range(1.0, 10.0) var speed: float = 3.0
@export var target: Node3D  # ссылка на другой узел

# @onready — присваивается в _ready, удобно для ссылок на детей
@onready var sprite: MeshInstance3D = $Sprite
@onready var anim: AnimationPlayer = $AnimationPlayer

var hp: int

func _ready() -> void:
    hp = max_hp
    print("Enemy ready: %s" % name)

func _process(delta: float) -> void:
    # каждый кадр
    rotation_degrees.y += 30.0 * delta
Веб

React-компонент: useState для полей, useEffect(() => {}, []) для инициализации, render-функция каждый раз. props — это @export поля.

Unity

[SerializeField] private float speed@export var speed. private Animator anim + anim = GetComponent<Animator>() в Awake ↔ @onready var anim: AnimationPlayer = $AnimationPlayer однострочный. Лаконичнее.

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

Главные методы, которые Godot вызывает сам:

  1. _init() — конструктор объекта, ещё до того как узел добавлен в дерево.
  2. _enter_tree() — узел добавлен в scene tree. Родитель → дети (top-down).
  3. _ready() — все дети тоже готовы. Дети → родитель (bottom-up). Здесь делают инициализацию, которая зависит от детей.
  4. _input(event) / _unhandled_input(event) — каждое input-событие.
  5. _physics_process(delta) — фиксированный тик. По умолчанию 60 Hz (Project Settings → Physics → Common → Physics Ticks Per Second).
  6. _process(delta) — каждый кадр (variable timestep, как Unity Update).
  7. _exit_tree() — узел удалён из дерева (включая при выходе из сцены).
  8. _notification(what) — низкоуровневый колбэк для NOTIFICATION_* событий.
_ready vs Unity Start

_ready срабатывает один раз, когда узел впервые попадает в сцену. Если узел уйдёт из дерева и вернётся, _ready снова не вызовется. _enter_tree сработает повторно.

_process vs _physics_process

Аналогично Unity:

МетодЧастотаЧто туда
_physics_process(delta)Фиксированно (60 Hz по умолчанию)Физика: move_and_slide, apply_central_impulse
_process(delta)Каждый кадрАнимация, UI-обновления, нефизическая логика

delta — время с последнего вызова. В _process — переменное, в _physics_process — константное (1.0 / физических_тиков_в_секунду).

extends CharacterBody3D

@export var speed: float = 5.0

func _physics_process(delta: float) -> void:
    var input := Input.get_vector("move_left", "move_right", "move_forward", "move_back")
    velocity.x = input.x * speed
    velocity.z = input.y * speed

    if not is_on_floor():
        velocity.y += get_gravity().y * delta  # gravity из настроек проекта

    move_and_slide()

Сигналы — встроенный pub-sub

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

# Объявление
signal damaged(amount: int, attacker: Node)
signal died

# Эмит (новый синтаксис в 4.x — properties-style)
damaged.emit(10, attacker)
died.emit()

# Подключение из другого скрипта
enemy.damaged.connect(_on_enemy_damaged)
enemy.died.connect(_on_enemy_died, CONNECT_ONE_SHOT)  # один раз и отвязаться

func _on_enemy_damaged(amount: int, attacker: Node) -> void:
    print("Enemy took ", amount, " from ", attacker.name)

В Inspector → вкладка Node → Signals можно подключать сигналы визуально, без кода. Godot сам сгенерирует метод-обработчик.

Веб

EventEmitter или RxJS Subject. Один объект эмитит, многие подписаны.

Unity

Smesь UnityEvent (Inspector-bindings) и C# events. В Godot это одно — signal.

await — корутины через сигналы

Вместо StartCoroutine в Godot есть await:

# Подождать кадр
await get_tree().process_frame

# Подождать секунду (через таймер дерева)
await get_tree().create_timer(1.0).timeout

# Подождать сигнал
await player.died

# Подождать окончания анимации
$AnimationPlayer.play("attack")
await $AnimationPlayer.animation_finished
print("Attack done!")

Любой await возвращает поток управления движку и продолжается, когда сигнал эмитится. Работает на main thread, как Unity coroutines — параллельных потоков не создаёт.

get_node, $-шорткат и unique names

Способы добраться до других узлов:

# Полные пути
var sprite = get_node("Sprite3D")
var sprite_alt = $Sprite3D                              # шорткат
var deep = $"Body/Head/Sprite3D"
var parent = get_parent()
var first_child = get_child(0)

# Unique names — % префикс
# В сцене узел отмечен "Access as Unique Name" → доступен из любого места сцены через %Name
var label = %ScoreLabel  # эквивалент $"path/to/ScoreLabel" но независимо от расположения
Unique names спасают от хрупкости

Если вы переставите узел в дереве, обычный $Path/To/Child сломается. Unique names (%Name) ищутся в пределах сцены и устойчивы к переименованию контейнеров. Используйте для часто ссылочных узлов: главного игрока, HUD-элементов.

Управление активностью

  • set_process(false) — выключить _process (узел остаётся в сцене).
  • set_physics_process(false) — выключить _physics_process.
  • set_process_input(false) — выключить input-обработку.
  • visible = false — скрыть (для CanvasItem/Node3D).
  • queue_free() — отложенное удаление в конце кадра (безопаснее, чем free()).
# Уничтожить через секунду
await get_tree().create_timer(1.0).timeout
queue_free()

В следующей главе — ввод через Input Map.