por Fernando Lorenzon em 25/01/2010 as 13:26

Uma das coisas mais importantes em um jogo é a colisão. Isso não significa que deva haver uma explosão ao colidir sprites, mas simplesmente fazer com que o Super Mario possa caminhar sobre o chão sem atravessá-lo.

Existem muitas implementações para colisão. Desde implementações simples até das mais complexas. Por exemplo, a colisão usada nos jogos do Mario para coletar as moedas só precisa informar se Mario tocou a moeda. Mas para testar a colisão em um koopa, é necessário verificar se Mario está acima ou no lado ao tocar o inimigo. Caso esteja em cima, Mario sai ileso e elimina o inimigo. Caso esteja no lado, Mario perde uma vida.

Para testar uma colisão dessas, é bem mais complicado. Principalmente se os sprites se movem com mais velocidade.

O que vou mostrar agora é a colisão simples (aquela da moeda). Até porque a minha colisão que detecta a direção do toque não é 100% confiável em altas velocidades (parece até coisa de mecânica quântica). Eu digo "minha" colisão porque fiz questão de tentar criar uma solução por conta própria, sem pesquisar nada na internet. É claro que futuramente terei que ir atrás disso, pois não sou um bom programador de jogos e nem um bom matemático.

Para detectar uma colisão simples, o ideal é usar cálculos e algoritmos que usem poucos ciclos de CPU. É bem difícil saber o quanto um cálculo usa de CPU. Mas podemos deduzir a complexidade dele pela quantidade de linhas de códigos e quantidade de loops usados.

Uma das minhas idéias iniciais para detectar colisão deixou o jogo com uns 5 quadros de animação por segundo. Enquanto as outras duas mantinham a velocidade de 50. Aí podemos ver que não daria certo usá-la :)

Mas porque ficou tão pesado? Porque o que eu fazia era comparar pixel por pixel a área utilizada por cada um dos sprites que eu estava testando a colisão. Quando eu vi a degradação da performance, logo busquei outra alternativa, bem mais leve.

Vamos a elas:

Mas para fins didáticos, vou mostrar minha idéia dos 5 FPS:

Comparando colisão entre sprite1 com sprite2:

1 - gerar lista com todos os pixels do sprite1

2 - gerar lista com todos os pixels do sprite2

3 - comparar cada pixel com sprite1 com cada pixel do sprite2

Este método é bastante lento, mas 100% confiável. O problema é que se um sprite com 50x50 pixels for comparado com outro sprite 50x50, estaremos comparando 2500 pixels contra outros 2500. Isso tudo 50 vezes por segundo.

O método mais simples e igualmente preciso que eu usei no jogo foi este:

1 - Verificação Horizontal (Eixo X):

squaresx1

1.1 - Subtrair a distância (sempre em pixels) da extremidade da direita do sprite mais à direita com a extremidade esquerda do sprite mais à esquerda;

1.2 - Somar a largura do sprite1 com a largura do sprite2;

1.3 - se a distância for menor ou igual à soma das larguras, realizar a verificação Vertical.

2 - Verificação Vertical (Eixo Y):

squaresy

2.1 - Subtrair a distância (sempre em pixels) do topo do sprite mais acima com o fundo do sprite mais abaixo;

2.2 - Somar a altura do sprite1 com a altura do sprite2;

2.3 - se a distância for menor ou igual à soma das alturas, a colisão foi confirmada.

No passo 1.3, vejam que há condição para continuar. Se uma colisão em uma das dimensões não tiver sido detectada, não há necessidade de testar na outra dimensão.

Colidir em uma dimensão parece coisa de doido, mas o que quero dizer é o seguinte:

colisionx

Na figura acima, houve uma colisão no eixo Y, mas não no eixo X. Portanto, para um jogo 2D, não houve colisão. Veja que os dois quadrados ocupam as mesmas posições referentes a altura.

colisiony

Já nesta figura acima, houve uma colisão no eixo X, mas não no eixo Y.

squarescolision1

E aqui, finalmente ocorreu uma colisão nos dois eixos.

Segue o código abaixo.

