|
Neste tutorial veremos um tópico importante, pois dependendo do jogo que você estiver criando, pode ser necessário detectar colisão entre os limites da tela, entre os personagens do jogo e objetos, etc. Este tutorial então descreverá então como fazer a colisão usando retângulos em jogos bidimensionais, trabalhando um pouco na teoria e com um exemplo criado no XNA 4.0. É importante ressaltar que o programador deve conhecer que existe, diferentes formas de detectar a colisão e você deve escolher a mais adequada para o seu jogo. Além disso, é necessário decidir o que deve ser feito quando uma colisão no seu jogo é detectada.
Limites de um sprite
Como vimos no começo dos tutoriais de XNA, quando desenhamos uma sprite na tela sua posição inicial (canto superior esquerdo) está em alguma posição (X,Y) a sprite também tem uma largura (Width) e altura (Height). A altura e largura do sprite quase nunca mudam, a não ser que você troque as sprites durante uma animação por exemplo de ataque. Porém, a posição da sprite está em constante mudança, seja pelos comandos de entrada do usuário ou por alguma inteligência artificial. A figura abaixo, ilustra os pontos principais de uma sprite desenhada.
Para dar continuidade aos estudos, baixe o pré-projeto para este tutorial clicando no icone de download abaixo. O pré-projeto já inclui a imagem do fantasma e o código onde o fantasma é desenhado e pode ser movimentado com o teclado. Caso queira obter somente a imagem do fantasma clique aqui.
Abaixo é possível analisar o código do pré-projeto. A única coisa ainda não explicada detalhadamente são as entradas do teclado, mas já utilizamos em outros tutorais.
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
// Variável para armazenar a imagem
Texture2D img = null;
// Variável para armazenar a posição da sprite (ou posição do personagem)
// Inicializa a variável na posição (0,0)
Vector2 pos1 = Vector2.Zero;
// Constante de velocidade (5 pixels)
const float velocidade = 5;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
}
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
// Carrega a imagem do fantasma para variável "img"
img = Content.Loa<Texture2D>(@"fantasma");
}
protected override void Update(GameTime gameTime)
{
// Recebe o estado atual do teclado (estado deste exato momento)
KeyboardState teclado = Keyboard.GetState();
// Se a seta esquerda estiver pressionada
if (teclado.IsKeyDown(Keys.Left))
{
// Decrementa a posição X em 10 pixels
pos1.X -= velocidade;
}
// Se a seta direita estiver pressionada
if (teclado.IsKeyDown(Keys.Right))
{
// Incrementa a posição X em 10 pixels
pos1.X += velocidade;
}
// Se a seta cima estiver pressionada
if (teclado.IsKeyDown(Keys.Up))
{
// Decrementa a posição Y em 10 pixels
pos1.Y -= velocidade;
}
// Se a seta baixo estiver pressionada
if (teclado.IsKeyDown(Keys.Down))
{
// Incrementa a posição Y em 10 pixels
pos1.Y += velocidade;
}
// chama o método da superclasse passando o parâmentro de tempo do jogo
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
// Inicia o bloco de desenho
spriteBatch.Begin();
// Desenho o fantasma que se movimenta com o teclado
spriteBatch.Draw(img, pos1, Color.White);
// Finaliza o bloco de desenho
spriteBatch.End();
// chama o método da superclasse passando o parâmentro de tempo do jogo
base.Draw(gameTime);
}
}
Detectando colisão com a tela
Inicialmente iremos começar a detectar colisões de um objeto retangular com a janela do jogo, sendo assim, é necessário conhecer os limites da janela e os limites do objeto (sprite). O ponto superior esquerdo da janela, como já vimos no primeiro tutorial de como desenhar na tela, é o (0, 0). Para sabermos a largura da tela podemos utilizar as propriedades PreferredBackBufferWidth e PreferredBackBufferHeight do objeto do tipo GraphicsDeviceManager. Essas propriedades permitem determinar respectivamente a largura e altura da janela. Assim a posição da tela pode ser definida como a figura abaixo.
Agora que sabemos como situar a sprite na tela e também conhecemos os limites da janela, podemos detectar se a sprite colidiu com algum dos pontos da janela da seguinte forma:
// Testa o limite direito da tela
if (pos1.X + img.Width > graphics.PreferredBackBufferWidth)
{
// Se colidiu faz alguma coisa:
// fixa a posição do fantamas no máximo da janela em X
pos1.X = graphics.PreferredBackBufferWidth - img.Width;
}
// Testa o limite inferior da tela
if (pos1.Y + img.Height > graphics.PreferredBackBufferHeight)
{
// Se colidiu faz alguma coisa:
// fixa a posição do fantamas no máximo da janela em Y
pos1.Y = graphics.PreferredBackBufferHeight - img.Height;
}
// Testa o limite esquerdo da tela
if (pos1.X < 0)
{
// Se colidiu faz alguma coisa:
// fixa a posição do fantamas no início da janela em X
pos1.X = 0;
}
// Testa o limite superior da tela
if (pos1.Y < 0)
{
// Se colidiu faz alguma coisa:
// fixa a posição do fantamas no início da janela em Y
pos1.Y = 0;
}
O código acima é colocado dentro do método Update(GameTime gameTime), após, realizar o movimento do fantasma e antes de chamar o método update da superclasse.
Colisões entre objetos (retângulos)
O sistema de colisão por retângulos (Rectangle) é um sistema bem simples. Este tipo de colisão não é uma das melhores formas, pois, pode ocorrer das imagens não ocuparem o retângulo por completo, dando uma falsa impressão de colisão (problemas visual). Porém é um método prático e rápido de ser implementado. Neste tipo de colisão é verificado se algum vértice de um retângulo está dentro de um outro retângulo. A figura abaixo mostra três possíveis casos, sendo o primeiro de não colisão, o segundo de colisão e o terceiro de colisão porém com falsa impressão de colisão.
Este tutorial não irá ficar se explicando a parte matemática, apesar da mesma neste situação ser muito simples. Neste caso iremos investigar os principais atributos, propriedades e métodos da estrutura Rectangle para que possamos utilizá-lo em nossos jogos.
| Fields | Propriedade | Método | Descrição |
| X | Especifica a coordenada X do retângulo | ||
| Y | Especifica a coordenada Y do retângulo | ||
| Width | Especifica a largura do retângulo | ||
| Height | Especifica a altura do retângulo | ||
| Bottom | Retorna a coordenada Y do fundo (baixo) do retângulo | ||
| Center | Retorna o ponto (x,y) do centro do retângulo | ||
| Left | Retorna a coordenada X do lado esquerdo do retângulo | ||
| Right | Retorna a coordenada X do lado direito do retângulo | ||
| Top | Retorna a coordenada Y do topo (cima) do retângulo | ||
| Contains | Determina se o retângulo contém um ponto específico ou outro retângulo | ||
| Equals | Retorna um valor informando se o retângulo é igual ao outro | ||
| Inflate | Modifica as bordas do retângulo para um novo valor | ||
| Intersects | Verifica se o retângulo colidiu com outro retângulo | ||
| ToString | Retorna uma string contendo informações do retângulo | ||
| Union | Cria um novo retângulo que contenha dois outros retângulos |
Exemplo de colisão entre retângulos
Agora vamos fazer um teste de colisão de objetos usando retângulos, para isso, crie uma nova variável do tipo Vector2 para armazenarmos a posição de um segundo fantasma. Declare também uma flag do tipo Boolean para sabermos se aconteceu ou não uma colisão entre os objetos. Veja o código de declarações das novas variáveis.
// Posição do fantasma parado Vector2 pos2 = new Vector2(350, 170); // Flag para quando detectar uma colisão Boolean colidiu = false;
Logo em seguida, vamos fazer uma mudança no método Draw para que possamos desenhar o novo fantasma e também adicionar a nossa flag de colisão para caso aconteça uma colisão a tela mude de cor. Veja como ficaria o código do método Draw.
protected override void Draw(GameTime gameTime)
{
if (colidiu)
{
// Se aconteceu uma colisão, pintamos a tela de salmon
GraphicsDevice.Clear(Color.Salmon);
}
else
{
// Caso não aconteça uma colisão pintamos com a cor padrão
GraphicsDevice.Clear(Color.CornflowerBlue);
}
// Inicia o bloco de desenho
spriteBatch.Begin();
// Desenho o fantasma que fica parado na tela
spriteBatch.Draw(img, pos2, Color.White);
// Desenho o fantasma que se movimenta com o teclado
spriteBatch.Draw(img, pos1, Color.White);
// Finaliza o bloco de desenho
spriteBatch.End();
// chama o método da superclasse passando o parâmentro de tempo do jogo
base.Draw(gameTime);
}
Por fim, só precisamos lembrar que a cada atualização a posição do personagem pode mudar, ou seja, é necessário atualizar os retângulos. Após atualizar a posição dos retângulos então usar o método Intersect e verificar se houve colisão, e se isso acontecer então mudaremos nossa variável colidiu para verdadeiro. Veja como este código ficaria (código adicionado ao método Update depois que verificamos a colisão com o cenário.
// Retângulo do personagem que se movimenta
// É necessário fazer um conversão (cast) para int, pois Vector2 é do tipo float
Rectangle rect1 = new Rectangle((int)pos1.X, (int)pos1.Y, (int)img.Width, (int)img.Height);
// Retângulo do personagem que fica parado
// É necessário fazer um conversão (cast) para int, pois Vector2 é do tipo float
Rectangle rect2 = new Rectangle((int)pos2.X, (int)pos2.Y, (int)img.Width, (int)img.Height);
// Verifica se o retângulo 1 colidiu com o segundo retângulo
if (rect1.Intersects(rect2))
{
// se aconteceu a colisão mudamos para verdadeiro
colidiu = true;
}
else
{
// se não aconteceu a colisão mudamos para verdadeiro
colidiu = false;
}
Alguns resultados do que vai acontecer, podem ser vistos nas figuras abaixo. A primeira figura exibe os personagens sem colisão, enquanto na segunda figura, os personagens se colidiram então a cor de fundo muda para Salmon.
Download
Para fazer download do exemplo criado neste tutorial clique no ícone de download abaixo.
Conclusões
Espero que este tutorial seja de uso para todos, como foi visto é muito simples fazer colisões em jogos bidimensionais usando a classe Rectangle fornecida pelo XNA.
-
10/09/2011 13:14:08 |189.46.145.xxx| Ronaldo

Otimo tutorial
apenas para complementar, Existe uma outra abordagem para detectar colisao de sprites, usando oq chamam de colisao por pixel. Ela é um pouco mais lenta, porem bem mais exata.Parabens pessoal, otimos tutoriais de XNA !!! Encontrei conteudo desde o basico ate o avancado !
-
10/09/2011 13:29:41 | Vinícius Godoy de Mendonça

Na verdade, ela é muito mais lenta.
Mas sempre se pode combinar as duas.a) Usa-se a colisão por retângulos para ver se houve colisão;
b) Se houver aí sim, usa-se a colisão por pixel para confirmar se houve colisão mesmo.
-
10/09/2011 14:33:15 | Kleber Andrade - re:
Marcos Vasconcelos Escreveu:A matematica para retangulos é bem simples e bem util para muitos casos.
Eu já fiz projetos que usei uma abordagem por distancia entre vetores. Que é até simples para objetos redondos.Legal, eu gosto da colisão por objetos redondos, o mais legal ainda no XNA, é que ele já implementa isso também, é só você declarar um objeto do tipo BoundingSphere, serve tanto para jogos 2D como 3D. Será tema de um próximo tutorial.
Ronaldo Escreveu:Otimo tutorial
apenas para complementar, Existe uma outra abordagem para detectar colisao de sprites, usando oq chamam de colisao por pixel. Ela é um pouco mais lenta, porem bem mais exata.
Parabens pessoal, otimos tutoriais de XNA !!! Encontrei conteudo desde o basico ate o avancado !É sempre bom lembrar dos diversos tipos de colisão, e uma boa solução para isso é usar a heuristica do Vinicius Godoy, primeiro retângulo depois pixel. Em tutoriais mais para frente, pois os próximos agora são as conclusões do jogo Pong, veremos também a colisão por Pixel.
Abraços.
-
28/10/2011 23:23:45 |187.57.169.xxx| Luiz Paulo - Ótimo post!

