Up to date

This page is up to date for Godot 4.2. If you still find outdated information, please open an issue.

Den Spieler programmieren

In dieser Lektion fügen wir Spielerbewegungen und Animationen hinzu und richten ihn so ein, dass er Kollisionen erkennt.

Jetzt müssen wir einige Funktionen hinzufügen, die wir von einen Built-in-Node nicht enthalten sind, also fügen wir ein Skript hinzu. Klicken Sie auf den Node Player und dann auf den "Skript hinzufügen"-Button:

../../_images/add_script_button.webp

Im Fenster mit den Skripteinstellungen können Sie die Default-Einstellungen beibehalten. Klicken Sie einfach auf "Erstellen":

Bemerkung

Wenn Sie ein C#-Skript oder andere Sprachen verwenden wollen, wählen Sie die Sprache aus dem Dropdown-Menü Sprache, bevor Sie auf Erstellen klicken.

../../_images/attach_node_window.webp

Bemerkung

Wenn Sie noch nie etwas mit GDScript zu tun hatten, lesen Sie bitte Skriptsprachen, bevor Sie weitermachen.

Beginnen Sie, indem Sie die Member-Variablen deklarieren, die dieses Objekt benötigt:

extends Area2D

@export var speed = 400 # How fast the player will move (pixels/sec).
var screen_size # Size of the game window.

Wenn Sie das Schlüsselwort export für die erste Variable speed verwenden, ermöglicht Ihnen dies, dessen Wert im Inspektor einzustellen. Dies kann für Werte nützlich sein, die Sie genau wie die Built-in-Eigenschaften eines Nodes anpassen möchten. Klicken Sie auf den Node Player, und die Eigenschaft wird jetzt im Bereich mit den Skriptvariablen des Inspektors angezeigt. Denken Sie daran, wenn Sie den Wert hier ändern, wird der im Skript verwendete Wert überladen.

Warnung

Wenn Sie C# verwenden, müssen Sie die Projekt-Assemblies (neu) erstellen, wenn Sie neue Exportvariablen oder Signale sehen möchten. Dieser Build kann manuell ausgelöst werden, indem man auf den Build-Button oben rechts im Editor klickt.

../../_images/build_dotnet.webp
../../_images/export_variable.webp

Ihr Skript player.gd sollte bereits eine _ready() und eine _process()-Funktion enthalten. Wenn Sie nicht die oben gezeigte Default-Vorlage gewählt haben, erstellen Sie diese Funktionen, während Sie der Lektion folgen.

Die Funktion _ready() wird aufgerufen, wenn ein Node in den Szenenbaum eintritt, was ein guter Zeitpunkt ist, um die Größe des Spielfensters zu ermitteln:

func _ready():
    screen_size = get_viewport_rect().size

Jetzt können wir die Funktion _process() verwenden, um festzulegen, was der Spieler tun soll. _process() wird mit jedem Frame aufgerufen, deshalb werden wir es verwenden, um Elemente unseres Spiels zu aktualisieren, von denen wir erwarten, dass sie sich häufig ändern werden. Für den Spieler müssen wir Folgendes tun:

  • Auf Eingaben prüfen.

  • Sich in die angegebene Richtung bewegen.

  • Die entsprechende Animation abspielen.

Zuerst müssen wir auf Eingaben prüfen - drückt der Spieler eine Taste? Für dieses Spiel haben wir 4 Richtungseingaben zu überprüfen. Eingabeaktionen werden in den Projekteinstellungen unter "Eingabe-Zuordnung" definiert. Sie können benutzerdefinierte Ereignisse definieren und ihnen verschiedene Tasten, Mausereignisse oder andere Eingaben zuweisen. Für diese Demo werden wir die Standardereignisse verwenden, die den Pfeiltasten auf der Tastatur zugeordnet sind.

Klicken Sie auf Projekt -> Projekteinstellungen, um die Projekteinstellungen zu öffnen und wechseln dann zum Tab Eingabe-Zuordnung. Schreiben Sie "move_right" in die obere Leiste und betätigen dann den Button "Hinzufügen", um die Aktion move_right hinzuzufügen.

../../_images/input-mapping-add-action.webp

Wir müssen dieser Aktion eine Taste zuweisen. Klicken Sie auf das "+"-Icon auf der rechten Seite, um das Fenster der Ereigniskonfiguration zu öffnen.

../../_images/input-mapping-add-key.webp

Das Feld "Warte auf Eingabe..." sollte automatisch ausgewählt sein. Drücken Sie die "Rechts"-Taste auf Ihrer Tastatur, und das Menü sollte nun wie folgt aussehen.

../../_images/input-mapping-event-configuration.webp

Wählen Sie die "OK"-Button. Die Taste "rechts" ist nun mit der Aktion move_right verbunden.

Wiederholen Sie diese Schritte, um drei weitere Aktionen hinzuzufügen:

  1. move_left für die "links"-Taste.

  2. move_up für den "oben"-Taste.

  3. Und move_down für die "unten"-Taste.

Der Tab "Eingabe-Zuordnung" sollte wie folgt aussehen:

