Desenvolvimento de jogos com XNA em C# - Perseguição de oponente 2D

Nesse exemplo a perseguição de oponente se comporta como uma Máquina de Estados.
O oponente (alvo) é a nave do jogador.
O UFO rastreia o alvo. Rotaciona até ficar de frente pra ele. Atira nele. E finalmente o persegue (em linha reta) até colidir.
A rotina que controla esses estados é Reposicionar.




  Programa.cs  
using System;

namespace Perseguicao

{
  static class TPrograma

  {
    static void Main (string[] args)

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

  Principal.cs  
namespace Perseguicao

{
  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 double       Intervalo= 1, Tempo= 0;
    public SpriteBatch  Titulo;
    public SpriteFont   TituloFonte;
    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 ();

      Intervalo     = AGT.ElapsedGameTime.TotalMilliseconds;
      Tempo        += AGT.ElapsedGameTime.TotalMilliseconds;
      Dispositivo.KS= Keyboard.GetState ();
      if (Intervalo == 0)
        Intervalo= 1;

      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 Perseguicao

{
  public class TRobo

  {
    public bool         Atirou, Visivel;
    public float        Angulo, AnguloFinal, AnguloIncremento, 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)

    {
      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;
    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);
      Veiculo.Incremento.X= 10;
      Veiculo.Incremento.Y= 10;
    }
    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     = 0;
      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 DetectarColisoes ()

    {
      // 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);
      Veiculo.AtualizarEnvolucro (false);
      Tiro.AtualizarEnvolucro (false);
      if (Principal.UFO.Veiculo.Envolucro.Intersects (Veiculo.Envolucro))
        Principal.DispararAoColidir_Nave_Com_UFO ();
      else if (Veiculo.Atirou && Tiro.Envolucro.Intersects (Principal.UFO.Veiculo.Envolucro)) {
        Principal.DispararAoColidir_NaveTiro_Com_UFO ();
        Veiculo.Atirou= false;
      }
    }
    public virtual void MostrarTiro ()

    {
      if (Veiculo.Atirou)
        if (Tiro.Posicao.Y <= 0)
          Veiculo.Atirou= false;
        else {
          Tiro.Mostrar (1.0f);
          Tiro.Posicao.Y-= Tiro.Incremento.Y;
        }
    }
    public virtual void Reinicializar ()

    {
      Veiculo.Posicao.X= (Veiculo.Dispositivo.VP.Width-Veiculo.Textura.Width)/2;
      Veiculo.Posicao.Y= Veiculo.Area.Height;
      Veiculo.Atirou   = false;
    }
  }





  public class TUFO: TVeiculo

  {
    protected const int cEAguardando= 0;
    protected const int cERastrear  = 1;
    protected const int cEGirando   = 2;
    protected const int cEAtirando  = 3;
    protected const int cECorrendo  = 4;
    protected const int cEMorto     = 5;

    public int     Estado= cERastrear, TempoGiro;
    public Random  Aleatorio;
    public Vector2 Alvo, NovaPosicao;
    public TUFO (TPrincipal APrincipal)

    {
      Principal           = APrincipal;
      Aleatorio           = new Random ();
      Alvo                = new Vector2 (0, 0);
      NovaPosicao         = new Vector2 (0, 0);
      Tiro                = new TRobo (APrincipal.Dispositivo);
      Veiculo             = new TRobo (APrincipal.Dispositivo);
      Veiculo.Incremento.X= 5;
      Veiculo.Incremento.Y= 5;
    }
    public void CarregarTexturas (string ANave, string ATiro)

    {
      Veiculo.CarregarTextura (ANave);
      Tiro.CarregarTextura (ATiro);
      Veiculo.Area.X     = Veiculo.Textura.Width;
      Veiculo.Area.Y     = Veiculo.Textura.Height;
      Veiculo.Area.Width = Veiculo.Dispositivo.VP.Width - Veiculo.Textura.Width;
      Veiculo.Area.Height= Veiculo.Dispositivo.VP.Height - Veiculo.Textura.Height;
      Veiculo.Origem.X   = Veiculo.Textura.Width/2;
      Veiculo.Origem.Y   = Veiculo.Textura.Height/2;
      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;
      Veiculo.Olhando.Y  = Veiculo.Dispositivo.VP.Height;
    }
    protected 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 ();
      }
    }
    protected virtual void Correr ()

