Attention: Here be dragons
This is the latest
(unstable) version of this documentation, which may document features
not available in or compatible with released stable versions of Godot.
Checking the stable version of the documentation...
Использование сигналов
В этом уроке рассмотрим сигналы. Это сообщения, которые выдает узел при определенных событиях, например, при нажатии кнопки. Другие узлы могут получать этот сигнал и вызывать функции, соответствующие событию.
Сигналы это механизм делегирования, встроенный в Godot, который позволяет одному игровому объекту реагировать на изменение в другом без их привязки друг к другу. Использование сигналов снижает связность и делает код более гибким.
Например, у вас на экране может быть шкала жизни, которая соответствует здоровью игрока. Когда игрок получает урон или использует исцеляющее зелье, вы хотите, чтобы шкала реагировала на изменения. Для этого в Godot, вы бы использовали сигналы.
Такие методы как (Callable), говорят о том, что они являются исходными, начиная с версии Godot 4.0. Это означает, что вы можете передавать их как аргументы метода напрямую, не передавая их в виде строки, что упрощает автодополнение и снижает вероятность ошибок. Смотрите ссылку на класс Signal для получения списка того, что вы можете сделать непосредственно с сигналом.
См. также
Как уже упоминалось во введении, сигналы - это версия шаблона наблюдателя в Godot. Подробнее об этом можно узнать здесь: Game Programming Patterns.
Теперь мы будем использовать сигнал, чтобы заставить нашу иконку Godot из предыдущего урока (Отслеживание ввода игрока) двигаться и останавливаться при нажатии кнопки.
Примечание
Для этого проекта мы будем следовать правилам именования Godot.
GDScript: Классы (узлы) используют PascalCase, переменные и функции - snake_case, константы - ALL_CAPS (см. Руководство по стилю GDScript).
C#: Классы, экспортные переменные и методы используют PascalCase, приватные поля используют _camelCase, локальные переменные и параметры используют camelCase (См. Руководство по стилю C#). Будьте внимательны, чтобы точно набирать имена методов при подключении сигналов.
Настройка сцены
Чтобы добавить кнопку в нашу игру, мы создадим новую главную сцену, которая будет содержать как Button, так и сцену sprite_2d.tscn
, которую мы вписали в скрипт в предыдущем уроке.
Создайте новую сцену через меню Сцена -> Новая сцена.

На панели Сцена (Scene) нажмите кнопку "2D сцена" (2D Scene). Это сделает Node2D корневым узлом сцены.

В панели "Файловая система" (File System) зажмите и перетащите файл sprite_2d.tscn
, который вы сохранили ранее, на узел Node2D, чтобы создать экземпляр его класса.

Мы хотим добавить другой узел рядом с Sprite2D. Для этого нажмите ПКМ на Node2D и выберите Добавить дочерний узел...

Найдите узел Button и добавьте его.

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

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

Зажмите и перетащите саму кнопку, чтобы подвинуть её ближе к спрайту.
Вы также можете сделать надпись на кнопке, отредактировав свойство Text в Инспекторе. Введите Toggle motion
.

Дерево сцены и область просмотра должны выглядеть следующим образом.

Сохраните вашу только что созданную сцену как node_2d.tscn
, если этого ещё не сделали. Вы можете запустить её с помощью F6 (Cmd + R на macOS). Теперь будет видна кнопка, но если вы её нажмёте, ничего не произойдет.
Подключение сигнала в редакторе
Теперь мы хотим присоединить сигнал "нажат" (pressed) кнопки (Button) к нашему Sprite2D, и мы хотим вызвать новую функцию, которая будет включать и отключать его передвижение. Нам нужно, чтобы скрипт, который мы писали на предыдущем уроке, был прикреплён к узлу Sprite2D.
Вы можете подключить сигналы в панели Узел. Выберите узел Button и на правой стороне экрана нажмите вкладку "Узел" рядом с Инспектором.

На панели отображаются сигналы, доступные выбранному узлу.

Дважды кликните сигнал "pressed", откроется окно присоединения узлов.

Теперь вы можете подключить сигнал к узлу Sprite2D. Узлу нужна функция-приёмник, которую вызовет Godot, когда Button выдаст сигнал. Редактор самостоятельно создаст её. По соглашению, мы называем эти обратные вызовы "_on_node_name_signal_name". У нас это будет "_on_button_pressed".
Примечание
При подключении сигналов через вкладку Узел редактора, можно использовать два режима. Можно просто подключить сигнал к узлу, имеющему скрипт, и будет автоматически создана функция обратного вызова в нем.

В расширенном режиме можно присоединить сигнал к любому узлу и к любой встроенной функции, добавить аргументы и установить параметры обратного вызова. Режим можно изменить переключателем в правом-нижнем углу окна кнопкой "Дополнительно" (Advanced).
Примечание
If you are using an external editor (such as VS Code), this automatic code generation might not work. In this case, you need to connect the signal via code as explained in the next section.
Нажмите кнопку "Connect" ("Присоединить") для завершения соединения и перейдите к редактированию скрипта. Вы увидите новый метод (функцию) с иконкой соединения слева.

