Enemigo AI: persiguiendo a un jugador sin Navigation2D y encontrando el camino A *

¿Crear un juego en el que los enemigos deben perseguir a un jugador? Todo comienza con uno simple: hacemos que el enemigo corra hacia el jugador. Pero, ¿qué sucede si está detrás de un árbol o en la esquina de una pared? Bueno, ahora el enemigo se verá bastante estúpido: se topará con un objeto, tocando el lugar. ¡No muy bueno!

Para resolver este problema, puede usar los nodos Navigation2D o AStar integrados en Godot ( aquí está el tutorial de GDQuest en ambos nodos ). Pero en Helms of Fury, utilizamos un método diferente que funcionó muy bien para nuestro juego, y queremos compartirlo en este tutorial. Así es como se ve:


Llegando al trabajo


Suponemos que crea enemigos como objetos KinematicBody2D y usa una máquina de estados para controlar sus estados. ¿No estás seguro de qué es una máquina de estado? Me gusta este artículo que explica las máquinas de estado y cómo usarlas. Aquí hay otro artículo sobre la implementación de máquinas de estado simples en Godot .

Comencemos con un simple estado de Chase para un estúpido enemigo que simplemente corre hacia su objetivo y queda atrapado en algún lugar del camino:

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

Rastros de olor


Para mejorar el estado, forzaremos al jugador a dejar un rastro de sus posiciones anteriores cuando se mueva. Gracias a esto, cuando el enemigo no ve al jugador, verificará si se puede ver alguna de sus posiciones pasadas, y luego las seguirá al jugador. Como esto es similar a cómo un perro toma una huella, lo llamaremos huella de olor.

Para que funcione el rastro de olor, debemos agregar el nodo Temporizador al reproductor, habilitarlo para que se inicie automáticamente y establecer wait_time (usamos 0.1s), y luego agregar el código para que el reproductor deje un olor cuando finalice la cuenta regresiva.

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

Luego debe agregar el propio Scent.tscn abandonado. Esta es una escena simple de Node2D que contiene un temporizador para que expire el olor.

# 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 desea que los olores sean visibles al depurar, puede agregarles un nodo ColorRect y luego ocultarlo. Una vez hecho esto, verás que cuando corres detrás del jugador hay un rastro de olor.

Ahora tenemos que despertar a los enemigos de los sabuesos internos para que sigan estos nuevos olores cuando no ven al jugador. Pero para esto necesitamos agregar nodos RayCast2D a los enemigos y configurar capas de física en Godot para que el rayo sepa con qué puede hacer colisiones.

Capas de física


Para configurar las capas de física en Godot, debe hacer clic en Proyecto en el menú superior y luego en Configuración del proyecto, luego ir a la sección Nombres de capa en la esquina inferior izquierda y luego seleccionar Física 2D.


Dales cualquier nombre adecuado. Después de eso, vaya a los objetos del juego y expanda Colisión en la barra lateral del Inspector de propiedades, y luego haga clic en ·· para asignarlos. Para los objetos, deben asignarse como capas.


Después de asignar las capas de física a los objetos, debe cambiar el RayCast2D de los enemigos para que compruebe las capas a través de las cuales los enemigos no pueden moverse (en nuestro caso, esto es sólido, objeto, caja, agujero, puerta cerrada).

Después de configurar las capas de física, el paso final es cambiar el estado de Chase.

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

Ahora, cuando el enemigo entra en el estado de Chase, comienza a intentar volver a lanzar al jugador, y si no hay nada en su camino, ¡lo está persiguiendo! Si hay algo en el camino, entonces va a los rastros del olfato e intenta volver a lanzar cada uno de ellos hasta que encuentre uno de ellos, y luego, ¡lo persigue!


Y ahora todo funciona, los enemigos se convirtieron en sabuesos. Para mejorar el sistema, es posible implementar un sistema para evitar colisiones entre enemigos, pero este es un tema para un artículo separado.

All Articles