../../_images/input-mapping-completed.webp

Klicken Sie auf den "Schließen"-Button, um die Projekteinstellungen zu schließen.

Bemerkung

Wir haben jeder Eingabeaktion nur eine Taste zugewiesen, aber Sie können mehrere Tasten, Joystick-Buttons oder Maustasten derselben Eingabeaktion zuweisen.

Sie können herausfinden, ob eine Taste gedrückt wird, indem Sie Input.is_action_pressed() benutzen, welches true zurückgibt, wenn eine Taste gedrückt wird und false wenn nicht.

func _process(delta):
    var velocity = Vector2.ZERO # The player's movement vector.
    if Input.is_action_pressed("move_right"):
        velocity.x += 1
    if Input.is_action_pressed("move_left"):
        velocity.x -= 1
    if Input.is_action_pressed("move_down"):
        velocity.y += 1
    if Input.is_action_pressed("move_up"):
        velocity.y -= 1

    if velocity.length() > 0:
        velocity = velocity.normalized() * speed
        $AnimatedSprite2D.play()
    else:
        $AnimatedSprite2D.stop()

Wir fangen damit an, die velocity (Geschwindigkeit) auf (0,0) zu setzen - standardmäßig sollte sich der Spieler nicht bewegen. Dann überprüfen wir jede Eingabe und addieren/subtrahieren von der velocity, um eine Gesamtrichtung zu erhalten. Wenn Sie beispielsweise rechts und runter gleichzeitig halten, ist der resultierende velocity-Vektor (1, 1). In diesem Fall, da wir eine horizontale und vertikale Bewegung addieren, würde sich der Spieler schneller bewegen, als wenn er sich nur horizontal bewegen würde.

Wir können das verhindern, indem wir die Geschwindigkeit normieren, was bedeutet, dass wir ihre Länge auf 1 festlegen, und sie anschließend mit der gewünschten Geschwindigkeit multiplizieren. Das sorgt dafür, dass keine schnelle diagonale Bewegung mehr stattfindet.

Tipp

Wenn Sie noch nie zuvor Vektor-Mathematik verwendet haben oder eine Auffrischung benötigen, finden Sie eine Erklärung zur Vektorverwendung in Godot unter Vektormathematik. Es ist gut zu wissen, wird aber für den Rest dieses Tutorials nicht notwendig sein.

Wir überprüfen auch, ob der Spieler sich bewegt, damit wir play() oder stop() auf dem AnimatedSprite2D aufrufen können.

Tipp

$ ist eine Abkürzung für get_node(). Im obigen Code ist also $AnimatedSprite2D.play() das gleiche wie get_node("AnimatedSprite2D").play().

In GDScript gibt $ den Node im relativen Pfad zum aktuellen Node zurück, oder liefert null, wenn der Node nicht gefunden wird. Da AnimatedSprite2D ein Child des aktuellen Nodes ist, können wir $AnimatedSprite2D verwenden.

Jetzt, da wir eine Bewegungsrichtung haben, können wir die Position des Spielers aktualisieren und mit clamp() verhindern, dass er den Bildschirm verlässt. Clamping beschränkt einen Wert auf einen angegebenen Bereich. Am Ende der _process Funktion fügen wir also folgendes hinzu (stellen Sie sicher, dass es unter dem else nicht eingerückt ist):

position += velocity * delta
position = position.clamp(Vector2.ZERO, screen_size)

Tipp

Der Parameter delta in der Funktion _process () bezieht sich auf die Frame-Länge - die Zeit, die der vorherige Frame für die Fertigstellung benötigt hat. Durch die Verwendung dieses Werts wird sichergestellt, dass Ihre Bewegung auch dann konstant bleibt, wenn sich die Bildrate ändert.

Klicken Sie auf "Szene abspielen" (F6, Cmd + R unter macOS) und stellen Sie sicher, dass der Spieler sich auf dem Bildschirm in alle Richtungen bewegen kann.

Warnung

Wenn ein Fehler im "Debugger" Bereich auftaucht, der sagt

Attempt to call function 'play' in base 'null instance' on a null instance

dann bedeutet dies wahrscheinlich, dass Sie den Namen des AnimatedSprite2D-Nodes falsch geschrieben haben. Bei Node-Namen wird zwischen Groß- und Kleinschreibung unterschieden und $NodeName muss mit dem Namen übereinstimmen, den Sie im Szenenbaum sehen.

Animationen

Da sich der Spieler nun bewegen kann, müssen wir die Animation, die AnimatedSprite2D abspielt, bezüglich seiner Richtung ändern. Wir haben die "walk"-Animation, die den Spieler zeigt, wie er nach rechts geht. Diese Animation sollte horizontal gespiegelt werden, indem man die Eigenschaft flip_h für die Bewegung nach links verwendet. Wir haben auch die "up"-Animation, die vertikal mit flip_v für die Abwärtsbewegung gespiegelt werden sollte. Lassen Sie uns diesen Code am Ende der Funktion _process() einfügen:

if velocity.x != 0:
    $AnimatedSprite2D.animation = "walk"
    $AnimatedSprite2D.flip_v = false
    # See the note below about boolean assignment.
    $AnimatedSprite2D.flip_h = velocity.x < 0
elif velocity.y != 0:
    $AnimatedSprite2D.animation = "up"
    $AnimatedSprite2D.flip_v = velocity.y > 0

Bemerkung

Die booleschen Zuweisungen im obigen Code sind eine gebräuchliche Abkürzung für Programmierer. Da wir einen (booleschen) Vergleichstest durchführen und auch einen booleschen Wert zuweisen, können wir beide gleichzeitig durchführen. Betrachten Sie diesen Code im Vergleich zur obigen einzeiligen booleschen Zuweisung:

if velocity.x < 0:
    $AnimatedSprite2D.flip_h = true
else:
    $AnimatedSprite2D.flip_h = false

Spielen Sie die Szene erneut ab und überprüfen Sie, ob die Animationen in jeder Richtung korrekt sind.

Tipp

Ein allgemeiner Fehler ist hier, die Namen der Animationen falsch zu benennen. Die Animationsnamen in dem SpriteFrames-Bereich müssen mit dem, was im Code geschrieben steht, übereinstimmen. Wenn die Animation "Walk" heißt, dann muss auch ein großgeschriebenes "W" im Code stehen.

Wenn Sie sich sicher sind, dass die Bewegung korrekt funktioniert, fügen Sie diese Zeile zu _ready() hinzu, damit der Spieler zu Beginn des Spiels ausgeblendet wird:

hide()

Vorbereitung auf Kollisionen

Wir wollen, dass der Player erkennt, wann er von einem Gegner getroffen wird, aber wir haben noch keine Gegner erstellt! Das ist in Ordnung, denn wir werden Godots Signal-Funktionalität nutzen, damit es funktioniert.

Fügen Sie das Folgende am Anfang des Skripts ein. Wenn Sie GDScript verwenden, fügen Sie es nach extends Area2D ein. Wenn Sie C# verwenden, fügen Sie es nach public partial class Player : Area2D ein:

signal hit

Dies definiert ein benutzerdefiniertes Signal namens "hit", das wir von unserem Spieler aussenden lassen, wenn es mit einem Gegner kollidiert. Wir werden Area2D verwenden, um die Kollision zu erkennen. Wählen Sie den Node Player und klicken auf den "Node"-Tab neben dem Inspektor-Tab, um die Liste der Signale zu sehen, die der Spieler aussenden kann:

../../_images/player_signals.webp

Beachten Sie, dass unser benutzerdefiniertes "Hit"-Signal ebenfalls vorhanden ist! Da unsere Gegner RigidBody2D-Nodes sein werden, brauchen wir das Signal body_entered(body: Node2D). Dieses Signal wird ausgesendet, wenn ein Body den Spieler berührt. Klicken Sie auf "Verbinden..." und das Fenster "ein Signal verbinden" erscheint.

Godot wird eine Funktion mit genau diesem Namen direkt im Skript für Sie erstellen. Sie brauchen die Default-Einstellungen aktuell nicht ändern.

Warnung

Wenn Sie einen externen Texteditor verwenden (z.B. Visual Studio Code), verhindert ein Fehler derzeit, dass Godot dies tut. Sie werden dann zu Ihrem externen Editor weitergeleitet, aber die neue Funktion ist dort nicht vorhanden.

In diesem Fall müssen Sie die Funktion selbst in die Skriptdatei von Player schreiben.

../../_images/player_signal_connection.webp

Beachten Sie das grüne Icon, das anzeigt, dass ein Signal mit dieser Funktion verbunden ist; dies bedeutet nicht, dass die Funktion existiert, sondern nur, dass das Signal versucht, eine Verbindung zu einer Funktion mit diesem Namen herzustellen. Prüfen Sie also, ob die Schreibweise der Funktion genau übereinstimmt!

Fügen Sie dann diesen Code in die Funktion ein:

func _on_body_entered(body):
    hide() # Player disappears after being hit.
    hit.emit()
    # Must be deferred as we can't change physics properties on a physics callback.
    $CollisionShape2D.set_deferred("disabled", true)

Jedes Mal, wenn ein Gegner den Spieler trifft, wird das Signal ausgesendet. Wir müssen die Kollision des Spielers deaktivieren, damit das hit-Signal nicht mehr als einmal ausgelöst wird.

Bemerkung

Das Deaktivieren der Kollisions-Shape während der Kollisionsberechnung der Engine kann einen Fehler auslösen. Die Verwendung von set_deferred() weist Godot an, mit dem Deaktivieren der Shape bis zu einem sicheren Zeitpunkt zu warten.

Das letzte was wir tun müssen ist eine Funktion hinzuzufügen, die wir aufrufen können, um den Spieler beim erneuten Start des Spiels zurückzusetzen.

func start(pos):
    position = pos
    show()
    $CollisionShape2D.disabled = false

Da der Spieler nun funktioniert, werden wir in der nächsten Lektion an dem Gegner arbeiten.