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.

Gestaltung der Mob-Szene

In diesem Teil werden Sie die Monster programmieren, die wir Mobs nennen werden. In der nächsten Lektion werden wir sie zufällig im Spielbereich spawnen.

Lassen Sie uns die Monster selbst in einer neuen Szene entwerfen. Die Struktur des Nodes wird ähnlich wie bei der Szene player.tscn sein.

Erstellen Sie eine Szene mit einem CharacterBody3D-Node als Root. Nennen Sie ihn Mob. Fügen Sie einen Child-Node Node3D hinzu, nennen Sie ihn Pivot. Ziehen Sie die Datei mob.glb aus dem Dateisystem-Dock per Drag&Drop auf den Pivot, um das 3D-Modell des Monsters in die Szene einzufügen.

../../_images/drag_drop_mob.webp

Sie können den neu erstellten Mob-Node in Character umbenennen.

image0

Wir brauchen eine Kollisions-Shape für unseren Body, damit er funktioniert. Klicken Sie mit der rechten Maustaste auf den Node Mob, den Root der Szene, und klicken Sie auf Child-Node hinzufügen.

image1

Fügen Sie ein CollisionShape3D hinzu.

image2

Weisen Sie im Inspektor der Eigenschaft Shape eine BoxShape3D zu.

../../_images/08.create_box_shape3D.jpg

Wir sollten die Größe ändern, damit die Shape besser an das 3D-Modell passt. Sie können dies interaktiv tun, indem sie auf die orangen Punkte klicken und ziehen.

Die Box sollte den Boden berühren und ein wenig dünner als das Modell sein. Physik-Engines arbeiten so, dass eine Kollision stattfindet, wenn die Kollisionskugel des Spielers auch nur eine Ecke des Kastens berührt. Wenn die Kiste im Vergleich zum 3D-Modell nur ein wenig zu groß ist, könnte man trotz sichtbarer Entfernung von einem Monster sterben, was das Spiel auf die Spieler unfair wirken lässt.

image4

Beachten Sie, dass meine Box höher ist als das Monster. Das ist in diesem Spiel in Ordnung, da wir von oben auf die Szene herabschauen und eine feste Perspektive verwenden. Die Collision-Shapes müssen nicht genau mit dem Modell übereinstimmen. Ihre Form und Größe sollte davon abhängig sein wie sie sich beim Testen des Spiels anfühlen.

Monster außerhalb des Bildschirms entfernen

Wir werden in regelmäßigen Abständen Monster in der Spielebene spawnen. Wenn wir nicht aufpassen, könnte ihre Anzahl ins Unendliche steigen, und das wollen wir nicht. Jede Mob-Instanz hat sowohl Speicher- als auch Rechenkosten, und die wollen wir nicht zahlen, wenn der Mob außerhalb des Bildschirms ist.

Sobald ein Monster den Bildschirm verlässt, brauchen wir es nicht mehr, also sollten wir es löschen. Godot hat einen Node, der erkennt, wenn Objekte den Bildschirm verlassen, VisibleOnScreenNotifier3D, und wir werden ihn benutzen, um unsere Mobs zu zerstören.

Bemerkung

Wenn Sie ein Objekt immer wieder instanziieren, gibt es eine Technik, mit der Sie die Kosten für das ständige Erstellen und Zerstören von Instanzen vermeiden können: das Pooling. Sie besteht darin, ein Array von Objekten im Voraus zu erstellen und sie immer wieder zu verwenden.

Wenn Sie mit GDScript arbeiten, müssen Sie sich darüber keine Gedanken machen. Der Hauptgrund für die Verwendung von Pools ist die Vermeidung von Freezes bei Garbage-Collected-Sprachen wie C# oder Lua. GDScript verwendet eine andere Technik zur Verwaltung des Speichers, das Reference Counting, bei der diese Einschränkung nicht gilt. Sie können hier mehr darüber erfahren: Speicher-Management.

Wählen Sie den Mob-Node und fügen Sie einen Child-Node VisibleOnScreenNotifier3D hinzu. Ein weiterer Kasten, diesmal rosa, erscheint. Wenn diese Box den Bildschirm vollständig verlässt, sendet der Node ein Signal aus.

image5

Ändern Sie die Größe der Box mithilfe der orangen Punkte, bis sie das gesamte 3D-Modell abdeckt.

|image6|

Die Bewegung des Mobs programmieren

Wir wollen nun die Bewegung des Monsters umsetzen. Wir werden dies in zwei Schritten tun. Zuerst schreiben wir ein Skript für den Mob, das eine Funktion zur Initialisierung des Monsters definiert. Dann programmieren wir den zufälligen Spawn-Mechanismus in der Szene main.tscn und rufen die Funktion von dort aus auf.

Hängen Sie ein Skript an den Mob an.

|image7|

Hier ist der Code für die Bewegung, mit dem wir beginnen. Wir definieren zwei Eigenschaften, min_speed und max_speed, um einen zufälligen Geschwindigkeitsbereich zu definieren, den wir später benutzen werden, um CharacterBody3D.velocity zu definieren.

