Neste compendium vamos explicar como criar um inimigo que persegue o jogador pelo mapa, se movendo pelo cenário e evitando obstáculos. Para isso usaremos o Node Navigation2D.

Tópicos:

O que é Pathfind

Exemplo de pathfind no jogo The Maestros

Pathfinding significa em inglês busca de caminho. O algoritmo responsável por mover um personagem de um ponto a outro, desviando de obstáculos, é famoso pois é usado muito por programadores de games. A movimentação de agentes (um personagem do jogo) em um espaço é um dos segmento da Inteligência Artificial mais comuns na produção de games. O objetivo desse tipo de algoritmo é achar o caminho mais rápido entre dois pontos ( por exemplo: o inimigo é o jogador) evitando obstáculos, como paredes ou outros obstáculos.

O que é o Navigation2D

O node Navigation2D é responsável por encontrar este caminho. Ele possui métodos para encontrar o caminho dentro de uma malha, esta malha pode ser um node  NavigationPolygonInstance ou um node TileMap. Basicamente esta malha é o caminho que representa onde um personagem controlado pelo Navigation2D pode se locomover.

O espaço verde é a malha do node NavigationPolygonInstance, que representa o espaço físico onde um agente pode se mover

Estrutura da Cena

Antes de prosseguir, é preciso entender a estrutura do Navigation2D. Perceba que o filho dele é um NavigationPolygonInstance. Isto é importante, pois o node que representa o caminho que o agente vai se mover deve ser filho de um Navigation2D para funcionar. Os próximos dois subtópicos irão mostrar os dois tipos de node de caminho que pode ser usado com um Navigation2D, então se sinta a vontade de pular um dos dois para ir direto o que se assemelha mais ao seu projeto.

Neste momento temos essa estrutura. Um node para colocar o StaticBody2D, um node para colocar os inimigos, um Player que se movimenta em 8 direções (Veja o Compendium -  Movimento em 8 Direções 2D) e o Navigation2D.

Adicione um NavigationPolygonInstance filho do Navigation2D.

Esta mensagem de erro informa que precisamos adicionar um caminho para o node funcionar. Podemos criar um caminho clicando no botão Criar Pontos na parte de cima da viewport:

 Quando aparecer a mensagem na tela, clique em Create. Use o botão esquerdo do mouse para adicionar os pontos.

Esta parte esverdeada representa o caminho que os inimigos deste exemplo vão poder se mover, lembrando, somente na parte verde. Então temos um problema, pois na parte de baixo possui um quadrado que é sólido e os inimigos nem o jogadores não deveriam ser capaz de andar nesta área.

Para resolver este problema, clicamos novamente no botão Criar Pontos e adicionamos um “buraco” na malha de navegação:

Com isso a estrutura do Navigation2D usando polígonos está pronto

Neste tópico não vamos nos aprofundar em como criar um TileMap, para isso veja o Compendium - Utilizando TileMaps 2D. A estrutura da cena ficará neste formato:

Perceba que o Navigation2D ficará mais acima do Player e dos inimigos, pois como iremos usar um TileMap a ordem importa aqui, caso contrário o cenário ficaria na “frente” dos personagens do jogo.

Adicione então o node TileMap como filho do Navigation2D:

 

Após isso vá no inspetor e selecione a opção New TileSet

Depois clique novamente em New TileSet para editar as opções deste TileMap. Você pode criar um TileMap de uma única imagem ou de várias imagens como visto no Compendium - Utilizando TileMaps 2D. Para este exemplo vamos utilizar várias imagens:

Após isso, devemos definir as áreas de colisão para o nosso personagem e a área de navegação para nossos inimigos. Selecione uma imagem e clique em New Single Tile

Selecione o Ícone de Snap para facilitar na hora de definir a área do Tile:

Após isso vamos definir a área de navegação do inimigos clicando em: New Single Tile-> Selecionando a área da imagem-> Clique em Navigation ->Create New Rectangle-> Selecione área de Navegação:

Vamos fazer o mesmo só que para as paredes que serão sólida no jogo. Só que em vez de selecionar Navigation vamos selecionar Collision para definir a parte estática:

