Cursos / Jogos Digitais / Inteligência Artificial para Jogos / Aula
Bom… são vários detalhes que você vai precisar pensar. Mas comece por partes. A ideia por trás do comportamento para evitar obstáculos é simples, basta aplicar uma força contrária aos obstáculos que estão logo à frente do personagem. Simples assim!
Entretanto, há alguns detalhes que precisam ser levados em conta. Por exemplo, se você tiver com vários obstáculos à frente do NPC, será que a força de navegação levará em conta todos eles? Se não, de qual deles desviar? Como eu vejo quando um obstáculo está na minha frente? Ou melhor, quando um obstáculo se encontra na minha rota de colisão? O quanto de força eu devo aplicar para afastar apenas o suficiente para evitar a colisão? Bem... são muitos questionamentos. rsrs
A solução é a seguinte: se houver vários obstáculos, você irá selecionar aquele que representar maior “ameaça" de colisão. Nesse cenário, bem como na maioria dos cenários, a maior “ameaça" é o obstáculo mais próximo. Mas isso pode ser diferente. Por exemplo, é possível que o índice de ameaça seja uma razão entre o tamanho e a distância. Então, o NPC pode se desviar de obstáculos mais distantes, desde que eles sejam grandes o suficiente para gerarem um desvio maior que um pequeno que está mais próximo. No seu caso, os obstáculos são outros jogadores, todos do mesmo tamanho. Então, fique com a versão mais simples, tratando de desviar de quem está mais próximo.
Uma vez escolhido de quem se desviar, aplique uma força de navegação contrária ao obstáculo a cada novo frame. O resultado é que o personagem irá se desviar do obstáculo escolhido. Se um novo obstáculo aparecer (ou continuar presente), ele aplicará novamente o mesmo procedimento a cada um dos obstáculos no caminho. E assim por diante...
Nesse cenário, por exemplo, suponha que um jogador esteja indo em direção ao seu alvo, como ilustra a Figura 01.
No meio do caminho, aparecerá um jogador adversário para atrapalhá-lo. Ele precisará, então, desviar, aplicando uma força contrária (ou tangencial) à posição do adversário, de forma que a velocidade resultante faça com que ele contorne o obstáculo.
Bom, ideia é boa! Mas como saber quando o adversário se encontrará no caminho do jogador? Veja agora como resolver esse segundo problema.
Para saber se há obstáculos à frente, você precisará definir uma margem (distância) que considera adequada para o jogador se desviar deles. Ou seja, precisa estabelecer em que momento a força de desvio de obstáculo pode ser aplicada, caso contrário, os jogadores podem se desviar de obstáculos que ainda estão muito afastados deles. Por exemplo, da trave que se encontra do outro lado do campo. E não é isso que você quer, correto?
Com o valor dessa margem, crie um vetor na mesma direção do jogador, cujo tamanho seja igual à margem preestabelecida. Pode-se dizer que esse vetor será uma espécie de sensor para ver o que está “adiante", ou seja, que vai detectar se algum objeto (no exemplo, o juiz) do jogo cruza com ele. Se houver alguma interseção desse vetor com o juiz, é porque ele encontra-se no “meio do caminho" do jogador. Quanto maior o vetor (margem de colisão), mais prematuramente o jogador vai se desviar dos obstáculos, como ilustra a Figura 02. Faz sentido, não é?
No Unity, esse vetor “adiante" pode ser implementado como um sensor, como o EdgeCollider2D ou BoxCollider2D. Você irá, entretanto, usar uma versão mais genérica, usando um “raio" de colisão.
O vetor “adiante", chamado a partir de agora de ahead, pode ser calculado projetando a posição do jogador através de um vetor na direção de sua velocidade, porém previamente com um tamanho igual à margem de colisão. Assim, o vetor pode ser calculado através das instruções a seguir.
adiante = posição + normaliza (velocidade) * margem_de_colisão
Para você saber se há algum obstáculo no caminho do jogador, basta verificar se a semirreta que vai da posição do jogador à sua posição adiante cruza (tem interseção) com algum dos objetos do cenário. Esse cálculo requer que os objetos do jogo possuam uma figura geométrica para representar seus limites, também conhecidos como regiões ou caixas envoltórias (bounding box). Apesar de o termo usar “caixa", nem sempre essa figura geométrica é uma caixa (em jogos 3D) ou retângulo (em jogos 2D). Muitas vezes, é utilizado um círculo (em 2D) ou uma esfera (em 3D).
No Unity, as caixas envoltórias são definidas por componentes Colliders, os quais podem-se anexar aos objetos do jogo. Nesse caso, pode-se associar um CircleCollider2D ou BoxCollider2D. A Figura 03 mostra as regiões em que a interseção com outro objeto seria considerada como colisão. Apesar das duas possibilidades, normalmente comportamentos mais realistas são conseguidos usando o círculo como região envoltória. Por essa razão, usa-se o CircleCollider2D como sensor de colisão.
Você aplicará o raio a partir do centro do objeto CircleCollider2D (normalmente posicionado no centro da imagem do jogador), e se o jogador cruzar com um collider é porque há um obstáculo à frente. Mas lembre-se que se houver mais de um obstáculo à frente, você terá de escolher a “maior ameaça" à colisão. Como visto anteriormente, isso é calculado por meio da distância do personagem ao obstáculo. Então, você pode ordenar pela distância todos os obstáculos do caminho e o primeiro do conjunto ordenado será o mais próximo.
Muito bem!O segundo problema, que é “de quem você deve se esquivar", foi resolvido.
Você vai calcular qual a força de navegação a ser aplicada para o jogador se desviar do obstáculo encontrado. Como na seção anterior, é preciso definir uma força que “empurre" o jogador a uma direção desejada, nesse caso, para longe do obstáculo (ver Figura 04). O cálculo da força de desvio é similar ao cálculo da força de navegação usado no comportamento Seek (que, como você deve lembrar, foi utilizado na aula passada). Calcula-se, então, o vetor força através da diferença de dois outros vetores. Porém, como não é atração em direção ao alvo, mas sim repulsão do obstáculo, faz-se a subtração entre o vetor “adiante" e o centro do objeto de colisão, e depois se aplicará a mesma transformação que se aplicou no Seek para definir o tamanho do vetor resultante com força máxima, conforme fórmula abaixo.
força_desvio = adiante – centro_obstáculo
força_desvio = normaliza (força_desvio) * força_de_desvio_máxima
Resta mais uma questão: o comportamento de desvio de obstáculos não funciona sozinho. O NPC só se desvia de um obstáculo se ele estiver indo para algum lugar, perseguindo ou escapando de alguém. Então, os comportamentos precisam ser combinados. Mas como fazer isso?
Uma opção é você combinar as forças, que é simplesmente somar os seus vetores. Isso vai funcionar com vários comportamentos. Porém, com o desvio de obstáculos, essa pode não ser a melhor estratégia. Uma força pode anular a outra e os objetos irão colidir. Nessa condição de os objetos colidirem, a soma de vetores é suficiente. Porém, se quiser evitar isso, uma segunda opção é priorizar um dos comportamentos, que é o NPC seguir em direção ao seu alvo ou ele se desviar.
Como funcionaria esse esquema? É mais simples do que você imagina! A cada momento do jogo, você vai fazer seu jogador escolher entre que tipo de comportamento (e por consequência, qual força de navegação) ele vai aplicar. Se o caminho estiver livre, ele vai em direção ao alvo (comportamento Seek ou Arrival). Se aparecer um cachorro jogador adversário no meio do caminho, ele escolherá desviar do obstáculo (comportamento Obstacle Avoidance). Até aqui nenhuma novidade, né? Só que para você fazer os desvios ficarem pequenos o suficiente, você vai fazer o jogador se perguntar isso a cada frame do jogo, de forma que o desvio “contorne" o obstáculo apenas o suficiente para liberar o seu caminho.
A Figura 05 ilustra o que ocorrerá nesse tipo de combinação de forças.
Essa estratégia de desvio de obstáculo resolve a questão para cenários mais simples, porém ainda existem situações onde a simulação resultante dele apresentará problemas. Por exemplo, é possível que, ao estar muito próximo do obstáculo, o NPC tente desviar mesmo que esteja quase parado. Outro problema surge quando os obstáculos estão tão próximos do personagem que a manobra de desvio não o impede de colidir com o mesmo. Isso acontece porque os comportamentos de navegação alteram a velocidade do personagem paulatinamente, ao longo de várias iterações do jogo, e pode ser que, mesmo desviando, não haja tempo de evitar a colisão (às vezes isso acontece na vida real, não é mesmo?). Nesse caso, uma possível solução é fazer com que a margem do sensor de colisão seja dinâmica, ou seja, que varie em função da velocidade do personagem.
Veja que essa ideia faz muito sentido. Por exemplo, um motorista de um carro prestará atenção nos obstáculos que se encontram a vários metros de distância dele, enquanto alguém passeando de bicicleta concentrará sua atenção aos obstáculos que estarão há apenas alguns metros. Então, você poderá fazer o mesmo com nosso o jogador de futebol. Fazendo a margem de desvio de obstáculo variar, dependendo se ele estiver correndo ou andando em campo.
Agora que as ideias foram todas devidamente apresentadas, coloque em prática tudo isso que você viu na teoria!
Versão 5.3 - Todos os Direitos reservados