Сигналы

Введение

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

Примечание

Вы можете прочитать больше о шаблоне наблюдателя здесь: http://gameprogrammingpatterns.com/observer.html

Сигналы - это способ разъединения ваших игровых объектов, что делает код более упорядоченным и управляемым. Вместо того чтобы принуждать игровые объекты постоянно зависеть от существования других объектов, они могут посылать сигналы, на которые все заинтересованные объекты могут реагировать.

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

Пример таймера

Чтобы увидеть, как работают сигналы, давайте попробуем использовать узел Timer. На панели "Scene" переименуйте Node2D в TimerExample.

Для текстуры Спрайта вы можете использовать иконку Godot или любое другое желаемое изображение. Сделайте это выбрав Загрузить в выпадающем меню атрибута Texture узла Sprite. Прикрепите скрипт к корневому узлу, но пока не добавляйте никакого кода.

Дерево сцены должно выглядеть так:

../../_images/signals_node_setup.png

В свойствах узла Timer установите флажок "Вкл" рядом с полем Autostart. Это приведет к автоматическому запуску таймера при запуске сцены. Вы можете оставить Wait Time в 1 секунду.

Рядом с вкладкой "Инспектор" находится вкладка "Узел". Нажмите на эту вкладку, и вы увидите все сигналы, которые выбранный узел может посылать. В случае с узлом Timer всё, что нас интересует, это "timeout". Этот сигнал отправляется каждый раз, когда таймер достигает 0.

../../_images/signals_node_tab_timer.png

Нажмите на сигнал «timeout()» и нажмите «Присоединить...» в нижней части панели сигналов. Вы увидите следующее окно, где вы можете определить, как вы хотите подключить сигнал:

../../_images/signals_connect_dialog_timer.png

На левой стороне вы увидите узлы в вашей сцене и можете выбрать узел, который вы хотите использовать для "прослушки" сигнала. Обратите внимание, что узел Timer является синим - это визуальный признак того, что это узел, который передает сигнал. Выберите корневой узел.

Предупреждение

На выбранный узел должен быть прикреплен скрипт, иначе вы получите сообщение об ошибке.

Если вы развернете дополнительное меню, то с правой стороны вы можете связать произвольное количество аргументов (возможно) разных типов. Это может быть полезно, когда к одному и тому же методу подключено более одного сигнала, поскольку каждое распространение сигнала приведет к различным значениям для этих дополнительных аргументов вызова.

В нижней части окна находится поле с надписью "Метод-приёмник". Это имя функции в скрипте целевого узла, который вы хотите использовать. По умолчанию Godot создаст эту функцию, используя соглашение об именовании _on_<node_name>_<signal_name>, но вы можете изменить его по своему усмотрению.

Нажмите кнопку "Присоединить", и вы увидите, что функция была создана в скрипте:

extends Node2D


func _on_Timer_timeout():
    pass # Replace with function body.
public class TimerExample : Node2D
{
    public void _on_Timer_timeout()
    {
        // Replace with function body.
    }
}

Теперь мы можем заменить изначальный код на любой код, который мы хотим запустить при получении сигнала. Давайте заставим спрайт мигать:

extends Node2D


func _on_Timer_timeout():
    # Note: the `$` operator is a shorthand for `get_node()`,
    # so `$Sprite` is equivalent to `get_node("Sprite")`.
    $Sprite.visible = !$Sprite.visible
public class TimerExample : Node2D
{
    public void _on_Timer_timeout()
    {
        var sprite = GetNode<Sprite>("Sprite");
        sprite.Visible = !sprite.Visible;
    }
}

Запустите сцену, и вы увидите что спрайт мигает и пропадает каждую секунду. Можно изменить свойство таймера Wait Time, чтобы изменить частоту мигания.

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

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

Во-первых, отключите сигнал, выбрав подключенный сигнал таймера на вкладке "Узел" и нажав "Отсоединить".

../../_images/signals_disconnect_timer.png

Чтобы присоединить сигнал в коде, мы можем использовать функцию connect. Мы поставим его в _ready() так, что соединение будет сделано сразу при запуске сцены. Синтаксис функции <source_node>.connect(<signal_name>, <target_node>, <target_function_name>). Вот код для подключения нашего таймера:

extends Node2D


func _ready():
    $Timer.connect("timeout", self, "_on_Timer_timeout")


func _on_Timer_timeout():
    $Sprite.visible = !$Sprite.visible
public class TimerExample : Node2D
{
    public override void _Ready()
    {
        GetNode("Timer").Connect("timeout", this, nameof(_on_Timer_timeout));
    }

    public void _on_Timer_timeout()
    {
        var sprite = GetNode<Sprite>("Sprite");
        sprite.Visible = !sprite.Visible;
    }
}

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

Вы также можете объявить свои собственные пользовательские сигналы в Godot:

extends Node2D


signal my_signal
public class Main : Node2D
{
    [Signal]
    public delegate void MySignal();
}

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

Чтобы отправить сигнал через код, используйте функцию emit_signal:

extends Node2D


signal my_signal


func _ready():
    emit_signal("my_signal")
public class Main : Node2D
{
    [Signal]
    public delegate void MySignal();

    public override void _Ready()
    {
        EmitSignal(nameof(MySignal));
    }
}

Сигнал также может опционально объявлять один или несколько аргументов. Укажите имена аргументов в круглых скобках:

extends Node


signal my_signal(value, other_value)
public class Main : Node
{
    [Signal]
    public delegate void MySignal(bool value, int other_value);
}

Примечание

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

Чтобы передать значения, добавьте их в качестве второго аргумента в функцию emit_signal:

extends Node


signal my_signal(value, other_value)


func _ready():
    emit_signal("my_signal", true, 42)
public class Main : Node
{
    [Signal]
    public delegate void MySignal(bool value, int other_value);

    public override void _Ready()
    {
        EmitSignal(nameof(MySignal), true, 42);
    }
}

Вывод

Many of Godot's built-in node types provide signals you can use to detect events. 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.

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