Jogo simples de batalha 2D com DarkGDK em C++ - Detecção e reação de colisão

O programa abaixo é um jogo arcade bem simples. Basta acertar os morcegões e não ser atingido pelos seus tiros. Quando se é atingido a tela é congelada por 2s e aparece FOI ATINGIDO no título.
Os sons (.WAV) dos tiros e colisões estão no diretório do DarkGDK. Basta copiá-los por diretório objeto do programa abaixo.


DBSpriteCollision → retorna um número maior que 0 se dois sprites se colidem. Ou seja, detecta a colisão.

DispararAoColidir_NaveTiroComAdversario e DispararAoColidir_AdversarioTiroComNave → são reações às colisões. A primeira rotina é executada quando o tiro da nave acerta o morcegão. A segunda quando a nave é atingida pelo tiro do morcegão.





Principal.cpp
#include "DarkGDK.h"

struct TPonto {
  float X, Y;
};

struct TVeiculo {
  bool   Atirou, Sinal;
  float  TiroVelocidade;
  int    Id, IdDirecao, IdTiro, Escala, TiroInc;
  TPonto Tiro, TiroInicio;
};

const int cIdAdversarioExplosaoSom= 300;
const int cIdAdversarioTiroSom    = 301;
const int cIdFundo                = 400;
const int cIdNaveExplosaoSom      = 500;
const int cIdNaveTiroSom          = 501;
const int cIdVetorDirecao         = 600;
const int cIdVetorOrigem          = 601;
const int cIncNaveTiroY           = 5;
const int cIncNaveX               = 5;
const int cIncNaveY               = 5;
const int cIntervalo              = 3000;
const int cTamAdv                 = 50;
const int cXMin                   = cIncNaveX;

char *cAdversarioExplosaoSomNome= "Boom 1.wav";
char *cAdversarioNome           = "ufo.bmp";
char *cAdversarioTiroNome       = "ufotiro.bmp";
char *cAdversarioTiroSomNome    = "rocket.wav";
char *cFundo                    = "Fundo640x480.bmp";
char *cNaveExplosaoSomNome      = "Bomb near.wav";
char *cNaveNome                 = "nave.bmp";
char *cNaveTiroNome             = "tiro.bmp";
char *cNaveTiroSomNome          = "fire.wav";

bool     gFoiAtingido= false;
int      gAcertos= 0, gAngulo= 0, gAtingido= 0;
int      gAlturaMaxAdv, gDecorrido, gLarguraMaxAdv, gLarguraTelaAdv, gYMin, gYMax, gYMaxAdv;
float    gP0= 0;
TVeiculo gAdversario, gNave;

void  Mostrar_Texto (char *ATexto);
float OlharPara     (int APosicaoX, int APosicaoY, int AOlhandoX, int AOlhandoY, int ADestinoX, int ADestinoY);
void Atirar_Adversario (void)

{
  if (!gAdversario.Atirou) {
    dbShowSprite (gAdversario.IdTiro);
    dbPlaySound (cIdAdversarioTiroSom);

    gAdversario.Atirou = true;
    gAdversario.TiroInc= 0;
  }
}
void Atirar_Nave (void)

{
  if (!gNave.Atirou) {
    gNave.Atirou= true;
    gNave.Tiro.X= dbSpriteX (gNave.Id) + (dbSpriteWidth (gNave.Id) / 2) - (dbSpriteWidth (gNave.IdTiro) / 2);
    gNave.Tiro.Y= dbSpriteY (gNave.Id) - 15;
    dbShowSprite (gNave.IdTiro);
    dbPlaySound (cIdNaveTiroSom);
  }
}
void DispararAoColidir_NaveTiroComAdversario (void)

{
  gNave.Atirou= false;
  dbHideSprite (gAdversario.Id);
  dbPlaySound (cIdAdversarioExplosaoSom);
  gAcertos++;
}
void DispararAoColidir_AdversarioTiroComNave (void)

