Colisões Bounding Box

Bounding Box conhecido como bbox é uma técnica em que é feito o uso de retângulos para detectar colisões. Nos jogos 2D, essa técnica consiste em delimitar a área dos objetos do jogo através de retângulos, conhecido como retângulos de colisão.

Existem dois tipos de bbox, o orientado e o alinhado aos eixos. O primeiro conhecido como obbox(Oriented Bounding Box) trata de colisões em que o objeto esteja rotacionado. Já o segundo tipo, conhecido como aabb(Axis-Aligned Bounding Box) trata de colisões em que o objeto não está rotacionado. Nesse artigo trataremos do aabb.


A Imagem 1 mostra um cenário em que o jogador tenta coletar uma moeda.

Imagem 1 criada através das imagens disponibilizadas por kenney, surt e por Tio Aimar. Acesso em 07/12/2017.

Note a presença dos retângulos de colisão no personagem e nas três primeira moedas. Na maioria das vezes, um objeto com apenas um retângulo de colisão será suficiente, mas quando não for, poderá ser utilizado mais retângulo de colisão, para assim ter uma colisão um pouco mais precisa. A Imagem 1.1 mostra o caso em que apenas um retângulo de colisão não é suficiente.

Imagem 1.1 criada através das imagens disponibilizadas por kenney, surt e por Tio Aimar. Acesso em 07/12/2017.

Perceba que houve uma colisão, só que uma colisão falsa, pois o personagem não está realmente colidindo com a pedra. Então nesse caso precisamos especificar mais de um retângulo de colisão. A Imagem 1.2 mostra isso.

Imagem 1.2 criada através das imagens disponibilizadas por kenney, surt e por Tio Aimar. Acesso em 07/12/2017.

Observe que agora não existe uma falsa colisão, o que significa que o jogador terá uma experiência muito melhor e uma colisão um pouco mais precisa. O trecho de código a seguir ilustra a detecção de colisão aabb em JavaScript.

Vamos entender o porque o trecho de código anterior é tudo o que você precisa quando se trata de detectar colisão aabb. A Imagem 1.3 mostra o que seria as propriedades left, right, top e bottom dos objetos no jogo.

Imagem 1.3

Como percebido, essas propriedades representam as laterais do objeto, sendo:
left = coordenada x do objeto.
right = coordenada x do objeto mais a largura do mesmo.
top = coordenada y do objeto.
bottom = coordenada y do objeto mais a sua altura.


A Image 1.4 ilustra que se rect1.left > rect2.right, então não existe colisão pela esquerda de rect1.

Imagem 1.4

A Image 1.5 mostra que se rect1.right < rect2.left, então não existe colisão pela direita de rect1.

Imagem 1.5

A Image 1.6 mostra que se rect1.top > rect2.bottom, então existe colisão por cima de rect1.

Imagem 1.6

A Imagem 1.7 mostra que se rect1.bottom < rect2.top, então não existe colisão por baixo de rect1.

Imagem 1.7

Para ilustrar o conceito de colisão aabb mostrado, vamos construir um exemplo simples. Nele teremos a classe Rectangle e a classe AABB filha de Rectangle. A Listagem 1 e Listagem 1.1 mostra a classe Rectangle e AABB, respectivamente.

Listagem 1
Listagem 1.1
Observe que na classe Rectangle temos um conjunto de métodos que nos permite obter as posições esquerda, direita, cima e baixo, além de permitir acessar o ponto médio e a intersecção entre retângulos. Já a classe AABB é uma classe que deriva de Rectangle, herdando dela todos os métodos, sendo a diferença básica entre elas que AABB recebe um objeto em seu construtor representando a entidade dona dela. Dessa forma podemos especificar um retângulo de colisão mais preciso, pois podemos especificar um retângulo de colisão entre as dimensões da entidade que o contém. Veja a Imagem 1.8.

Imagem 1.8

Na Imagem 1.8 o retângulo externo em azul representa as dimensões da entidade, enquanto que o retângulo interno em vermelho representa o retângulo de colisão.

Na Listagem 1.2 temos a classe Entity a qual os objetos que compõem o jogo são derivados. O construtor de Entity recebe como argumento a posição x e y, a largura e altura, além da cor e do array de objetos de colisão AABB.

Listagem 1.2
E por fim temos a classe Collision na Listagem 1.3. Embora essa não seja uma implementação eficiente, a mesma serve para o nosso exemplo simples.

Listagem 1.3
Na Listagem 1.4 temos o restante do código com a criação do player e de alguns obstáculos, além do código responsável pelo loop do jogo, manipulação de teclas direcionais e renderização.

Listagem 1.4
Na Listagem 1.4, os métodos player.update e player.collided são os dois mais importantes, sendo player.collided o mais importante entre eles. Vamos entender o porquê.

