IA inimiga: perseguindo um jogador sem o Navigation2D e encontrando o caminho A *

Criando um jogo em que inimigos devem perseguir um jogador? Tudo começa com um simples - fazemos o inimigo correr para o jogador. Mas o que acontece se estiver atrás de uma árvore ou na esquina de uma parede? Bem, agora o inimigo parecerá muito estúpido - ele vai se deparar com um objeto, mexendo no lugar. Não muito bom!

Para resolver esse problema, você pode usar os nós Navigation2D ou AStar embutidos no Godot ( aqui está o tutorial do GDQuest nos dois nós ). Mas no Helms of Fury, usamos um método diferente que funcionou muito bem para o nosso jogo, e queremos compartilhá-lo neste tutorial. Aqui está o que parece:


Indo pro trabalho


Assumimos que você crie inimigos como objetos KinematicBody2D e use uma máquina de estados para controlar seus estados. Não sabe o que é uma máquina de estado? Eu gosto deste artigo explicando as máquinas de estado e como usá-las. Aqui está outro artigo sobre a implementação de máquinas de estado simples em Godot .

Vamos começar com um simples estado de perseguição para um inimigo estúpido que apenas corre para o alvo e fica preso em algum lugar ao longo do caminho:

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

Vestígios de cheiro


Para melhorar o estado, forçaremos o jogador a deixar rastros de suas posições anteriores ao se mover. Graças a isso, quando o inimigo não vê o jogador, ele verifica se alguma de suas posições passadas pode ser vista e as segue até o jogador. Como isso é semelhante ao modo como um cachorro pega uma pegada, chamaremos de pegada de cheiro.

Para que o traço de cheiro funcione, precisamos adicionar o nó Timer ao player, permitir que ele inicie e defina automaticamente wait_time (usamos 0,1s) e, em seguida, adicione o código para que o jogador deixe um cheiro quando a contagem regressiva terminar.

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

Então você precisa adicionar o próprio Scent.tscn abandonado. Esta é uma cena simples do Node2D que contém um timer para que o odor 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)

Se você deseja que os odores sejam visíveis durante a depuração, adicione um nó ColorRect a eles e depois oculte-o. Feito isso, você verá que, ao correr atrás do jogador, há um traço de cheiro.

Agora precisamos despertar os inimigos dos cães de caça internos para que eles sigam esses novos cheiros quando não vêem o jogador. Mas, para isso, precisamos adicionar nós RayCast2D aos inimigos e configurar Camadas de Física em Godot para que o raio saiba com o que pode fazer colisões.

Camadas de Física


Para configurar as Camadas de Física no Godot, você precisa clicar em Projeto no menu superior e, em seguida, em Configurações do Projeto, em seguida, vá para a seção Nomes de Camadas no canto inferior esquerdo e selecione Física em 2D.


Dê a eles qualquer nome adequado. Depois disso, vá para os objetos no jogo e expanda Colisão na barra lateral do Property Inspector e clique em ·· para atribuí-los. Para objetos, eles devem ser atribuídos como Camadas.


Depois de atribuir as Camadas de Física aos objetos, você precisa alterar o RayCast2D dos inimigos para que ele verifique as camadas pelas quais os inimigos não podem se mover (no nosso caso, isso é sólido, objeto, caixa, buraco, gate_closed).

Depois de configurar as Camadas de Física, o passo final é alterar o estado do 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)

Agora, quando o inimigo entra no estado de Perseguição, ele começa a tentar relançar o jogador e, se não houver nada no caminho, ele o seguirá! Se houver algo a caminho, ele segue os traços do olfato e tenta relançar cada um deles até encontrar um deles, e então - o persegue!


E agora tudo funciona, os inimigos se transformaram em cães de caça. Para melhorar o sistema, é possível implementar um sistema para evitar colisões entre inimigos, mas este é um tópico para um artigo separado.

All Articles