{
  gAdversario.Atirou= false;
  gFoiAtingido      = true;
  dbPlaySound (cIdNaveExplosaoSom);
  gAtingido++;
}
void Navegar (void)

{
  if (dbUpKey ())
    if (dbSpriteY (gNave.Id) - cIncNaveY >= gYMin)
      dbSprite (gNave.Id, dbSpriteX (gNave.Id), dbSpriteY (gNave.Id) - cIncNaveY, 1);

  if (dbDownKey ())
    if (dbSpriteY (gNave.Id) + cIncNaveY <= gYMax)
      dbSprite (gNave.Id, dbSpriteX (gNave.Id), dbSpriteY (gNave.Id) + cIncNaveY, 1);

  if (dbKeyState (203) || dbKeyState (30)) // Movimentar nave pra esquerda <Seta esquerda>, <a>
    if (dbSpriteX (gNave.Id) >= cXMin)
      dbSprite (gNave.Id, dbSpriteX (gNave.Id) - cIncNaveX, dbSpriteY (gNave.Id), 1);

  if (dbKeyState (205) || dbKeyState (32)) // Movimentar nave pra direita <Seta direita>, <d>
    if (dbSpriteX (gNave.Id) <= dbScreenWidth () - dbSpriteWidth (gNave.Id) - cXMin)
      dbSprite (gNave.Id, dbSpriteX (gNave.Id) + cIncNaveX, dbSpriteY (gNave.Id), 1);

  if (dbKeyState (57)) // Atirar <Barra de espaço>
    Atirar_Nave ();
}
// O oponente vai crescendo até cTamAdv%, daí atira. A seguir passa a encolher
void Mostrar_Adversario (void)

{
  int XCentroNave;

  if (gAdversario.Escala > 0) {
    if (gAdversario.Escala == cTamAdv) {
      // Atira e muda o sinal pra diminuir
      if (dbSpriteVisible (gAdversario.Id))
        Atirar_Adversario ();
      gAdversario.Sinal= false;
      gAdversario.Escala--;
    }
    else {
      // Aumenta/diminui tamanho
      dbStretchSprite (gAdversario.Id, gAdversario.Escala, gAdversario.Escala);
      if (gAdversario.Sinal)
        gAdversario.Escala++;
      else
        gAdversario.Escala--;
    }
  }
  else if (dbTimer () - gDecorrido >= cIntervalo) {
    // Mostra o oponente a cada 'gDecorrido' milisegundos
    gDecorrido        = dbTimer ();
    gAdversario.Escala= 1;
    gAdversario.Sinal = true;
    dbSprite (gAdversario.Id, dbRND (gLarguraTelaAdv) + gLarguraMaxAdv, dbRND (gYMaxAdv) + gAlturaMaxAdv, 1);

    dbOffsetSprite (gAdversario.Id, gLarguraMaxAdv/2, gAlturaMaxAdv/2);

    gAdversario.TiroInicio.X= dbSpriteX (gAdversario.Id) - dbSpriteWidth (gAdversario.IdTiro) / 2;
    gAdversario.TiroInicio.Y= dbSpriteY (gAdversario.Id);
    XCentroNave             = dbSpriteX (gNave.Id) + dbSpriteWidth (gNave.Id) / 2;
    gAngulo                 = OlharPara (dbSpriteX (gAdversario.Id), dbSpriteY (gAdversario.Id), dbSpriteX (gAdversario.Id), dbSpriteY (gNave.Id), XCentroNave, dbSpriteY (gNave.Id));

    // Vetor_Direcao = Destino - Fonte
    dbSetVector2 (cIdVetorDirecao, XCentroNave - gAdversario.TiroInicio.X, dbSpriteY (gNave.Id) - gAdversario.TiroInicio.Y);
    dbNormalizeVector2 (gAdversario.IdDirecao, cIdVetorDirecao);

    dbShowSprite (gAdversario.Id);
    dbRotateSprite (gAdversario.Id, gAngulo);
  }
  else
    dbHideSprite (gAdversario.Id);
}
void Mostrar_AdversarioTiro (void)

