Cursos / Jogos Digitais / Inteligência Artificial para Jogos / Aula

arrow_back Aula 02 - Comportamentos de navegação

2.1 Comportamento Seek (busca por um alvo)

Vou começar explicando a matemática dos comportamentos de navegação através de um comportamento específico: Seek. Esse comportamento é normalmente atribuído aos personagens quando se quer que eles tenham movimentos suaves em direção a uma posição do cenário. Você já percebeu que o jogador criado na seção anterior é muito “robótico”? Por exemplo, quando você clica em uma posição oposta à direção que ele está indo, ele ignora todas as leis da inércia e simplesmente muda de direção conservando a mesma velocidade. Um jogador de verdade não teria capacidade para isso (alguém teria de mudar as Leis da Física para tanto). O mais natural seria que o jogador fosse reduzindo sua velocidade para poder mudar de direção e depois seguir em direção ao clique do usuário.

Pois bem, você vai fazer isso.

As forças dos comportamentos de navegação que citei podem ser representadas por vetores 2D ou 3D, dependendo do tipo de cenário com que você estiver trabalhando. Nesse caso, trata-se de um jogo 2D e, portanto, serão utilizados vetores bidimensionais.

Como na matemática vetorial será utilizada (soma, subtração de vetores), é interessante que a posição do personagem também esteja representada com um vetor. No Unity, isso já está implícito, uma vez que todos os objetos do jogo possuem uma posição definida através de um vetor. Detalhe: essa posição é, por padrão, representada com um vetor 3D, mas por enquanto basta ignorar o eixo Z e os objetos já estarão em posições definidas através de vetores 2D.

O que resta a fazer agora é calcular a velocidade adequada do personagem, que sofrerá influência das forças de atração e/ou repulsão que você desejar. O que você precisa é aplicar uma força à velocidade atual na direção do alvo (velocidade desejada). A soma dos dois vetores de velocidade resultará em uma nova velocidade, denominada resultante, que deve ser limitada a um valor máximo para evitar que a mesma aumente indefinidamente ao longo do tempo (essa soma ocorre a cada novo frame, lembra?). A Figura 09 ilustra graficamente o cálculo da velocidade resultante. Ela será a nova velocidade do personagem..

Movimentação baseada na aplicação de forças (vetores)

Agora você deve estar se questionando: “mas a velocidade resultante não direciona o personagem ao alvo”?. De fato, não. Porém, se você aplicar essa estratégia a cada frame, vai ter um movimento suave da posição e velocidade atual até o alvo, baseado no método de Euler (apresentado na seção anterior), conforme ilustra a Figura 10.

Atualização contínua da velocidade resultante

A atualização contínua da velocidade fará com que o personagem se desloque paulatinamente ao alvo. O cálculo da velocidade resultante tal como foi descrito é intuitivo e fácil de compreender. Porém, demora para convergir. Ou seja, o personagem pode ficar rodando em torno do alvo e nunca o alcançar. Então, existe uma outra abordagem para remover esse problema. Aplique uma força sobre a velocidade atual, chamada força de navegação (steering), que vai “empurrar” o personagem de forma que a velocidade atual vá na direção à velocidade desejada, conforme ilustra a Figura 11.

A força de navegação (steering force) "empurrando" o personagem para a direção desejada

O algoritmo a ser seguido, baseado no que foi explicado até o momento, encontra-se no pseudocódigo a seguir. Primeiro, calcule a velocidade desejada, através da subtração dos vetores alvo e posição (linha 1), e depois transforme essa velocidade em um vetor de tamanho igual à velocidade máxima. Esse cálculo é feito normalizando a velocidade calculada (o vetor fica com tamanho de 1) e depois multiplicando-a com o valor de velocidade máxima (linha 2).

Com o resultado da velocidade desejada, calcule o vetor de força de navegação através da subtração dos vetores de velocidade desejada e atual (linha 3). Agora, você precisa truncar a força calculada de forma que o tamanho do vetor não exceda um certo limite (força máxima) (linha 4). Isso vai permitir uma certa suavidade nos movimentos. Aplique, então, a força de navegação à velocidade atual, limitando-a igualmente a um tamanho máximo (linhas 5 e 6). Por fim, atualize a posição do personagem com sua nova velocidade (linha 7).

Algoritmo 02 – Comportamento Seek

