Использование сигналов¶
В этом уроке рассмотрим сигналы. Это сообщения, которые выдает узел при определенных событиях, например, при нажатии кнопки. Другие узлы могут получать этот сигнал и вызывать функции, соответствующие событию.
Signals are a delegation mechanism built into Godot that allows one game object to react to a change in another without them referencing one another. Using signals limits coupling and keeps your code flexible.
Например, у вас на экране может быть шкала жизни, которая соответствует здоровью игрока. Когда игрок получает урон или использует исцеляющее зелье, вы хотите, чтобы шкала реагировала на изменения. Чтобы сделать это в Godot, вы бы использовали сигналы.
Примечание
Как уже упоминалось во введении, сигналы - это версия шаблона наблюдателя в Godot. Подробнее об этом можно узнать здесь: https://gameprogrammingpatterns.com/observer.html
Теперь мы будем использовать сигнал, чтобы заставить нашу иконку Годо из предыдущего урока (Отслеживание ввода игрока) двигаться и останавливаться при нажатии кнопки.
Настройка сцены¶
Чтобы добавить кнопку в нашу игру, мы создадим новую "главную" сцену, которая будет содержать как кнопку, так и сцену Sprite.tscn
, которую мы вписали в скрипт в предыдущих уроках.
Создайте новую сцену через меню Сцена -> Новая сцена.

В доке Scene нажмите кнопку 2D Scene. Это добавит Node2D в качестве корня сцены.

Нажмите и переместите файл Sprite.tscn
, который Вы сохранили ранее, на панели задач файловой системы, на Node2D, чтобы создать экземпляр его класса.

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

Выполните поиск узла типа Button и добавьте его.

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

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

Нажмите на саму кнопку и перетащите курсор, чтобы приблизить её к спрайту.
You can also write a label on the Button by editing its Text property in the Inspector. Enter "Toggle motion".

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

Save your newly created scene. You can then run it with F6. At the moment, the button will be visible, but nothing will happen if you press it.
Подключение сигнала в редакторе¶
Здесь мы хотим присоединить сигнал "нажатие" Кнопки к нашему Спрайту, и мы хотим вызвать новую функцию, которая будет показывать и скрывать свои действия. Нам нужен скрипт, прикреплённый к узлу Спрайта, который мы напишем, исходя из предыдущего урока.
Вы можете подключить сигналы в панели Узел. Выберите узел Button и на правой стороне экрана нажмите вкладку "Узел" рядом с Инспектором.

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

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

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

The advanced view lets you connect to any node and any built-in function, add arguments to the callback, and set options. You can toggle the mode in the window's bottom-right by clicking the Advanced button.
Click the Connect button to complete the signal connection and jump to the Script workspace. You should see the new method with a connection icon in the left margin.

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

Давайте заменим строку со словом pass
кодом, который изменяет движение узла.
Наш спрайт движется благодаря коду функции _process()
. Godot предоставляет метод для включения и выключения обработки: Node.set_process(). Другой метод класса Node, is_processing()
, возвращает true
, если обработка активна. Мы можем использовать ключевое слово ``not``для инвертирования значения.
func _on_Button_pressed():
set_process(not is_processing())
Эта функция будет переключать обработку и, в том числе, движение значка по нажатию кнопки.
Перед тем, как попробовать поиграть, нам необходимо упростить нашу функцию _process()
, чтобы движение узла было автоматическим и не ожидало команд пользователя. Замените текущий код функции на тот, который мы видели два урока назад:
func _process(delta):
rotation += angular_speed * delta
var velocity = Vector2.UP.rotated(rotation) * speed
position += velocity * delta
Ваш полный код Sprite.gd
должен выглядеть следующим образом.
extends Sprite
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())
Запустите сцену и нажмите кнопку, чтобы увидеть запуск и остановку спрайта.
Подключение сигналов в коде¶
Вы можете присоединять сигналы в коде вместо использования редактора. Это нужно, когда узлы или элементы сцены создаются в скрипте.
Давайте используем различные узлы здесь. У Godot есть узел Timer, который полезен для реализации задержки перезарядки способностей, перезарядки оружия и другого.
Вернёмся к рабочему пространству 2D. Для этого можно нажать "2D" вверху экрана, или на клавиатуре Ctrl + F1 (Alt + 1 для macOS).
Нажмите правой кнопкой мыши на панели задач на узел Спрайт и добавьте дочерний узел. Найдите Таймер и добавьте соответствующий узел. Теперь ваша сцена должна выглядеть так.

Выбрав узел Таймера, откройте Инспектор и проверьте свойства Autostart.

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

