Ergebnis und Wiederholung

In diesem Teil werden wir einen Punktestand, Hintergrundmusik und die Möglichkeit, das Spiel neu zu starten, hinzufügen.

Wir müssen den aktuellen Punktestand in einer Variabel speichern und ihn mit einem minimalen Interface auf dem Bilschirm anzeigen. Wir werden eine einfache Beschriftung dafür benutzen.

Fügen Sie in der Hauptszene ein neues Control Node als Unternode von Main hinzu und benennen Sie es zu UserInterface um. Sie werden automatisch zu dem 2D-Bildschirm gebracht, wo Sie ihr User Interface (UI), auch Benutzeroberfläche genannt, bearbeiten können.

Fügen Sie ein Label Node hinzu und benennen Sie es zu ScoreLabel um.

image0

Legen Sie im Inspektor den Text des Labels auf einen Platzhalter fest, wie "Punkte: 0".

image1

Außerdem ist der Text standardmäßig weiß, so wie der Hintergrund unseres Spiels. Wir müssen seine Farbe ändern um ihn im Spiel sehen zu können.

Scrollen Sie nach unten bis zu Theme Overrides, öffnen Sie die Colors Optionen und klicken Sie auf das schwarze Kästchen neben Font Color, um die Farbe des Textes zu verändern.

image2

Wählen sie einen dunklen Farbton, damit er sich gut von der 3D-Szene abhebt.

image3

Klicken Sie schließlich auf den Text im Ansichtsfenster und ziehen Sie ihn um ihn von der linken oberen Ecke zu entfernen.

image4

Das UserInterface Node erlaubt es uns unsere Benutzeroberfläche in einem Zweig des Szenenbaums zu gruppieren und ihr eine Motiv Ressource zuzuweisen, die sich auf alle Unternodes überträgt. Wir werden uns dies zu Nutze machen um die Schriftart des Spiels zu ändern.

Ein UI-Thema erstellen

Wählen Sie erneut das UserInterface Node aus. Erstellen Sie im Inspektor eine neue Motiv-Ressource unter Theme -> Theme.

image5

Klicken Sie auf die neu erstellte Ressource, um den Motiv-Editor im unteren Panel zu öffnen. Er gibt Ihnen eine Vorschau wie alle integrierten UI-Widgets mit Ihrer Motiv-Ressource aussehen werden.

|image6|

Standardmäßig hat ein Motiv nur eine Eigenschaft, die Default Font (Standartschriftart).

Siehe auch

Sie können in der Motiv-Ressource mehr Eigenschaften hinzufügen um komplexe Benutzeroberflächen zu gestalten, aber das würde den Rahmen dieser Serie sprengen. Um mehr über das Erstellen und Bearbeiten von Motiven zu erfahren, siehe Einführung in GUI Skinning.

Klicken sie auf die Default Font Eigenschaft und erstellen Sie eine neue DynamicFont.

|image7|

Erweitern Sie die DynamicFont indem sie darauf klicken, und erweitern Sie dann den Abschnitt Font. Dort sehen Sie ein leeres Feld mit der Aufschrift Font Data.

|image8|

Diese Eigenschaft erwartet eine Schriftdatei, wie Sie sie auf Ihrem Computer haben. DynamicFont unterstützt folgende Formate:

  • TrueType (.ttf)

  • OpenType (.otf)

  • Web Open Font Format 1 (.woff)

  • Web Open Font Format 2 (.woff2, ab Godot 3.5)

Erweitern Sie den fonts Ordner im Datensystem Panel und ziehen Sie die Datei Monsterrat-Medium.tff, die wir in das Projekt aufgenommen haben, in das Font Data Feld. Der Text in der Motiv-Vorschau sollte nun wieder erscheinen.

Der Text ist ein wenig klein. Legen sie die Einstellung Settings -> Size auf 22 Pixel fest, um den Text zu vergrößern.

|image9|

Behalten Sie den Überblick über den Spielstand