1: veloc_desejada = alvo - posição
2: veloc_desejada = normaliza(veloc_desejada) * veloc_máxima
3: força_nav = veloc_desejada – velocidade
4: força_nav = limita(força_nav, força_máxima)
5: nova_veloc = velocidade + força_nav
6: velocidade = limita(nova_veloc, veloc_máxima)
7: posição = posição + velocidade

Se você quiser aprimorar ainda mais esse mecanismo, considerando a massa do personagem para simular a inércia dos jogadores mais “gordinhos”, inclua um novo cálculo na força de navegação, inserindo a instrução a seguir depois da linha 4. Ou seja, a força de navegação a ser aplicada é inversamente proporcional à massa do jogador. Quanto mais “gordinho”, menor a força a ser aplicada.

Algoritmo 03 – Introdução de inércia no comportamento Seek

5: força_nav = força_nav / massa

Implementar esse algoritmo no Unity é muito tranquilo porque ele facilita muito os cálculos através dos métodos predefinidos da classe Vector2 e do componente Rigidbody2D inseridos no jogador.

Passo 03 – Implementação do comportamento Seek

Inicialmente, é necessário introduzir algumas variáveis públicas na classe, para que o usuário possa facilmente configurar o comportamento do NPC. Já existe uma variável que define a velocidade máxima, que é speed. Falta agora uma para especificar a força máxima de navegação. Defina-a, por exemplo, com o nome de maxSteeringForce.

Para facilitar, divida o Algoritmo 02 em duas partes: uma para o cálculo da força de navegação e a outra para aplicá-la na velocidade do NPC. Assim, você conseguirá criar e adaptar novos comportamentos de forma mais simples, pois a segunda parte continuará a mesma. Enquanto a primeira parte pode ficar em um método qualquer, por exemplo Seek(), a segunda parte ficará no próprio método UpdateFixed().

Além desses métodos, é necessário alterar o método Update(), uma vez que não faz mais sentido girar o NPC para o alvo depois que o usuário clicar em um ponto. Logo, faz mais sentido atualizar o Sprite do NPC no método UpdateFixed(), e não no método Update(), para que o NPC gire paulatinamente, à medida que as forças vão sendo aplicadas.

Mas como para tudo na vida há um limite, você vai colocar um para essa velocidade também! Se a velocidade do NPC for muito pequena, ao ponto de poder ser considerado um objeto parado, não precisa mais atualizar sua direção. Então crie o atributo stopVal para definir esse limite do que se considera “parado”. Essas ideias podem ser vistas implementadas no Unity no Código 02 abaixo.

O vídeo a seguir mostra que os movimentos do personagem agora estão mais suaves. Diferentemente da primeira versão, dá para ver o jogador virando para um lado ou para o outro.

play_arrow Vídeo 04 - Movimento suave do jogador no movimento Seek

Esse comportamento foi gerado com os seguintes valores:

  • Speed = 4
  • Max Steering Force = 0.3

É interessante brincar um pouco com esses valores. Você vai ver que quanto maior o limite máximo de força, o NPC irá se virar mais rápido para o seu destino. Quanto menor o valor, mais lentamente ele irá se virar, ficando parecido com um carro, que precisa dar uma volta bem maior para fazer a curva.

O vídeo 05 abaixo, ilustra um problema do comportamento que foi implementado e que você já deve ter notado. Ao chegar no destino, o NPC fica indo de um lado para o outro feito barata tonta!

play_arrow Vídeo 05 - Movimento Seek com o vai-e-vem ao passar do destino

O problema que ocorre aí é que o NPC vai a toda velocidade até o ponto destino, e se esquece de frear! Quando chega no ponto desejado, não dá para simplesmente parar usando as forças de navegação previamente descritas. Então, ele passa direto do destino feito um maluco. Depois, volta ao destino em alta velocidade… e passa novamente. E fica nesse vai-e-vem de cachorro atrás da própria cauda. E aí, entendeu!?

Esse comportamento é útil quando os jogadores disputam a bola rolando. Quem chegar primeiro fica com a bola. Nesse caso, eles precisam ir sem pisar no freio. Porém, se o objetivo é só ir até um ponto do campo, o ideal é que eles comecem a reduzir sua velocidade à medida em que eles se aproximam do destino. Faz sentido, não é?

Agora vou lhe apresentar um outro comportamento, chamado Arrival (do inglês, chegada), similar ao Seek, mas que vai reduzindo a velocidade ao chegar no destino.

Versão 5.3 - Todos os Direitos reservados