Если нажать иконку, всплывёт окно с информацией о соединении. Это доступно только при присоединении узлов в редакторе.

Давайте заменим строку со словом pass
кодом, который изменяет движение узла.
Наш Sprite2D движется благодаря коду функции _process()
. Godot предоставляет метод для включения и выключения обработки: Node.set_process(). Другой метод класса Node, is_processing()
, возвращает true
, если обработка активна. Мы можем использовать ключевое слово not
для инвертирования значения.
func _on_button_pressed():
set_process(not is_processing())
// We also specified this function name in PascalCase in the editor's connection window.
private void OnButtonPressed()
{
SetProcess(!IsProcessing());
}
Эта функция будет переключать обработку и, в том числе, движение иконки по нажатию кнопки.
Перед тем, как попробовать поиграть, нам необходимо упростить нашу функцию _process()
, чтобы движение узла было автоматическим и не ожидало команд пользователя. Замените текущий код функции на тот, который мы видели два урока назад:
func _process(delta):
rotation += angular_speed * delta
var velocity = Vector2.UP.rotated(rotation) * speed
position += velocity * delta
public override void _Process(double delta)
{
Rotation += _angularSpeed * (float)delta;
var velocity = Vector2.Up.Rotated(Rotation) * _speed;
Position += velocity * (float)delta;
}
Ваш полный код sprite_2d.gd
должен выглядеть следующим образом.
extends Sprite2D
var speed = 400
var angular_speed = PI
func _process(delta):
rotation += angular_speed * delta
var velocity = Vector2.UP.rotated(rotation) * speed
position += velocity * delta
func _on_button_pressed():
set_process(not is_processing())
using Godot;
public partial class MySprite2D : Sprite2D
{
private float _speed = 400;
private float _angularSpeed = Mathf.Pi;
public override void _Process(double delta)
{
Rotation += _angularSpeed * (float)delta;
var velocity = Vector2.Up.Rotated(Rotation) * _speed;
Position += velocity * (float)delta;
}
// We also specified this function name in PascalCase in the editor's connection window.
private void OnButtonPressed()
{
SetProcess(!IsProcessing());
}
}
Запустите сцену и нажмите кнопку, чтобы увидеть запуск и остановку спрайта.
Подключение сигналов в коде
Вы можете присоединять сигналы в коде вместо использования редактора. Это нужно, когда узлы или элементы сцены создаются в скрипте.
Давайте теперь используем различные узлы. У Godot есть узел Timer, который полезен для реализации задержки перезарядки способностей, перезарядки оружия и другого.
Вернёмся к рабочему пространству 2D. Для этого можно нажать "2D" вверху окна, или нажать на клавиатуре Ctrl + F1 (Alt + 1 для macOS).
Нажмите правой кнопкой мыши на панели Сцена (Scene) на узел Спрайт (Sprite2D) и добавьте дочерний узел. Найдите Таймер (Timer) и добавьте соответствующий узел. Теперь ваша сцена должна выглядеть так.

Выбрав узел Timer, откройте инспектор (Inspector) и активируйте свойство Autostart.

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

