Kontrolle der Spiel-Benutzeroberfläche mittels Code

Einführung

In dieser Anleitung werden wir einen Charakter mit einer Lebensleiste verbinden und den Gesundheitsverlust animieren.

../../_images/lifebar_tutorial_final_result.gif

Hier ist, was Sie erschaffen werden: eine Bar und einen Zähler werden Sie animieren, wenn der Charakter Schaden nimmt. Sie verblassen, wenn er stirbt.

Sie lernen hier:

  • Wie man einen Charakter mit einer GUI verbindet, indem man Signale benutzt
  • Wie man eine GUI mit GDScript kontrolliert
  • Wie man eine Lebensbar mit einer Tween-Node animiert

Wenn Sie stattdessen lernen möchten, wie man ein Interface konfiguriert, werfen Sie einen Blick auf die Schritt-für-Schritt UI-Anleitung:

Wenn Sie mit der Entwicklung beginnen, kümmern Sie sich zunächst um die Kernaspekte des Spiels: die Grundmechaniken, Eingabesteuerung, Sieg- und Verlustbedingungen. Die UI kommt etwas später nach. Halten Sie sämtliche Elemente des Projekts soweit es geht modular. Jeder Charakter sollte in einer eigenen Szene mit dazugehörigen Skripten untergebracht werden. Gleiches gilt für UI-Elemente. Dies beugt potenziellen Bugs vor und hält das Projekt überschaubar und erlaubt es Teammitgliedern, gemeinsam an unterschiedlichen Teilen des Spiels zu arbeiten.

Sobald das Kern-Gameplay und die Benutzeroberfläche fertig sind, müssen Sie sie irgendwie verbinden. In unserem Beispiel haben wir den Feind, der den Spieler in konstanten Zeitintervallen angreift. Wir möchten, dass die Lebensleiste aktualisiert wird, wenn der Spieler Schaden nimmt.

Dafür benutzt man Signale.

Bemerkung

Signale sind die Implementierung des Beobachter-Entwurfsmusters (Observer) in Godot. Sie erlauben das Absenden von Nachrichten, die von einem anderen Objekt empfangen werden kann. Dazu wird ein Node mit dem Objekt verbunden, welches das Signal emittiert und erhält so die Informationen des Signals. Ein mächtiges Werkzeug, vor allem bei der Umsetzung von Benutzeroberflächen und Errungenschaften. Da das Verbinden zweier Nodes gleichzeitig eine gewisse Abhängigkeit voneinander erzeugt, sollten sie allerdings stets mit Bedacht verwendet werden, bei zu vielen Verbindungen kann es schwierig werden, den Überblick zu behalten. Für mehr Informationen, schauen Sie sich das signals video tutorial von GDquest an.

Laden und sehen Sie sich das Startprojekt an

Laden Sie das Godot-Projekt herunter: ui_code_life_bar.zip. Es enthält sämtliche Nutzerinhalte und Skripte die Sie zum Start benötigen. Entpacken Sie das Zip-Archiv, um diese beiden Ordner zu erhalten: start und end.

Laden Sie das start-Projekt in Godot. Im Dateisystem-Fenster, doppelklicken auf LevelMockup.tscn, um es zu öffnen. Es ist ein RPG-Mock-up in dem sich zwei Charaktere gegenüberstehen. Der rosafarbene Gegner greift das grüne Viereck an und fügt ihm regelmäßig Schaden zu, bis es stirbt. Probieren Sie das Spiel aus: eine grundlegende Kampfmechnik ist bereits enthalten. Allerdings funktioniert die GUI noch nicht, da der Spieler bisher nicht mit der Lebensanzeige verbunden ist.

Bemerkung

Das ist typisch für jedes Spiel: Als Erstes implementieren Sie die Mechanik, die mit der GUI interagiert, in diesem Fall den Lebensverlust des Spielers, und erst dann fügen Sie die Benutzeroberfläche hinzu. Das liegt daran, dass die UI darauf reagiert was im Spiel geschieht. Somit kann sie nicht korrekt funktionieren, solange der Spielablauf nicht integriert ist und muss unter Umständen von neuem erstellt werden.

