Work in progress

The content of this page was not yet updated for Godot 4.2 and may be outdated. If you know how to improve this page or you can confirm that it's up to date, feel free to open a pull request.

Springen und Zerstampfen von Monstern

In diesem Teil werden wir die Fähigkeit hinzufügen, zu springen und die Monster zu zerstampfen. In der nächsten Lektion werden wir dafür sorgen, dass der Spieler stirbt, wenn ein Monster ihn am Boden trifft.

Zuerst müssen wir einige Einstellungen in Bezug auf Physik-Interaktionen ändern. Wir betreten die Welt der Physik-Layer.

Steuerung der Physik-Interaktionen

Physics-Bodies haben Zugriff auf zwei komplementäre Eigenschaften: Ebenen und Masken. Ebenen definieren auf welcher/-n Physik-Ebene(n) sich ein Objekt befindet.

Masken steuern die Ebenen, auf die ein Body hört und die er erkennt. Dies wirkt sich auf die Kollisionserkennung aus. Wenn zwei Bodies miteinander interagieren sollen, muss mindestens einer eine Maske haben, die dem anderen entspricht.

Wenn das verwirrend ist, keine Sorge, wir werden gleich drei Beispiele sehen.

Wichtig ist, dass Sie Ebenen und Masken verwenden können, um Physik-Interaktionen zu filtern, die Performance zu steuern und um zusätzliche Bedingungen in Ihrem Code überflüssig zu machen.

Standardmäßig sind alle Physics-Bodies und Flächen sowohl auf die Ebene als auch auf die Maske 1 eingestellt. Das bedeutet, dass sie alle miteinander kollidieren.

Physik-Ebenen werden durch Zahlen dargestellt, aber wir können ihnen Namen geben, um den Überblick darüber zu behalten, welche es sind.

Ebenen-Namen festlegen

Geben wir unseren Physikebenen einen Namen. Gehen Sie zu Projekt -> Projekteinstellungen.

image0

Navigieren Sie im linken Menü nach unten zu Ebenen-Namen -> 3D-Physik. Auf der rechten Seite sehen Sie eine Liste von Ebenen mit einem Feld neben jeder von ihnen. Dort können Sie ihre Namen festlegen. Nennen Sie die ersten drei Ebenen player, enemies und world.

image1

Jetzt können wir sie unseren Physik-Nodes zuweisen.

Ebenen und Masken zuweisen

Wählen Sie in der Main Szene den Ground-Node. Erweitern Sie im Inspektor den Abschnitt Kollision. Dort sehen Sie die Ebenen und Masken des Nodes als ein Raster von Buttons.

image2

Der Boden ist Teil der Welt, also soll er Teil der dritten Ebene sein. Klicken Sie auf den beleuchteten Button, um die erste Ebene auszuschalten, und schalten Sie die dritte Ebene ein. Schalten Sie dann die Maske aus, indem Sie sie anklicken.

image3

Wie bereits erwähnt, erlaubt die Eigenschaft Maske einem Node, auf die Interaktion mit anderen Physik-Objekten zu hören, aber wir brauchen ihn nicht, um Kollisionen zu bekommen. Ground muss auf nichts hören; er ist nur dazu da, um zu verhindern, dass Kreaturen herunterfallen.

Beachten Sie, dass Sie auf den "..."-Button auf der rechten Seite der Eigenschaften klicken können, um eine Liste der benannten Kontrollkästchen anzuzeigen.

image4

Als nächstes sind der Player und der Mob dran. Öffnen Sie player.tscn durch einen Doppelklick auf die Datei im Dateisystem-Dock.

Wählen Sie den Player-Node aus und setzen Sie seine Kollision -> Maske sowohl auf "enemies" als auch auf "world". Sie können die Default-Eigenschaft Ebene so lassen, wie sie ist, da die erste Ebene die Ebene "player" ist.

image5

Öffnen Sie dann die Mob-Szene durch Doppelklick auf mob.tscn und wählen Sie den Mob-Node aus.

Setzen Sie Collision -> Layer auf "enemies" und entfernen sie alle Auswahlfelder in der Collision -> Mask. So dass die Masken leer bleiben.

