Esse site utiliza cookies
Nós armazenamos dados temporariamente para melhorar a sua experiência de navegação e recomendar conteúdo do seu interesse.
Ao utilizar os nossos serviços, você concorda com as nossas políticas de privacidade.
Esse site utiliza cookies
Nós armazenamos dados temporariamente para melhorar a sua experiência de navegação e recomendar conteúdo do seu interesse.
Ao utilizar os nossos serviços, você concorda com as nossas políticas de privacidade.
Categoria de Programação
Postado em 19 junho 2023
Atualizado em 19 junho 2023
Palavras-chave: steering,behavior,collision,avoid,avoidance,comportamento,colisao,seek,busca
Visualizações: 618
Os comportamentos de navegação (steering behaviors) são um conjunto de algoritmos que simulam comportamentos de seres vivos. Uma porção desse conjunto pode ser eficiente para o desenvolvimento de jogos ou máquinas que podem se locomover. Alguns desses comportamentos já foram documentados aqui:
Usando como exemplo jogos de duas dimensões, é bastante comum o desenvolvedor fazer o inimigo perseguir o jogador. Se usarmos vetores geométricos, podemos calcular facilmente a direção que o inimigo deve ir para se aproximar do jogador e definir sua velocidade a cada frame. Porém, quando há obstáculos no meio do caminho, simplesmente calcular o caminho mais curto entre o jogador e o inimigo não é mais viável. Nesse caso, o inimigo deve ser capaz de desviar dos obstáculos para chegar ao local onde o jogador se encontra. Existem diversas técnicas para realizar esse movimento, o algoritmo A-estrela é um exemplo de pathfinding inteligente que encontra o caminho mais curto entre o usuário e o inimigo desviando dos obstáculos. Entretanto, alguns desenvolvedores podem optar por um algoritmo mais simples para fazer o inimigo desviar dos obstáculos. Uma boa opção é a prevenção de colisão (collision avoidance) desenvolvido por Craig Reynolds.
A prevenção de colisões é um algoritmo que pertence ao steering behaviors. O agente é capaz de prever uma colisão com obstáculos usando parâmetros como velocidade e coordenada atual. As coordenadas usadas como base para comparação são a posição atual do agente e a posição atual do obstáculo. Além disso, o agente possui habilidade de “ver adiante” usando a velocidade atual como base. Ao multiplicar a velocidade atual por um valor arbitrário, o agente tem capacidade de ver adiante e detectar colisões com antecedência, evitando obstáculos antes de colidir com eles.
Ao normalizar a velocidade do agente e multiplicar por um valor arbitrário, é possível manter a direção do agente com uma magnitude diferente. É assim que é calculado a previsão de colisões. A linha na frente do agente é quem diz se o agente vai desviar ou não do obstáculo. Caso a linha esteja dentro de um obstáculo, ela vai interferir na direção do agente e mudar a sua rota para evitar a colisão.
Entretanto, esse comportamento não é capaz de prevenir uma colisão com total certeza. Dependendo da velocidade e a força exercida sobre a direção do agente, o agente ainda pode colidir com o obstáculo. Por isso, os valores definidos nos parâmetros de velocidade e força interferem fortemente no comportamento de prevenção de colisões. Porém, isso não é necessariamente um ponto negativo. O desenvolvedor pode aumentar o número de colisões de um agente propositalmente para diversas finalidades, como por exemplo, no desenvolvimento de um jogo de corrida. Caso os agentes (carros de corrida) estejam em alta velocidade e não conseguem virar uma curva, eles acabam colidindo com o obstáculo que eles tentaram desviar. Esse tipo de comportamento pode tornar um jogo mais desafiador, uma vez que os agentes também tem que diminuir a velocidade para não colidir com obstáculos.
O comportamento de prevenção de colisões pode ser eficiente em alguns casos, mas ineficientes em outros casos. O contexto onde esse algoritmo é implementado influencia fortemente na sua funcionalidade. Simplesmente implementar esse comportamento em um jogo de RPG em um plano 2D pode ser ineficiente se o seu contexto não for compatível. O algoritmo “collision avoidance” possui alguns requerimentos para funcionar corretamente.
Em alguns jogos de RPG, um personagem pode se locomover um quadrado por comando. Ou seja, ao pressionar a tecla “direita” uma vez, o personagem irá se locomover um quadrado para a direita, independente de valores como velocidade e aceleração. Além disso, não há inércia na locomoção dos personagens, um NPC pode mudar a sua direção repentinamente sem se preocupar com a sua velocidade atual. Em casos como esse, o comportamento de prevenção de colisões não irá funcionar com eficiência. Para funcionar corretamente, há a necessidade de implementar mais alguns dos comportamentos de navegação, como o comportamento de busca.
O comportamento de busca faz o agente se locomover em direção a um local no mapa. Essa direção é um vetor e é chamada de velocidade desejada (desired velocity). Por mais que um agente deseje se locomover em uma direção, a sua velocidade atual não pode ser anulada repentinamente. Em outras palavras, a direção desejada do agente sofrerá variações com valores como velocidade atual e força de navegação (steer force).
Em resumo, dependendo do contexto onde o algoritmo de prevenção de colisões é aplicado, seu comportamento pode variar. Na maioria das vezes o desenvolvedor é quem deve ajustar o algoritmo para funcionar corretamente em um determinado contexto.
O agente é o objeto que desviará dos obstáculos para chegar em um determinado local. Um cenário pode ter vários agentes com destinos diferentes. A grande vantagem dos comportamentos de colisões é que cada agente é autônomo.
class boid {
constructor(position, acceleration) {
this.position = position.clone()
this.velocity = new Vector(0, 0)
this.acceleration = acceleration.clone()
this.maxVelocity = 4
this.maxForce = 0.5
}
draw() {
circle(this.position.x, this.position.y, BOID_SIZE)
}
move() {
if (this.position.x > width || this.position.x < 0) {
this.velocity.x *= -1
}
if (this.position.y > height || this.position.y < 0) {
this.velocity.y *= -1
}
this.velocity.add(this.acceleration)
this.velocity.limit(this.maxVelocity)
this.position.add(this.velocity)
this.acceleration.mult(0)
}
}
Nesse exemplo, a classe do agente será chamada de “boid”. Ela terá cinco atributos: coordenada, velocidade, aceleração, velocidade máxima e força máxima. Com excessão dos atributos “maxVelocity” e “maxForce”, todos os outros atributos são dinâmicos e mudam com frequência.
Dentro da função local “move”, também está sendo verificado se o agente não está saindo das extremidades da tela. Caso o agente esteja quase ultrapassando as extremidades da tela, a direção dele é invertida para a direção contrária.
Em seguida, na mesma função, o valor da aceleração é adicionado na velocidade. A velocidade é limitada para não exceder a velocidade máxima (maxVelocity). O resultado é adicionado na coordenada atual do agente, que finalmente irá se locomover.
let boids = []
function start() {
let initialPosition = new Vector(width / 2, height / 2)
let initialVelocity = new Vector(1, 0)
let boid = new boid(initialPosition, initialVelocity)
boids.push(boid)
}
function update() {
for (let i = 0; i < boids.length; i++) {
boids[i].draw()
boids[i].move()
}
}
Na variável “boids” são listadas as instâncias de cada agente. Nesse exemplo, apenas um agente estará sendo adicionado, mas múltiplos agentes também podem ser instanciados sem problemas, pois cada agente atua de forma independente. Na função “update” simplesmente estamos exibindo o agente e atualizando as suas coordenadas.
Em seguida, criamos uma classe para os obstáculos. Um obstáculo é um simples objeto que possui apenas coordenada e tamanho.
class obstacle {
constructor(position, size) {
this.position = position
this.size = size
}
draw() {
circle(this.position.x, this.position.y, this.size)
}
}
Os obstáculos são instanciados da mesma forma que o agente. Até aqui é a pré-implementação da prevenção de colisões.
Dentro da classe “boid”, adicionamos a função “steer”, que é responsável por calcular a velocidade desejada pelo agente.
steer(destination) {
let desiredVelocity = destination - this.position
desiredVelocity = desiredVelocity.normalize() * this.maxVelocity
let steer = desiredVelocity - this.velocity
steer.limit(this.maxForce)
this.acceleration += steer
}
A velocidade desejada é resultado da subtração do destino pela posição atual do agente. Essa velocidade é normalizada e multiplicada pela velocidade máxima do agente. Em seguida, a velocidade desejada é subtraída pela velocidade atual, limitada a força máxima e adicionada com a aceleração. Essa força adicionada a aceleração é a força de navegação (steer force).
O motivo de subtrair a velocidade desejada pela velocidade atual é que a velocidade atual vai sendo atualizada pela aceleração a cada frame. Depois de um certo tempo, a velocidade atual irá ceder aos valores impostos pela aceleração.
Finalmente, adicionamos a função “avoid” que é responsável por prevenir colisões com obstáculos.
avoid(obstacles) {
// Coordenada do agente
let pos = this.position.clone()
// Direção que o agente está se locomovendo
let nVel = this.velocity.clone().normalize()
// Distância que o agente pode ver
let future = nVel * SEE_AHEAD
let ahead = pos + future
line(pos.x, pos.y, ahead.x, ahead.y)
for (let i = 0; i < obstacles.length; i++) {
// Distância entre os obstáculos e a extremidade que o agente pode ver
let distanceInFuture = obstacles[i].position.distance(ahead)
// Se o agente ver um obstáculo, tenta desviar
if (distanceInFuture < obstacles[i].size / 2) {
// Diferença da extremidade que o agente pode ver e o centro do obstáculo
let direction = ahead - obstacles[i].position
line(obstacles[i].position.x, obstacles[i].position.y, obstacles[i].position.x + direction.x, obstacles[i].position.y + direction.y)
// Quanto mais perto do obstáculo, mais forte será a repulsão
let avoidanceForce = pos.distance(obstacles[i].position)
direction *= avoidanceForce
// Aplica a força
this.steer(direction)
}
}
// Caso não haja obstáculos, acelera o agente até a velocidade máxima
this.steer(pos.clone() + (nVel * this.velocity.mag()))
}
Antes mesmo do agente chegar perto do obstáculo, ele irá ser empurrado para fora do obstáculo. A distância que o agente pode ver no futuro e a quantidade da força de repulsão podem ser expressadas com linhas. Isso facilita a depuração (debug) dos comportamentos.
O resultado final é um agente capaz de desviar de obstáculos com efetividade. O algoritmo acima foi desenvolvido para funcionar corretamente nesse tipo de contexto. Como dito anteriormente, em alguns outros tipos de ambientes e contextos, ajustes extras podem ser necessários.
A prevenção de colisões (collision avoidance) é um algoritmo que prevê colisões no futuro baseando-se em atributos do agente. Quando uma colisão é prevista pelo agente, ele muda a sua direção prevenindo que aconteça colisões. Dependendo da velocidade e força de navegação, as chances de colisão podem ser diminuídas ou aumentadas. Enfim, a prevenção de colisões não é um comportamento que consegue prevenir em todas as ocasiões colisões entre agentes e obstáculos.
Projetos práticos
Usando JavaFX e arquitetura limpa para criar um aplicativo de caixa eletrônico extremamente simples.
Usando lógicas matemáticas como trigonometria para criar e calcular o esqueleto de um jogo de tiro 2D em javascript
Simulação dos gráficos do segundo turno das eleições presidenciais, utilizando python e ferramentas de análise de dados, pandas e jupyter.
Desenvolvimento dos conceitos mais básicos do clássico pacman, como: mapa, animação, deslocamento e detector de colisões.
Jogo simples de guerra espacial desenvolvido em javascript. Esse jogo usa cálculos de física para simular efeitos de atrito e inércia.
A quantidade de programadores só tende a aumentar com o tempo. Porém, muitos programadores ainda não conhecem os três pilares de segurança da informação.
Phishing tem esse nome pois a vítima se torna só mais um peixe na rede. Ter conhecimento de phishing é o melhor jeito de evitar ser um desses peixes
Dois tipos de comportamentos que preveem o trajeto do alvo. Em outras palavras, esses dois algoritmos tomam decisões se baseando em informações do futuro.
A busca binária usa o método de divisão de conquista que visa em dividir os problemas em pequenos problemas até que eles se resolvam sozinhos.
Um dos comportamentos de navegação (steering behaviors). Faz com que um agente siga um caminho pré-determinado pelo desenvolvedor.
Algoritmo que busca o caminho com o menor custo entre dois pontos. É usado em jogos e aplicativos de navegação para calcular a menor distância possível.