Die Szene enthält einen Hintergrund-Sprite, eine GUI sowie zwei Charaktere.

../../_images/lifebar_tutorial_life_bar_step_tut_LevelMockup_scene_tree.png

Der Szenenbaum mit der GUI Szene ist so gesetzt, dass die Kinder angezeigt werden

Die GUI-Szene kapselt die gesamte grafische Benutzeroberfläche des Spiels ein. Es wird mit einem Barebones-Skript geliefert, in dem der Pfad zu den Nodes in der Szene angegeben wird:

onready var number_label = $Bars/LifeBar/Count/Background/Number
onready var bar = $Bars/LifeBar/TextureProgress
onready var tween = $Tween
public class Gui : MarginContainer
{
    private Tween _tween;
    private Label _numberLabel;
    private TextureProgress _bar;

    public override void _Ready()
    {
        // C# doesn't have an onready feature, this works just the same.
        _bar = (TextureProgress) GetNode("Bars/LifeBar/TextureProgress");
        _tween = (Tween) GetNode("Tween");
        _numberLabel = (Label) GetNode("Bars/LifeBar/Count/Background/Number");
    }
}
  • number_label```zeigt einen Lebenszähler als eine Nummer. Es ist eine ``Label-Node
  • bar ist die Lebensanzeige, ein TextureProgress-Node
  • tween ist eine Node im Komponentenstil, die man animieren und ihr verschiedene Werte oder Methoden von anderen Nodes zuweisen kann

Bemerkung

Das Projekt ist organisatorisch einfach gestaltet, geeignet für Game Jams und kleinere Spiele.

Auf der Wurzelebene des Projekts, im res://-Verzeichnis, finden Sie LevelMockup. Das ist die Hauptszene des Spiels, mit der wir auch arbeiten werden. Sämtliche Komponenten, aus denen das Spiel besteht, befinden sich im scenes/-Ordner. Der assets/-Ordner enthält alle Sprites und die Schriftart (Font) für die HP-Anzeige. Unter scripts/ finden Sie die Objekte für Spieler und Gegner sowie die Controller-Skripte der GUI.

Klicken Sie auf das Szene-Bearbeiten Symbol auf der rechten Seite des Node in dem Szenenbaum um die Szene im Editor zu öffnen. Sie werden eine LifeBar und EnergieBar sehen, die selbst Unter-Szenen sind.

../../_images/lifebar_tutorial_Player_with_editable_children_on.png

Der Szenenbaum, mit der ausgewählten Spielerszene und den untergeordneten Elementen

Richte den Lebensbalken mit der maximalen Gesundheit des Spielers ein

Wir müssen der GUI irgendwie mitteilen, wie der aktuelle Gesundheitszustand des Players ist, die Textur des Lebensbalkens aktualisieren und den verbleibenden Gesundheitszustand im HP-Zähler in der oberen linken Ecke des Bildschirms anzeigen. Dazu senden wir die Gesundheit des Spielers bei jedem Schaden an die GUI. Die GUI aktualisiert dann die Nodes Lifebar und Number mit diesem Wert.

Wir könnten hier anhalten, um die Zahl anzuzeigen, aber wir müssen den max_value des Balkens initialisieren, damit er in den richtigen Proportionen aktualisiert wird. Der erste Schritt besteht also darin, der GUI zu sagen, was das max_health des grünen Zeichens ist.

Tipp

Die Leiste, eine TextureProgress, hat standardmäßig einen Maximalwert von 100. Wenn Sie nicht die Gesundheit des Charakters mit einer Number anzeigen wollen, brauchen Sie nicht ihre Maximalwert Eigenschaft ändern. Sie senden einen Prozentsatz von dem Spieler stattdessen zur GUI:..`health/max_health * 100`.

../../_images/lifebar_tutorial_TextureProgress_default_max_value.png

Klicken Sie das Skript Icon rechts neben GUI im Szenen Reiter um sein Skript zu öffnen. In der _ready Funktion werden wir eine neue Variable namens max_health (maximales leben) für den Player (Spieler) speichern und ihr den Wert von bar``s (Balken) ``max_value (maximaler Wert) geben:

func _ready():
    var player_max_health = $"../Characters/Player".max_health
    bar.max_value = player_max_health
public override void _Ready()
{
    // Add this below _bar, _tween, and _numberLabel.
    var player = (Player) GetNode("../Characters/Player");
    _bar.MaxValue = player.MaxHealth;
}

Schauen wir uns das mal genauer an. $"../Characters/Player"` ist eine Abkürzung, die im Szenenbaum einen Node nach oben geht und von dort aus den Node Characters/Player abruft. Das gibt uns Zugang zum Node. Der zweite Teil der Anweisung, .max_health, greift auf die max_health-Variable auf dem Player-Node zu.

