GameDev #9: Trabalhando com sistemas de detecção de colisão no XNA

em 09/10/2010

Na edição anterior da coluna GameDev , nós vimos como criar, importar e trabalhar com um sprite dentro de um jogo XNA. Ainda exploramos u... (por Jones Oliveira em 09/10/2010, via Nintendo Blast)

Na edição anterior da coluna GameDev, nós vimos como criar, importar e trabalhar com um sprite dentro de um jogo XNA. Ainda exploramos um pouco do XNA, criando métodos de movimentação para o Sprite e limitando-a as bordas da tela do nosso jogo.

No encontro de hoje, daremos continuidade com o nosso projeto de jogo (disponível em http://links.nintendoblast.com.br/srqhy) e adicionaremos a ela um sistema bem simples de detecção de colisão entre dois sprites.

Lembrando que, para desenvolvermos o que é proposto nessa coluna, é necessário ter instalado em sua máquina o XNA Game Studio e o Microsoft Visual C# Express Edition, ambos gratuitos, que podem ser encontrados na sessão downloads do XNA Creators Club (http://creators.xna.com/en-US/downloads).

logo_VSE2010xna_logo

 

Entendendo o conceito de colisões

clip_image001Fazer com que o sprite tenha sua movimentação limitada às bordas do nosso jogo não deixa de ser uma forma de detecção de colisão, bem simples, é verdade. Porém, em jogos 2D, geralmente se deseja realizar detecção de colisão entre dois ou mais sprites no jogo.

Existem várias formas de trabalhar detecção de colisões em qualquer jogo que se queira fazer – basta dar uma procurada no oráculo para constatar essa afirmação. Porém, o nosso objetivo aqui é apresentar um exemplo simples para os que acompanham a coluna possam entender o conceito.

Quando estamos trabalhando com detecção de colisões, não é sensato pensar em trabalhar testando pixel por pixel em cada um dos sprites do jogo. Já imaginou o quão trabalhoso isso seria? E o quanto a performance comprometeria a performance do jogo?

Dessa forma, os algoritmos de detecção de colisão trabalham com representações aproximadas das formas dos sprites, o que já facilita bastante a elaboração da sua fórmula. Os algoritmos de detecção de colisão mais comuns utilizam o conceito de bounding boxes que aproxima a forma do objeto com um ou mais retângulos, ou caixas (dai a palavra boxes). A figura abaixo nos mostra um sprite de um avião cuja forma é demonstrada por dois retângulos.

 

Implementando o bounding box

Uma maneira simples de implementar o teste bounding box é simplesmente verificar se as posições das coordenadas X e Y da primeira caixa (que envolve o Sprite que você deseja testar) está dentro da segunda caixa (que envolve o segundo objeto a ser testado).

Em outras palavras, o que estamos dizendo aqui é que devemos verificar se os valores de X e Y da caixa que queremos testar é menor ou igual aos valores de X e Y da outra caixa, mais a largura da outra caixa.

Sendo assim, mãos à obra. No arquivo classSprite.cs, vamos adicionar o seguinte método (nomeado de detectaColisao) que irá receberá um sprite como parâmetro e o testará com o Sprite atual. Se houver uma colisão, o método nos retornará verdadeiro.

image

 

Para entender a lógica desse código, analise-o observando a figura abaixo e prossiga somente se tiver certeza do que está acontecendo aqui.

retangulos1

clip_image002Analisando o exemplo do código, as duas caixas só irão se sobrepor se ambas as coordenadas X e Y do retângulo 2 estiverem no intervalo (X para X + largura, Y para Y + altura) do retângulo 1. Observando a figura acima, você verá que a coordenada Y do retângulo 2 não é maior que a coordenada Y mais a altura do retângulo 1. Isso significa que suas caixas podem estar colidindo. Porém, quando verificamos a coordenada X do retângulo 2, você verá que é que maior que a coordenada X mais a largura do retângulo 1, o que significa que as caixas não estão colidindo.

A figura abaixo ilustra uma situação em que você tem uma colisão. Nesse caso, você pode perceber que ambas as posições, X e Y, do retângulo 2 estão contidas no intervalo do retângulo 1. No exemplo de código, você também faz o teste oposto, verificando se as coordenadas X e Y do retângulo 1 estão contidas no intervalo do retângulo 2. Dessa forma, estamos nos prevenindo de, por exemplo, o canto superior esquerdo do retângulo 2 estar fora do retângulo 1, mas o topo deste estar dentro do retângulo 2.

retangulos2

Para testarmos o método na prática, vamos criar um segundo sprite no meio da tela do jogo. Para fazer isso, basta replicarmos o sprite que criamos no encontro anterior e incluir o código para testar colisões no método Update na classe Game1.

Primeiramente, vamos declarar uma nova variável de sprite no início da classe Game1, junto com as outras definições:

image

Agora, no método LoadContent, incluiremos o código para a criação e inicialização do Sprite:

image

E não nos esqueçamos de adicionar o código para descarregar o sprite no UnloadContent:

image

No método Update, incluiremos o código para movimentar o sprite na tela:

image

E, finalmente, no método Draw, vamos incluir o código para desenhar o novo sprite na tela do nosso jogo:

image

Nesse momento, se você executar o seu jogo, perceberá que os dois sprites estão se movimentado, mas ainda não estão colidindo. Nós resolveremos isso adicionando uma chamada ao método detectaColisao no método Update e alterando a velocidade entre os sprites, conforme segue abaixo:

image

Agora sim, se você executar o seu jogo, irá perceber que os sprites estão colidindo um com o outro, além de colidirem com as bordas da janela. Bacana, não?

clip_image002[4]Contudo, apesar do sistema de detecção de colisões estar utilizando o algoritmo do bouncing box, depois de alguns testes você perceberá um problema. Se os sprites colidirem diagonalmente, eles irão colidir antes de atingirem um ao outro de verdade. Isso está ocorrendo justamente por utilizarmos caixas para representar a forma geométrica das esferas.

Quando queremos testar colisões entre esferas, teremos que verificar se a distância entre os centros delas são menores que a soma dos seus raios. Em caso positivo, então uma colisão ocorreu. Essa é a forma mais eficiente de detectar colisões entre duas esferas.

Para darmos suporte a esse sistema, na classe classSprite vamos criar duas novas propriedades, uma chamada centro e outra raio, que serão calculadas de acordo com outra propriedade do Sprite.

image

Em seguida, criaremos um novo método para testar esse tipo específico de colisão:

image

Finalmente, vamos atualizar o método Update na classe Game1 para chamar o método detectaColisaoCirculo ao invés do detectaColisao. Agora sim, você perceberá que os círculos só irão colidir quando eles de fato colidirem ;)

No encontro de hoje nós trabalhamos a ideia básica de um sistema de detecção de colisões entre dois sprites em um jogo 2D. Aos poucos estamos avançando nos conceitos de programação de jogos e no nosso próximo encontro capturaremos a entrada de dados do jogador via teclado e a utilizaremos para controlar um dos nossos sprites. Fique ligado e até lá!

O projeto atualizado com o código desse encontro pode ser baixado no endereço http://links.nintendoblast.com.br/khfvf

Siga o Blast nas Redes Sociais

Escreve para o Nintendo Blast sob a licença Creative Commons BY-SA 3.0. Você pode usar e compartilhar este conteúdo desde que credite o autor e veículo original.
Este texto não representa a opinião do Nintendo Blast. Somos uma comunidade de gamers aberta às visões e experiências de cada autor. Você pode compartilhar este conteúdo creditando o autor e veículo original (BY-SA 3.0).