Desenvolver jogos com XNA em C# - Detecção e reação de colisão 2D

O programa abaixo é uma versão XNA do exemplo Colisão.


Como o XNA não tem um detectador de colisão embutido nos "sprites", a detecção aqui é feita com retângulos (objeto "Rectangle").


Intersects: retornar verdadeiro se dois retângulos se colidem.

Random: usado pra gerar números aleatórios.



Programa.cs
using System;

namespace Colisao

{
  static class TPrograma

  {
    static void Main (string[] args)

    {
      using (TPrincipal Jogo= new TPrincipal ())
        Jogo.Run ();
    }
  }
}


Principal.cs
namespace Colisao

{
  public class TDispositivo

  {
    public Game                  G;
    public GraphicsDeviceManager GDM;
    public KeyboardState         KS;
    public Viewport              VP;
    public TDispositivo (Game AG)

    {
      G  = AG;
      GDM= new GraphicsDeviceManager (AG);
    }
  }





  public class TPrincipal_Base: Microsoft.Xna.Framework.Game

  {
    public int          AcertosNave, AcertosUFO;
    public SpriteBatch  Titulo;
    public SpriteFont   TituloFonte;
    public string       TituloTexto;
    public TDispositivo Dispositivo;
    public TNave        Nave;
    public TUFO         UFO;
    public Vector2      TituloPosicao;
    public TPrincipal_Base ()

    {
      Dispositivo          = new TDispositivo (this);
      Content.RootDirectory= "Content";
      this.IsFixedTimeStep = true;
    }
    protected override void Initialize ()

    {
      Disparar_AoInicializar ();
      base.Initialize ();
      this.IsMouseVisible= true;
    }
    protected override void LoadContent ()

    {
      Dispositivo.VP= Dispositivo.GDM.GraphicsDevice.Viewport;
      Titulo        = new SpriteBatch (GraphicsDevice);
      TituloFonte   = Content.Load<SpriteFont> ("Courier New");
      TituloPosicao = new Vector2 (0, 0);
      Nave.CarregarTexturas (@"objeto\nave", @"objeto\navetiro");
      UFO.CarregarTexturas (@"objeto\ufo", @"objeto\ufotiro");
    }
    protected override void Update (GameTime AGT)

    {
      if (GamePad.GetState (PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
        this.Exit ();

      Nave.Tempo   += AGT.ElapsedGameTime.TotalMilliseconds;
      UFO.Tempo     = Nave.Tempo;
      Dispositivo.KS= Keyboard.GetState ();

      base.Update (AGT);
    }
    protected override void Draw (GameTime AGT)

    {
      Dispositivo.GDM.GraphicsDevice.Clear (Color.Black);

      Disparar_AoDesenhar ();

      base.Draw (AGT);
    }
    protected virtual void Disparar_AoDesenhar ()

    {
    }
    protected virtual void Disparar_AoInicializar ()

    {
    }
  }
}


Maquina.cs
namespace Colisao

{
  public class TRobo

  {
    public bool         Atirou, Visivel;
    public float        Angulo, Escala;
    public Rectangle    Area, Envolucro;
    public SpriteBatch  Robo;
    public TDispositivo Dispositivo;
    public Texture2D    Textura, TexturaEnvolucro;
    public Vector2      Deslocamento, Direcao, Incremento, Inicio, Olhando, Origem, Posicao;
    public TRobo (TDispositivo ADispositivo)

    {
      Dispositivo = ADispositivo;
      Angulo      = 0.0f;
      Area        = new Rectangle (0, 0, 0, 0);
      Atirou      = false;
      Deslocamento= new Vector2 (0, 0);
      Envolucro   = new Rectangle (0, 0, 0, 0);
      Escala      = 1.0f;
      Incremento  = new Vector2 (10, 10);
      Inicio      = new Vector2 (0, 0);
      Olhando     = new Vector2 (0, 0);
      Origem      = new Vector2 (0, 0);
      Posicao     = new Vector2 (0, 0);
      Robo        = new SpriteBatch (Dispositivo.GDM.GraphicsDevice);
      Visivel     = true;
    }
    public virtual void AtualizarEnvolucro (bool AAjusta)

