Trajetória de tiros e acompanhamento de oponentes em 2D usando com DarkGDK em C++

O objetivo desse programa é disparar um tiro da nave pra posição atual do ponteiro.
Toda vez que se move o ponteiro a nave se rotaciona pra ficar de frente pro seu cursor.

As principais rotinas são→ Mostrar_Tiro e OlharPara.

A primeira monta a trajetória do tiro, simplesmente criando um vetor de direção.
A fórmula é a multiplicação de vetor por escalar→ PONTO = VETOR_DIRECAO * ESCALAR.
A outra obtém o ângulo entre vetores, usado pra nave encarar o cursor do ponteiro.

Há 2 outras versões de OlharParaOlharPara1 e OlharPara2.
Todas retornam o ângulo entre dois vetores.
Os vetores são formados entre a ORIGEM DA NAVE e o PONTO DE REFERÊNCIA e entre a ORIGEM DA NAVE e o ALVO.

ORIGEM DA NAVE é a posição da nave (no centro da tela).
PONTO DE REFERÊNCIA é pronde a nave aponta originalmente (pra baixo).
ALVO é a posição corrente do ponteiro (encontrada com as rotinas DBMouseX e DBMouseY).


Sprites em DarkGDK são usados pra mostrar imagens 2D em ambiente 2D ou 3D.
Nesse exemplo há apenas 2→ a nave (morcegão) e o tiro.


DBCreateAnimatedSprite → criar um sprite.

DBSprite → posicionar o sprite na tela.

DBOffsetSprite → atribuir um referencial no sprite. Por definição é (0; 0). Aqui é usado pra apontar pro centro (por causa da rotação).

DBSpriteHeight e DBSpriteWidth → obter respectivamente altura e largura dum sprite.

DBSetSpritePriority → definir a ordem de sobreposição dos sprites. Valores mais altos indicam que o sprite vai ficar por cima dos de valores mais baixos. Aqui o tiro deve ficar embaixo da nave.


Todos os objetos do DarkGDK são identificados por números (Ids).
Significa que sprites e vetores são acessados via Id (em vez de variáveis).
O que é ridículo pras linguagens de alto nível (como C++). Mas, enfim, em égua ganha não se olha os dentes.
Talvez a única explicação dessa peculariedade é pra manter compatibilidade com a versão em Basic (DarkBasic).





Principal.cpp
#include "DarkGDK.h"

struct TPonto {
  float X, Y;
};

char        *cDiretorio                  = "objeto";
char        *cOrigemNome                 = "ufo.bmp";
char        *cOrigemTiroNome             = "ufotiro.bmp";

const int   cId_Origem                   = 10;
const int   cId_OrigemTiro               = 11;
const int   cId_Vetor_Direcao            = 12;
const int   cId_Vetor_Direcao_Normalizado= 13;
const int   cId_Vetor_Origem             = 14;

const int   cIntervalo                   = 500;
const float cTiroVelocidade              = 10.0f;

bool  gAtirou;
float gAngulo;
int   gTiroInc= 0;
int   gDecorrido, gDestinoX, gDestinoY, gOrigemTiroX, gOrigemTiroY, gOrigemX, gOrigemY;

void Rotacionar (int ADestinoX, int ADestinoY);
void Mostrar_Texto ()

{
  char Balaio[256];

  sprintf (Balaio, "Ponteiro (%d; %d) %d°", dbMouseX (), dbMouseY (), (int) gAngulo);
  dbText (0, 0, Balaio);
}
void Mostrar_Tiro (void)

{
  float X, Y;

  if (gAtirou) {
    X= gOrigemTiroX + dbXVector2 (cId_Vetor_Direcao_Normalizado)*cTiroVelocidade*gTiroInc;
    Y= gOrigemTiroY + dbYVector2 (cId_Vetor_Direcao_Normalizado)*cTiroVelocidade*gTiroInc;
    gTiroInc++;

    dbSprite (cId_OrigemTiro, X, Y, 1);

    if ((X < 0) || (X > dbScreenWidth ()) || (Y < 0) || (Y > dbScreenHeight ()))
      gAtirou= false;
  }
  else if (dbTimer () - gDecorrido >= cIntervalo) {
    gTiroInc  = 0;
    gAtirou   = true;
    gDecorrido= dbTimer ();

    // Vetor_Direcao = Destino - Fonte
    dbSetVector2 (cId_Vetor_Direcao, gDestinoX-gOrigemX, gDestinoY-gOrigemY);
    dbNormalizeVector2 (cId_Vetor_Direcao_Normalizado, cId_Vetor_Direcao);
  }
}
// Usando a fórmula:   Angulo = ArcCos (ProdutoEscalar (A; B) / Modulo (A)*Modulo (B))
float OlharPara1 (TPonto APosicao, TPonto APosicaoOlhando, TPonto APosicaoDesejada)