Die zweite Zeile weist den Wert bar.max_value zu. Die zwei Zeilen könnten auch zu einer Kombiniert werden, doch player_max_health wird später in der Anleitung noch gebraucht werden.

Am Anfang des Spieles wird health auf max_health gesetzt, damit wir es gebrauchen können. Warum brauchen wir max_health immer noch? Es gibt zwei Gründe:

Wir können nicht davon ausgehen, dass health in jedem Fall max_health entspricht: Eine spätere Version des Spiels könnte beispielsweise einen Level laden, in welchem der Spieler bereits etwas Gesundheit verloren hat.

Bemerkung

Wenn Sie eine Szene im Spiel öffnen, kreiert Godot Nodes einen nach dem anderen, in der Reihenfolge des Szenen Reiters, von oben nach unten. GUI und Player sind nicht teil des selben Node-Zweiges. Um sicherzustellen, dass beide existieren wenn wir auf sie zugreifen, benutzen wir die _ready Funktion. Godot führt _ready genau dann aus, wenn es alle Nodes geladen hat, bevor das Spiel startet. Es ist die perfekte Funktion um alles einzustellen und die Spiel Sitzung vorzubereiten. Mehr zu _ready: Skripten (Fortsetzung)

Aktualisieren Sie die Gesundheit mit einem Signal, wenn der Spieler einen Treffer erhält.

Unsere GUI ist bereit um Aktualisierungen der health Werte vom Player zu bekommen.

Bemerkung

Es gibt viele nützliche eingebaute Signale wie enter_tree und exit_tree, die alle Nodes aussenden, wenn sie jeweils erzeugt und zerstört werden. Sie können auch Ihre eigenen mit dem Schlüsselwort signal erstellen. Auf dem Node Player finden Sie zwei Signale, die wir erstellt haben: died und health_changed.