    {
      double A, Diametro, L;

      A               = Textura.Height*Escala;
      L               = Textura.Width*Escala;
      Envolucro.X     = Convert.ToInt32 ((Origem.X == 0) ? Posicao.X : Posicao.X - (L/2));
      Envolucro.Y     = Convert.ToInt32 ((Origem.Y == 0) ? Posicao.Y : Posicao.Y - (A/2));
      Envolucro.Width = Convert.ToInt32 (L);
      Envolucro.Height= Convert.ToInt32 (A);
      if (AAjusta) {
        Diametro        = Math.Sqrt (Envolucro.Width*Envolucro.Width + Envolucro.Height*Envolucro.Height);
        Envolucro.X     = Convert.ToInt32 (Envolucro.X + (Envolucro.Width-Diametro)/2);
        Envolucro.Y     = Convert.ToInt32 (Envolucro.Y + (Envolucro.Height-Diametro)/2);
        Envolucro.Width = Convert.ToInt32 (Diametro);
        Envolucro.Height= Convert.ToInt32 (Diametro);
      }
    }
    public virtual void CarregarTextura (string AArquivo)

    {
      Textura         = Dispositivo.G.Content.Load<Texture2D> (AArquivo);
      TexturaEnvolucro= Dispositivo.G.Content.Load<Texture2D> (@"objeto\retangulo");
    }
    public virtual void Mostrar (float AProfundidade)

    {
      AtualizarEnvolucro (true);
      if (Visivel) {
        Robo.Begin (SpriteBlendMode.Additive);
        Robo.Draw (Textura, Posicao, null, Color.White, Angulo, Origem, Escala, SpriteEffects.None, AProfundidade);
        Robo.End ();
      }
    }
    public virtual void MostrarEnvolucro ()

    {
      Robo.Begin (SpriteBlendMode.Additive);
      Robo.Draw (TexturaEnvolucro, Envolucro, Color.Blue);
      Robo.End ();
    }
  }





  public class TVeiculo

  {
    public double     Decorrido= 0.0f, Tempo= 0.0f;
    public TPrincipal Principal;
    public TRobo      Tiro, Veiculo;
  }





  public class TNave: TVeiculo

  {
    public TNave (TPrincipal APrincipal)

    {
      Principal= APrincipal;
      Tiro     = new TRobo (APrincipal.Dispositivo);
      Veiculo  = new TRobo (APrincipal.Dispositivo);
    }
    public void CarregarTexturas (string ANave, string ATiro)

    {
      Veiculo.CarregarTextura (ANave);
      Tiro.CarregarTextura (ATiro);
      Veiculo.Area.Width = Veiculo.Dispositivo.VP.Width-Veiculo.Textura.Width; 
      Veiculo.Area.Height= Veiculo.Dispositivo.VP.Height-Veiculo.Textura.Height;
      Veiculo.Area.Y     = Veiculo.Dispositivo.VP.Height/2;
      Veiculo.Posicao.X  = (Veiculo.Dispositivo.VP.Width-Veiculo.Textura.Width)/2;
      Veiculo.Posicao.Y  = Veiculo.Area.Height;
      Veiculo.Olhando.X  = Veiculo.Posicao.X;
      Veiculo.Olhando.Y  = 0;
    }
    public virtual void Atirar ()

    {
      if (!Veiculo.Atirou) {
        Veiculo.Atirou= true;
        Tiro.Posicao.X= Veiculo.Posicao.X + (Veiculo.Textura.Width-Tiro.Textura.Width)/2;
        Tiro.Posicao.Y= Veiculo.Posicao.Y - Tiro.Textura.Height;
      }
    }
    public virtual void MostrarTiro ()

    {
      if (Veiculo.Atirou)
        if (Tiro.Posicao.Y <= 0)
          Veiculo.Atirou= false;
        else {
          // Como o envólucro não é uma circunferência, faz-se um ajuste pra abrigar a área de rotação (retângulo externo à circunferência de rotação)
          Principal.UFO.Veiculo.AtualizarEnvolucro (true);
          Tiro.AtualizarEnvolucro (false);
          if ((Principal.UFO.Veiculo.Escala > 0.0f) && Tiro.Envolucro.Intersects (Principal.UFO.Veiculo.Envolucro)) { // Detecção de colisão 2D
            Principal.DispararAoColidir_NaveTiro_Com_UFO ();
            Veiculo.Atirou= false;
          }
          else {
            Tiro.Mostrar (1.0f);
            Tiro.Posicao.Y-= Tiro.Incremento.Y;
          }
        }
    }
  }