{
  float X, Y;

  if (gAdversario.Atirou) {
    // Mostra o tiro
    X= gAdversario.TiroInicio.X + dbXVector2 (gAdversario.IdDirecao)*gAdversario.TiroVelocidade*gAdversario.TiroInc;
    Y= gAdversario.TiroInicio.Y + dbYVector2 (gAdversario.IdDirecao)*gAdversario.TiroVelocidade*gAdversario.TiroInc;
    gAdversario.TiroInc++;
    dbSprite (gAdversario.IdTiro, X, Y, 1);

    // Retira o tiro se ultrapassar a janela ou se bater na nave
    if (Y > dbScreenHeight ())
      gAdversario.Atirou= false;
    else if (dbSpriteCollision (gAdversario.IdTiro, gNave.Id) && dbSpriteVisible (gNave.Id))
      DispararAoColidir_AdversarioTiroComNave ();
    if (!gAdversario.Atirou)
      dbHideSprite (gAdversario.IdTiro);
  }
}
void Mostrar_Fundo (void)

{
  const float cPVelocidade= 10.0f;
  int A, L;

  gP0+= 0.25f*cPVelocidade;
  if (gP0 > dbScreenHeight ())
    gP0= 0;

  dbSprite (cIdFundo,   0, gP0,                   cIdFundo);
  dbSprite (cIdFundo+1, 0, gP0-dbScreenHeight (), cIdFundo);
}
void Mostrar_NaveTiro (void)

{
  if (gNave.Atirou) {
    if (gNave.Tiro.Y <= 0)
      gNave.Atirou= false;
    else if (dbSpriteCollision (gNave.IdTiro, gAdversario.Id) && dbSpriteVisible (gAdversario.Id))
      DispararAoColidir_NaveTiroComAdversario ();
    else {
      dbSprite (gNave.IdTiro, gNave.Tiro.X, gNave.Tiro.Y, 1);
      gNave.Tiro.Y= gNave.Tiro.Y - cIncNaveTiroY*gNave.TiroVelocidade;
    }
    if (!gNave.Atirou)
      dbHideSprite (gNave.IdTiro);
  }
}
void Mostrar_Texto (char *ATexto)

{
  char Balaio[256];

  if (ATexto != "")
    dbSetWindowTitle (ATexto);
  else {
    sprintf (Balaio, "Oponentes: %d; Nave: %d", gAcertos, gAtingido);
    dbSetWindowTitle (Balaio);
  }
}
float OlharPara (TPonto APosicao, TPonto APosicaoOlhando, TPonto APosicaoDesejada)

{
  float Angulo, Determinante, ModuloD, ModuloO;

  dbSetVector2 (cIdVetorDirecao, APosicaoDesejada.X-APosicao.X, APosicaoDesejada.Y-APosicao.Y);
  dbSetVector2 (cIdVetorOrigem, APosicaoOlhando.X-APosicao.X, APosicaoOlhando.Y-APosicao.Y);

  // Ângulo entre vetores
  ModuloD     = dbLengthVector2 (cIdVetorDirecao);
  ModuloO     = dbLengthVector2 (cIdVetorOrigem);
  Angulo      = dbACOS (dbDotProductVector2 (cIdVetorOrigem, cIdVetorDirecao)/(ModuloD*ModuloO));
  Determinante= dbXVector2 (cIdVetorOrigem)*dbYVector2 (cIdVetorDirecao) - dbXVector2 (cIdVetorDirecao)*dbYVector2 (cIdVetorOrigem);
  if (Determinante < 0)
    Angulo= 360-Angulo;

  return Angulo;
}
float OlharPara (int APosicaoX, int APosicaoY, int AOlhandoX, int AOlhandoY, int ADestinoX, int ADestinoY)

{
  TPonto Destino, Olhando, Posicao;

  Destino.X= ADestinoX;
  Destino.Y= ADestinoY;

  Posicao.X= APosicaoX;
  Posicao.Y= APosicaoY;

  Olhando.X= AOlhandoX;
  Olhando.Y= AOlhandoY;

  return OlharPara (Posicao, Olhando, Destino);
}
void DarkGDK (void)