C:
  1. public static bool DetectCollision(Sprite sprite1, Sprite sprite2)
  2. {
  3. bool partial1 = false;
  4. bool partial2 = false;
  5.  
  6. int xDistance = sprite2.Vertex2.X - sprite1.Vertex1.X;
  7. int yDistance = sprite2.Vertex4.Y - sprite1.Vertex1.Y;
  8.  
  9. //Verifica se sprite2 está mais à esquerda que sprite1
  10. if (sprite2.Vertex1.X <sprite1.Vertex1.X)
  11. {
  12. xDistance = sprite1.Vertex2.X - sprite2.Vertex1.X;
  13. }
  14.  
  15. //Verifica se sprite2 está mais ao topo que sprite1
  16. if (sprite2.Vertex1.Y &lt;sprite1.Vertex1.Y)
  17. {
  18. yDistance = sprite1.Vertex4.Y - sprite2.Vertex1.Y;
  19. }
  20.  
  21. int xSize = sprite1.PictureBoxMain.Size.Width + sprite2.PictureBoxMain.Size.Width;
  22. int ySize = sprite1.PictureBoxMain.Size.Height + sprite2.PictureBoxMain.Size.Height;
  23.  
  24. if (xDistance <= xSize)
  25. {
  26. partial1 = true;
  27. }
  28.  
  29. if (partial1)
  30. {
  31. if (yDistance <= ySize)
  32. {
  33. partial2 = true;
  34. }
  35. }
  36.  
  37. return partial2;
  38. }

Vocês devem ter visto que existe uma classe chamada Sprite. Esta classe é uma implementação minha para facilitar as coisas. Uma das facilidades é poder retornar o X/Y de cada um dos vértices do pictureBox, obtido através do Location e do Size. A implementação da classe Sprite segue abaixo:

C:
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. using System.Windows.Forms;
  5. using System.Drawing;
  6.  
  7. namespace WinFormsLander.Classes
  8. {
  9. public class Sprite
  10. {
  11. /*
  12. *
  13. *  1------2
  14. *  |      |
  15. *  |      |
  16. *  |      |
  17. *  4------3
  18. *
  19. * */
  20.  
  21. public Point Vertex1
  22. {
  23. get
  24. {
  25. return new Point(this.PictureBoxMain.Location.X, this.PictureBoxMain.Location.Y);
  26. }
  27. }
  28.  
  29. public Point Vertex2
  30. {
  31. get
  32. {
  33. return new Point(this.PictureBoxMain.Location.X + this.PictureBoxMain.Size.Width, this.PictureBoxMain.Location.Y);
  34. }
  35. }
  36.  
  37. public Point Vertex3
  38. {
  39. get
  40. {
  41. return new Point(this.PictureBoxMain.Location.X + this.PictureBoxMain.Size.Width, this.PictureBoxMain.Location.Y + this.PictureBoxMain.Size.Height);
  42. }
  43. }
  44.  
  45. public Point Vertex4
  46. {
  47. get
  48. {
  49. return new Point(this.PictureBoxMain.Location.X, this.PictureBoxMain.Location.Y + this.PictureBoxMain.Size.Height);
  50. }
  51. }
  52.  
  53. public PictureBox PictureBoxMain;
  54. public string ID;
  55. public Image ImageDefault;
  56. public int FlipVCounter;
  57. public int FlipHCounter;
  58. public bool FlipV;
  59. public bool FlipH;
  60. public float VerticalVelocity;
  61. public float HorizontalVelocity;
  62. public float TotalVelocity;
  63. public float Y;
  64. public float X;
  65. public float StretchedY;
  66. public float StretchedX;
  67. public int DefaultHeight;
  68. public int DefaultWidth;
  69. public float VerticalVelocityLimit;
  70. public float HorizontalVelocityLimit;
  71. public float Drag;
  72. public float GForce;
  73. public bool vBouncingZone;
  74. public bool hBouncingZone;
  75. public int FlickCount;
  76. public float Accel;
  77. public float AccelMpS;
  78.  
  79. public Sprite(PictureBox pictureBoxMain)
  80. {
  81. this.PictureBoxMain = pictureBoxMain;
  82. this.PictureBoxMain.BackColor = Color.Transparent;
  83. this.ID = "";
  84. this.ImageDefault = null;
  85. this.FlipVCounter = 0;
  86. this.FlipHCounter = 0;
  87. this.FlipV = false;
  88. this.FlipH = false;
  89. this.VerticalVelocity = 0;
  90. this.HorizontalVelocity = 0;
  91. this.TotalVelocity = 0;
  92. this.Y = PictureBoxMain.Location.Y;
  93. this.X = PictureBoxMain.Location.X;
  94. this.StretchedY = 0;
  95. this.StretchedX = 0;
  96. this.DefaultHeight = this.PictureBoxMain.Size.Height;
  97. this.DefaultWidth = this.PictureBoxMain.Size.Width;
  98. this.VerticalVelocityLimit = 50;
  99. this.HorizontalVelocityLimit = 50;
  100. this.Drag = 0;
  101. this.GForce = 0;
  102. this.vBouncingZone = false;
  103. this.hBouncingZone = false;
  104. this.FlickCount = 0;
  105. this.Accel = 0;
  106. this.AccelMpS = 0;
  107.  
  108. this.PictureBoxMain.BackgroundImage = this.ImageDefault;
  109. }
  110. }
  111. }

Parte 6 - Colisão de Borda

Tags: , , , , ,
Categorias: Desenvolvimento | No Comments »


Deixe um comentário