Coding the player

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

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

../../_images/add_script_button.png

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

Bemerkung

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

../../_images/attach_node_window.png

Bemerkung

Wenn du noch nie etwas mit GDScript zu tun hattest, lese bitte zuerst Skriptsprachen.

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 es Ihnen dessen Wert im Inspektor einzustellen. Dies kann für Werte nützlich sein, die Sie wie die integrierten Eigenschaften eines Nodes anpassen möchten. Klicken Sie auf den Node Player, und die Eigenschaft wird jetzt im Bereich "Script Variables" des Inspektors angezeigt. Denken Sie daran, wenn Sie den Wert hier ändern, wird der im Skript verwendete Wert überschrieben.

Warnung

Wenn Sie C# verwenden, müssen Sie die Projekt-Bausteine neu übersetzen, um neue Exportvariablen oder Signale sichtbar zu machen. Das kann manuell durch einen Klick auf das Wort "Mono" im unteren Fensterbereich geschehen, es wird das Mono-Panel eingeblendet und ein anschließender Klick auf "Build Project" führt die Aktion aus.

../../_images/export_variable.png

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 Eingabe prüfen.

  • 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.

Klicke auf Projekt -> Projekteinstellungen, um die Projekteinstellungen zu öffnen und wechsle dann zum Reiter Eingabe-Zuordnung. Schreibe "move_right" in die obere Leiste und betätige dann den Knopf "Hinzufügen", um die Aktion move_right hinzuzufügen.

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

Wir müssen eine Taste zu der Aktion hinzufügen, indem wir auf rechts das "+" Symbol klicken und dann "Taste" aus dem Menü auswählen. In den Dialog wird dann die entsprechende Taste eingegeben. Drücken Sie den Pfeil nach rechts auf der Tastatur und bestätigen dann mit einem Klick auf "Ok".

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

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

  1. move_left für den Pfeil nach links.

  2. move_up für den Pfeil nach oben.

  3. Und move_down für den Pfeil nach unten.

Die Registerkarte "Eingabe-Zuordnung" sollte wie folgt aussehen:

../../_images/input-mapping-completed.png

Klicken Sie auf die Schaltfläche "Schließen", um die Projekteinstellungen zu schließen.

Bemerkung

Wir haben jeder Eingabeaktion nur eine Taste zugewiesen, aber Sie können mehrere Tasten, Joystick-Tasten 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
        $AnimatedSprite.play()
    else:
        $AnimatedSprite.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 normalisieren, was bedeutet, dass wir ihre Länge auf 1 festlegen, anschließend multiplizieren wir mit der gewünschten Geschwindigkeit. 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 Vektor-Mathematik. Es ist gut zu wissen, wird aber für den Rest dieser Anleitung nicht notwendig sein.

We also check whether the player is moving so we can call play() or stop() on the AnimatedSprite.

Tipp

$ ist eine Abkürzung für get_node(). Im obigen Code ist $AnimatedSprite.play() dasselbe wie get_node("AnimatedSprite").play().

In GDScript, $ returns the node at the relative path from the current node, or returns null if the node is not found. Since AnimatedSprite is a child of the current node, we can use $AnimatedSprite.

Jetzt, da wir eine Bewegungsrichtung haben, können wir die Position des Spielers aktualisieren und mit clamp() verhindern, dass er den Bildschirm verlässt. clamp() beschränkt dabei einen Wert auf einen angegebenen Bereich. Am Ende der _process Funktion fügen wir also folgendes hinzufügen:

position += velocity * delta
position.x = clamp(position.x, 0, screen_size.x)
position.y = clamp(position.y, 0, screen_size.y)

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

this likely means you spelled the name of the AnimatedSprite node wrong. Node names are case-sensitive and $NodeName must match the name you see in the scene tree.

Animationen

Now that the player can move, we need to change which animation the AnimatedSprite is playing based on its direction. We have the "walk" animation, which shows the player walking to the right. This animation should be flipped horizontally using the flip_h property for left movement. We also have the "up" animation, which should be flipped vertically with flip_v for downward movement. Let's place this code at the end of the _process() function:

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

Bemerkung

Die booleschen Zuweisungen im obigen Code sind eine gebräuchliche Abkürzung für Programmierer. Da wir einen Vergleichstest (boolescher Wert) 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:
    $AnimatedSprite.flip_h = true
else:
    $AnimatedSprite.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.

Spielen Sie die Szene noch einmal ab und überprüfen, ob die Animationen in jede Richtung korrekt sind. 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 Feind getroffen wird, aber wir haben uns noch keine Feinde erstellt! Das ist in Ordnung, denn wir werden Godots Signal-Funktionalität nutzen, damit es funktioniert.

Fügen Sie folgendes oben im Skript nach extends Area2D hinzu:

signal hit

Dies definiert ein benutzerdefiniertes Signal namens "hit", das wir von unserem Spieler aussenden (send out) 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 die Registerkarte "Node" neben der Registerkarte Inspektor, um die Liste der Signale zu sehen, die der Spieler ausgeben kann:

../../_images/player_signals.png

Beachten Sie, dass unser benutzerdefiniertes "hit"-Signal auch da ist! Da unsere Feinde RigidBody2D Nodes sein werden, brauchen wir das Signal body_entered(body: Node); dieses wird ausgesendet, wenn ein Körper den Spieler berührt. Klicken Sie auf "Verbinden..." und dann, im "Signal verbinden"-Fenster, wieder auf "Verbinden". Wir müssen keine dieser Einstellungen ändern - Godot erstellt automatisch eine Funktion im Skript des Spielers. Diese Funktion wird immer dann aufgerufen, wenn das Signal ausgelöst wird - sie handhabt das Signal.

../../_images/player_signal_connection.png

Beachten Sie das grüne Symbol, was anzeigt, dass das Signal mit dieser Funktion verbunden ist. Fügen Sie diesen Code zu der Funktion hinzu:

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

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

Bemerkung

Das Deaktivieren der Kollisionsform während der Kollisionsberechnung der Engine kann einen Fehler auslösen. Die Verwendung von set_deferred() weist Godot an, mit dem Deaktivieren der Form 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.