Agora você pode “desenhar” sua fase selecionando a grama, onde os inimigos irão se mover, e a madeira onde será sólido para o jogador. Para “pintar” com o tile pressione o botão esquerdo do mouse:

Código Principal

Antes de prosseguir, vamos adicionar um node Timer na cena, este timer será responsável por ficar chamando a função que irá calcular o melhor caminho para o alcançar o jogador. Como o jogador se move o tempo todo o tempo desse timer deve ser baixo.

Crie já um sinal no Timer para chamar no código da fase. Mude as configurações do Timer para estes valores:

As primeira linhas de código serão dentro do sinal criado pelo Timer:

Na linha 4 estamos pegando a posição do jogador e na linha 5 pegamos o node Navigation2D. Agora vamos criar um código para ficar atualizando o movimento de todos os inimigos. O código será esse:

Na linha 7 iniciamos um loop que irá executar o código para cada inimigo filho do node Inimigos. A função get_simple_path( ) busca o melhor caminho entre o inimigo atual ( inimigo.global_position ) e o jogador ( player ). O último parâmetro, false, serve para não haver uma compressão no caminho, tornando o caminho o mais preciso possível.

Basicamente o que isto vai acontecer internamente quando o código executar:

Pontos ilustrativos. O inimigo é o personagem vermelho e o jogador o personagem azul. Os pontos gerados serão usados pelo inimigo para ele se mover até o jogador.

Por isso que a linha 9 remove o ponto 0, para o inimigo iniciar seu movimento diretamente para o pronto 1. O inimigo inicia seu movimento, assim que recebe a caminho na linha 11. Agora falta criar de fato a cena inimigo e criar a função define_target( ).

Código do Inimigo 

Para criar a cena do inimigo vamos usar um KinematicBody2D. Mude o nome desse node para Inimigo. Coloque o Sprite e o CollisionShape2D que irá representar a colisão do objeto

Crie um Script para o inimigo. As primeiras variáveis desse script serão path e speed.

A variável path será usado para armazenar aquele caminho de pontos que vimos anteriormente, para o nosso inimigo se mover. Já a variável speed representa a velocidade do inimigo.

Agora vamos criar um processo para movimentar o inimigo assim que ele possuir um caminho. Adicione este script após as variáveis.

A linha 8 faz o inimigo se mover para o próximo ponto do caminho. E na linha 10 ele verifica se o inimigo está próximo o suficiente desse ponto para então removê-lo e avançar para o próximo ponto.

Crie a função define_target( ) que vimos anteriormente para  receber o caminho:

Agora volte na cena da fase e adicione alguns inimigos dentro do node Inimigos

Agora teste o resultado. Você terá um resultado como este abaixo:

É um resultado interessante, mas nos próximos tópicos vamos ver como é possível melhorar este movimento.

Melhorando a Movimentação do Inimigo

A primeira coisa que poderíamos fazer para melhorar a forma como o inimigo segue o jogador é fazê-lo olhar em direção para onde ele está indo. Para isso podemos adicionar essas linhas no script do inimigo.

A linha 10 e 11 farão o inimigo rotacionar para o ponto que ele está se encaminhando. O resultado será esse:

Se o seu inimigo estiver “andando de costas” para a direção do jogador você pode resolver isso de forma bem simples. Selecione o node Sprite do inimigo e no inspector marque a opção flip_h

Isto fará o sprite ficar “espelhado” para o outro lado. Se você testar novamente o projeto o resultado será esse:

Você deve ter percebido que se o jogo ficar executando por algum tempo a probabilidade de um inimigo ficar “em cima do outro” é grande.

Por esse motivo que fizemos nosso inimigo como um KinematicBody, pois assim podemos usufruir das funcionalidades desse node. Adicione essas linhas de código no script do Inimigo.

A função test_move( ) na linha 8 verifica se o inimigo está colidindo com outro corpo ( a parede, o jogador, outro inimigo ). Se sim, diminuímos um pouco a velocidade do inimigo e “empurramos” o inimigo para longe desse obstáculo usando a função move_and_slide( ) na linha 11.

Se não estiver colidindo a velocidade do inimigo volta a ser 100 na linha 13. O resultado dessa modificação será esse: