GDScript, lifecycle и сигналы
Как Godot вызывает ваш код — _ready, _process, _physics_process, signals, await.
Скрипт на узле
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 поля.
[SerializeField] private float speed ↔ @export var speed. private Animator anim +
anim = GetComponent<Animator>() в Awake ↔ @onready var anim: AnimationPlayer = $AnimationPlayer
однострочный. Лаконичнее.
Жизненный цикл узла
Главные методы, которые Godot вызывает сам:
_init()— конструктор объекта, ещё до того как узел добавлен в дерево._enter_tree()— узел добавлен в scene tree. Родитель → дети (top-down)._ready()— все дети тоже готовы. Дети → родитель (bottom-up). Здесь делают инициализацию, которая зависит от детей._input(event)/_unhandled_input(event)— каждое input-событие._physics_process(delta)— фиксированный тик. По умолчанию 60 Hz (Project Settings → Physics → Common → Physics Ticks Per Second)._process(delta)— каждый кадр (variable timestep, как Unity Update)._exit_tree()— узел удалён из дерева (включая при выходе из сцены)._notification(what)— низкоуровневый колбэк для NOTIFICATION_* событий.
_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. Один объект эмитит, многие подписаны.
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" но независимо от расположения
Если вы переставите узел в дереве, обычный $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.