{
  float Angulo, Determinante, ModuloD, ModuloO;

  dbSetVector2 (cId_Vetor_Origem, APosicaoOlhando.X-APosicao.X, APosicaoOlhando.Y-APosicao.Y);
  dbSetVector2 (cId_Vetor_Direcao, APosicaoDesejada.X-APosicao.X, APosicaoDesejada.Y-APosicao.Y);

  // Angulo entre vetores
  ModuloD     = dbLengthVector2 (cId_Vetor_Direcao);
  ModuloO     = dbLengthVector2 (cId_Vetor_Origem);
  Angulo      = dbACOS (dbDotProductVector2 (cId_Vetor_Origem, cId_Vetor_Direcao)/(ModuloD*ModuloO));
  Determinante= dbXVector2 (cId_Vetor_Origem)*dbYVector2 (cId_Vetor_Direcao) - dbXVector2 (cId_Vetor_Direcao)*dbYVector2 (cId_Vetor_Origem);
  if (Determinante < 0)
    Angulo= 360-Angulo;

  return Angulo;
}
// Usando a fórmula:   Angulo = ArcTg (ProdutoEscalarPerpendicular (A; B) / ProdutoEscalar (A; B))
float OlharPara2 (TPonto APosicao, TPonto APosicaoOlhando, TPonto APosicaoDesejada)

{
  float Angulo, PerpDot;

  dbSetVector2 (cId_Vetor_Origem, APosicaoOlhando.X-APosicao.X, APosicaoOlhando.Y-APosicao.Y);
  dbSetVector2 (cId_Vetor_Direcao, APosicaoDesejada.X-APosicao.X, APosicaoDesejada.Y-APosicao.Y);
  
  // Angulo entre vetores
  PerpDot= dbXVector2 (cId_Vetor_Origem)*dbYVector2 (cId_Vetor_Direcao) - dbXVector2 (cId_Vetor_Direcao)*dbYVector2 (cId_Vetor_Origem);
  Angulo = dbATANFULL  (PerpDot, dbDotProductVector2 (cId_Vetor_Origem, cId_Vetor_Direcao));

  return Angulo;
}
// Usando a fórmula:   Angulo = ArcTg (B.Y; B.X) - ArcTg (A.Y; A.X)
float OlharPara (TPonto APosicao, TPonto APosicaoOlhando, TPonto APosicaoDesejada)

{
  float Angulo;

  dbSetVector2 (cId_Vetor_Origem, APosicaoOlhando.X-APosicao.X, APosicaoOlhando.Y-APosicao.Y);
  dbSetVector2 (cId_Vetor_Direcao, APosicaoDesejada.X-APosicao.X, APosicaoDesejada.Y-APosicao.Y);

  // Angulo entre vetores
  Angulo= dbATANFULL (dbYVector2 (cId_Vetor_Direcao), dbXVector2 (cId_Vetor_Direcao)) - dbATANFULL (dbYVector2 (cId_Vetor_Origem), dbXVector2 (cId_Vetor_Origem));

  return Angulo;
}
void Reposicionar (void)

{
  gDestinoX= dbMouseX ();
  gDestinoY= dbMouseY ();

  Rotacionar (gDestinoX, gDestinoY);
}
void Rotacionar (int ADestinoX, int ADestinoY)

{
  TPonto Destino, Olhando, Posicao;

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

  Posicao.X= gOrigemX;
  Posicao.Y= gOrigemY;

  Olhando.X= Posicao.X;
  Olhando.Y= dbScreenHeight (); // A nave está voltada pra baixo

  gAngulo  = OlharPara (Posicao, Olhando, Destino);

  dbRotateSprite (cId_Origem, gAngulo);
}
void DarkGDK (void)

{
  dbSyncOn ();
  dbSyncRate (60);

  SetCurrentDirectory (cDiretorio);

  dbCreateAnimatedSprite (cId_Origem, cOrigemNome, 1, 1, cId_Origem);
  dbCreateAnimatedSprite (cId_OrigemTiro, cOrigemTiroNome, 1, 1, cId_OrigemTiro);
  dbSetSpritePriority (cId_Origem, 2);

  dbMakeVector2 (cId_Vetor_Direcao);
  dbMakeVector2 (cId_Vetor_Direcao_Normalizado);
  dbMakeVector2 (cId_Vetor_Origem);

  dbSprite (cId_Origem, dbScreenWidth () / 2, dbScreenHeight () / 2, 1);
  dbOffsetSprite (cId_Origem, dbSpriteWidth (cId_Origem) / 2, dbSpriteHeight (cId_Origem) / 2);

  gOrigemX    = dbSpriteX (cId_Origem);
  gOrigemY    = dbSpriteY (cId_Origem);
  gOrigemTiroX= gOrigemX - dbSpriteWidth (cId_OrigemTiro) / 2;
  gOrigemTiroY= gOrigemY;
  gDecorrido  = dbTimer () - cIntervalo - 500;
  while (LoopGDK () && !dbEscapeKey ()) {
    Reposicionar ();
    Mostrar_Tiro ();
    Mostrar_Texto ();
    dbSync ();
  }
  dbDeleteVector2 (cId_Vetor_Direcao);
  dbDeleteVector2 (cId_Vetor_Direcao_Normalizado);
  dbDeleteVector2 (cId_Vetor_Origem);
}


Clique aqui pra baixar o código


Chaves:
Ângulo entre vetores
Multiplicação de vetor por escalar
Módulo dum vetor (Norm)
Produto Escalar (Dot product)
Produto Vetorial (Cross product)
Perpendicular Dot
Signed angle
LookAt





http://transeberiano.brinkster.net