  public class TUFO: TVeiculo

  {
    protected const float cIntervalo           = 1800.0f;
    protected const float cEscalaMax           = 0.5f; // 50%
    protected const float cEscalaIncrementoTaxa= cEscalaMax/40;

    public float   EscalaIncremento;
    public Random  Aleatorio;
    public Vector2 Alvo;
    public TUFO (TPrincipal APrincipal)

    {
      Principal       = APrincipal;
      Aleatorio       = new Random ();
      Alvo            = new Vector2 (0, 0);
      Tiro            = new TRobo (APrincipal.Dispositivo);
      Veiculo         = new TRobo (APrincipal.Dispositivo);

      Decorrido       = -cIntervalo;
      EscalaIncremento= 0.1f;
      Veiculo.Escala  = 0.0f;
    }
    public void CarregarTexturas (string ANave, string ATiro)

    {
      Veiculo.CarregarTextura (ANave);
      Tiro.CarregarTextura (ATiro);
      Veiculo.Area.X     = Convert.ToInt32 (Veiculo.Textura.Width*cEscalaMax);
      Veiculo.Area.Y     = Convert.ToInt32 (Veiculo.Textura.Height*cEscalaMax);
      Veiculo.Area.Width = Veiculo.Dispositivo.VP.Width - Convert.ToInt32 (Veiculo.Textura.Width*cEscalaMax);
      Veiculo.Area.Height= Principal.Nave.Veiculo.Area.Y - Convert.ToInt32 (Veiculo.Textura.Height*cEscalaMax);
      Veiculo.Origem.X   = Veiculo.Textura.Width/2;
      Veiculo.Origem.Y   = Veiculo.Textura.Height/2;
      Veiculo.Olhando.X  = Veiculo.Posicao.X;
      Veiculo.Olhando.Y  = Veiculo.Dispositivo.VP.Height;
    }
    public virtual void Atirar ()

    {
      if (Veiculo.Visivel && !Veiculo.Atirou) {
        Veiculo.Atirou   = true;
        Tiro.Deslocamento= Vector2.Zero;
        Tiro.Inicio.X    = Veiculo.Posicao.X - Tiro.Textura.Width/2;
        Tiro.Inicio.Y    = Veiculo.Posicao.Y - Tiro.Textura.Height/2;
        Tiro.Posicao     = Tiro.Inicio;
        Tiro.Direcao     = Alvo-Tiro.Inicio;
        Tiro.Direcao.Normalize ();
      }
    }
    public void Mostrar ()

    {
      if (Veiculo.Escala > 0.0f) {
        if (Veiculo.Escala >= cEscalaMax) { // Atira e muda o sinal pra diminuir
          Atirar ();
          EscalaIncremento= -cEscalaIncrementoTaxa;
        }
        Veiculo.Mostrar (0.0f);
        Veiculo.Escala+= EscalaIncremento;
      }
      else if (Tempo - Decorrido >= cIntervalo) { // Mostra o oponente a cada cIntervalo milisegundos
        Decorrido        = Tempo;
        EscalaIncremento = cEscalaIncrementoTaxa;
        Veiculo.Escala   = 0.1f;
        Veiculo.Posicao.X= Aleatorio.Next (Veiculo.Area.X, Veiculo.Area.Width);
        Veiculo.Posicao.Y= Aleatorio.Next (Veiculo.Area.Y, Veiculo.Area.Height);
        Veiculo.Olhando.X= Veiculo.Posicao.X;
        Alvo.X           = Principal.Nave.Veiculo.Posicao.X + Principal.Nave.Veiculo.Textura.Width/2;
        Alvo.Y           = Principal.Nave.Veiculo.Posicao.Y;
        Veiculo.Angulo   = OlharPara (Veiculo.Posicao, Veiculo.Olhando, Alvo);
        Veiculo.Visivel  = true;
        Veiculo.Mostrar (0.0f);
      }
    }
    public virtual void MostrarTiro ()