    {
      NovaPosicao= Veiculo.Inicio + Veiculo.Direcao*Veiculo.Deslocamento;
      if ((NovaPosicao.X >= Veiculo.Area.X) && (NovaPosicao.Y >= Veiculo.Area.Y) && (NovaPosicao.X <= Veiculo.Area.Width) && (NovaPosicao.Y <= Veiculo.Area.Height)) {
        Veiculo.Posicao      = NovaPosicao;
        Veiculo.Olhando.X    = Veiculo.Posicao.X;
        Veiculo.Deslocamento+= Veiculo.Incremento;
      }
    }
    public virtual void DetectarColisoes ()

    {
      Principal.Nave.Veiculo.AtualizarEnvolucro (false);
      Veiculo.AtualizarEnvolucro (false);
      Tiro.AtualizarEnvolucro (false);
      if (Principal.Nave.Veiculo.Envolucro.Intersects (Veiculo.Envolucro))
        Principal.DispararAoColidir_UFO_Com_Nave ();
      else if (Veiculo.Atirou && Tiro.Envolucro.Intersects (Principal.Nave.Veiculo.Envolucro)) {
        Principal.DispararAoColidir_UFOTiro_Com_Nave ();
        Veiculo.Atirou= false;
      }
    }
    protected virtual void Girar ()

    {
      Veiculo.Angulo+= Veiculo.AnguloIncremento;
    }
    public void Rastrear ()

    {
      Alvo.X                  = Principal.Nave.Veiculo.Posicao.X + Principal.Nave.Veiculo.Textura.Width/2;
      Alvo.Y                  = Principal.Nave.Veiculo.Posicao.Y;
      Veiculo.AnguloFinal     = OlharPara (Veiculo.Posicao, Veiculo.Olhando, Alvo);
      Veiculo.AnguloIncremento= (Veiculo.AnguloFinal-Veiculo.Angulo)/(TempoGiro/(float) Principal.Intervalo);
      Veiculo.Inicio          = Veiculo.Posicao;
      Veiculo.Deslocamento    = Vector2.Zero;
      Veiculo.Direcao         = Alvo-Veiculo.Posicao;
      Veiculo.Direcao.Normalize ();
    }
    public virtual void MostrarTiro ()

    {
      if (Veiculo.Atirou)
        if ((Tiro.Posicao.X < 0) || (Tiro.Posicao.X > Tiro.Dispositivo.VP.Width) || (Tiro.Posicao.Y < 0) || (Tiro.Posicao.Y > Tiro.Dispositivo.VP.Height))
          Veiculo.Atirou= false;
        else {
          Tiro.Posicao      = Tiro.Inicio + Tiro.Direcao*Tiro.Deslocamento;
          Tiro.Deslocamento+= Tiro.Incremento;
          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 virtual void Reinicializar ()

    {
      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;
      Veiculo.Angulo   = 0;
      Veiculo.Atirou   = false;
      Estado           = cERastrear;
    }
    public virtual void Reposicionar ()

    {
      switch (Estado) {
        case cERastrear: Estado   = cEGirando;
                         TempoGiro= Aleatorio.Next (1000, 2000);
                         Rastrear ();
                         Decorrido= Principal.Tempo;
                         break;
        case cEGirando : if (Principal.Tempo - Decorrido >= TempoGiro) {
                           Veiculo.Angulo= Veiculo.AnguloFinal;
                           Estado        = cEAtirando;
                           Decorrido     = Principal.Tempo;
                         }
                         else
                           Girar ();
                         break;
        case cEAtirando: if (Principal.Tempo - Decorrido >= Aleatorio.Next (1000, 3000)) {
                           Estado   = cECorrendo;
                           Decorrido= Principal.Tempo;
                         }
                         else
                           Atirar ();
                         break;
        case cECorrendo: if (Principal.Tempo - Decorrido >= Aleatorio.Next (1000, 3000)) {
                           Estado   = cERastrear;
                           Decorrido= Principal.Tempo;
                         }
                         else
                           Correr ();
                         break;
      }
    }
  }





  public class TPrincipal: TPrincipal_Base

  {
    protected bool MostraEnvolucro= false;
    public int     AcertosNave, AcertosUFO, Colisoes;
    public string  TituloTexto;
    protected override void Disparar_AoInicializar ()

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

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

    {
      Colisoes++;
      UFO.Reinicializar ();
    }
    public virtual void DispararAoColidir_UFO_Com_Nave ()

    {
      Colisoes++;
      Nave.Reinicializar ();
    }
    public virtual void DispararAoColidir_NaveTiro_Com_UFO ()

    {
      AcertosNave++;
      UFO.Reinicializar ();
    }
    public virtual void DispararAoColidir_UFOTiro_Com_Nave ()

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

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

      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 ();
    }
  }
}



Código fonte





http://transeberiano.brinkster.net