Warum holen wir nicht direkt den Player Node in der _process Funktion und betrachten den Health-Wert? Wenn wir auf diese Weise auf Nodes zugreifen, entsteht eine enge Kopplung zwischen ihnen. Wenn Sie es sparsam machen, kann es funktionieren. Wenn das Spiel größer wird, haben Sie möglicherweise viel mehr Verbindungen. Wenn Sie auf diese Weise Nodes erhalten, wird es schnell komplex. Nicht nur das: Sie müssen in der Funktion _process` ständig auf die Zustandsänderung achten. Diese Überprüfung findet 60 Mal pro Sekunde statt und kann das Spiel verlangsamen, wenn es in einem großen Spiel mit zu vielen Verbindungen passiert.

Auf einem bestimmten Frame können Sie sich die Eigenschaft eines anderen Nodes ansehen, vor dessen Aktualisierung: Sie erhalten einen Wert aus dem letzten Frame. Dies führt zu obskuren Bugs, die schwer zu beheben sind. Mit Signalen kann man das verhindern: Ein Signal wird garantiert direkt nach der Änderung mit dem neuen Wert aufgerufen und verhindert damit diese Fehler.

Bemerkung

Das Observer-Pattern, von dem die Signals abgeleitet sind, fügt den einzelnen Nodes trotzdem eine kleine Menge an Kopplung hinzu. Aber es erzeugt im Allgemeinen nicht so viel Kopplung und ist sicherer als der direkte Zugriff auf Funktionen anderer Klassen bzw. Nodes. Es kann in Ordnung sein, dass Elternknoten Werte von den Kindknoten erhalten, aber bei der Kommunikation von Knoten in unterschiedlichen Verzweigungen sollten Signals bevorzugt werden. Im kostenlosen Online verfügbaren Buch "Game Programming Patterns", ist das Observer Pattern noch detaillierter beschrieben: <http://gameprogrammingpatterns.com/observer.html>.

In diesem Sinne verbinden wir das GUI mit dem Player. Klicke auf den Node Player im Szene-Fenster, um ihn auszuwählen. Gehen Sie zum Inspektor und klicken Sie auf das Fenster "Node". Dies ist der Ort, an dem man Nodes verbinden kann, um den von Ihnen ausgewählten Node anzuhören.

Der erste Abschnitt listet benutzerdefinierteSignale in Player.gd auf:

  • died wird angezeigt wenn der Charakter stirbt. Wir werden dies in einem Moment verwenden um die UI zu verbergen.
  • health_changed wird angezeigt wenn der Charakter getroffen wird.
../../_images/lifebar_tutorial_health_changed_signal.png

Wir verbinden das health_changed Signal

Wählen Sie health_changed aus und klicken auf "Connect" unten rechts im Fenster um das "Signal Verbinden"-Fenster zu öffnen. Links können Sie den Node auswählen, der auf das Signal reagieren soll. Wählen Sie hier den GUI-Node aus. Rechts können Sie zusätzliche Werte auswählen, die mit dem Signal gesendet werden sollen. Wir haben uns allerdings bereits in Player.gd darum gekümmert. Ich empfehle Ihnen, vor allem bei mehreren Argumenten, die Argumente lieber im Code als in diesem Fenster zu definieren, da es im Code einfacher ist.

../../_images/lifebar_tutorial_connect_signal_window_health_changed.png

Das "Connect Signal"-Fenster mit der ausgewählten GUI Node

Tipp

Man kann Signale auch im Code verbinden. Im Editor hat man aber 2 Vorteile:

  1. Godot kann automatisch Funktionen in dem Script erstellen, mit dem es verbunden ist
  2. Ein "Emitter"-Icon erscheint neben der Node im Szenen-Fenster

Am unteren Rand des Fensters finden Sie den Pfad zu dem von Ihnen ausgewählten Nodes. Wir sind an der zweiten Zeile "Methode im Knoten" interessiert. Dies ist die Methode auf dem GUI`-Node, die aufgerufen wird, wenn das Signal ausgegeben wird. Dieses Verfahren empfängt die mit dem Signal gesendeten Werte und ermöglicht deren Verarbeitung. Rechts gibt es einen Button "Make Function", der standardmäßig eingeschaltet ist. Klicken Sie auf den Button Verbinden am unteren Rand des Fensters. Godot erstellt die Methode innerhalb des GUI Nodes. Der Skripteditor öffnet sich mit dem Cursor innerhalb einer neuen _on_Player_health_changed Funktion.

Bemerkung

Wenn eine Node im Editor verbunden wird, generiert Godot eine Methode nach dem folgenden Musster: _on_EmitterName_signal_name. Falls die Methode bereits existiert, bleibt die "Erstelle Funktion". Der Name kann nach belieben verändert werden.

../../_images/lifebar_tutorial_godot_generates_signal_callback.png

Godot erstellt die Callback Methode für Sie und führt Sie dorthin