|image6|

Diese Einstellungen bedeuten, dass die Monster einander durchdringen. Wenn Sie möchten, dass die Monster miteinander kollidieren und aneinander entlanggleiten, schalten Sie die Maske "enemies" ein.

Bemerkung

Die Mobs brauchen die "world"-Ebene nicht zu maskieren, da sie sich nur auf der XZ-Ebene bewegen. Wir wenden keine Schwerkraft auf sie an.

Springen

Die Sprungmechanik selbst erfordert nur zwei Codezeilen. Öffnen Sie das Skript Player. Wir brauchen einen Wert, um die Stärke des Sprungs zu kontrollieren, und aktualisieren _physics_process(), um den Sprung zu erstellen.

Fügen sie am Anfang des Skripts nach der Zeile, die fall_acceleration definiert, jump_impulse ein.

#...
# Vertical impulse applied to the character upon jumping in meters per second.
@export var jump_impulse = 20

Innerhalb von _physics_process() fügen Sie den folgenden Code vor dem Codeblock move_and_slide() ein.

func _physics_process(delta):
    #...

    # Jumping.
    if is_on_floor() and Input.is_action_just_pressed("jump"):
        target_velocity.y = jump_impulse

    #...

Das ist alles, was man zum Springen braucht!

Die Methode is_on_floor() ist eine Hilfsfunktion der Klasse CharacterBody3D. Sie gibt true zurück, wenn der Body in diesem Frame mit dem Boden kollidiert ist. Das ist der Grund, warum wir die Schwerkraft auf den Player anwenden: damit wir mit dem Boden kollidieren, anstatt über ihm zu schweben wie die Monster.

Wenn der Charakter auf dem Boden steht und der Spieler auf "springen" drückt, geben wir ihm sofort eine Menge vertikaler Geschwindigkeit. In Spielen muss die Steuerung wirklich reaktionsschnell sein, und solche sofortigen Geschwindigkeitsschübe sind zwar unrealistisch, fühlen sich aber toll an.

Beachten Sie, dass die Y-Achse positiv nach oben verläuft. Das ist anders als in 2D, wo die Y-Achse positiv nach unten verläuft.

Monster zerstampfen

Fügen wir als Nächstes die Stampf-Mechanik hinzu. Wir werden den Charakter über Monster springen lassen und sie gleichzeitig töten.

Wir müssen Kollisionen mit einem Monster erkennen und sie von Kollisionen mit dem Boden unterscheiden. Dazu können wir Godots group-Tagging-Feature verwenden.

Öffnen Sie die Szene mob.tscn erneut und wählen Sie den Mob-Node. Gehen Sie zum Node-Dock auf der rechten Seite, um eine Liste der Signale zu sehen. Das Node-Dock hat zwei Tabs: Signale, die Sie bereits verwendet haben, und Gruppen, mit denen Sie den Nodes Tags zuweisen können.

Klicken Sie darauf, um ein Feld zu öffnen, in das Sie einen Tag-Namen eingeben können. Geben Sie "mob" in das Feld ein und klicken Sie auf den Hinzufügen-Button.

|image7|

Ein Icon erscheint im Szenen-Dock, um anzuzeigen, dass der Node Teil mindestens einer Gruppe ist.

|image8|

Wir können nun die Gruppe aus dem Code heraus verwenden, um Kollisionen mit Monstern von Kollisionen mit dem Boden zu unterscheiden.

Die Stampf-Mechanik programmieren

Gehen Sie zurück zum Skript Player, um den Stampf und den Sprung zu programmieren.

Am Anfang des Skripts brauchen wir eine weitere Variable, bounce_impulse. Wenn wir einen Gegner zerstampfen, wollen wir nicht unbedingt, dass der Charakter so hoch fliegt wie beim Springen.

# Vertical impulse applied to the character upon bouncing over a mob in
# meters per second.
@export var bounce_impulse = 16