extends CharacterBody3D

# Minimum speed of the mob in meters per second.
@export var min_speed = 10
# Maximum speed of the mob in meters per second.
@export var max_speed = 18


func _physics_process(_delta):
    move_and_slide()

Ähnlich wie beim Spieler bewegen wir den Mob in jedem Frame, indem wir die Funktion CharacterBody3D.move_and_slide() aufrufen. Dieses Mal aktualisieren wir die Geschwindigkeit nicht bei jedem Frame; wir wollen, dass sich das Monster mit einer konstanten Geschwindigkeit bewegt und den Bildschirm verlässt, auch wenn es auf ein Hindernis treffen sollte.

Wir müssen eine weitere Funktion definieren, um die CharacterBody3D.velocity zu berechnen. Diese Funktion wird das Monster in Richtung des Spielers drehen und sowohl seinen Bewegungswinkel als auch seine Geschwindigkeit zufällig bestimmen.

Die Funktion nimmt als Argumente eine start_position, die Spawnposition des Mobs und die player_position.

Wir positionieren den Mob an der start_position und drehen ihn mit der Methode look_at_from_position() in Richtung des Spielers. Der Winkel wird durch eine zufällige Drehung um die Y-Achse bestimmt. Unten gibt randf_range() einen Zufallswert zwischen -PI / 4 und PI / 4 im Bogenmaß aus.

# This function will be called from the Main scene.
func initialize(start_position, player_position):
    # We position the mob by placing it at start_position
    # and rotate it towards player_position, so it looks at the player.
    look_at_from_position(start_position, player_position, Vector3.UP)
    # Rotate this mob randomly within range of -45 and +45 degrees,
    # so that it doesn't move directly towards the player.
    rotate_y(randf_range(-PI / 4, PI / 4))

Wir haben eine zufällige Position, jetzt brauchen wir eine zufällige Geschwindigkeit: random_speed. randi_range() ist nützlich, da es zufällige int Werte liefert, und wir werden min_speed und max_speed benutzen. random_speed ist nur ein Integer, und wir benutzen ihn nur, um unsere CharacterBody3D.velocity zu multiplizieren. Nachdem random_speed angewandt wurde, rotieren wir den CharacterBody3D.velocity-Vector3 in Richtung des Spielers.

func initialize(start_position, player_position):
    # ...

    # We calculate a random speed (integer)
    var random_speed = randi_range(min_speed, max_speed)
    # We calculate a forward velocity that represents the speed.
    velocity = Vector3.FORWARD * random_speed
    # We then rotate the velocity vector based on the mob's Y rotation
    # in order to move in the direction the mob is looking.
    velocity = velocity.rotated(Vector3.UP, rotation.y)

Verlassen des Bildschirms

Wir müssen die Mobs noch zerstören, wenn sie den Bildschirm verlassen. Um dies zu tun, verbinden wir das screen_exited-Signal des VisibleOnScreenNotifier3D-Nodes mit dem Mob.

Kehren Sie zum 3D-Viewport zurück, indem sie auf das Label 3D am oberen Rand des Editors klicken. Sie können auch Strg + F2`(:kbd:`Alt + 2 unter macOS) drücken.

|image8|

Wählen Sie den VisibleOnScreenNotifier3D-Node und navigieren Sie auf der rechten Seite der Benutzeroberfläche zum Node-Dock. Doppelklicken Sie auf das Signal screen_exited().

|image9|

Verbinden Sie das Signal mit dem Mob

|image10|

Dies bringt Sie zurück zum Skript-Editor und fügt eine neue Funktion für Sie hinzu, _on_visible_on_screen_notifier_3d_screen_exited(). Von dort aus rufen Sie die Methode queue_free() auf. Diese Funktion zerstört die Instanz, die sie aufruft.

func _on_visible_on_screen_notifier_3d_screen_exited():
    queue_free()

Unser Monster ist endlich bereit, das Spiel zu betreten! I'm nächsten Teil werden Sie Monster im Spiellevel spawnen.

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

extends CharacterBody3D

# Minimum speed of the mob in meters per second.
@export var min_speed = 10
# Maximum speed of the mob in meters per second.
@export var max_speed = 18

func _physics_process(_delta):
    move_and_slide()

# This function will be called from the Main scene.
func initialize(start_position, player_position):
    # We position the mob by placing it at start_position
    # and rotate it towards player_position, so it looks at the player.
    look_at_from_position(start_position, player_position, Vector3.UP)
    # Rotate this mob randomly within range of -90 and +90 degrees,
    # so that it doesn't move directly towards the player.
    rotate_y(randf_range(-PI / 4, PI / 4))

    # We calculate a random speed (integer)
    var random_speed = randi_range(min_speed, max_speed)
    # We calculate a forward velocity that represents the speed.
    velocity = Vector3.FORWARD * random_speed
    # We then rotate the velocity vector based on the mob's Y rotation
    # in order to move in the direction the mob is looking.
    velocity = velocity.rotated(Vector3.UP, rotation.y)

func _on_visible_on_screen_notifier_3d_screen_exited():
    queue_free()