{
  dbSetDisplayMode (640, 480, 32);

  dbAutoCamOff ();
  dbSyncOn ();
  dbSyncRate (60);

  dbSetImageColorKey (1, 100, 120);

  SetCurrentDirectory ("objeto");

  gNave.Atirou              = false;
  gNave.Id                  = 10;
  gNave.IdTiro              = 11;
  gNave.TiroVelocidade      = 2.0f;

  gAdversario.Atirou        = false;
  gAdversario.Escala        = 0;
  gAdversario.Id            = 20;
  gAdversario.IdTiro        = 21;
  gAdversario.IdDirecao     = 22;
  gAdversario.Sinal         = true;
  gAdversario.TiroInc       = 0;
  gAdversario.TiroVelocidade= 5.0f;

  dbCreateAnimatedSprite (gNave.Id,           cNaveNome,           1, 1, gNave.Id);
  dbCreateAnimatedSprite (gNave.IdTiro,       cNaveTiroNome,       1, 1, gNave.IdTiro);
  dbCreateAnimatedSprite (gAdversario.Id,     cAdversarioNome,     1, 1, gAdversario.Id);
  dbCreateAnimatedSprite (gAdversario.IdTiro, cAdversarioTiroNome, 1, 1, gAdversario.IdTiro);

  dbLoadImage (cFundo, cIdFundo);

  gAlturaMaxAdv  = dbSpriteHeight (gAdversario.Id);
  gLarguraMaxAdv = dbSpriteWidth (gAdversario.Id);
  gLarguraTelaAdv= dbScreenWidth () - 2*gLarguraMaxAdv;

  dbStretchSprite (gNave.Id,           50, 50);
  dbStretchSprite (gNave.IdTiro,       50, 50);
  dbStretchSprite (gAdversario.IdTiro, 50, 50);

  dbSetSpritePriority (gNave.Id, 3);
  dbSetSpritePriority (gNave.IdTiro, 3);
  dbSetSpritePriority (gAdversario.Id, 3);
  dbSetSpritePriority (gAdversario.IdTiro, 3);

  dbLoadSound (cNaveTiroSomNome,           cIdNaveTiroSom);
  dbLoadSound (cNaveExplosaoSomNome,       cIdNaveExplosaoSom);
  dbLoadSound (cAdversarioTiroSomNome,     cIdAdversarioTiroSom);
  dbLoadSound (cAdversarioExplosaoSomNome, cIdAdversarioExplosaoSom);

  dbMakeVector2 (cIdVetorDirecao);
  dbMakeVector2 (cIdVetorOrigem);
  dbMakeVector2 (gAdversario.IdDirecao);

  gYMax   = dbScreenHeight () - dbSpriteHeight (gNave.Id) - 2;
  gYMaxAdv= dbScreenHeight () / 2;
  gYMin   = gYMaxAdv + gAlturaMaxAdv*2;

  dbSprite (gNave.Id, (dbScreenWidth ()-dbSpriteWidth (gNave.Id))/2, gYMax, 1);

  gDecorrido= dbTimer () - cIntervalo - 500;

  while (LoopGDK () && !dbEscapeKey ()) {
    Mostrar_Fundo ();
    Navegar ();
    Mostrar_NaveTiro ();
    Mostrar_AdversarioTiro ();
    Mostrar_Adversario ();
    Mostrar_Texto ("");
    if (gFoiAtingido)
      Mostrar_Texto ("FOI ATINGIDO!!!");
    dbSync ();
    if (gFoiAtingido) {
      dbWait (2000);
      gFoiAtingido= false;
    }
  }

  dbDeleteVector2 (cIdVetorDirecao);
  dbDeleteVector2 (cIdVetorOrigem);
  dbDeleteVector2 (gAdversario.IdDirecao);
}


Clique aqui pra baixar o código




http://transeberiano.brinkster.net