quarta-feira, 2 de maio de 2007

Variáveis Globais (Continuando a Atirar)


Variáveis globais são aquelas que valem para todo o programa e não apenas para uma função. Como o C trabalha? Variáveis declaradas dentro de uma função são apenas daquela função. Variáveis declaradas fora de funções valem para todo o programa. Não é interessante usar só variáveis globais? Não. Variáveis globais consomem mais memória que variáveis locais, pois continuam ocupando o seu espaço na memória depois que a função termina de executar. Além disso, se eu mudar o valor de uma variável em uma função e esta variável estiver sendo usada em outra função, o valor na outra função também será alterado. Olhem o exemplo:

s16 i=0;
void func2() {
  i++;
}
void func1 () {
  for (i=0; i<40; i++) {
    func2();
    PA_OutputText(0,i>>1,'i=%d',i);
  }
}

No exemplo acima, vai imprimir os números de 0 a 39 pulando de dois em dois, apesar do for em func1 mandar i++. Isso complica muito a interpretação e pode mascarar erros. Então vamos usar variáveis globais para aquilo que envolve todo o programa, como a posição do tanque, o ângulo do canhão, a posição do tiro, etc.

Para organizar melhor a função main, vamos usar funções para as diversas ações do jogo. Desta forma a função main contém o esqueleto principal, e o detalhamento de cada ação fica em uma função diferente. Olhem o fonte do programa como ficou:

// Includes
#include <PA9.h> // Include for PA_Lib
#include "gfx/all_gfx.c"
#include "gfx/all_gfx.h"

// Variaveis Globais
s16 angulo_body=0;
s16 angulo_turret=0;
s16 velocidade=1;
s16 x=111;
s16 y=79;
s16 angulo_tiro=0;
s32 x_tiro=-8<<8;
s32 y_tiro=-8<<8;
s16 vel_tiro=4;
s8 atirando=0;

void giraturret() {
  if (Stylus.Held) {
    angulo_turret=PA_GetAngle(x+16,y+16,Stylus.X,Stylus.Y);
    PA_SetRotsetNoZoom(0,0,angulo_turret);
  }
}

void movetanque() {
  if ((Pad.Held.Left && Pad.Held.A) || (Pad.Held.Right && Pad.Held.Y) || (Pad.Held.Up && Pad.Held.B) || (Pad.Held.Down && Pad.Held.X) || (Pad.Held.X && Pad.Held.B) || (Pad.Held.Y && Pad.Held.A)) {
   //o usuário está sacaneando, não executa nada
  } else if (!(Pad.Held.Up || Pad.Held.Down || Pad.Held.Left || Pad.Held.Right || Pad.Held.A || Pad.Held.B || Pad.Held.X || Pad.Held.Y)) {
   //nenhuma direção foi pressionada
  } else {
    if ((Pad.Held.Left || Pad.Held.Y || Pad.Held.Right || Pad.Held.A) && !(Pad.Held.Up || Pad.Held.X || Pad.Held.Down || Pad.Held.B)) {
      angulo_body=256*(Pad.Held.Left || Pad.Held.Y);
      x+=velocidade*(Pad.Held.Right || Pad.Held.A)-velocidade*(Pad.Held.Left || Pad.Held.Y);
    } else if (!(Pad.Held.Left || Pad.Held.Y || Pad.Held.Right || Pad.Held.A) && (Pad.Held.Up || Pad.Held.X || Pad.Held.Down || Pad.Held.B)) {
      angulo_body=128*(Pad.Held.Up || Pad.Held.X)+384*(Pad.Held.Down || Pad.Held.B);
      y+=velocidade*(Pad.Held.Down || Pad.Held.B)-velocidade*(Pad.Held.Up || Pad.Held.X);
    } else {
      angulo_body=256*(Pad.Held.Left || Pad.Held.Y);
      x+=velocidade*(Pad.Held.Right || Pad.Held.A)-velocidade*(Pad.Held.Left || Pad.Held.Y);
      if (angulo_body) {
        angulo_body=192*(Pad.Held.Up || Pad.Held.X)+320*(Pad.Held.Down || Pad.Held.B);
      } else {
        angulo_body=64*(Pad.Held.Up || Pad.Held.X)+448*(Pad.Held.Down || Pad.Held.B);
      }
      y+=velocidade*(Pad.Held.Down || Pad.Held.B)-velocidade*(Pad.Held.Up || Pad.Held.X);
    }
    if (x<0) { x=0; }
    if (x>223) { x=223; }
    if (y<0) { y=0; }
    if (y>159) { y=159; }
    PA_SetRotsetNoZoom(0,1,angulo_body);
    PA_SetSpriteXY(0,0,x,y);
    PA_SetSpriteXY(0,1,x,y);
  }
}