Lassen Sie uns als nächstes an dem Punktestand arbeiten. Fügen sie dem ScoreLabel ein Skript hinzu und definieren sie die Variable ``Score`.

extends Label

var score = 0

Die Punktzahl sollte sich jedes mal wenn wir ein Monster zerquetschen um 1 erhöhen. Wir können ihr squashed Signal verwenden um zu wissen wann das passiert. Allerdings können wir, da wir die Monster über Code Instanziieren, das Signal nicht in dem Editor verbinden.

Stattdessen müssen wir jedes Mal wenn ein Monster erzeugt wird eine neue Verbindung über Code erstellen.

Öffnen Sie das Skript Main.gd. Wenn es noch geöffnet ist, können Sie auf seinen Namen in der linken Spalte des Skripteditors klicken.

|image10|

Alternativ können Sie die Datei Main.gd in dem Datensystem Panel doppelt anklicken.

Fügen Sie am Ende der Funktion _on_MobTimer_timeout() die folgende Zeile ein.

func _on_MobTimer_timeout():
    #...
    # We connect the mob to the score label to update the score upon squashing one.
    mob.connect("squashed", $UserInterface/ScoreLabel, "_on_Mob_squashed")

Diese Zeile verursacht dass wenn ein Mob das squashed Signal sendet, das ScoreLabel Node es empfangen wird und die Funktion _on_Mob_squashed() aufruft.

Gehe zurück zum Skript ScoreLabel.gd, um dort die Callback-Funktion _on_Mob_squashed() zu definieren.

Dort wird die Punktzahl erhöht und der angezeigte Text aktualisiert.

func _on_Mob_squashed():
    score += 1
    text = "Score: %s" % score

Die zweite Zeile nutzt den Wert der score Variable um den Platzhalter %s zu ersetzen. Wenn Sie diese Funktion verwenden, wandelt Godot die Werte automatisch in Text um, was für die Ausgabe von Text in Beschriftungen oder über die Funktion print() praktisch ist.

Siehe auch

Hier können sie mehr über Zeichenketten-Formantierung lernen: GDScript Zeichenketten formatieren.

Sie können nun das Spiel testen und ein paar Gegner zerquetschen, um zu sehen wie die Punktzahl ansteigt.

|image11|

Bemerkung

In einem komplexen Spiel möchten Sie vielleicht die Benutzeroberfläche vollständig von der Spielwelt trennen. In diesem Fall würden Sie die Punktzahl nicht in der Beschriftung aufbewahren. Stattdessen sollten Sie sie in einem seperaten dedizierten Objekt speichern. Beim Prototyping oder wenn ihr Projekt simpel ist, ist es in Ordnung den Code auch simpel zu halten. Programmieren ist immer ein Balanceakt.

Das Spiel erneut versuchen

Wir werden nun die Möglichkeit hinzufügen, nach dem Tod das Spiel neu zu starten. Wenn der Spieler stirbt, werden wir eine Nachricht auf dem Bildschirm anzeigen und auf eine Eingabe warten.

Kehren Sie zur Szene Main zurück, wählen Sie das UserInterface Node, fügen Sie ein ColorRect als Unternode hinzu und benennen Sie es um zu Retry. Dieses Node füllt ein Rechteck mit einer einheitlichen Farbe aus und dient als Overlay, um den Bildschirm zu verdunkeln.

Um es über das gesamte Anichtsfenster zu spannen, können Sie das Layout Menü in der Symbolleiste verwenden.

|image12|

Öffnen Sie es und wenden Sie den Befehl Full Rect an.

|image13|

Wie Sie sehen, sehen Sie nichts. Also fast nichts: nur die vier grünen Pins bewegen sich zu den Ecken des Auswahlfeldes.

|image14|

Das liegt daran dass UI-Nodes (alle mit einem grünen Symbol) mit Ankern und Rändern relativ zum Begrenzungsrahmen ihrer Übernodes arbeiten. Hier ist das UserInterface Node nicht sonderlich groß und das Retry Node ist dadurch begrenzt.

Wählen sie das UserInterface Node aus un wenden sie auch hier Layout -> Full Rect an. Das Retry Node sollte sich nun über das gesamte Ansichtsfenster erstrecken.

Lassen Sie uns die Farbe ändern so dass sie den Spielbereich verdunkelt. Wählen Sie das Retry Node aus und setzen Sie in dem Inspektor die Eigenschaft Color auf einen sowohl dunklen als auch transparenten Wert. Um dies zu tun, bewegen Sie den Regler A nach links. Er kontrolliert den Alphakanal der Farbe, in anderen Worten ihre Deckkraft.

|image15|

Als nächstes fügen Sie ein Label als Unternode von Retry hinzu und geben Sie ihm den Text "Drücken Sie Enter, um neu zu starten."

|image16|

Um es in die Mitte des Bildschirms verschieben und dort zu Ankern, wenden Sie Layout -> Center an.

|image17|

Die Wiederholungsoption programmieren

Kommen wir nun zu dem Code der das Retry Node zeigt und verbirgt wenn der Spieler stirbt oder das Spiel neu startet.

Öffnen Sie das Skript Main.gd. Zuerst wollen wir das Overlay zu Beginn des Spiels verbergen. Fügen Sie diese Zeile in die _ready() Funktion ein.

func _ready():
    #...
    $UserInterface/Retry.hide()

Wenn der Spieler getroffen wird, zeigen wir das Overlay.

func _on_Player_hit():
    #...
    $UserInterface/Retry.show()

Wenn das Retry Node Sichtbar ist, müssen wir auf die Eingaben des Spielers hören und das Spiel neu starten, sobald er Enter drückt. Dazu verwenden wir die integrierte Callback-Funktion _unandled_input().

Wenn der Spieler die vordefinierte Eingabeaktion ui_accept drückt und Retry Sichtbar ist, laden wir die akuelle Szene neu.

func _unhandled_input(event):
    if event.is_action_pressed("ui_accept") and $UserInterface/Retry.visible:
        # This restarts the current scene.
        get_tree().reload_current_scene()

Die Funktion get_tree() ermöglicht uns den Zugriff auf das globale Object SceneTree, mit dem wir die akuelle Szene neu laden und starten können.

Musik hinzufügen

Um Musik hinzuzufügen, die kontinuirlich im Hintergrund spielt, werden wir eine weitere Funktion von Godot verwenden: autoloads.

Um Audio abzuspielen, müssen Sie lediglich ein AudioStreamPlayer Node zu Ihrer Szene hinzufügen und eine Audiodatei an dieses anhängen. Wenn sie die Szene starten, kann diese automatisch abgespielt werden. Wenn Sie die Szene dann allerdings so neu laden, wie wir es momentan tun um unser Spiel neu zu starten, werden Audionodes auch zurückgesetzt und die Musik fängt wieder von Vorne an.

Sie können die Autoload Funktionalität verwenden, damit Godot automatisch ein Node oder eine Szene bei Spielstart instanziiert, außerhalb der aktuellen Szene. Sie können sie auch verwenden, um global zugängliche Objekte zu erstellen.

Erstellen sie eine neue Szene indem Sie zu dem Szene Menü navigieren und auf Neue Szene klicken.

|image18|

Klicken Sie auf den Anderer Node Knopf, erstellen Sie einen AudioStreamPlayer und benennen sie ihn zu MusicPlayer um.

image19

Wir haben einen Musik-Soundtrack in dem Ordner art/ beigefügt, namens House In a Forest Loop.ogg. Klicken und ziehen sie diese Datei in die Eigenschaft Stream im Inspektor. Schalten Sie außerdem Autoplay ein, damit die Musik automatisch zu Beginn des Spiels abgespielt wird.

image20

Anschließend speichern Sie die Szene unter dem Namen MusicPlayer.tscn.

Sie müssen es als Autoload registrieren. Gehen Sie zu Projekt -> Projekteinstellungen... und klicken sie auf den Autoload-Reiter.

Geben Sie in das Feld Pfad den Pfad zu Ihrer Szene ein. Klicken Sie das Order Symbol um die Dateiauswahl zu öffnen und doppelklicken Sie auf MusicPlayer.tscn. Dann klicken Sie auf den Hinzufügen Knopf auf der rechten Seite um das Node zu registrieren.

image21

Wenn sie nun das Spiel starten, wird die Musik automatisch abgespielt. Und selbst wenn Sie verlieren und neu anfangen, läuft sie weiter.

Bevor wir diese Lektion abschließen, werfen wir noch einen kurzen Blick darauf was im inneren des Spiels vorgeht. Wenn Sie es starten, ändert sich ihr Szene Panel und sie erhalten zwei Registerkarten: Fern und Lokal.

image22

Die Registerkarte Fern ermöglicht es Ihnen den Szenenbaum Ihres laufenden Spiels zu visualisieren. Dort sehen Sie das Main Node und alles was die Szene beinhalted, sowie Instanziierte Mobs darunter.

image23

Ganz oben befinden sich der automatisch geladenene MusicPlayer und ein root Node, welches das Ansichtsfenster des Spiels ist.

Und das war's für diese Lektion. In dem nächsten Teil, fügen wir eine Animation hinzu, damit das Spiel schöner aussieht und sich auch so anfühlt.

Hier ist das vollständige Main.gd Skript als Referenz.

extends Node

export (PackedScene) var mob_scene


func _ready():
    randomize()
    $UserInterface/Retry.hide()


func _unhandled_input(event):
    if event.is_action_pressed("ui_accept") and $UserInterface/Retry.visible:
        get_tree().reload_current_scene()


func _on_MobTimer_timeout():
    var mob = mob_scene.instance()

    var mob_spawn_location = get_node("SpawnPath/SpawnLocation")
    mob_spawn_location.unit_offset = randf()

    var player_position = $Player.transform.origin
    mob.initialize(mob_spawn_location.translation, player_position)

    add_child(mob)
    mob.connect("squashed", $UserInterface/ScoreLabel, "_on_Mob_squashed")


func _on_Player_hit():
    $MobTimer.stop()
    $UserInterface/Retry.show()