Fügen Sie innerhalb der Klammern nach dem Funktionsnamen ein Player_Health Argument hinzu. Wenn der Spieler das Signal health_changed aussendet, sendet er seine aktuelle health mit. Ihr Code sollte so aussehen:

func _on_Player_health_changed(player_health):
    pass
public void OnPlayerHealthChanged(int playerHealth)
{
}

Bemerkung

Die Engine konvertiert PascalCase nicht in snake_case, für C#-Beispiele werden wir PascalCase für Methodennamen und camelCase für Methodenparameter verwenden, was den offiziellen `C#-Namenskonventionen folgt. <https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/capitalization-conventions>``_

../../_images/lifebar_tutorial_player_gd_emits_health_changed_code.png

Wenn der Spieler in Player.gd das Signal "health_changed signal" ausgibt, sendet er auch seinen Gesundheitswert

Innerhalb von _on_Player_health_changed, rufen wir eine zweite Funktion namens update_health auf und übergeben ihr die Player_health Variable.

Bemerkung

Wir könnten den Gesundheitswert auf LifeBar und Number direkt aktualisieren. Es gibt zwei Gründe, diese Methode stattdessen zu verwenden:

  1. Der Name macht deutlich, dass wir, wenn der Spieler Schaden genommen hat, die Gesundheitszählung auf der Benutzeroberfläche ( GUI ) aktualisieren
  2. Wir werden diese Methode etwas später wiederverwenden

Erstellen eine neue update_health Methode unter _on_Player_health_changed`. Sie nimmt einen neuen_Wert als einziges Argument:

func update_health(new_value):
    pass
public void UpdateHealth(int health)
{
}

Diese Methode soll es sein:

  • Setzen Sie``Number`` node's text auf``new_value`` , der in eine Zeichenkette umgewandelt wird
  • setzen Sie den TextureProgress````value``auf ``new_value
func update_health(new_value):
    number_label.text = str(new_value)
    bar.value = new_value
public void UpdateHealth(int health)
{
    _numberLabel.Text = health.ToString();
    _bar.Value = health;
}

Tipp

str ist eine eingebaute Funktion, die etwa jeden Wert in Text umwandelt. Die Nummer Text-Eigenschaft erfordert eine Zeichenkette, so dass wir sie nicht direkt new_value zuweisen können

Rufen Sie auch update_health am Ende der Funktion _ready auf, um den Nummer-Node Text mit dem richtigen Wert zu Beginn des Spiels zu initialisieren. Drücken Sie F5, um das Spiel zu testen: die Rettungsleiste wird bei jedem Angriff aktualisiert!

../../_images/lifebar_tutorial_LifeBar_health_update_no_anim.gif

Sowohl der Nummer-Node als auch der TextureProgress werden aktualisiert, wenn der Spieler einen Treffer erhält

Animieren Sie den Verlust von Leben mit dem Tween-Node

Unsere Schnittstelle ist funktional, aber sie könnte eine Animation gebrauchen. Das ist eine gute Gelegenheit, den Tween-Node einzuführen, ein wesentliches Werkzeug zur Animation von Eigenschaften. Tween animiert alles, was Sie von einem Anfangs- bis zu einem Endzustand über eine bestimmte Dauer haben möchten. Zum Beispiel kann es die Gesundheit auf dem TextureProgress von seinem aktuellen Level bis zur neuen health des Spielers animieren, wenn der Charakter Schaden nimmt.

Die GUI` Szene enthält bereits einen Tween Kind-Node, der in der Tween` Variable gespeichert ist. Lassen Sie uns diese nun verwenden. Wir müssen einige Änderungen an update_health vornehmen.

Wir werden die Tween```Nodes ``Interpolate_property Methode verwenden. diese benötigt sieben Argumente:

  1. Ein Verweis auf den Node, dem die zu animierende Eigenschaft gehört
  2. Der Bezeichner der Eigenschaft als Zeichenkette
  3. Der Start Wert
  4. Der End-Wert
  5. Die Animationsdauer in Sekunden
  6. Der Typ des Übergangs
  7. Die einfache Handhabung in Kombination mit der Gleichung.