No player.update tratamos apenas da movimentação do objeto quando as teclas de seta para esquerda, para cima, para a direita e para baixo são pressionadas, aplicando uma velocidade de 5px na horizontal ou vertical. Na linha 27 invocamos o método checkForCollision da classe Collision. Note que esse método para checar colisão não é muito eficiente pois a checagem de colisão é feita contra todos os objetos na lista, mesmo que o objeto não esteja visível na tela ou próximo do player, o que implica em uma perda de performance. O ideal seria criar um mapa de colisão, mas para o nosso exemplo simples a forma apresentada serve.

Na linha 35 temos o início da declaração de player.collided. Ele é invocado pela classe Collision quando uma intersecção entre retângulos é detectada, passando para o mesmo um objeto de propriedades, contendo os dois retângulos de colisão, e objeto target representando a entidade com a qual o player está colidindo. Nas linhas 39 e 42 obtemos os retângulos de colisão rect1 representando o retângulo de colisão de player e rect2 representando o retângulo de colisão que colide com player, target. Nas linhas 45 e 48 obtemos os pontos médios de rect1 e rect2, esses pontos médios nos dizem de onde está vindo a colisão, da esquerda, direita, cima ou baixo de player. Mas esses pontos médios só nos dirão isso quando obtivermos a diferença das coordenadas x e das coordenadas y dos mesmos, deltaX e detlaY, respectivamente. Como estamos calculando o deltaX = midpoint1.x - midpoint2.x, teremos nesse caso um valor negativo se o player estiver a esquerda de target e um valor positivo se ele estiver a direita de target. Veja a Imagem 1.9 e Imagem 2.

Imagem 1.9

Imagem 2

Em deltaY é feito algo semelhante, se midpoint1.y - midpoint2.y < 0, então o player está em cima de target, caso contrário o player está em baixo de target. Veja a Imagem 2.1 e Imagem 2.2.

Imagem 2.1

Imagem 2.2

Da linha 58 até a linha 61 é obtida as laterais do retângulo de sobreposição. Esse retângulo nos permite especificar se a colisão é vertical ou horizontal, nos permitindo assim que o player deslize pela parede. Sem isso o player ficaria travado e o jogador acabaria tendo uma experiência de jogo desagradável. A Imagem 2.3 ilustra o retângulo de sobreposição.

Imagem 2.3

Observe que retângulo de sobreposição em vermelho é obtido através das laterais dos retângulos de colisão rect1 e rect2. Note que o lado esquerdo do retângulo de sobreposição é obtido pela maior coordenada left, entre rect1 e rect2, nesse caso a maior é a de rect2. Já o lado direito é obtido pela menor coordenada right, entre rect1 e rect2, a menor é a de rect1. Essa mesma ideia é utilizada para a lateral de cima e de baixo. A lateral de cima do retângulo de sobreposição é obtido pela maior coordenada top entre rect1 e rect2, a maior é a de rect2. A lateral de baixo é obtido pela menor coordenada bottom entre rect1 e rect2, nesse caso a de rect1 é menor.

Após obter as laterais do retângulo de sobreposição, temos que saber qual o valor da sobreposição vertical e horizontal. A quantidade de sobreposição horizontal, overlapX, é obtido através da diferença entre right e left, respectivamente. Veja a Imagem 2.4.

Imagem 2.4

Já a quantidade de sobreposição vertical, overlapY, é obtido pela diferença entre bottom e top, respectivamente. Veja a Imagem 2.5.

Imagem 2.5

Na Imagem 2.4 e Imagem 2.5 mostra que o overlapY é maior que o overlapX, o que implica em colisão horizontal. Já a Imagem 2.6 e Imagem 2.7 mostram uma sobreposição horizontal maior que vertical, resultado assim em uma colisão vertical.

Imagem 2.6

Imagem 2.7

Observe que o overlapX é maior que o overlapY, o que implica em uma colisão vertical. Entender esse conceito é importante pois na hora de resolver a colisão é preciso saber a direção para poder posicionar a entidade na coordenada correta. Tendo a Imagem 2.7 como referência, a resolução da colisão implicaria que o player teria que retroceder em y o mesmo valor de overlapY. Não confunda o valor de overlapY utilizado no retrocesso do player com a direção vertical determinada por overlapX, até porque um valor de overlapX maior que overlapY indica uma colisão vertical em que a entidade precisará em sua resolução de colisão retroceder ou avançar a mesma quantidade que overlapY.

Na Listagem 1.5 apresento o código completo. Para acessar a demostração online click aqui.

Listagem 1.5
O projeto com os códigos apresentados pode ser baixado aqui.

Comentários

Postagens mais visitadas deste blog

Mapas em Jogos 2D - Parte I

Frames Per Second - FPS