Нам необходимо выполнить две операции для подключения узлов через код:
Получите ссылку на таймер из Спрайта.
Вызвать метод
connect()
Таймера.
Примечание
Для подключения к сигналу через код, Вам нужно использовать метод connect()
узла, который вы хотите прослушать. В этом случае, мы хотим прослушать сигнал Таймера "тайм-аут".
Мы хотим подключить сигнал, когда сцена инстанцируется, и мы можем это сделать, используя встроенную функцию Node._ready(), которая автоматически вызывается движком, когда узел полностью инстанцирован.
Чтобы получить ссылку на узел, относящийся к действующему узлу, мы используем метод Node.get_node(). Мы можем сохранить ссылку в переменной.
func _ready():
var timer = get_node("Timer")
Функция get_node()``анализирует дочерний Спрайт и получает узлы по их имени. К примеру, если Вы переименуете в редакторе узел Таймера в "BlinkingTimer", то Вам нужно будет поменять вызов на ``get_node("BlinkingTimer")
.
Сейчас мы можем подключить Таймер к Спрайту в функции _ready()
.
func _ready():
var timer = get_node("Timer")
timer.connect("timeout", self, "_on_Timer_timeout")
Строка читается так: мы подключаем сигнал Таймера "timeout" к узлу, к которому прикреплён скрипт (self
). Когда Таймер испускает "timeout", мы хотим вызвать функцию "_on_Timer_timeout", которую нам необходимо определить. Давайте добавим её в нижней части нашего скрипта и используем её для переключения видимости нашего спрайта.
func _on_Timer_timeout():
visible = not visible
Логическое свойство visible
контролирует видимость нашего узла. Строка visible = not visible
переключает значение. Если visible
равно true
, оно станет false
, и наоборот.
If you run the scene now, you will see that the sprite blinks on and off, at one second intervals.
Готовый скрипт¶
Вот и все для нашей маленькой движущейся и мигающей иконки Годо! Вот полный файл Sprite.gd
для справки.
extends Sprite
var speed = 400
var angular_speed = PI
func _ready():
var timer = get_node("Timer")
timer.connect("timeout", self, "_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
Пользовательские сигналы¶
Примечание
Этот отдел является справкой о том, как обозначать и использовать Ваши собственные сигналы, и не опирается на проекты, созданные в предыдущих уроках.
Вы можете определить собственные сигналы в скрипте. Например, вы хотите вывести экран "Конец игры", когда здоровье игрока станет равным нулю. Для этого вы можете определить сигнал "died" или "health_depleted", и выдать его, когда здоровье игрока будет 0.
extends Node2D
signal health_depleted
var health = 10
Примечание
Поскольку сигналы представляют собой события, которые только что произошли, мы обычно используем в их названиях глагол действия в прошедшем времени.
Ваши сигналы работают таким же образом, как и встроенные: они появляются во вкладке Узла, и Вы можете подключить к ним любые другие сигналы.

Чтобы излучать сигнал в скриптах, вызовите emit_signal()
.
func take_damage(amount):
health -= amount
if health <= 0:
emit_signal("health_depleted")
Сигнал может дополнительно объявить один или несколько аргументов. Укажите имена аргументов между круглыми скобками:
extends Node
signal health_changed(old_value, new_value)
Примечание
Эти аргументы показываются в док-узлах редактора, и Godot может использовать их, чтобы производить для вас функции обратного вызова. Однако, вы всё ещё можете отправлять любое число аргументов при отправке сигналов; отправка правильных значений зависит от вас.
Чтобы выдать значения вместе с сигналом, добавьте их в качестве дополнительных аргументов к функции emit_signal()
:
func take_damage(amount):
var old_health = health
health -= amount
emit_signal("health_changed", old_health, health)
Подведение итогов¶
Любой узел в Godot излучает сигналы, когда с ним происходит что-то особенное, как например, нажатие на кнопку. Другие узлы могут подключиться к индивидуальным сигналам и среагировать на выбранные события.
Сигналы имеют множество применений. С их помощью Вы можете отреагировать на узел при выходе и входе в игровой мир, на столкновение, на персонажа, входящего или покидающего какую-либо область, на изменение в масштабе элемента интерфейса и на многое другое.
Например, Area2D, представляющий монету, испускает сигнал body_entered
всякий раз, когда физическое тело игрока входит в его форму столкновения, что позволяет узнать, когда игрок подобрал монету.
В следующем разделе, Ваша первая 2D игра, вы создадите полноценную 2D-игру и примените на практике всё, чему научились до сих пор.