Using signals

В этом уроке рассмотрим сигналы. Это сообщения, которые выдает узел при определенных событиях, например, при нажатии кнопки. Другие узлы могут получать этот сигнал и вызывать функции, соответствующие событию.

It is 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, вы бы использовали сигналы.

Примечание

As mentioned in the introduction, signals are Godot's version of the observer pattern. You can learn more about it here: https://gameprogrammingpatterns.com/observer.html

We will now use a signal to make our Godot icon from the previous lesson (Listening to player input) move and stop by pressing a button.

Настройка сцены

To add a button to our game, we will create a new "main" scene which will include both a button and the Sprite.tscn scene that we scripted in previous lessons.

Создайте новую сцену через меню Сцена -> Новая сцена.

../../_images/signals_01_new_scene.png

In the Scene dock, click the 2D Scene button. This will add a Node2D as our root.

../../_images/signals_02_2d_scene.png

In the FileSystem dock, click and drag the Sprite.tscn file you saved previously onto the Node2D to instantiate it.

../../_images/signals_03_dragging_scene.png

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

../../_images/signals_04_add_child_node.png

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

../../_images/signals_05_add_button.png

The node is small by default. Click and drag on the bottom-right handle of the Button in the viewport to resize it.

../../_images/signals_06_drag_button.png

If you don't see the handles, ensure the select tool is active in the toolbar.

../../_images/signals_07_select_tool.png

Click and drag on the button itself to move it closer to the sprite.

Вы можете сделать надпись на кнопке, отредактировав свойство Text в Инспекторе.

../../_images/signals_08_toggle_motion_text.png

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

../../_images/signals_09_scene_setup.png

Save your newly created scene. You can then run it with F6.

Подключение сигнала в редакторе

Here, we want to connect the Button's "pressed" signal to our Sprite, and we want to call a new function that will toggle its motion on and off. We need to have a script attached to the Sprite node, which we do from the previous lesson.

Вы можете подключить сигналы в панели Узел. Выберите узел Button и на правой стороне экрана нажмите вкладку "Узел" рядом с Инспектором.

../../_images/signals_10_node_dock.png

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

../../_images/signals_11_pressed_signals.png

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

../../_images/signals_12_node_connection.png

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

Примечание

При подключении сигналов через вкладку Узел редактора, можно использовать два режима. Можно просто подключить сигнал к узлу, имеющему скрипт, и будет автоматически создана функция обратного вызова в нем.

../../_images/signals_advanced_connection_window.png

В расширенном режиме можно присоединить сигнал к любому узлу и к любой встроенной функции, добавить аргументы и установить параметры. Режим можно изменить переключателем в правом-нижнем углу окна.

Нажмите кнопку "Присоединить", откроется окно редактирования скрипта. Вы увидите новый метод (функцию) с иконкой соединения на левой границе.

../../_images/signals_13_signals_connection_icon.png

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

../../_images/signals_14_signals_connection_info.png

Давайте заменим строку со словом 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).

In the Scene dock, right-click on the Sprite node and add a new child node. Search for Timer and add the corresponding node. Your scene should now look like this.

../../_images/signals_15_scene_tree.png

With the Timer node selected, go to the Inspector and check the Autostart property.

../../_images/signals_18_timer_autostart.png

Click the script icon next to Sprite to jump back to the scripting workspace.

../../_images/signals_16_click_script.png

Нам необходимо выполнить две операции для подключения узлов через код:

  1. Получите ссылку на таймер из Спрайта.

  2. Вызвать метод connect() Таймера.

Примечание

To connect to a signal via code, you need to call the connect() method of the node you want to listen to. In this case, we want to listen to the Timer's "timeout" signal.

We want to connect the signal when the scene is intantiated, and we can do that using the Node._ready() built-in function, which is called automatically by the engine when a node is fully instantiated.

To get a reference to a node relative to the current one, we use the method Node.get_node(). We can store the reference in a variable.

func _ready():
    var timer = get_node("Timer")

The function get_node() looks at the Sprite's children and gets nodes by their name. For example, if you renamed the Timer node to "BlinkingTimer" in the editor, you would have to change the call to 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, и наоборот.

Complete script

That's it for our little moving and blinking Godot icon demo! Here is the complete Sprite.gd file for reference.

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

Пользовательские сигналы

Примечание

This section is a reference on how to define and use your own signals, and does not build upon the project created in previous lessons.

Вы можете определить собственные сигналы в скрипте. Например, вы хотите вывести экран "Конец игры", когда здоровье игрока станет равным нулю. Для этого вы можете определить сигнал "died" или "health_depleted", и выдать его, когда здоровье игрока будет 0.

extends Node2D

signal health_depleted

var health = 10

Примечание

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

Your signals work the same way as built-in ones: they appear in the Node tab and you can connect to them like any other.

../../_images/signals_17_custom_signal.png

Чтобы излучать сигнал в скриптах, вызовите emit_signal().

func take_damage(amount):
    health -= amount
    if health <= 0:
        emit_signal("health_depleted")

A signal can optionally declare one or more arguments. Specify the argument names between parentheses:

extends Node

signal health_changed(old_value, new_value)

Примечание

Эти аргументы показываются в док-узлах редактора, и Godot может использовать их, чтобы производить для вас функции обратного вызова. Однако, вы всё ещё можете отправлять любое число аргументов при отправке сигналов; отправка правильных значений зависит от вас.

To emit values along with the signal, add them as extra arguments to the emit_signal() function:

func take_damage(amount):
    var old_health = health
    health -= amount
    emit_signal("health_changed", old_health, health)

Подведение итогов

Any node in Godot emits signals when something specific happens to them, like a button being pressed. Other nodes can connect to individual signals and react to selected events.

Signals have many uses. With them, you can react to a node entering or exiting the game world, to a collision, to a character entering or leaving an area, to an element of the interface changing size, and much more.

For example, an Area2D representing a coin emits a body_entered signal whenever the player's physics body enters its collision shape, allowing you to know when the player collected it.

In the next section, Ваша первая 2D игра, you'll create a complete 2D game and put everything you learned so far into practice.