Ennemi AI: pourchasser un joueur sans Navigation2D et trouver le chemin A *

Créer un jeu dans lequel les ennemis doivent chasser un joueur? Tout commence par un simple - nous faisons courir l'ennemi vers le joueur. Mais que se passe-t-il s'il se trouve derrière un arbre ou au coin d'un mur? Eh bien, maintenant l'ennemi aura l'air assez stupide - il rencontrera un objet, se doigtera en place. Pas très bien!

Pour résoudre ce problème, vous pouvez utiliser les nœuds Navigation2D ou AStar intégrés à Godot ( voici le tutoriel GDQuest sur les deux nœuds ). Mais dans Helms of Fury, nous avons utilisé une méthode différente qui fonctionnait très bien pour notre jeu, et nous voulons la partager dans ce tutoriel. Voici à quoi ça ressemble:


Se rendre au travail


Nous supposons que vous créez des ennemis en tant qu'objets KinematicBody2D et utilisez une machine à états pour contrôler leurs états. Vous ne savez pas ce qu'est une machine d'état? J'aime cet article expliquant les machines à états et comment les utiliser. Voici un autre article sur l'implémentation de machines à états simples dans Godot .

Commençons par un simple état Chase pour un ennemi stupide qui court vers sa cible et se retrouve coincé quelque part en cours de route:

# 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)

Traces d'odeur


Pour améliorer l'état, nous obligerons le joueur à laisser une trace de ses positions précédentes lors de son déplacement. Grâce à cela, lorsque l'ennemi ne voit pas le joueur, il vérifiera si l'une de ses positions passées peut être vue, puis les suivra jusqu'au joueur. Comme cela est similaire à la façon dont un chien prend une empreinte, nous l'appellerons une empreinte d'odeur.

Pour que la trace de l'odeur fonctionne, nous devons ajouter le nœud Timer au lecteur, lui permettre de démarrer automatiquement et de définir wait_time (nous avons utilisé 0,1 s), puis d'ajouter le code afin que le joueur laisse une odeur à la fin du compte à rebours.

# 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)

Ensuite, vous devez ajouter le Scent.tscn abandonné lui-même. Il s'agit d'une simple scène Node2D contenant une minuterie afin que l'odeur expire.

# 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)

Si vous souhaitez que les odeurs soient visibles pendant le débogage, vous pouvez leur ajouter un nœud ColorRect, puis le masquer. Cela fait, vous verrez qu'en courant après le joueur, il y a une trace d'odeur.

Maintenant, nous devons réveiller les ennemis des limiers internes afin qu'ils suivent ces nouvelles odeurs lorsqu'ils ne voient pas le joueur. Mais pour cela, nous devons ajouter des nœuds RayCast2D aux ennemis et configurer des couches physiques dans Godot afin que le faisceau sache avec quoi il peut faire des collisions.

Couches de physique


Pour configurer les couches physiques dans Godot, vous devez cliquer sur Projet dans le menu supérieur puis sur Paramètres du projet, puis allez dans la section Noms des couches dans le coin inférieur gauche, puis sélectionnez Physique 2D.


Donnez-leur un nom approprié. Après cela, accédez aux objets du jeu et développez Collision dans la barre latérale de l'inspecteur des propriétés, puis cliquez sur ·· pour les affecter. Pour les objets, ils doivent être affectés en tant que couches.


Après avoir affecté les couches physiques aux objets, vous devez modifier le RayCast2D des ennemis afin qu'il vérifie les couches à travers lesquelles les ennemis ne peuvent pas se déplacer (dans notre cas, il s'agit de solide, objet, caisse, trou, gate_closed).

Après avoir configuré les couches physiques, la dernière étape consiste à modifier l'état de la poursuite.

# 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)

Maintenant, lorsque l'ennemi entre dans l'état Chase, il commence à essayer de relancer le joueur, et s'il n'y a rien sur son chemin, il le suivra! S'il y a quelque chose sur le chemin, alors il va aux traces de l'odorat et essaie de refondre chacun d'eux jusqu'à ce qu'il en trouve un, puis - le poursuit!


Et maintenant tout fonctionne, les ennemis se sont transformés en limiers. Pour améliorer le système, il est possible de mettre en place un système permettant d'éviter les collisions entre ennemis, mais c'est un sujet pour un article séparé.

All Articles