Нам необходимо выполнить две операции для подключения узлов через код:
Получить ссылку на Timer из Sprite2D.
Вызвать метод
connect()
для сигнала "timeout" таймера (Timer).
Примечание
Для подключения к сигналу через код вам нужно вызвать метод connect()
узла, который вы хотите прослушивать. В этом случае, мы хотим прослушать сигнал "timeout" таймера.
Мы хотим подключить сигнал, когда сцена инстанцируется, и мы можем это сделать, используя встроенную функцию Node._ready(), которая автоматически вызывается движком, когда узел полностью инстанцирован.
Чтобы получить ссылку на узел, относящийся к действующему узлу, мы используем метод Node.get_node(). Мы можем сохранить ссылку в переменной.
func _ready():
var timer = get_node("Timer")
public override void _Ready()
{
var timer = GetNode<Timer>("Timer");
}
Функция get_node()
смотрит на дочерние узлы Sprite2D и получает узлы по их имени. К примеру, если вы переименуете в редакторе узел Таймер в "BlinkingTimer", то вам нужно будет поменять вызов на get_node("BlinkingTimer")
.
Сейчас мы можем подключить Timer к Sprite2D в функции _ready()
.
func _ready():
var timer = get_node("Timer")
timer.timeout.connect(_on_timer_timeout)
public override void _Ready()
{
var timer = GetNode<Timer>("Timer");
timer.Timeout += OnTimerTimeout;
}
Строка читается так: мы подключаем сигнал таймера "timeout" к узлу, к которому прикреплён текущий скрипт. Когда таймер подаёт сигнал timeout
, мы хотим вызвать функцию _on_timer_timeout()
, которую нам необходимо определить. Давайте добавим её в нижней части нашего скрипта и используем её для переключения видимости нашего спрайта.
Примечание
По соглашению в GDScript мы даём названия такие обратные вызовы так: "_on_node_name_signal_name", а в C# так: "OnNodeNameSignalName". В текущем примере оно будет выглядеть как "_on_timer_timeout" для GDScript и "OnTimerTimeout()" для C#.
func _on_timer_timeout():
visible = not visible
private void OnTimerTimeout()
{
Visible = !Visible;
}
Свойство visible
имеет логический тип (boolean), контролирующий видимость нашего узла. Строка visible = not visible
переключает значение. Если visible
равно true
, оно станет false
, и наоборот.
Если вы запустите сцену Node2D сейчас, то увидите, что спрайт мерцает, появляясь и исчезая с интервалом в одну секунду.
Готовый скрипт
Вот и всё для нашей маленькой движущейся и мигающей демонстрации с иконкой Godot! Вот полный файл sprite_2d.gd
для справки.
extends Sprite2D
var speed = 400
var angular_speed = PI
func _ready():
var timer = get_node("Timer")
timer.timeout.connect(_on_timer_timeout)
func _process(delta):
rotation += angular_speed * delta
var velocity = Vector2.UP.rotated(rotation) * speed
position += velocity * delta
func _on_button_pressed():
set_process(not is_processing())
func _on_timer_timeout():
visible = not visible
using Godot;
public partial class MySprite2D : Sprite2D
{
private float _speed = 400;
private float _angularSpeed = Mathf.Pi;
public override void _Ready()
{
var timer = GetNode<Timer>("Timer");
timer.Timeout += OnTimerTimeout;
}
public override void _Process(double delta)
{
Rotation += _angularSpeed * (float)delta;
var velocity = Vector2.Up.Rotated(Rotation) * _speed;
Position += velocity * (float)delta;
}
// We also specified this function name in PascalCase in the editor's connection window.
private void OnButtonPressed()
{
SetProcess(!IsProcessing());
}
private void OnTimerTimeout()
{
Visible = !Visible;
}
}
Пользовательские сигналы
Примечание
Этот раздел является справкой о том, как определять и использовать ваши собственные сигналы, не опираясь на проект, созданный в предыдущих уроках.
Вы можете определить собственные сигналы в скрипте. Например, вы хотите вывести экран "Конец игры", когда здоровье игрока станет равным нулю. Для этого вы можете определить сигнал "died" или "health_depleted", и выдать его, когда здоровье игрока будет равно нулю.
extends Node2D
signal health_depleted
var health = 10
using Godot;
public partial class MyNode2D : Node2D
{
[Signal]
public delegate void HealthDepletedEventHandler();
private int _health = 10;
}
Примечание
Поскольку сигналы представляют собой события, которые только что произошли, мы обычно используем в их названиях глагол в прошедшем времени.
Ваши сигналы работают таким же образом, как и встроенные: они появляются во вкладке Узел (Node), и вы можете подключиться к ним, как и к любым другим.

Чтобы передать сигнал в ваших скриптах, вызовите метод emit()
в сигнале.
func take_damage(amount):
health -= amount
if health <= 0:
health_depleted.emit()
public void TakeDamage(int amount)
{
_health -= amount;
if (_health <= 0)
{
EmitSignal(SignalName.HealthDepleted);
}
}
Сигнал может дополнительно объявить один или несколько аргументов. Укажите имена аргументов между круглыми скобками:
extends Node2D
signal health_changed(old_value, new_value)
var health = 10
using Godot;
public partial class MyNode : Node
{
[Signal]
public delegate void HealthChangedEventHandler(int oldValue, int newValue);
private int _health = 10;
}
Примечание
Аргументы сигнала показываются в панели узел редактора, и Godot может использовать их, чтобы производить для вас функции обратного вызова. Однако, вы всё ещё можете отправлять любое число аргументов при отправке сигналов. Это значит, что отправка правильных значений зависит от вас.
Чтобы передать значения вместе с сигналом, добавьте их в качестве дополнительных аргументов к функции emit()
:
func take_damage(amount):
var old_health = health
health -= amount
health_changed.emit(old_health, health)
public void TakeDamage(int amount)
{
int oldHealth = _health;
_health -= amount;
EmitSignal(SignalName.HealthChanged, oldHealth, _health);
}
Подведение итогов
Любой узел в Godot передает сигналы, когда с ним происходит что-то конкретное, например, нажатие на кнопку. Другие узлы могут подключиться к отдельным сигналам и среагировать на выбранные события.
Сигналы имеют множество применений. С их помощью вы можете отреагировать на узел при выходе и входе в игровой мир, на столкновение, на персонажа, входящего или покидающего какую-либо область, на изменение размера элемента интерфейса и на многое другое.
Например, Area2D, представляющий монету, испускает сигнал body_entered
всякий раз, когда физическое тело игрока входит в его форму столкновения, что позволяет узнать, когда игрок подобрал монету.
В следующем разделе, Ваша первая 2D игра, вы создадите полноценную 2D-игру и примените на практике всё, чему научились до сих пор.