Die letzten beiden kombinierten Argumente entsprechen einer Lockerungsgleichung. Das steuert, wie sich der Wert vom Anfang bis zum Ende entwickelt.

Klicken Sie auf das Skript-Symbol neben dem GUI Node, um es wieder zu öffnen. Der Number-Node benötigt Text, damit er sich selbst aktualisiert und die Bar benötigt eine Float- oder Integer-Variable. Wir können interpolate_property benutzen, um eine Nummer zu animieren, aber nicht direkt den Text. Wir verwenden es stattdessen, um eine neue GUI-Variable namens animated_health zu animieren.

Definieren Sie oben im Skript eine neue Variable, benennen Sie sie animated_health, und setzen Sie ihren Wert auf 0. Navigieren Sie zurück zur Methode update_health und löschen Sie ihren Inhalt. Lassen Sie uns den Wert von animated_health animieren. Rufen Sie die Methode interpolate_property des Tween-Nodes auf:

func update_health(new_value):
    tween.interpolate_property(self, "animated_health", animated_health, new_value, 0.6)
// Add this to the top of your class.
private float _animatedHealth = 0;

public void UpdateHealth(int health)
{
    _tween.InterpolateProperty(this, "_animatedHealth", _animatedHealth, health, 0.6f, Tween.TransitionType.Linear,
        Tween.EaseType.In);
}

Lassen Sie uns den Aufruf aufschlüsseln:

