Sygnały

Wprowadzenie

Sygnały w Godot są odpowiednikiem wzorca obserwatora. Pozwalają węzłowi na wysyłanie "wiadomości", które inne węzły mogą odczytywać i na nie reagować. Przykładowo, zamiast bez przerwy sprawdzać, czy przycisk został właśnie wciśnięty, to przycisk sam może wyemitować sygnał, kiedy zostanie wciśnięty.

Informacja

O wzorcu projektowym "obserwator", możesz przeczytać więcej tutaj: https://gameprogrammingpatterns.com/observer.html

Sygnały są sposobem na rozdzielenie Twoich obiektów, co powoduje, że pisany kod jest lepiej zorganizowany i łatwiej nim zarządzać. Zamiast wymuszać na obiektach, żeby zawsze spodziewały się określonych obiektów, mogą zamiast tego emitować sygnały, na które zareagują wszystkie zainteresowane obiekty.

Poniżej możesz zobaczyć przykłady, jak używać sygnałów w swoich projektach.

Przykład dla węzła Timer

Żeby zobaczyć, jak działają sygnały, użyjmy węzła Timer. Utwórz nową scenę z węzłem typu Node2D oraz jego dwoma węzłami-dziećmi: węzłem Timer oraz a Sprite. W doku Sceny, zmień nazwę węzła z Node2D na TimerExample.

Dla tekstury węzła Sprite, możesz użyć ikony Godota lub jakiegokolwiek innego obrazu. Żeby to zrobić, naciśnij przycisk Załaduj w menu rozwijalnym atrybutu Tekstura, należącym do węzła Sprite. Dodaj skrypt do głównego węzła, ale nie dodawaj do niego jeszcze żadnego kodu.

Drzewo sceny powinno tak wyglądać:

../../_images/signals_node_setup.png

We właściwościach węzła Timer, zaznacz pole "Tak", znajdujące się obok etykiety "Autostart". To spowoduje, że zegar wystartuje automatycznie, kiedy uruchomisz scenę. Możesz zostawić Czas oczekiwania na 1 sekundę.

Obok zakładki "Inspektor" znajduje się zakładka z etykietą "Węzeł". Kliknij na tę zakładkę, żeby zobaczyć wszystkie sygnały, jakie ten węzeł może wyemitować. w przypadku węzła typu Timer, zależy nam na sygnale "timeout". Ten sygnał jest emitowany za każdym razem, kiedy czas Timera osiąga 0.

../../_images/signals_node_tab_timer.png

Kliknij na sygnał "timeout()" i wybierz "Połącz..." na dole panelu z sygnałami. Zobaczysz wtedy okno, w którym będziesz mógł zdefiniować, w jaki sposób chcesz połączyć sygnał:

../../_images/signals_connect_dialog_timer.png

Po lewej stronie znajdują się węzły w Twojej scenie - możesz wybrać węzeł, który chcesz aby "nasłuchiwał" na nasz sygnał. Zauważ, że węzeł Timer jest zaznaczony na niebiesko - to informacja, że jest to węzeł, który emituje ten sygnał. Wybierz główny węzeł (TimerExample).

Ostrzeżenie

Docelowy węzeł musi mieć dołączony skrypt, w przeciwnym wypadku otrzymasz informację o błędzie.

Jeżeli rozwiniesz Zaawansowane menu, po prawej stronie zobaczysz, że możesz przypisać dowolną liczbę argumentów (różnych) typów. To może być użyteczne w momencie kiedy masz więcej niż jeden sygnał połączony do tej samej metody, ponieważ każdy dodatkowy argument spowoduje podział na różne wartości dla każdego sygnału.

Na dole okna znajduje się pole "Metoda odbiorcy". Jest to nazwa funkcji w skrypcie docelowego węzła, której chcesz użyć. Domyślnie Godot utworzy tę funkcję, korzystając z następującej konwencji : _on_<nazwa_wezla>_<nazwa_sygnalu> . Oczywiście, jeśli chcesz, możesz zmienić tę nazwę według uznania.

Naciśnij przycisk "Połącz". Możesz zobaczyć, że funkcja została utworzona w skrypcie:

extends Node2D


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

Teraz możemy zastąpić tymczasowy kod jakimkolwiek innym, który ma być uruchomiony w momencie odebrania sygnału. Spowodujmy, żeby węzeł Sprite zaczął migać:

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;
    }
}

Odtwórz scenę i zwróć uwagę, że węzeł Sprite miga z każdą sekundą. Można kontrolować czas migotania przy użyciu właściwości Czas oczekiwania, należącej do węzła Timer.

Podłączenie sygnałów z poziomu kodu

Możesz także połączyć sygnał z poziomu kodu zamiast w edytorze. Jest to zazwyczaj niezbędne przy korzystaniu z Węzłów tworzonych w kodzie, w takim przypadku użycie edytora do stworzenia połączenia jest niemożliwe.

Najpierw odłącz sygnał wybierając połączenie z zakładki "Węzeł", należącej do węzła Timer i kliknij odłącz.

../../_images/signals_disconnect_timer.png

Aby dodać połączenie z poziomu kodu, może skorzystać z funkcji connect. Wstawimy tę funkcję do metody _ready() po to, żeby połączenie zostało nawiązane zaraz po uruchomieniu. Składnia tej funkcji wygląda następująco: <wezel_zrodlowy>.connect(<nazwa_sygnalu>, <docelowy_wezel>, <nazwa_funkcji_docelowej>). Poniżej znajduje się kod dla połączenia węzła Timer:

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;
    }
}

Niestandardowe sygnały

W Godot możesz także zadeklarować własne niestandardowe sygnały:

extends Node2D


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

Raz zadeklarowane, twoje niestandardowe sygnały pojawią się w Inspektorze i będą mogły być podłączone w taki sam sposób jak sygnały wbudowane w węzeł.

Aby wyemitować sygnał poprzez kod, użyj funkcji 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));
    }
}

"Sygnał" może również opcjonalnie deklarować jeden lub więcej argumentów. Nazwy argumentów umieść w nawiasie rozdzielając je przecinkiem:

extends Node


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

Informacja

Argumenty Sygnału pojawią się w doku Węzłów edytora, Godot może użyć ich aby wygenerować funkcje zwrotne za ciebie. Jednak wciąż możliwe jest wyemitowanie dowolnej ilości argumentów emitując sygnał samemu. Aczkolwiek to na programiście będzie w tedy spoczywała odpowiedzialność za wyemitowanie poprawnych wartości.

Aby przekazać wartości dodaj je jako drugi argument funkcji 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);
    }
}

Wniosek

Godot ma wiele wbudowanych typów węzłów, które dostarczają sygnałów aby wykrywać wydarzenia. Na przykład, Area2D reprezentująca monetę emituje sygnał body_entered, kiedy fizyczne ciało gracza wchodzi w jej kształt kolizyjny, co daję Tobie znać kiedy gracz ją zebrał.

W następnej sekcji, Twoja pierwsza gra zbudujesz kompletną grę z wykorzystaniem sygnałów, aby połączyć różne elementy gry.