Uma vez estabelecidos
os principais parâmetros do jogo, definidas e produzidas as imagens,
o passo seguinte é a programação. É claro
que, durante este trabalho, algumas modificações nas imagens
podem ser necessárias, para que o conjunto todo funcione de forma
mais homogênea. Pequenos ajustes são sempre imprescindíveis.
Partindo da
concepção, definimos que a área visual do jogo, ou
seja a "tela" onde acontecerá a principal ação, terá
as seguintes dimensões: 524 pixels de largura por 254
pixels de altura. A razão dessas medidas? Simples, não teremos
uma tela exageradamente larga a ponto de demorar muito para a travessia
do tanque, nem uma tela minúscula, que não dê pelo
menos três chances ao jogador de acertar o alvo (desde que ele já
saiba em que direção atirar). Nem tão lá,
nem tão cá, essas medidas ainda tem a vantagem de serem
operacionais se algum usuário estiver com seu Windows em modo 640
x 480.
A tela é
basicamente uma TImage (Image1) com essas dimensões, que
no início do programa é aproveitada para mostrar a figura
de abertura do jogo. Este procedimento tem uma dupla função:
mostrar como o jogo é sensacional (as imagens de abertura sempre
dão uma idéia mais eloqüente do que é, ou poderia
ser, o jogo de fato) e garantir que a tela seja montada, na memória,
ajustada como true-color, para que possamos visualizar todas as cores
do nosso programa sem nenhuma perda.
A estrutura
do sistema que operará as animações é bem
simples: iremos criar um buffer de tela, com as mesmas dimensões
e características da TImage da tela, na memória.
Um TBitmap que será chamado de "Buf". Todas as figuras,
tanques, armas, efeitos, etc, serão compiladas como TImage
e serão transferidas para este buffer de tela, para montar uma
cena do jogo. Daí, quando todas as figuras e informações
estiverem no Buf, ele é transferido para a tela. Com isso evitamos
atuar diretamente sobre uma imagem que esteja sendo mostrada na tela,
o que sempre causa um problema chamado "flicking".
var
Buf: TBitmap; // Buffer de tela
...
//Inicia o processamento do jogo:
procedure TForm1.FormActivate(Sender: TObject);
begin
Buf:= TBitmap.Create; //Cria
o buffer de tela
Buf.Width:= Image1.Width; //Largura
Buf.Height:= Image1.Height; //Altura
Buf.Canvas.Font.Color:= clLime; //Parâmetros
para a
Buf.Canvas.Font.Name:= 'arial'; //impressão
dos números
Buf.Canvas.Font.Style:= [fsBold];
Buf.Canvas.Font.Height:= 32;
end;
Vale lembrar
que todas as TImage que conterão as demais figuras deverão
estar com suas propriedades Visible em false, para que não
sejam visualizadas pelo jogador.
Todo o conjunto
de animação é controlado por um TTimer, que
está ajustado para um interval de 60. O evento OnTimer é
quem fará todo o controle do jogo e funcionará a partir
de uma variável global chamada Stat (byte). Se Stat
for zero, não há tanque andando, ou seja, o jogo ainda não
começou. Por isso é que Stat é declarada como
constante, com valor igual a zero, e não como variável simples.
Se Stat
for igual a 1, então tem um tanque andando pela tela e portanto
pode ser atingido pelo jogador. Se Stat for igual a 2, então
o evento OnTimer deverá preparar um novo tanque para iniciar
a sua corrida.
Faz parte do
jogo deixar o tanque "furado como uma peneira", durante o seu percurso.
Também quando a torre explode, a imagem final da animação
deverá ser um tanque destroçado, que ainda assim anda até
o final da tela. Sempre que um novo tanque for liberado, ele deverá
estar 0 Km, impecável.
Resolvemos
isso, sem maiores problemas de programação, trabalhando
com duas TImage: uma (Image6) contém a figura do tanque
em perfeitas condições e é usada como uma matriz
para os novos tanques. A outra (Image2) não é inicializada
e é quem receberá todas as alterações que
o tanque sofrerá ao longo da sua jornada. É esta TImage
que é usada para colocar o tanque no buffer de tela (Buf).
Outra diferença
entre essas duas TImage é que a matriz do tanque (Image6)
possui apenas 32 pixels que altura, que é a altura total do tanque,
mas Image2 possui 64 pixels de altura, pois deverá conter as figuras
da explosão da torre. Podíamos fazer de forma diferente,
montando cada pedaço da animação no seu respectivo
local dentro do Buf, mas como estamos fazendo iremos simplificar extraordinariamente
a programação.
O tanque inicia sua trajetória quando Stat recebe o valor 2. Vejamos
isso na programação (evento OnTimer):
if Stat = 2 then begin
//Apaga o buffer do tanque
Image2.Canvas.Brush.Color:= clBlack;
Image2.Canvas.Pen.Color:= clBlack;
Image2.Canvas.Rectangle(0,0,128,64);
//Transf a imagem de um tanque intacto para o buffer
Matrz:= Image6.Canvas.Handle;
BitBlt(Tank,0,32,128,32,Matrz,0,0,SRCCOPY);
//Ajusta demais parâmetros
Tx:= -128; Ty:= 90; Explod:= 0;
inc(Tanques); Stat:= 1;
end;
Primeiro apagamos
todo o buffer do tanque, para eliminar qualquer resquício do tanque
anterior. Depois copiamos a figura do tanque (Image6) para o buffer (usamos
a função BitBlt, já abordada em inúmeras
matérias do Club TILT).
Tx é
uma variável que contém a coordenada X do canto superior
esquerdo da área de impressão do tanque. Sendo negativo
(a largura total do tanque é de 128 pixels), a imagem não
será vista na tela, mesmo que a procedure comande a transferência,
via BitBlt, da imagem. Está aqui uma das incríveis
vantagens de se programar no Windows + Delphi. Em outro sistema teríamos
que criar complexas rotinas para verificar a posição do
tanque e enviar para a tela apenas o pedaço visível dele.
Ty é
a variável que contém a coordenada Y do canto superior esquerdo
da área de impressão do tanque e, a rigor, não é
usada em nosso programa (não tem utilidade prática). Mas
a usamos assim mesmo para o caso de mudar a altura do tanque, ou seja,
usar um modelo diferente de veículo. Isto fica para uma futura
implementação.
Explod
é uma variável que controlará a explosão da
torre do canhão. Se for zero, a torre está intacta. E finalmente
Stat recebe o valor 1, que é para iniciar o movimento do
tanque.
Ao iniciar
a impressão de uma cena, do movimento do tanque, o buffer de tela
é todo limpo (com a cor preta) e o chão é montado
usando-se uma matriz TImage como padrão de fundo.
procedure
LimpaBuf; // Apaga o buffer de tela
var
Chao,HBuf: HBitmap;
begin
HBuf:= Buf.Canvas.Handle;
Chao:= Form1.Image3.Canvas.Handle;
Buf.Canvas.Brush.Color:= clBlack;
Buf.Canvas.Pen.Color:= clBlack;
Buf.Canvas.Rectangle(0,0,524,154);
BitBlt(HBuf,0,154,131,100,Chao,0,0,SRCCOPY);
BitBlt(HBuf,131,154,131,100,Chao,0,0,SRCCOPY);
BitBlt(HBuf,262,154,131,100,Chao,0,0,SRCCOPY);
BitBlt(HBuf,393,154,131,100,Chao,0,0,SRCCOPY);
end;
O movimento
do tanque se baseia na sua coordenada X, tendo como variável de
controle Tx (dentro do evento OnTimer).
BitBlt(HBuf,Tx,Ty,128,64,Tank,0,0,SRCCOPY);
...
inc(Tx,2);
if Tx > 530 then Stat:= 2;
A primeira
linha transfere todo o conteúdo do buffer do tanque para o buffer
da tela. A segunda linha faz com que o tanque "ande" dois pixels para
a direita. Por que dois? Com um pixel ele anda muito devagar e com 3 pixels
muito depressa. O cálculo efetivo, para decidir pelo valor dois
é feito através do método nada científico
"achei que assim ficava melhor".
Quando Tx
passar de 530, significa então que o tanque desapareceu do lado
direito da tela (lembra que ela só tem 524 pixels de largura. Daí,
Stat recebe o valor 2 que é para liberar um novo tanque. Como no
programa isso acontecerá ainda neste evento (OnTimer), tomamos
a decisão de lançar um novo tanque 6 pixels (ou seja, 3
eventos OnTimer) depois do tanque já ter desaparecido. Isto
dá tempo para a ordem de partida chegar até a guarnição
e os motores do tanque serem ligados (brincadeira). Na verdade, essa diferença
de tempo é para que um tanque não comece a aparecer imediatamente
após o primeiro ter sumido. Assim o jogador respira um pouco e
reposiciona sua arma, afinal o objetivo é acertar o tanque e não
provocar um ataque do coração no jogador.
O tiro é
disparado por um botão que fica fora da tela e é controlado
por uma variável chamada (obviamente) Tiro. Se Tiro
for zero, o jogador não disparou ainda. Se for 220, então
o jogador acabou de apertar o gatilho. Por que 220? Porque usaremos esta
variável para determinar também a coordenada Y da bala e
220 a coloca justamente na boca da arma do jogador. A coordenada X da
bala e da arma é dada pela propriedade Position, do componente
TScrollBar que movimenta a arma.
Por falar em
arma, a sua impressão é feita em duas posições
distintas: em modo normal (repouso), na linha 222 ou durante o recuo da
arma, na linha 230. Quem define isso é o trecho:
if Tiro = 220 then Acan:= 230 else Acan:= 222;
...
BitBlt(HBuf,ScrollBar1.Position,Acan,24,32,
Cano,24,0,SRCAND);
BitBlt(HBuf,ScrollBar1.Position,Acan,24,32,
Cano,0,0,SRCPAINT);
Aqui usamos
o recurso de imprimir uma figura (o cano da arma) através de sua
máscara de impressão. Este é outro assunto prá
lá de manjado no Club TILT e dispensa maiores comentários.
O tiro é
um perfeito exemplo de aproveitamento de bits. Obteremos seu desenho diretamente
da máscara do cano da arma. Seu comprimento varia de 1 a 2 pixels,
dependendo da distância que ele está do tanque.
Explico melhor:
ao disparar, a bala faz uma trajetória em linha reta até
o tanque. Nosso sistema de coordenadas é bidimensional, mas pretendemos
dar a impressão de que a bala se distancia da arma, o que implicaria
numa terceira dimensão inexistente.
Para obter
este efeito, precisamos atuar de três formas distintas: na "velocidade"
com a qual a bala se distancia da arma, no ângulo que ela deve fazer
para atingir o alvo e no seu tamanho, que diminui a medida que afasta.
if Tiro > 0 then begin
if Tiro < 155 then Tmb:=
1 else Tmb:= 2;
BitBlt(HBuf,Cx,Tiro,1,Tmb,Cano,24,0,SRCCOPY);
Para diminuir
é fácil, basta começar com uma bala com 2 pixels
de comprimento e, depois de uma certa coordenada (no meio do caminho mais
ou menos) reduzí-la para um pixel. A variável Tmb irá
guardar o tamanho da bala.
O ângulo
e a velocidade são feitos em conjunto, diminuindo o tamanho do
salto que a bala faz, em pixels, usando uma daquelas fórmulas que
aprendemos na escola... o inverso do quadrado da distância... Bem,
isso é apenas um jogo, então vamos frear a bala usando uma
equação mais simples: a metade da distância percorrida
no salto anterior.
dec(Tiro,Vt); Vt:= ((Vt + 2) div 2) + 1;
Primeiro somamos
dois a Vt para que nunca ocorra uma divisão por zero (senão
o computador trava) e garantimos no final (+1) que pelo menos um pixel
a bala vai andar.
Como complemento
ao movimento do tiro, precisamos verificar se ele foi disparado neste
evento e se foi, colocar o fogo na boca da arma:
if Tiro = 220 then begin
BitBlt(HBuf,Cx-4,216,10,13,Fogo,20,0,SRCAND);
BitBlt(HBuf,Cx-4,216,10,13,Fogo,0,0,SRCPAINT);
end;
Resta agora
saber onde o tiro atingiu o tanque (se é que atingiu).
if Tiro < 140 then begin
Tiro:= 0; Mosca:= Cx-Tx;
//Se acertou no tanque...
if (Mosca < 120)
and (Mosca > 10) then begin
//Som da bala
penetrando no metal
Som.Sound[1].Replay;
//Marca o tanque
com o furo
BitBlt(Tank,Mosca,51,2,2,Cano,20,0,SRCCOPY);
//Se acertou
na área da frente, grito do homem
if (Mosca
> 95) and (Mosca < 99) then
Som.Sound[2].Replay;
//Se acertou
na área do meio, grito do whookie
if (Mosca
> 75) and (Mosca < 79) then
Som.Sound[3].Replay;
//Se acertou
na traseira (painel da munição)
if (Mosca
> 45) and (Mosca < 48) and (Explod = 0)
then
begin
//Explode
o tanque
Som.Sound[5].Replay;
Explod:=
1; inc(Destrd); inc(Ammo,5);
Quando o tiro
acerta no painel de munição, a variável Explod recebe
o valor 1 e irá variar, de acordo com o frame da explosão
que for impresso, até o valor 8 (temos 7 frames de explosão).
Os frames são enviados para o buffer do tanque e seguem a mesma
seqüência da animação geral.
if (Explod > 0) and (Explod < 8) then begin
Boom:= Image7.Canvas.Handle;
//Transfere o frame da explosão
para o tanque
BitBlt(Tank,0,0,128,50,Boom,0,(Explod-1)*50,SRCCOPY);
inc(Explod);
end;
Agora só
falta imprimir os parâmetros de munição, tanques enviados
e tanques destruídos.
//Imprime quantidade de tiros que ainda tem
Buf.Canvas.Font.Color:= clLime;
Num:= '00' + IntToStr(Ammo);
Num:= copy(Num,length(Num)-2,3);
Buf.Canvas.TextOut(5,0,Num);
//Imprime quantos tanques já passaram
Num:= '0' + IntToStr(Tanques);
Num:= copy(Num,length(Num)-1,2);
Buf.Canvas.TextOut(430,0,Num);
//Imprime quantos tanques foram destruídos
Buf.Canvas.Font.Color:= clRed;
Num:= '0' + IntToStr(Destrd);
Num:= copy(Num,length(Num)-1,2);
Buf.Canvas.TextOut(480,0,Num);
//Transfere o buffer para a tela
BitBlt(Tela,0,0,524,254,HBuf,0,0,SRCCOPY);
Image1.Repaint;
Está
pronto o sistema principal de controle das animações. Agora
é relaxar, estalar os dedos, fazer pontaria e detonar o maior número
possível de tanques. Baixe o pacote dos fontes, no telefone ao
lado, e acompanhe o que cada linha de programação faz. Dá
para acrescentar um monte de implementações, recursos e
novas etapas neste jogo (afinal, é para isso que ele é publicado
com os fontes abertos).
Clique aqui e baixe o pacote zip contendo
os fontes do jogo.
|