Dann, nach dem Springen Codeblock, den wir oben in _physics_process() hinzugefügt haben, fügen Sie die folgende Schleife ein. Mit move_and_slide() bewegt Godot den Body manchmal mehrmals hintereinander, um die Bewegung des Charakters zu glätten. Wir müssen also eine Schleife über alle Kollisionen ausführen, die passiert sein könnten.

In jeder Iteration der Schleife prüfen wir, ob wir auf einem Mob gelandet sind. Wenn ja, töten wir ihn und hüpfen weiter.

Mit diesem Code wird die Schleife nicht durchlaufen, wenn in einem bestimmten Frame keine Kollisionen aufgetreten sind.

func _physics_process(delta):
    #...

    # Iterate through all collisions that occurred this frame
    for index in range(get_slide_collision_count()):
        # We get one of the collisions with the player
        var collision = get_slide_collision(index)

        # If the collision is with ground
        if collision.get_collider() == null:
            continue

        # If the collider is with a mob
        if collision.get_collider().is_in_group("mob"):
            var mob = collision.get_collider()
            # we check that we are hitting it from above.
            if Vector3.UP.dot(collision.get_normal()) > 0.1:
                # If so, we squash it and bounce.
                mob.squash()
                target_velocity.y = bounce_impulse
                # Prevent further duplicate calls.
                break

Das sind eine Menge neuer Funktionen. Hier finden Sie weitere Informationen über sie.

Die Funktionen get_slide_collision_count() und get_slide_collision() kommen beide von der Klasse CharacterBody3D und sind mit move_and_slide() verbunden.

get_slide_collision() gibt ein KinematicCollision3D Objekt zurück, das Informationen darüber enthält, wo und wie die Kollision stattfand. Zum Beispiel benutzen wir die Eigenschaft get_collider, um zu überprüfen, ob wir mit einem "Mob" kollidiert sind, indem wir is_in_group() auf ihn anwenden: collision.get_collider().is_in_group("mob").

Bemerkung

Die Methode is_in_group() ist auf jedem Node verfügbar.

Um zu überprüfen, ob wir auf dem Monster landen, verwenden wir das Vektor-Skalarprodukt: Vector3.UP.dot(collision.get_normal()) > 0.1. Die Kollisionsnormale ist ein 3D-Vektor, der senkrecht zu der Ebene steht, in der die Kollision stattgefunden hat. Mit Hilfe des Skalarprodukts können wir ihn mit der Aufwärtsrichtung vergleichen.

Wenn das Ergebnis des Skalarprodukts größer ist als 0, befinden sich die beiden Vektoren in einem Winkel von weniger als 90 Grad. Ein Wert, der größer als 0.1 ist, bedeutet, dass wir uns ungefähr über dem Monster befinden.

Nachdem wir die Squash- und Bounce-Logik abgearbeitet haben, beenden wir die Schleife vorzeitig mit der break-Anweisung, um weitere doppelte Aufrufe von mob.squash() zu verhindern, die sonst zu unbeabsichtigten Fehlern führen könnten, wie z.B. das mehrfache Zählen der Punkte für einen Kill.

Wir rufen eine undefinierte Funktion, mob.squash(), auf, also müssen wir sie der Mob-Klasse hinzufügen.

Öffnen Sie das Skript Mob.gd durch Doppelklick im Dateisystem-Dock. Am Anfang des Skripts wollen wir ein neues Signal namens squashed definieren. Unten können Sie die Squash-Funktion hinzufügen, mit der wir das Signal aussenden und den Mob zerstören.

# Emitted when the player jumped on the mob.
signal squashed

# ...


func squash():
    squashed.emit()
    queue_free()

Bemerkung

Wenn Sie C# verwenden, erstellt Godot automatisch die entsprechenden Ereignisse für alle Signale, die auf EventHandler enden, siehe C#-Signale.

In der nächsten Lektion werden wir das Signal verwenden, um Punkte zum Punktestand zu addieren.

Damit sollten Sie in der Lage sein, Monster zu töten, indem Sie auf sie springen. Sie können F5 drücken, um das Spiel auszuprobieren und main.tscn als Hauptszene für Ihr Projekt festzulegen.

Allerdings wird der Spieler noch nicht sterben. Daran werden wir im nächsten Teil arbeiten.