void atira() {
  if (atirando) {
    x_tiro+=PA_Cos(angulo_tiro)*vel_tiro;
    y_tiro-=PA_Sin(angulo_tiro)*vel_tiro;
    PA_SetSpriteXY(0,2,x_tiro>>8,y_tiro>>8);
    if (x_tiro<-8<<8 || x_tiro>260<<8 || y_tiro<-8<<8 || y_tiro>200<<8) {
      atirando=0;
    }
  } else {
    if (Pad.Newpress.L || Pad.Newpress.R) {
      atirando=1;
      angulo_tiro=angulo_turret;
      x_tiro=(x+12)<<8;
      y_tiro=(y+12)<<8;
      x_tiro+=PA_Cos(angulo_tiro)*24;
      y_tiro-=PA_Sin(angulo_tiro)*24;
      PA_SetRotsetNoZoom(0,2,angulo_tiro);
      PA_SetSpriteXY(0,2,x_tiro>>8,y_tiro>>8);
    }
  }
}

// Function: main()
int main(int argc, char ** argv)
{
  
  PA_Init(); // Initializes PA_Lib
  PA_InitVBL(); // Initializes a standard VBL
  PA_Init16bitBg(0,0);
  PA_Draw16bitRect(0,0,0,255,191,32768+10*1024+31*32+25);
  PA_LoadSpritePal(0, 0, (void*)tank_turret_Pal);
  PA_CreateSprite(0, 0, (void*)tank_turret_Sprite, OBJ_SIZE_32X32, 1, 0, x, y);
  PA_LoadSpritePal(0, 1, (void*)tank_body_Pal);
  PA_CreateSprite(0, 1, (void*)tank_body_Sprite, OBJ_SIZE_32X32, 1, 0, x, y);
  PA_LoadSpritePal(0, 2, (void*)tiro_Pal);
  PA_CreateSprite(0, 2, (void*)tiro_Sprite, OBJ_SIZE_8X8, 1, 0, -8, -8);
  PA_SetSpriteRotEnable(0, 0, 0);
  PA_SetSpriteRotEnable(0, 1, 1);
  PA_SetSpriteRotEnable(0, 2, 2);
  
  // Infinite loop to keep the program running
  while (1)
  {
    giraturret();
    movetanque();
    atira();
    PA_WaitForVBL();
  }
  
  return 0;
} // End of main()


O que o programa faz:
Bom, quase todo o programa já foi explicado nos últimos artigos. Somente a rotina de tiro foi alterada, para o tiro sair do canhão ao invés do centro da tela, e permitir que o canhão se mova enquanto o tiro também se move. A melhor maneira de fazer isso é substituir o loop dentro da rotina de tiro (que iria paralizar o tanque enquanto o tiro se movesse) por uma única alteração de posição. O loop passa a ser o principal do programa, o loop infinto da função main.
Na função atira, o programa verifica se foi disparado um tiro. Se existe algum tiro disparado, atualiza a posição dele. Se passar do limite da tela, encerra o disparo, permitindo que um novo disparo seja feito. Se o flag de tiro não está ativo (flag é uma variável que representa se algo aconteceu ou não), o programa verifica se foi pressionado o botão L ou o R. Se foi, inicia o tiro a 24 pixels de raio do centro do canhão aplicados seno e cosseno do ângulo do canhão. Isso vai assegurar que nenhum tiro saia de cima do tanque ou do meio do canhão. Outra coisa é que o programa fixa o ângulo do tiro igual ao do canhão no momento do disparo. Se o canhão trocar de ângulo depois de disparar, ele não influenciará o tiro.
A função main pode ser reduzida mais ainda, criando uma função "inicia", onde são colocadas todos aqueles comandos anteriores ao loop infinito.

Nenhum comentário: