por Fernando Lorenzon em 03/02/2010 as 10:22

Neste post, vou mostrar algo bem simples. É a colisão de borda.

A borda nada mais é que um delimitador da área do jogo, que pode ser os extremos da tela, por exemplo.

Esta detecção é importante caso seja necessário manter um sprite dentro da tela. Por exemplo, nos jogos de tiro em scroll vertical como a série Sonic Wings, você não consegue movimentar o avião além da própria tela. Quando o aviãozinho atinge o limite da tela, ele simplesmente não se locomove mais naquela direção.

Utilizando a classe Sprite criada no post anterior, podemos chamar uma função que retorne um booleano para detecção de colisão para uma determinada borda. Quando o retorno for true, o que podemos fazer é zerar a velocidade naquela direção, e talvez voltar um pixel para que o sprite saia do estado de colisão e não fique "preso".

super-mario-sprite

Uma coisa que eu esqueci de mencionar no post anterior é que para que possa ser detectado com facilidade as colisões, o ideal é usarmos sprites retangulares. O problema é que um Super Mario quadradinho não venderia tão bem...

Para contornar este problema, devemos sempre usar, associado à imagem do sprite, um retângulo invisível que servirá de zona de detecção. Quando mais ajustado ao tamanho da imagem, melhor.

A classe Sprite possui um objeto pictureBox dentro dela, e esse pictureBox possui a imagem do sprite, e além disso, ela é um retângulo cujas dimensões podem ser facilmente obtidas. Ou seja, o próprio pictureBox serve tanto como imagem quanto zona de detecção. Se fosse no XNA puro, teríamos que renderizar a imagem em cima de uma retângulo invisível e usar a imagem para exibição, e o retângulo para detecção.

Fugindo um pouco, mas nem tanto... Vocês já perceberam que nos primeiros Resident Evil as imagens que formam os cenários são navegáveis pelos personagens como se fossem 3D? Como o jogo sabe numa imagem onde começa o corredor ou onde está a porta? É simples. O jogo não sabe. Os programadores posicionam em cada imagem do cenário um cubo de detecção exatamente com as dimensões das áreas "caminháveis" de cada imagem. E é por este cubo invisível que a engine do jogo se enxerga para deslocar os personagens. A foto não influencia em nada.

resident_evil_2_possible_borders
Possíveis bordas feitas por mim

No caso do nosso sprite, até seria possível ficar testando a sobreposição de imagens, pixel a pixel, para proporcionar uma colisão perfeita. Mas se fizessem isso com Mario, ele nunca rodaria nos consoles antigos. Lembram que eu mencionei sobre uma detecção de colisão que eu fiz comparando pixel a pixel os retângulos? Nem usei os pixels da imagem, que certamente pesaria mais ainda, e ainda assim o jogo ficou com uns 5 quadros.

Em imagens mais complexas, são utilizados mais retângulos por sprite, e as colisões são testadas individualmente. Um exemplo disso são os jogos de luta 2D. Quem já fuçou no MUGEN já deve ter visto isso.

Voltando à colisão de borda, temos que testar a colisão de cada borda individualmente. Para isso, basta chamar a função para cada borda que deve ser testada, passando o limite dela, o sprite, e a direção.

Para criar a função, eu criei também um enumerador com as quatro direções, e assim fica mais fácil passar a direção como parâmetro.

Para cada direção, eu uso um dos vértices do retângulo do sprite para testar a colisão. Eu criei para a classe Sprite quatro propriedades para retornar cada vértice do retângulo, começando a partir do superior esquerdo (0,0) e girando no sentido horário:

vertices

Para testar uma colisão do lado esquerdo, preciso usar o vértice 1 ou o 4.

Para testar uma colisão em cima, preciso usar o vértice 1 ou o 2.

Para testar uma colisão do lado direito, preciso usar o vértice 2 ou 3.

Para testar uma colisão embaixo, preciso usar o vértice 4 ou 3.

Todos os sprites ocupam uma posição absoluta na tela. A tela nada mais é que uma grade de pixels, que começa no canto superior esquerdo como 0,0, e termina no canto inferior direito como 640,480 (caso esteja usando esta resolução).

Então, se o vértice 1 estiver na posição do eixo X=0 ou menos, houve uma colisão do lado esquerdo. Se não pararmos o sprite neste momento, nada impede que ele saia da área visível da tela. É perfeitamente possível um sprite ocupar coordenadas de valores negativos (-52,300).

Vamos à função:

C:
  1. public static bool DetectBorderCollision(Sprite sprite, Enum.Direction direction, int collisionPoint)
  2. {
  3.     bool collided = false;
  4.  
  5.     if (direction == Enum.Direction.Down)
  6.     {
  7.         if (sprite.Vertex3.Y>= collisionPoint)
  8.         {
  9.             collided = true;
  10.         }
  11.     }
  12.     else if (direction == Enum.Direction.Up)
  13.     {
  14.         if (sprite.Vertex1.Y <= collisionPoint)
  15.         {
  16.             collided = true;
  17.         }
  18.     }
  19.     else if (direction == Enum.Direction.Left)
  20.     {
  21.         if (sprite.Vertex1.X <= collisionPoint)
  22.         {
  23.             collided = true;
  24.         }
  25.     }
  26.     else if (direction == Enum.Direction.Right)
  27.     {
  28.         if (sprite.Vertex2.X>= collisionPoint)
  29.         {
  30.             collided = true;
  31.         }
  32.     }
  33.  
  34.     return collided;
  35. }

Uma função extremamente simples e leve. Se quisermos testar a colisão nos quatros lados da tela, teremos que chamar a função 4 vezes. Para cada vez, passamos uma das direções e o limite da borda, em pixels. Na colisão à esquerda, o collisionPoint deve ser 0. Para a colisão à direita, o collisionPoint deve ser 640.

O que será feito com o retorno da função depende de cada game ou situação. No WinForms Lander, só testo a colisão na parte de baixo. Caso haja colisão, a nave quebra, o jogador perde uma vida, e o estágio é reiniciado. Só passa de fase se o jogador pousar na plataforma.

Parte 7 - Final

Tags: , , , , ,
Categorias: CubaGames | Comments Off


Comments are closed.