    {
      if (Veiculo.Atirou)
        if (Tiro.Posicao.Y > Tiro.Dispositivo.VP.Height)
          Veiculo.Atirou= false;
        else {
          Tiro.Posicao      = Tiro.Inicio + Tiro.Direcao*Tiro.Deslocamento;
          Tiro.Deslocamento+= Tiro.Incremento;
          Principal.Nave.Veiculo.AtualizarEnvolucro (false);
          Tiro.AtualizarEnvolucro (false);
          if (Tiro.Envolucro.Intersects (Principal.Nave.Veiculo.Envolucro)) { // Detecção de colisão 2D
            Principal.DispararAoColidir_UFOTiro_Com_Nave ();
            Veiculo.Atirou= false;
          }
          else
            Tiro.Mostrar (1.0f);
        }
    }
    protected virtual float OlharPara (Vector2 APosicao, Vector2 APosicaoOlhando, Vector2 APosicaoDesejada)

    {
      Vector2 Direcao, Origem;

      Origem = APosicaoOlhando-APosicao;
      Direcao= APosicaoDesejada-APosicao;

      // Ângulo entre vetores
      return (float) (Math.Atan2 (Direcao.Y, Direcao.X) - Math.Atan2 (Origem.Y, Origem.X));
    }
  }





  public class TPrincipal: TPrincipal_Base

  {
    protected bool MostraEnvolucro= false;
    protected override void Disparar_AoInicializar ()

    {
      Nave       = new TNave (this);
      AcertosNave= 0;
      AcertosUFO = 0;
      UFO        = new TUFO (this);
    }
    protected override void Disparar_AoDesenhar ()

    {
      Navegar ();
      if (MostraEnvolucro) {
        UFO.Veiculo.AtualizarEnvolucro (true);
        UFO.Veiculo.MostrarEnvolucro ();
      }
      Nave.Veiculo.Mostrar (0.0f);
      UFO.Mostrar ();
      UFO.MostrarTiro ();
      Nave.MostrarTiro ();
      MostrarTitulo ();
    }
    public virtual void DispararAoColidir_NaveTiro_Com_UFO ()

    {
      AcertosNave++;
      UFO.Veiculo.Visivel= false;
    }
    public virtual void DispararAoColidir_UFOTiro_Com_Nave ()

    {
      AcertosUFO++;
    }
    protected virtual void MostrarTitulo ()

    {
      TituloTexto= "PLACAR (Nave: " + AcertosNave + "; UFO: " + AcertosUFO + ")";

      Titulo.Begin ();
      Titulo.DrawString (TituloFonte, TituloTexto, TituloPosicao, Color.LightGreen);
      Titulo.End ();
    }
    protected virtual void Navegar ()

    {
      if (Dispositivo.KS.IsKeyDown (Keys.Enter))
        MostraEnvolucro= true;
      else if (MostraEnvolucro && Dispositivo.KS.IsKeyDown (Keys.Escape))
        MostraEnvolucro= false;

      if (Dispositivo.KS.IsKeyDown (Keys.Up) && (Nave.Veiculo.Posicao.Y-Nave.Veiculo.Incremento.Y >= Nave.Veiculo.Area.Y))
        Nave.Veiculo.Posicao.Y-= Nave.Veiculo.Incremento.Y;

      if (Dispositivo.KS.IsKeyDown (Keys.Down) && (Nave.Veiculo.Posicao.Y+Nave.Veiculo.Incremento.Y <= Nave.Veiculo.Area.Height))
        Nave.Veiculo.Posicao.Y+= Nave.Veiculo.Incremento.Y;

      if (Dispositivo.KS.IsKeyDown (Keys.Left) && (Nave.Veiculo.Posicao.X >= Nave.Veiculo.Area.X))
        Nave.Veiculo.Posicao.X-= Nave.Veiculo.Incremento.X;

      if (Dispositivo.KS.IsKeyDown (Keys.Right) && (Nave.Veiculo.Posicao.X <= Nave.Veiculo.Area.Width))
        Nave.Veiculo.Posicao.X+= Nave.Veiculo.Incremento.X;

      if (Dispositivo.KS.IsKeyDown (Keys.Space))
        Nave.Atirar ();
    }
  }
}



Clique aqui pra baixar o código (30,9 Ko)





http://transeberiano.brinkster.net