tween.interpolate_property(self, "animated_health", ...

Wir zielen auf die animated_health auf self, das heißt auf den GUI`-Node. Die interpolierende Tween-Eigenschaft nimmt den Namen der Eigenschaft als Zeichenkette an. Deshalb schreiben wir es als "animated_health".

... _health", animated_health, new_value, 0.6 ...

Der Ausgangspunkt ist der aktuelle Wert der Lebensleiste. Wir müssen diesen Teil noch programmieren, aber in unserem Fall verwenden wir animated_health. Der Endpunkt der Animation ist die Gesundheit (health) des Player nach der Änderung (health_changed): das ist der neue Wert (new_value). Und 0.6 ist die Dauer der Animation in Sekunden.

Die Animation wird erst abgespielt, wenn wir den Tween-Node mit tween.start() aktiviert haben. Wir müssen dies nur einmal tun, wenn der Node nicht aktiv ist. Fügen Sie diesen Code nach der letzten Zeile ein:

if not tween.is_active():
    tween.start()
if (!_tween.IsActive())
{
    _tween.Start();
}

Bemerkung

Obwohl wir die Eigenschaft health auf dem Player animieren könnten, sollten wir es nicht tun. Charaktere sollten sofort Leben verlieren, wenn sie getroffen werden. Das macht es viel einfacher, ihren Zustand zu managen, z.B. zu wissen, wann man gestorben ist. Man möchte Animationen immer in einem separaten Datencontainer oder Node speichern. Der Tween-Node ist perfekt für codegesteuerte Animationen. Für handgemachte Animationen sehen Sie sich den AnimationPlayer an.

Weisen Sie der LifeBar die animierte _health zu

Jetzt wird die animated_health Variable animiert, aber wir aktualisieren die eigentlichen Bar und Number-Node nicht mehr. Lasst uns das in Ordnung bringen.

Bisher sieht die Update-_health-Methode wie folgt aus:

func update_health(new_value):
    tween.interpolate_property(self, "animated_health", animated_health, new_value, 0.6)
    if not tween.is_active():
        tween.start()
public void UpdateHealth(int health)
{
    _tween.InterpolateProperty(this, "_animatedHealth", _animatedHealth, health, 0.6f, Tween.TransitionType.Linear,
        Tween.EaseType.In);

    if(!_tween.IsActive())
    {
        _tween.Start();
    }
}

In diesem speziellen Fall, weil das number_label Text erwartet, müssen wir die _process-Methode verwenden, um es zu animieren. Lass uns nun, wie schon zuvor, die Number und TextureProgress-Node innerhalb von _process aktualisieren:

func _process(delta):
    number_label.text = str(animated_health)
    bar.value = animated_health
public override void _Process(float delta)
{
    _numberLabel.Text = _animatedHealth.ToString();
    _bar.Value = _animatedHealth;
}

Bemerkung

number_label und bar`sind Variablen, die Referenzen auf die Nodes `Number`und `TextureProgress speichern.

Spiele das Spiel, um die flüssig animierte Lebensleiste anzuschauen. Allerdings zeigt der Text Dezimalzahlen an und wirkt unaufgeräumt. Und angesichts des Spielstils, wäre es hübsch, die Lebensleiste auf eine abgehacktere Weise zu animieren.

../../_images/lifebar_tutorial_number_animation_messed_up.gif

Die Animation ist flüssig, aber die Zahlenanzeige kaputt

Wir können beide Probleme beheben, indem wir animated_health runden. Benutze eine lokale Variable namens round_value, um die gerundete animated_health zu speichern. Dann ordne sie number_label.txt und bar.value zu:

func _process(delta):
    var round_value = round(animated_health)
    number_label.text = str(round_value)
    bar.value = round_value
public override void _Process(float delta)
{
    var roundValue = Mathf.Round(_animatedHealth);
    _numberLabel.Text = roundValue.ToString();
    _bar.Value = roundValue;
}

Spiele das Spiel erneut, um eine hübsche, klotzige Animation zu betrachten.

../../_images/lifebar_tutorial_number_animation_working.gif

Indem wir animated_health runden, schlagen wir zwei Fliegen mit einer Klappe

Tipp

Jedes Mal, wenn der Spieler einen Treffer erhält, ruft die GUI _auf_Spielgesundheit_geändert auf, was wiederum Update_Gesundheit nennt. Dadurch wird die Animation aktualisiert, und die Zahlen-Label und Balken folgen in _Prozess. Der animierte Lebensbalken, der zeigt, wie die Gesundheit allmählich abnimmt, ist ein Trick. Sie gibt dem GUI ein lebendiges Gefühl. Wenn der Spieler 3 Schaden nimmt, geschieht dies in einem Augenblick.

Blenden den Balken aus, wenn der Spieler stirbt

Wenn die grüne Figur stirbt, spielt sie eine Todesanimation ab und wird ausgeblendet. An diesem Punkt sollten wir das Interface nicht mehr zeigen. Lassen wir auch den Balken ausblenden, wenn der Charakter stirbt. Wir werden denselben Tween-Node wieder verwenden, da er für uns mehrere Animationen parallel verwaltet.

Zunächst muss die GUI` mit dem Player`````died''-Signal verbunden werden, um zu wissen, wann er gestorben ist. Drücken Sie :kbd:``F1`, um zurück in den 2D-Arbeitsbereich zu springen. Wählen Sie den Node ``Player im Szenen-Dock und klicken Sie auf die Registerkarte "Knoten" neben dem Inspektor.

Suchen das ''died''-Signal, wählen es aus, und klicken Sie auf die Schaltfläche Verbinden.

../../_images/lifebar_tutorial_player_died_signal_enemy_connected.png

Der Feind sollte bereits mit dem Signal verbunden sein

Stellen Sie im Fenster "Verbindungssignal" wieder eine Verbindung zum Node GUI her. Der Pfad zum Node sollte `../../GUI sein und die Methode im Node sollte _on_Player_died anzeigen. Lassen Sie die Option Make Function eingeschaltet und klicken Sie unten im Fenster auf Connect. Dies führt Sie zu der Datei GUI.gd im Skript-Arbeitsbereich.

../../_images/lifebar_tutorial_player_died_connecting_signal_window.png

Sie sollten diese Werte im Fenster Verbindungssignal erhalten

Bemerkung

Sie sollten jetzt ein Muster sehen: Jedes Mal, wenn die GUI eine neue Information benötigt, geben wir ein neues Signal aus. Verwenden Sie sie mit Bedacht: Je mehr Verbindungen Sie hinzufügen, desto schwieriger sind sie zu verfolgen.

Um ein Fade auf einem UI-Element zu animieren, müssen wir seine Eigenschaft modulate property. modulate ist eine Color, die unserer Texturen Farben vervielfacht.

Bemerkung

modulate kommt von der CanvasItem Klasse, alle 2D und UI Nodes erben von dieser. Es lässt einen die Sichtbarkeit der Node umschalten, einen Shader hinzufügen, und es zu modifizieren, indem eine Farbe für modulate verwendet wird.

Modulieren nimmt einen Farbe Wert mit 4 Kanälen an: Rot, Grün, Blau und Alpha. Wenn wir einen der ersten drei Kanäle verdunkeln, verdunkelt das die Schnittstelle. Wenn wir den Alphakanal absenken, wird unser Interface ausgeblendet.

Wir bewegen uns zwischen zwei Farbwerten: von einem Weiß mit einem Alpha-Wert von 1, d.h. bei voller Deckkraft, zu einem reinen Weiß mit einem Alpha-Wert von 0, vollständig transparent. Fügen wir zwei Variablen am Anfang der _on_Player_died Methode hinzu und nennen sie start_color und end_color. Benutzen Sie den Color() Konstruktor, um zwei Color Werte zu erzeugen.

func _on_Player_died():
    var start_color = Color(1.0, 1.0, 1.0, 1.0)
    var end_color = Color(1.0, 1.0, 1.0, 0.0)
public void OnPlayerDied()
{
    var startColor = new Color(1.0f, 1.0f, 1.0f);
    var endColor = new Color(1.0f, 1.0f, 1.0f, 0.0f);
}

Color(1.0, 1.0, 1.0) entspricht hier Weiß. Das vierte Argument, entsprechend 1.0 and 0.0 in start_color and end_color, ist der Alphakanal.

Danach müssen wir die Methode interpolate_property des Tween-Nodes erneut aufrufen:

tween.interpolate_property(self, "modulate", start_color, end_color, 1.0)
_tween.InterpolateProperty(this, "modulate", startColor, endColor, 1.0f, Tween.TransitionType.Linear,
  Tween.EaseType.In);

Dieses Mal ändern wir die Eigenschaft Modulieren und lassen sie von start_color auf die end_color animieren. Die Dauer beträgt eine Sekunde, mit einem linearen Übergang. Hier ist die komplette _on_Player_died Methode:

func _on_Player_died():
    var start_color = Color(1.0, 1.0, 1.0, 1.0)
    var end_color = Color(1.0, 1.0, 1.0, 0.0)
    tween.interpolate_property(self, "modulate", start_color, end_color, 1.0)
public void OnPlayerDied()
{
    var startColor = new Color(1.0f, 1.0f, 1.0f);
    var endColor = new Color(1.0f, 1.0f, 1.0f, 0.0f);

    _tween.InterpolateProperty(this, "modulate", startColor, endColor, 1.0f, Tween.TransitionType.Linear,
        Tween.EaseType.In);
}

Und das war's. Sie mögen nun das Spiel spielen, um das fertige Ergebnis zu sehen!

../../_images/lifebar_tutorial_final_result.gif

Das fertige Ergebnis. Herzlichen Glückwunsch, dort hinzukommen!

Bemerkung

Mit genau denselben Techniken können Sie die Farbe des Balkens ändern, wenn der Spieler vergiftet wird, den Balken rot färben, wenn seine Gesundheit sinkt, die UI schütteln, wenn er einen kritischen Treffer erhält... das Prinzip ist das gleiche: Sie senden ein Signal aus, um die Informationen vom Spieler an die GUI weiterzuleiten und lassen sie von der GUI verarbeiten.