Feindliche KI: Jage einen Spieler ohne Navigation2D und finde den Pfad A *

Ein Spiel erstellen, in dem Feinde einen Spieler jagen müssen? Alles beginnt mit einem einfachen - wir lassen den Feind zum Spieler rennen. Aber was passiert, wenn es sich hinter einem Baum oder um die Ecke einer Mauer befindet? Nun, jetzt wird der Feind ziemlich dumm aussehen - er wird auf ein Objekt stoßen und an Ort und Stelle fingern. Nicht sehr gut!

Um dieses Problem zu lösen, können Sie die in Godot integrierten Navigation2D- oder AStar-Knoten verwenden ( hier ist das GDQuest-Lernprogramm für beide Knoten ). Aber in Helms of Fury haben wir eine andere Methode verwendet, die für unser Spiel hervorragend funktioniert hat, und wir möchten sie in diesem Tutorial teilen. So sieht es aus:


An die Arbeit gehen


Wir gehen davon aus, dass Sie Feinde als KinematicBody2D-Objekte erstellen und eine Zustandsmaschine verwenden, um deren Zustände zu steuern. Nicht sicher, was eine Zustandsmaschine ist? Ich mag diesen Artikel, in dem Zustandsautomaten und ihre Verwendung erklärt werden. Hier ist ein weiterer Artikel über die Implementierung einfacher Zustandsmaschinen in Godot .

Beginnen wir mit einem einfachen Verfolgungszustand für einen dummen Feind, der nur zu seinem Ziel rennt und irgendwo auf dem Weg stecken bleibt:

# ChaseState.gd

func _init(enemy, params):
  enemy.dir = (enemy.target.position - enemy.position).normalized()

func _physics_process(delta):
  var motion = enemy.dir * enemy.speed
  enemy.move_and_slide(motion)

Geruchsspuren


Um den Zustand zu verbessern, werden wir den Spieler zwingen, beim Bewegen eine Spur von seinen vorherigen Positionen zu hinterlassen. Wenn der Feind den Spieler nicht sieht, prüft er dank dessen, ob eine seiner früheren Positionen sichtbar ist, und folgt ihnen dann zum Spieler. Da dies ähnlich ist, wie ein Hund einen Fußabdruck nimmt, nennen wir es einen Geruchsfußabdruck.

Damit die Geruchsverfolgung funktioniert, müssen wir dem Player den Timer-Knoten hinzufügen, ihn automatisch starten und wait_time einstellen (wir haben 0,1s verwendet) und dann den Code hinzufügen, damit der Player am Ende des Countdowns einen Geruch hinterlässt.

# Player.gd
extends KinematicBody2D

const scent_scene = preload("res://Player/Scent.tscn")

var scent_trail = []

func _ready():
  $ScentTimer.connect("timeout", self, "add_scent")

func add_scent():
  var scent      = scent_scene.instance()
  scent.player   = player
  scent.position = player.position

  Game.level.effects.add_child(scent)
  scent_trail.push_front(scent)

Dann müssen Sie die verlassene Scent.tscn selbst hinzufügen. Dies ist eine einfache Node2D-Szene, die einen Timer enthält, damit der Geruch abläuft.

# Player.gd
extends KinematicBody2D

const scent_scene = preload("res://Player/Scent.tscn")

var scent_trail = []

func _ready():
  $ScentTimer.connect("timeout", self, "add_scent")

func add_scent():
  var scent      = scent_scene.instance()
  scent.player   = player
  scent.position = player.position

  Game.level.effects.add_child(scent)
  scent_trail.push_front(scent)

Wenn Sie möchten, dass Gerüche während des Debuggens sichtbar sind, können Sie ihnen einen ColorRect-Knoten hinzufügen und ihn dann einfach ausblenden. Wenn Sie dies getan haben, werden Sie feststellen, dass beim Laufen hinter dem Spieler eine Spur von Geruch vorhanden ist.

Jetzt müssen wir die Feinde der inneren Bluthunde wecken, damit sie diesen neuen Gerüchen folgen, wenn sie den Spieler nicht sehen. Dazu müssen wir den Feinden RayCast2D-Knoten hinzufügen und in Godot Physik-Ebenen einrichten, damit der Strahl weiß, womit er Kollisionen verursachen kann.

Physikschichten


Um Physik-Ebenen in Godot einzurichten, müssen Sie im oberen Menü auf Projekt und dann auf Projekteinstellungen klicken, dann zum Abschnitt Ebenennamen in der unteren linken Ecke gehen und dann 2D-Physik auswählen.


Gib ihnen einen passenden Namen. Gehen Sie danach zu den Objekten im Spiel und erweitern Sie Kollision in der Seitenleiste des Eigenschafteninspektors. Klicken Sie dann auf ··, um sie zuzuweisen. Für Objekte müssen sie als Ebenen zugewiesen werden.


Nachdem Sie den Objekten die Physik-Ebenen zugewiesen haben, müssen Sie die RayCast2D der Feinde so ändern, dass die Ebenen überprüft werden, durch die sich die Feinde nicht bewegen können (in unserem Fall ist dies fest, Objekt, Kiste, Loch, Tor geschlossen).

Nach dem Einrichten der Physik-Ebenen besteht der letzte Schritt darin, den Status der Verfolgung zu ändern.

# ChaseState.gd

func _init(enemy, params):
  chase_target()

func chase_target():
  var look     = enemy.get_node("RayCast2D")
  look.cast_to = (enemy.target.position - enemy.position)
  look.force_raycast_update()

  # if we can see the target, chase it
  if !look.is_colliding():
    enemy.dir = look.cast_to.normalized()

  # or chase first scent we can see
  else:
    for scent in enemy.target.scent_trail:
      look.cast_to = (scent.position - enemy.position)
      look.force_raycast_update()

      if !look.is_colliding():
        enemy.dir = look.cast_to.normalized()
        break

func _physics_process(delta):
  var motion = enemy.dir * enemy.speed
  enemy.move_and_slide(motion)

Wenn der Feind in den Verfolgungszustand eintritt, beginnt er zu versuchen, den Spieler neu zu besetzen, und wenn ihm nichts im Weg steht, wird er ihm folgen! Wenn etwas unterwegs ist, geht er zu den Geruchsspuren und versucht, jeden von ihnen neu zu wirken, bis er einen von ihnen findet, und verfolgt ihn dann!


Und jetzt funktioniert alles, die Feinde wurden zu Bluthunden. Um das System zu verbessern, ist es möglich, ein System zur Vermeidung von Kollisionen zwischen Feinden zu implementieren. Dies ist jedoch ein Thema für einen separaten Artikel.

All Articles