Ótimo guia!
Agora eu tenho uma dúvida: A função Intersects é lenta?
Estou fazendo um jogo de plataforma que vai ter sistema de tiles, e eu estou tendo que rodar o Intersects para todo tile que está visivel na tela, (média de 40), e está um pouco travado o movimento do personagem depois que eu implementei a colisão.Eu não tenho muita experiencia para saber se o jeito que estou fazendo a colisão e movimentação é um modo bom...
-
28/10/2011 23:27:35 | Kleber Andrade

Boa pergunta Luiz Paulo, nunca fiz o teste de tempo da função... mas essa é uma função simples que verifica com alguns "ifs" se um vértice esta dentro do outro retângulo. Então de qualquer forma esta seria a forma menos lenta de verificar colisão. Deve ter algum outro problema, mas não vou saber te falar, precisaria verificar o tempo de acesso a determinadas funções no seu código.
[]s
-
29/10/2011 11:21:46 |189.69.51.xxx| Luiz Paulo

Eu faço 2 ifs para ver se o tile está vísivel, se tiver, verifica se eh um colisível, se for testa com a intersects, se for verdadeiro, verifica qual lado está colidindo (4 ifs).
Isso roda 60 vezes por segundo para todos os tiles do jogo. Está dentro do "normal"?
Obrigado!












A matematica para retangulos é bem simples e bem util para muitos casos.
Eu já fiz projetos que usei uma abordagem por distancia entre vetores. Que é até simples para objetos redondos.