Escolar Documentos
Profissional Documentos
Cultura Documentos
21 - Organização de Código
Ao terminar a leitura do capitulo 20, você já deve ter uma ótima noção de programação.
- Essa noção que você ganhou vale para qualquer linguagem de programação.
- Essa noção que você ganhou vale também para iniciar a produção de um jogo em
qualquer linguagem.
Se você está lendo a biblioteca, você com certeza já escreveu algum código em C++ para treinar sua
programação e sua forma de pensar.
Antes de sair por ai programando jogos, precisamos ter uma certa organização do código fonte dos
programas, para que consigamos ler o mesmo código algum outro dia.
- A organização de código é obrigatória para que você possa reutilizar seu código
fonte.
Declarando variáveis
1 - Ao declarar variáveis, você deve tomar cuidado para não agrupar uma linha muito grande de variáveis.
Utilize sempre várias linhas, dando um enter entre os tipos de variáveis.
Exemplo correto:
int a = 0;
int b = 0;
int valor = 0;
int xab = 0;
int numero_jogadores = 0;
int posicao_peca_x = 0;
2 - Observe sempre de separar com espaços variáveis que não combinam com as de cima
( por exemplo, posicionamento de peças com números de jogadores ).
Isto torna a leitura mais fácil.
Exemplo correto:
int a = 0;
int b = 30;
Usando o exemplo acima, variáveis que tem nome menor ficam longe das que tem nome maior
Isso mantem o código ainda mais organizado
Exemplo correto:
int b = 0;
int a = 0;
Exemplo correto:
int posicao_personagem;
int personagem_px; // posição x do personagem;
int personagem_py; // posicao y do personagem;
Exemplo correto:
int num = 0;
int auxiliar = 0;
auxiliar = ( ( 4 / 2 ) * ( 3- 1 ) );
num = 10 * auxiliar;
while (ab!=true) {
....
}
for (i=0;i<10;i++) {
....
}
Exemplo correto:
while ( ab != true )
{
// nunca abra as chaves na linha de cima
......
}
Exemplo correto:
int chave;
pprimeiro = pprimeiro->proximo;
}
return true;
}
Qualquer código em qualquer linguagem pode ser escrito seguido essas observações importantes.
Com este tutorial adicional, pretendo dar mais dicas para tornar a programação de jogos mais organizada.
Fazer um jogo é um projeto que contém várias etapas.
Nem sempre precisamos seguir todas as etapas, mais algumas são indispensáveis para a conclusão sem
atrasos ( perda de tempo ) do projeto.
Este tipo de conteúdo é discutido em vários sites e em várias outras áreas, simplesmente por que é um
assunto relacionado ao tempo!
Quanto mais tempo gasto, mais caro se torna a sua idéia.
Quanto mais organizada for a sua ideia e o seu projeto, melhor ele será implementado e mais rápido o jogo
será concluído.
1 - A idéia:
Como a idéia é criar um jogo ao estilo do Arkanoid,
precisamos estudar como este jogo funciona e descrever todos os funcionamentos e idéias do jogo.
Se você está criando um jogo que ainda não foi inventado, o processo será o mesmo.
A descrição da idéia é muito importante para sabermos o que realmente queremos fazer e para adotar
mudanças no projeto.
Exemplo:
- Um jogo aonde o jogador controla uma raquete ( ou shape ) que vai rebater uma ou várias bolas.
- O objetivo do jogo será destruir todas as peças do cenário, fazendo com que a bola entre em contato com
essas peças.
- Cada peça vai fazer com que uma ação seja disparada no jogo, criando novas bolas, aumentando a
velocidade, etc.
- Quanto todas as peças forem destruidas, o jogador é enviado diretamente para a proxima fase.
- Quando a bola não for rebatida, o jogador perde uma vida.
Observe que na minha idéia de Arkanoid, mudei alguns conceitos do jogo original.
Neste ponto também, são definidas algumas historias e cenarios do jogo, não precisam ser completas.
2 - Descrição de Regras
Agora iremos definir as regras do jogo.
Essas regras podem sofrer alterações durante a execução do projeto,
por que as vezes nem sempre conseguimos programar tudo o que queremos.
- O jogo vai ter uma animação inicial ?? Como ela vai ser ?
- O Jogo vai ter uma tela inicial ? - Quais serão as opções do menu ?
- Descrição das regras para o Jogador - Descrição das regras para o Jogador para cada tela ou cenário
Quando você for definir as regras, você não precisa levar em consideração tecnologias que serão utilizadas.
Isto por que, as regras poderão ser alteradas mais tarde.
Esta não é a hora de pensar em qual linguagem programar ou qual programa de modelagem utilizar.
Exemplo:
- Bola
- Raquete do jogador
- Cenario
Após identificar os atores do passo 3, podemos procurar quais deles serão classes, e quais funções cada
classe vai executar.
Pense da seguinte maneira!
Você vai criar uma classe para alguma coisa que irá ter várias variáveis e que irá desempenhar várias tarefas
no seu jogo.
Bola, Jogador e Cenário desempenham várias tarefas, como vimos acima, então ficou fácil de saber quem
serão nossas classes.
Com as tarefas de cada um, também podemos conhecer de antemão quais serão os metodos que cada um vai
fazer.
Exemplo:
Classe Bola
metodo mover
metodo desenhar no cenario
metodo colisao com peças
metodo colisao com jogador
Neste ponto, nem sempre vamos descobrir todos os métodos e todas as classes.
Porém, neste ponto já podemos descidir como vamos fazer as coisas e se conseguimos fazer tais coisas.
Por exemplo:
A classe bola vai ter o método que verifica a colisão? ou a classe cenario ?
Quem vai controlar as vidas ? O cenário ou o jogador ?
Exemplo de código:
CÓDIGO...
#ifndef BOLAS_H
#define BOLAS_H
#include <allegro.h>
class Bolas {
private:
public:
int posicao_x;
int posicao_y;
int tamanho;
int cor;
void adiciona_velocidade_x();
void remove_velocidade_x();
};
#endif
FIM DE CÓDIGO...
5 - Programação
Agora você vai criar as classes que encontrou e tentar juntar todas as classes de forma que o jogo rode no
final.
Bom.. essa minha explicação foi bem superficial, já que isto é bem complicado.
- Para programar um jogo você deve ter noção de Orientação a Objeto básica para criar as classes.
- Para programar um jogo você precisa ter alguma noção de UML para identificar eventos e classes.
http://pt.wikipedia.org/wiki/Orienta%C3%A7%C3%A3o_a_objetos
http://pt.wikipedia.org/wiki/UML
http://bdjogos.com/adrw/c/classes.htm
Neste código fonte existem todos os comentários que você vai precisar para entender como ler o código e
como organizar o projeto.
Esta forma de programar jogos com certeza não é a melhor forma que existe.
Eu apenas abordei um modo prático de programar jogos, procure pesquisar mais sobre isto.
21 - Adicional 2 - Jogo da memória com código fonte
Para esse artigo preparei o código fonte de um jogo da memória simples e objetivo a fim de revisar e fixar os
artigos vistos até o momento.
Parece óbvio, mas mesmo assim é bom deixar claro que essa não é a única forma de fazer um jogo da
memória.
O código varia muito de cada programador.
Para quem não lembra o jogo da memória consiste em encontrar o par da carta que você acabou de virar.
Ao final do código, teremos um resultado simples conforme abaixo:
O Jogo
Precisamos ter em mente como nosso jogo irá funcionar antes de começar a programar ele.
4. Logo no inicio o jogo deve mostrar todas as cartas para que o jogador possa memorizar.
5. Assim que o tempo de memorização das cartas terminar o relógio para marcar o tempo de jogo deve
ser disparado.
6. A cada erro o jogador perde uma chance e espera 3 segundos para memorizar as duas cartas
erradas.
Se zerar as chances o jogo termina e a tabela de Recordes deve ser mostrada.
7. A cada seqüência de acertos a pontuação base deve ser multiplicada pela quantidade de seqüências
de acertos até o momento. Por exemplo, se acertar 2 pares seguidos deve ser acrescentado na
pontuação 2x100.
Nível 1:
Nível 2:
Nível 3:
o A cada troca de nível devem ser acrescentadas mais duas chances ao jogador.
o Ao fim do jogo quando o jogador zerar os 3 níveis a tabela de recordes deve aparecer.
o A tabela de recordes deve ter 9 posições e ser organizada pelo nível e pontuação mais altos.
Biblioteca Allegro
A biblioteca allegro fornece várias rotinas de baixo nível para programadores de C/C++, normalmente
necessárias na programação de jogos, como entrada do teclado, manipulação de gráficos, manipulação de
sons e cronômetros (timer).
É uma plataforma transversal e trabalha com muitos compiladores diferentes. Desenvolvido originalmente por
Shawn Hargreaves, é agora um projeto com contribuições do mundo inteiro.
Livre - não lhe custará uma moeda de dez centavos, e não há nenhuma limitação em seu uso.
O objetivo de Allegro.cc é fornecer uma riqueza da informação moderna que pertence à programação e ao
gaming Allegro. Eles confiam pesadamente na participação dos colaboradores Allegro e do público geral.
Nesse mesmo site existem publicações de vários jogos, quase todos são freewares e sharewares.
Antes de instalar qualquer jogo o Allegro recomenda ler a documentação completa de cada um porque eles
não serão responsáveis por qualquer problema que ocorrer após a instalação.
Isso não quer dizer que para desenvolver um jogo comercial de sucesso basta aprender Allegro.
O conhecimento vai muito mais além da simplicidade. De qualquer forma você vai precisar estudar muito para
desenvolver um jogo bom.
Vamos começar pelo inicio do inicio, entendendo o laço principal e utilizando a biblioteca Allegro para chamar
as funções. O código abaixo já é um bom começo:
Ao executar o programa você vai ver apenas uma tela do windows com a cor de fundo em preto.
Para fechar o programa pressione ESC.
CÓDIGO
int main()
{
allegro_init();
install_keyboard();
set_color_depth(16);
set_gfx_mode( GFX_AUTODETECT_WINDOWED, 640, 480, 0, 0 );
}
END_OF_MAIN();
FIM DE CÓDIGO...
#include <allegro.h>
Esse comando é o mais importante. Sem declarar essa linha não vamos conseguir usar nenhuma
função da biblioteca Allegro. Ao chamar as funcões do Allegro e tentar compilar o jogo sem essa
linha o compilador irá apresentar uma mensagem de erro dizendo que não conseguiu encontrar as
funções.
int allegro_init();
Macro responsável por iniciar a biblioteca allegro.
Sem esse comando não conseguimos utilizar nenhuma função do allegro.
Existem pessoas que utilizam a função allegro_exit(), mas o manual do allegro 4.2.1 informa que
esse comando não precisa ser usado explicitamente. O allegro chama essa função toda vez que o
jogo é fechado. Dessa forma o processo de liberação das bibliotecas da memória tornam-se
automáticos.
int install_keyboard();
Esse comando é responsável por instalar e tornar as interrupções do teclado disponíveis para
o nosso projeto.
Exemplo: set_color_depth(32);
O modo 32 bits proporciona o mesmo número de cores que o modo 24 bits, mas gráficos de 32
bits podem ser manipulados muito mais rapidamente do que gráficos de 24 bits. Além disso,
gráficos de 32 bits requerem cerca de 25% a mais de memória.
Para saber o que utilizar no argumento card segue uma tabela abaixo explicando cada opção:
Card Descrição
GFX_TEXT Fecha toda a modalidade de gráficos aberta
previamente com set_gfx_mode.
GFX_AUTODETECT O allegro escolhe o drive mais apropriado.
GFX_AUTODETECT_FULLSCREE Força o allegro a exibir o projeto em tela cheia.
N
GFX_AUTODETECT_WINDOWED Força o allegro a exibir o projeto em uma janela.
GFX_SAFE Modo especial para quando você quiser ajustar
confiantemente uma modalidade de gráficos e não
quer se importar a resolução.
GFX_BWINDOWSCREEN_ACCEL Modalidade exclusiva de Tela cheia. Suporta
definições de pixels somente acima ou igual a
640x480.Suporta também aceleração de hardware.
GFX_BWINDOWSCREEN Modalidade idêntica a anterior mas não suporta
aceleração de hardware.
GFX_BDIRECTWINDOW Modo gráfico rápido para utilização de janelas
porque usa a classe BdirectWindow. Mas nem
todas as placas de vídeos suportam.
GFX_BWINDOW Modo normal de trabalho em janela utilizando a
classe Bwindow. Esse modo é muito lento.
GFX_BWINDOW_OVERLAY Modalidade de tela cheia usando BWindow como
uma folha de prova BBitmap. Esta modalidade
não é suportada por todos as placas de vídeos, a
profundidade de cores sugeridas é 15, 16 e 32
bits.
Exemplo:
O exemplo acima define a tela do jogo como sendo auto detectável em modo janela do windows,
640x480 pixels de resolução a partir da posição x= 0 e y=0.
void vsync();
Espera para que um retrace vertical começa.
O retrace acontece quando o feixe de elétron em seu monitor alcançou o fundo da tela e está
movendo para trás para o superior, e se apronta para uma outra varredura.
Durante este período curto o cartão dos gráficos não está emitindo nenhum dado ao monitor,
assim você pode fazer-lhe as coisas que não são possíveis em outras vezes, tais como alterar o
palette de cores sem causar cintilar (neve).
Essa explicação é muito importante porque o uso do comando vsync no meio do projeto
causa uma perda considerável de desempenho.
while (!key[KEY_ESC]) {}
O comando acima trata-se do loop principal do jogo. Ele quer dizer que fará o loop enquanto
ninguém pressionar a tecla ESC.
Se você Encontrou alguma dificuldade no entendimento desse artigo favor entrar em contato.
Agora se prepare porque com essa tela preta dá pra fazer muita coisa!!!
23 - Conceito X e Y
- A Biblioteca Allegro
- Instalação do Allegro no Dev-CPP
Instalação do Allegro no Visual Studio C++ 2005
Os números naturais
Números naturais ou números que podem ser contados são os primeiros números que aprendemos na escola.
A muito tempo atraz a humanidade precisou inventar uma forma de contar seus alimentos, seus habitantes
ou suas criações, desta forma surgio a oportunidade de escrever números em formas de digitos em algum
lugar, e assim surgiram:
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ...
Podemos representa-los em nossa programação atravez do tipo inteiro ( usando principalmente unsigned
"sem sinal" ).
Os números negativos
Algum tempo depois a humanidade precisou comercializar ou trocar seus objetos ou comida. Ai foi inventado
um dos conceitos mais importantes que é o débito de valores ou os números negativos. Os números
negativos são representados com um sinal de - "menos" na frente de cada valor.
-1, -2, -3, -4, -5, -6, -7, -8, -9, - ...
Os números inteiros, são somente constituídos dos números naturais {0, 1, 2, ...} e dos seus opostos {-1,
-2, -3, ...}.
10 / 2 = 5;
Quando uma fração não consegue dividir um número, surge os números fracionais.
3 / 2 = 1.5
Estes números são inseridos entre os números inteiros e são representados em nossa programação atravez
do tipo float ou double.
PI = 3.14159
Os número Reais
Os números reais incluem os números inteiros e os números racionais. Eles são os mais utilizados até hoje
pela humanidade, e também são bastante complexos.
Os números inteiros são contáveis e os números reais são números incontáveis.
O plano cartesiano
O Plano cartesiano é um espaço aonde podes visualizar 2 variáveis importantes para a programação de jogos.
As variáveis X e Y.
No desenho acima temos o plano cartesiano, o plano sempre começa da posição 0 que é a origem. Esta
origem nem sempre é o meio do plano.
Esta origem pode ser por exemplo, o começo de uma fase do jogo Super Mario Bros.
Andando para a direita em linha reta, estamos saindo do ponto de origem e adicionando ao ponto X.
Andando para a direita e para cima, estamos saindo do ponto de origem e adicionando ao ponto X e Y.
Exemplos de vários pontos X,Y em um plano cartesiano que poderia ser a tela de um jogo:
CÓDIGO...
// Coordenadas X e Y
//Código de Exemplo
#include <allegro.h>
int main()
{
allegro_init();
set_color_depth(16);
install_keyboard();
set_gfx_mode(GFX_AUTODETECT_WINDOWED, 640, 480, 0, 0);
}
allegro_exit();
return 0;
}
END_OF_MAIN();
FIM DE CÓDIGO...
Neste momento você não precisa saber os comandos que vc não conhece, apenas teste o programa para
entender a função da variável X e Y..
CÓDIGO...
// Coordenadas X e Y
//Código de Exemplo
#include <allegro.h>
int main()
{
allegro_init();
set_color_depth(16);
install_keyboard();
set_gfx_mode(GFX_AUTODETECT_WINDOWED, 640, 480, 0, 0);
// nosso programa atual
// Testando coordenadas X e Y
int x = 0;
int y = 0;
x = x + 100;
textout_ex(screen, font, "Mensagem em 100,0 da tela", x, y, makecol(255, 0, 0), -1);
x = 0;
y = y + 100;
textout_ex(screen, font, "Mensagem em 0,100 da tela", x, y, makecol(255, 0, 0), -1);
x = x + 150;
y = y + 150;
textout_ex(screen, font, "Mensagem em 150,150 da tela", x, y, makecol(255, 0, 0), -1);
// Laço principal
while( !key[KEY_ESC] )
{
}
allegro_exit();
return 0;
}
END_OF_MAIN();
Todo desenho, imagem ou objeto que pode ser inserido na tela em qualquer linguagem de programação
possui a propriedade X e Y.
Muitas vezes essas propriedades podem estar com outros nomes ( no caso do VB é Top e Left ).
No código abaixo iremos ver alguns comandos novos. O mais importante são os comandos de reconhecimento
de teclado.
Atravéz desses comandos nós modificaremos o valor de X e de Y e redesenhamos um quadrado na tela na
nova posição.
O código é bem fácil de se entender, precione ESC para sair do programa quando for testar ele:
CÓDIGO...
int main()
{
allegro_init();
set_color_depth(16);
install_keyboard();
set_gfx_mode(GFX_AUTODETECT_FULLSCREEN, 640, 480, 0, 0);
// inicio da programação atual
int x = 0;
int y = 0;
// dimensao do quadrado
// variáveis constantes nunca mudam durante a execução do programa
const int tamanho = 100;
// Laço principal
while( !key[KEY_ESC] )
{
if ( key[KEY_UP] )
{
y--;
}
if ( key[KEY_DOWN] )
{
y++;
}
if ( key[KEY_LEFT] )
{
x--;
}
if ( key[KEY_RIGHT] )
{
x++;
}
// esta função limpa a tela ou um objeto que tem buffer no allegro
clear( screen );
// escrevemos um quadrado na tela na posição x e y que podem ter sido modificadas
rectfill( screen, x, y, x+tamanho, y+tamanho, makecol(255, 0, 0) );
// imprimimos as coordenadas x e y na tela para o usuário ver
textprintf_ex( screen, font, 10, 10, makecol(255,0,0), -1, "Variavel X: %d", x);
textprintf_ex( screen, font, 10, 20, makecol(255,0,0), -1, "Variavel Y: %d", y);
// essa função faz o allegro esperar um pouco antes de voltar para o while
rest(10);
}
allegro_exit();
return 0;
}
END_OF_MAIN();
O Nome deste efeito é flicker, onde o monitor acaba imprimindo uma tela em preto antes da nova imagem do
jogo ser desenhada na variável screen ( variável que representa o monitor no Allegro ). Formando a
impressão de que a tela está piscando.
Para resolver este problema, utilizamos a tecnica de Double Buffer, ou seja, criar um buffer ( lugar de
armazenamento ) aonde desenhamos tudo o que deve ser desenhado na tela, e só depois que tudo estiver
desenhado no buffer, ai sim, imprimimos o buffer na tela ( screen ).
Desta forma, a tela ( screen ) vai ter sempre a ultima imagem gerada para ela. E enquanto tudo não for
desenhado no buffer, a tela continua sendo a antiga. Assim a tela não irá piscar mais.
Esse problema geralmente acontece quando o movimento do objeto for muito rápido ou quando a tela é
atualizada antes de todos os objetos serem desenhados no screen.
A tecnica de double buffer no allegro é bem simples e também vale para qualquer outra biblioteca gráfica
( aqui estamos dizendo, o exemplo está em allegro, mais você pode usar em DX ou Open GL )
CÓDIGO...
// Colisão com os cantos da tela
#include <allegro.h>
int main()
{
allegro_init();
set_color_depth(16);
install_keyboard();
set_gfx_mode(GFX_AUTODETECT_FULLSCREEN, 640, 480, 0, 0);
// inicio da programação atual
int x = 0;
int y = 0;
// dimensao do quadrado
// variáveis constantes nunca mudam durante a execução do programa
const int tamanho = 100;
// Laço principal
while( !key[KEY_ESC] )
{
if ( key[KEY_UP] )
{
y--;
}
if ( key[KEY_DOWN] )
{
y++;
}
if ( key[KEY_LEFT] )
{
x--;
}
if ( key[KEY_RIGHT] )
{
x++;
}
// esta função limpa a tela ou um objeto que tem buffer no allegro
clear( screen );
// escrevemos um quadrado na tela na posição x e y que podem ter sido modificadas
rectfill( screen, x, y, x+tamanho, y+tamanho, makecol(255, 0, 0) );
// imprimimos as coordenadas x e y na tela para o usuário ver
textprintf_ex( screen, font, 10, 10, makecol(255,0,0), -1, "Variavel X: %d", x);
textprintf_ex( screen, font, 10, 20, makecol(255,0,0), -1, "Variavel Y: %d", y);
// essa função faz o allegro esperar um pouco antes de voltar para o while
rest(10);
}
allegro_exit();
return 0;
}
END_OF_MAIN();
FIM DE CÓDIGO...
CÓDIGO...
// Colisão com os cantos da tela
#include <allegro.h>
int main()
{
allegro_init();
set_color_depth(16);
install_keyboard();
set_gfx_mode(GFX_AUTODETECT_FULLSCREEN, 640, 480, 0, 0);
// inicio da programação atual
int x = 0;
int y = 0;
// dimensao do quadrado
// variáveis constantes nunca mudam durante a execução do programa
const int tamanho = 100;
// Criando BUFFER, ela é um ponteiro para um BITMAP, pode ter qualquer nome
BITMAP *buffer = NULL;
buffer = create_bitmap(800,800);
// Laço principal
while( !key[KEY_ESC] )
{
if ( key[KEY_UP] )
{
y--;
}
if ( key[KEY_DOWN] )
{
y++;
}
if ( key[KEY_LEFT] )
{
x--;
}
if ( key[KEY_RIGHT] )
{
x++;
}
// limpa o nosso novo buffer
clear( buffer );
// escreve o quadrado no buffer
rectfill( buffer, x, y, x+tamanho, y+tamanho, makecol(255, 0, 0) );
// imprimimos as coordenadas x e y na tela para o usuário ver no buffer
textprintf_ex( buffer, font, 10, 10, makecol(255,0,0), -1, "Variavel X: %d", x);
textprintf_ex( buffer, font, 10, 20, makecol(255,0,0), -1, "Variavel Y: %d", y);
// imprime o buffer na tela
blit(buffer, screen, 0,0,0,0, SCREEN_W, SCREEN_H);
// essa função faz o allegro esperar um pouco antes de voltar para o while
rest(10);
}
allegro_exit();
return 0;
}
END_OF_MAIN();
26 - Utilizando TILES
Tiles é uma representação gráfica de algo na tela, é um conjunto de tiles( pedaços de imagens ) que formará
uma imagem maior.
Com tiles podemos fazer um cenário, o que geralmente representará uma redução de uso de memória e
armazenamento em disco do que utilizar um único bitmap para a tela inteira.
Geralmente, o correto é trabalhar com tiles para as partes fixas do cenário ou com pouca mobilidade, por
exemplo:
paredes e chão de um cenário, objetos como baús, e outras coisas que não se "mexem" muito e inclusive a
água, havendo então uma troca seqüencial das tiles que fazem parte da água de forma a dar a impressão de
movimentação desta.
Nesse artigo iremos mostrar como montar um cenário usando apenas tiles estáticos, ou seja, sem
movimentação.
ARTE FINAL
TILES USADOS
Apenas amostra Pronto para ser utilizado
Baixar tiles
Com base nas informações acima você deve entender que a largura da tela é de 640 pixels e a largura do
nosso tile é 32 pixels. Logo, já que nosso tile tem 32 pixels de largura podemos colocar apenas 20 tiles na
horizontal. Já na altura, como nós temos 480 pixels podemos colocar apenas 15 tiles.
O tamanho dos tiles você define conforme a necessidade do se projeto. A idéia do artigo é dar uma visão
geral de como montar um cenário simples e estático através de tiles.
DICA...
Para criar os tiles usamos o editor de imagens GLIMP.
É uma ferramenta muito boa e tem uma opção que torna a vida do design iniciante
muito mais prática. Essa opção se chama “Tornar encaixavel”.
Assim o processo fica mais prático mas não muito profissional porque a imagem sofre
uma pequena distorção. Para quem está começando vale a pena verificar.
Para facilitar o aprendizado você poderá baixar os tiles utilizados no exemplo acima.
CÓDIGO...
#include <allegro.h>
int main()
{
allegro_init();
install_keyboard();
set_color_depth(32);
set_gfx_mode(GFX_AUTODETECT_WINDOWED, 640, 480, 0, 0);
//Monta o Cenário
for (x=0;x<15;x++)
{
for (y=0;y<20;y++)
{
draw_sprite(screen,bmpTexturas[ Mapa[x][y] ], y*32, x*32 );
}
}
readkey();
//Desaloca as Texturas
for (i=0;i<11;i++)
{
destroy_bitmap(bmpTexturas[i]);
}
destroy_bitmap(bmpTiles);
allegro_exit();
return 0;
}
END_OF_MAIN();
FIM DO CÓDIGO
Após a preparação do tiles é necessário organizar o código de maneira que fique clara a montagem do cenário
usando os tiles criados.
// COMENTADO NO FINAL - 1
Logo abaixo das variáveis temos mais duas variáveis constantes que armazenam a largura e a altura do tile.
Nesse exemplo possuímos tiles de 32x32 pixels. Declarando o tamanho como sendo global, posteriormente
podemos efetuar mudanças sem ter que percorrer o código atrás de referência ao tamanho do tile.
// COMENTADO NO FINAL - 2
A matriz que representará o layout do mapa deve ser bidimensional e deve ter como tamanho a quantidade
de tiles que podemos colocar na tela, nesse caso [15]x[20].
Você vai notar que ao informar o tamanho do mapa eu inverti o x pelo y.
Dessa forma, fica fácil de visualizar o layout do cenário através dos números. Normalmente o código de
montagem do cenário monta o cenário da esquerda para direita e de cima para baixo. Se você fizer da mesma
forma que eu fiz o cenário será montado de cima para baixo e da esquerda para a direita.
Você terá que iniciar cada posição do mapa com o número do tile que queremos colocar naquela posição. Para
isso você já deve ter em mente a organização dos tiles e a numeração que vai identificar cada uma na posição
do mapa.
// COMENTADO NO FINAL - 3
Ao invés de carregarmos cada textura do mapa separadamente vamos carregar uma imagem organizada e
contendo todas as texturas.
Logo abaixo teremos um array de imagens que vai conter cada textura separada. Como cada textura tem
tamanho 32x32 fizemos uma rotina genérica para definir o tamanho de todos seqüencialmente. Em seguida
carregamos cada posição do array de texturas com cada imagem correspondente. Para esse efeito usamos a
função blit.
// COMENTADO NO FINAL - 4
Até agora temos o mapa com cada posição iniciada com um número. Esse número é correspondente ao array
de texturas. Por exemplo, na posição Mapa[0][0] temos o número 3. Se você pegar a textura[3] você verá a
imagem de água escura conforme nossa organização, e assim sucessivamente com as outras posições.
A rotina para montar o cenário é simples. Devemos percorrer todas as posições do array
bidimensional “Mapa” identificando o número e representando através de uma textura na tela.
Como nosso tile tem 32 pixels então cada vez que você desenhar uma imagem você deve pular 32
pixels para poder desenhar a próxima (x*xTile, y*yTile).
Caso contrário uma imagem ficará em cima da outra. Imagine a posição x=0 e y=0. Se
desenharmos um tile nessa posição o tile vai ocupar 32 pixels na largura e 32 pixels na altura,
portanto o próximo tile deve ser desenhado a partir da posição x=32 e y=32. Por esse motivo a
rotina de montagem do mapa está multiplicando a posição x,y pelo tamanho de cada tile.
// COMENTADO NO FINAL - 5
Para finalizar, escrevemos na tela o texto “MERCADO” para caracterizar o local no canto superior direito da
tela.
Em seguida é fizemos a destruição das imagens e a finalização da biblioteca Allegro.
Caro leitor, chegamos ao fim do entendimento da montagem de tiles. Espero que o exemplo acima tanha
ajudado.
Qualquer dúvida entre em contato com o pessoal da BDJogos através do fórum.
27 - Detectando colisões
O Ato de detectar colisões entre desenhos é muito importante para qualquer tipo de jogo.
Em um jogo como o Super Mario por exemplo, não podemos sair da tela, nem podemos tocar do lado dos
inimigos.
Para isso foi programado uma detecção de colisão, que na verdade é uma formula bem simples.
Todo objeto na tela deve ter uma posição X e Y definida, desta forma,
podemos saber se o X e Y de um objeto está dentro da área do X e Y de outro objeto.
Usaremos uma variável da Allegro para saber qual é o tamanho máximo da tela para pegar-mos o X e Y do
limite direito e final da tela.
FÓRMULA
- A posição X do objeto não pode ser menor que zero
- A posição de X + o tamanho do objeto ( em X ) não pode ser maior que o limite da
tela. Se for maior, o objeto vai sair da tela.
CÓDIGO...
// Colisão com os cantos da tela
#include <allegro.h>
int main()
{
allegro_init();
set_color_depth(16);
install_keyboard();
set_gfx_mode(GFX_AUTODETECT_FULLSCREEN, 640, 480, 0, 0);
// inicio da programação atual
int x = 0;
int y = 0;
// dimensao do quadrado
// variáveis constantes nunca mudam durante a execução do programa
const int tamanho = 100;
// Laço principal
while( !key[KEY_ESC] )
{
if ( key[KEY_UP] )
{
y--;
}
if ( key[KEY_DOWN] )
{
y++;
}
if ( key[KEY_LEFT] )
{
x--;
}
if ( key[KEY_RIGHT] )
{
x++;
}
// limitando o quadrado dentro da tela
if ( x < 0 )
{
x = 0;
}
if ( (x+tamanho) > SCREEN_W )
{
x = SCREEN_W - tamanho;
}
if ( y < 0 )
{
y = 0;
}
if ( (y+tamanho) > SCREEN_H )
{
y = SCREEN_H - tamanho;
}
// esta função limpa a tela
clear( screen );
// escrevemos um quadrado na tela na posição x e y
rectfill( screen, x, y, x+tamanho, y+tamanho, makecol(255, 0, 0) );
// imprimimos as coordenadas x e y na tela para o usuário ver
textprintf_ex( screen, font, 10, 10, makecol(255,0,0), -1, "Variavel X: %d", x);
textprintf_ex( screen, font, 10, 20, makecol(255,0,0), -1, "Variavel Y: %d", y);
// essa função faz o allegro esperar um pouco antes de voltar para o while
rest(10);
}
allegro_exit();
return 0;
}
END_OF_MAIN();
FIM DE CÓDIGO...
Basicamente:
if ( x < 0 )
{
x = 0;
}
O mesmo para o y.
Agora, para testar os limites finais da tela:
Dica
Fique esperto com estes testes quando o tamanho do objeto for difrente para X e Y.
Agora vamos inserir um quadrado no meio da tela, e vamos fazer um teste de colisão neste quadrado.
CÓDIGO...
// Colisão com os cantos da tela
#include <allegro.h>
int main()
{
allegro_init();
set_color_depth(16);
install_keyboard();
set_gfx_mode(GFX_AUTODETECT_FULLSCREEN, 640, 480, 0, 0);
// inicio da programação atual
int x = 0;
int y = 0;
const int tamanho = 50;
const int obj_tam = 100;
const int obj_x = ( SCREEN_W - (obj_tam / 2) ) / 2;
const int obj_y = ( SCREEN_H - (obj_tam / 2) ) / 2;
bool colide = false;
// Criando BUFFER para double buffer
BITMAP *buffer = NULL;
buffer = create_bitmap(800,800);
// Laço principal
while( !key[KEY_ESC] )
{
if ( colide == false )
{
if ( key[KEY_UP] )
{
y--;
}
if ( key[KEY_DOWN] )
{
y++;
}
if ( key[KEY_LEFT] )
{
x--;
}
if ( key[KEY_RIGHT] )
{
x++;
}
}
// limpa o nosso novo buffer
clear( buffer );
// escreve o quadrado no buffer
rectfill( buffer, x, y, x+tamanho, y+tamanho, makecol(255, 0, 0) );
rectfill( buffer, obj_x, obj_y, obj_x+obj_tam, obj_y+obj_tam, makecol(0, 0, 255) );
// imprimimos as coordenadas x e y na tela para o usuário ver no buffer
textprintf_ex( buffer, font, 10, 10, makecol(255,0,0), -1, "Variavel X: %d", x);
textprintf_ex( buffer, font, 10, 20, makecol(255,0,0), -1, "Variavel Y: %d", y);
textprintf_ex( buffer, font, 10, 30, makecol(255,0,0), -1, "obj X: %d", obj_x);
textprintf_ex( buffer, font, 10, 40, makecol(255,0,0), -1, "obj Y: %d", obj_y);
// verifica se colide
if ( (x+tamanho) >= obj_x && x <= obj_x+obj_tam )
{
if ( (y+tamanho) >= obj_y && y <= obj_y+obj_tam )
{
textout_ex(buffer, font, "COLIDE", obj_x+25, obj_y+50, makecol(0, 255, 0), -1);
}
}
// imprime o buffer na tela
vsync();
blit(buffer, screen, 0,0,0,0, SCREEN_W, SCREEN_H);
// essa função faz o allegro esperar um pouco antes de voltar para o while
rest(10);
}
allegro_exit();
return 0;
}
END_OF_MAIN();
FIM DE CÓDIGO...
Primeiro exemplo:
Neste caso, a imagem vermelha está fora dos limites da imagem do centro azul.
Neste caso, temos o vermelho dentro do limite X da imagem azul, porém, o vermelho continua fora do limite
Y, logo, ainda não temos uma colisão.
Neste caso, temos o vermelho dentro do limite Y da imagem azul, porém, o vermelho continua fora do limite
X, logo, ainda não temos uma colisão.
E no último exemplo, temos a colisão em X e Y dentro dos limites nas imagem azul.
Juntando os 2 testes, temos:
O programa não muda muito, nem a formula, a única diferença é que precisamos juntar os ifs em um único if.
CÓDIGO...
// Colisão em uma função
#include <allegro.h>
int main()
{
allegro_init();
set_color_depth(16);
install_keyboard();
set_gfx_mode(GFX_AUTODETECT_FULLSCREEN, 640, 480, 0, 0);
// inicio da programação atual
int x = 0;
int y = 0;
int x_atual = x;
int y_atual = y;
const int tamanho = 50;
const int obj_tam = 100;
const int obj_x = ( SCREEN_W - (obj_tam / 2) ) / 2;
const int obj_y = ( SCREEN_H - (obj_tam / 2) ) / 2;
bool colidindo = false;
// Criando BUFFER para double buffer
BITMAP *buffer = NULL;
buffer = create_bitmap(800,800);
// Laço principal
while( !key[KEY_ESC] )
{
x_atual = x;
y_atual = y;
if ( key[KEY_UP] )
{
y--;
}
if ( key[KEY_DOWN] )
{
y++;
}
if ( key[KEY_LEFT] )
{
x--;
}
if ( key[KEY_RIGHT] )
{
x++;
}
colidindo = colide( x, y, tamanho, obj_x, obj_y, obj_tam );
if ( colidindo == false )
{
x_atual = x;
y_atual = y;
}
else
{
// volta para os valores antigos
x = x_atual;
y = y_atual;
}
// limpa o nosso novo buffer
clear( buffer );
// escreve o quadrado no buffer
rectfill( buffer, x_atual, y_atual, x_atual+tamanho, y_atual+tamanho, makecol(255, 0,
0) );
rectfill( buffer, obj_x, obj_y, obj_x+obj_tam, obj_y+obj_tam, makecol(0, 0, 255) );
// imprimimos as coordenadas x e y na tela para o usuário ver no buffer
textprintf_ex( buffer, font, 10, 10, makecol(255,0,0), -1, "Variavel X: %d", x);
textprintf_ex( buffer, font, 10, 20, makecol(255,0,0), -1, "Variavel Y: %d", y);
textprintf_ex( buffer, font, 10, 30, makecol(255,0,0), -1, "obj X: %d", obj_x);
textprintf_ex( buffer, font, 10, 40, makecol(255,0,0), -1, "obj Y: %d", obj_y);
textprintf_ex( buffer, font, 10, 50, makecol(255,0,0), -1, "colide: %d", colidindo);
// imprime o buffer na tela
vsync();
blit(buffer, screen, 0,0,0,0, SCREEN_W, SCREEN_H);
// essa função faz o allegro esperar um pouco antes de voltar para o while
rest(10);
}
allegro_exit();
return 0;
}
END_OF_MAIN();
// função que verifica colisão
bool colide( int x, int y, int tamanho, int obj_x, int obj_y, int obj_tam )
{
if ( x+tamanho > obj_x && x < obj_x+obj_tam && y+tamanho > obj_y && y < obj_y+obj_tam )
{
return true;
}
return false;
}
Exercício pratíco
- Utilizando o programa do capítulo 27, crie uma classe para imprimir vários quadrados "objetos" na tela.
O quadrado que se move deve detectar colisão nos outros objetos inseridos.
Imprima 5 quadrados na tela de tamanho diferentes e verifique a colisão com todos eles.
- Faça com que uma bola ande de um lado para outro na tela, de forma que ele se colida com o final da tela e
volte para o inicio da tela.
- Faça com que uma bola ande por toda tela, colidindo com as paredes.
Quando a bola colidir, ela deve alterar para aonde ela vai colidir da próxima vez, de forma que ela fique
andando por toda a tela.
http://www.bdjogos.com/adrw/c/classes.htm
http://www.bdjogos.com/adrw/c/classes_implementacao.htm
http://www.bdjogos.com/adrw/c/construtores_destruidores.htm
CÓDIGO...
#ifndef PAREDE_H
#define PAREDE_H
// ARQUIVO: parede.h
// Data: 01/08/2007
class Parede {
public:
private:
int x;
int y;
int tamanho_x;
int tamanho_y;
};
#endif
FIM DE CÓDIGO...
CÓDIGO...
#include <allegro.h>
#include "parede.h"
// ARQUIVO: parede.cpp
// Data: 01/08/2007
// Testa a colisão com o objeto atual ( isso quer dizer 1 de cada vez )
bool Parede::colide( int x, int y, int tamanho_x, int tamanho_y )
{
if (
( x+tamanho_x > this->x ) &&
( x < this->x+this->tamanho_x ) &&
( y+tamanho_y > this->y ) &&
( y < this->y+this->tamanho_y )
)
{
return true;
}
return false;
}
FIM DE CÓDIGO...
CÓDIGO...
// Colisão com vários objetos
#include <allegro.h>
#include "parede.h"
// ARQUIVO: main.cpp
// Data: 01/08/2007
int main()
{
allegro_init();
set_color_depth(16);
install_keyboard();
set_gfx_mode(GFX_AUTODETECT_FULLSCREEN, 640, 480, 0, 0);
// inicio da programação atual
int x = 0;
int y = 0;
int x_atual = x;
int y_atual = y;
const int tamanho_x = 20;
const int tamanho_y = 10;
const int total_paredes = 5;
bool colidindo = false;
// forma de iniciar vários objetos com construtor
Parede aparede[total_paredes] = {
Parede( 300, 50, 10, 30 ),
Parede( 150, 150, 50, 30 ),
Parede( 300, 150, 150, 50 ),
Parede( 250, 250, 50, 150 ),
Parede( 500, 250, 80, 80 )
} ;
int i = 0;
// Criando BUFFER para double buffer
BITMAP *buffer = NULL;
buffer = create_bitmap(800,800);
// Laço principal
while( !key[KEY_ESC] )
{
x_atual = x;
y_atual = y;
if ( key[KEY_UP] )
{
y -= 5;
}
if ( key[KEY_DOWN] )
{
y += 5;
}
if ( key[KEY_LEFT] )
{
x -= 5;
}
if ( key[KEY_RIGHT] )
{
x += 5;
}
// Aqui vem o complicado
// Nós olhamos objeto por objeto, se colidir com um deles
// paramos o for e não deixamos nosso quadrado andar
for ( i=0; i<total_paredes; i++ )
{
colidindo = aparede[i].colide( x, y, tamanho_x, tamanho_y );
if ( colidindo == true )
{
i = total_paredes;
}
}
if ( colidindo == false )
{
x_atual = x;
y_atual = y;
}
else
{
// volta para os valores antigos
x = x_atual;
y = y_atual;
}
// limpa o nosso novo buffer
clear( buffer );
// escreve o quadrado no buffer
rectfill( buffer, x_atual, y_atual, x_atual+tamanho_x, y_atual+tamanho_y, makecol(255,
0, 0) );
// imprimimos as coordenadas x e y na tela para o usuário ver no buffer
textprintf_ex( buffer, font, 10, 10, makecol(255,0,0), -1, "Variavel X: %d", x);
textprintf_ex( buffer, font, 10, 20, makecol(255,0,0), -1, "Variavel Y: %d", y);
textprintf_ex( buffer, font, 10, 50, makecol(255,0,0), -1, "colide: %d", colidindo);
// imprime todos os quadrados no destino ( buffer )
for ( i=0; i<total_paredes; i++ )
{
aparede[i].imprimir( buffer );
}
// imprime o buffer na tela
vsync();
blit(buffer, screen, 0,0,0,0, SCREEN_W, SCREEN_H);
// essa função faz o allegro esperar um pouco antes de voltar para o while
rest(10);
}
allegro_exit();
return 0;
}
END_OF_MAIN();
FIM DE CÓDIGO...
Por enquanto não se preocupe com otimização de código, poderiam ter 5 mil objetos na tela que o C++ iria
dar conta do recado. Utilize sua imaginação sem pensar que seu jogo irá ficar lento.
- Faça com que uma bola ande de um lado para outro na tela, de forma que ele se colida com o final da tela e
volte para o inicio da tela.
CÓDIGO...
// Bolinha andadora
#include <allegro.h>
int main()
{
allegro_init();
set_color_depth(16);
install_keyboard();
set_gfx_mode(GFX_AUTODETECT_FULLSCREEN, 640, 480, 0, 0);
// inicio da programação atual
int x = 0;
int y = SCREEN_H/2;
int tamanho = 20;
bool colide = false;
int variavel = 1;
// Criando BUFFER para double buffer
BITMAP *buffer = NULL;
buffer = create_bitmap(800,800);
// Laço principal
while( !key[KEY_ESC] )
{
if ( variavel == 1 )
{
x += 5;
}
else
{
x -= 5;
}
// limpa o nosso novo buffer
clear( buffer );
// escreve o quadrado no buffer
ellipsefill(buffer, x, y, tamanho, tamanho, makecol(255,255,0) );
// imprimimos as coordenadas x e y na tela para o usuário ver no buffer
textprintf_ex( buffer, font, 10, 10, makecol(255,0,0), -1, "Variavel X: %d", x);
textprintf_ex( buffer, font, 10, 20, makecol(255,0,0), -1, "Variavel Y: %d", y);
// limitando o quadrado dentro da tela
if ( x < 0 )
{
variavel = variavel * (-1);
}
if ( (x+tamanho) > SCREEN_W )
{
variavel = variavel * (-1);
}
// imprime o buffer na tela
vsync();
blit(buffer, screen, 0,0,0,0, SCREEN_W, SCREEN_H);
// essa função faz o allegro esperar um pouco antes de voltar para o while
rest(10);
}
allegro_exit();
return 0;
}
END_OF_MAIN();
FIM DE CÓDIGO...
- Faça com que uma bola ande por toda tela, colidindo com as paredes.
Quando a bola colidir, ela deve alterar para aonde ela vai colidir da próxima vez, de forma que ela fique
andando por toda a tela.
CÓDIGO...
// Bolinha colisão
#include <allegro.h>
int main()
{
allegro_init();
set_color_depth(16);
install_keyboard();
set_gfx_mode(GFX_AUTODETECT_FULLSCREEN, 640, 480, 0, 0);
// inicio da programação atual
int x = 0;
int y = SCREEN_H/2;
int tamanho = 20;
bool colide = false;
int variavel_x = 10;
int variavel_y = 10;
// Criando BUFFER para double buffer
BITMAP *buffer = NULL;
buffer = create_bitmap(800,800);
// Laço principal
while( !key[KEY_ESC] )
{
if ( variavel_x == 1 )
{
x += variavel_x;
}
else
{
x -= variavel_x;
}
if ( variavel_y == 1 )
{
y += variavel_y;
}
else
{
y -= variavel_y;
}
// limpa o nosso novo buffer
clear( buffer );
// escreve o quadrado no buffer
ellipsefill(buffer, x, y, tamanho, tamanho, makecol(255,255,0) );
// imprimimos as coordenadas x e y na tela para o usuário ver no buffer
textprintf_ex( buffer, font, 10, 10, makecol(255,0,0), -1, "Variavel X: %d", x);
textprintf_ex( buffer, font, 10, 20, makecol(255,0,0), -1, "Variavel Y: %d", y);
// limitando o quadrado dentro da tela
if ( x < 0 )
{
variavel_x = variavel_x * (-1);
}
if ( (x+tamanho) > SCREEN_W )
{
variavel_x = variavel_x * (-1);
}
if ( y < 0 )
{
variavel_y = variavel_y * (-1);
}
if ( (y+tamanho) > SCREEN_H )
{
variavel_y = variavel_y * (-1);
}
// imprime o buffer na tela
vsync();
blit(buffer, screen, 0,0,0,0, SCREEN_W, SCREEN_H);
// essa função faz o allegro esperar um pouco antes de voltar para o while
rest(10);
}
allegro_exit();
return 0;
}
END_OF_MAIN();
Este é o exemplo de uma animação e alguns de seus quadros, que juntos formam a animação.
Essa mudança de tempo nos quadros da animação influencia muito os jogos feitos para computadores.
Geralmente, nosso programa roda na velocidade de clock tick do nosso processador. Isto quer dizer que o
programa pode rodar mais rápido em algumas máquinas e mais lento em outras máquinas.
Para que o jogo rode igual em qualquer máquina, precisamos criar um controle de Frame Rate ( ou controle
de FPS ).
Para isto, primeiro precisamos descobrir como contar os FPS de nosso programa.
DICA...
Esta técnica de contar FPS e fazer o programa rodar em um FPS igual para todas as
máquinas pode e deve ser aplicado em qualquer tipo de linguagem de programação
para jogos ou animações.
Cada linguagem tem uma forma diferente de se chegar nesse resultado, mais a
tecnica é a mesma.Vamos aprender em Allegro um código completo.
- Controle de tempo
CÓDIGO...
#include <allegro.h>
// global
int fps = 0;
int fps_antigo = 0;
int main()
{
allegro_init();
set_color_depth(16);
install_keyboard();
set_gfx_mode(GFX_AUTODETECT_WINDOWED, 640, 480, 0, 0);
BITMAP *buffer = NULL;
int x = 0;
int y = SCREEN_H/2;
int tamanho = 20;
install_timer();
// a cada 1 segundo mostra quantas vezes a imagem foi impressa na tela
install_int( frame_rate, 1000 );
bool colide = false;
int variavel_x = 10;
int variavel_y = 10;
// Criando BUFFER para double buffer
buffer = create_bitmap(800,800);
// Laço principal
while( !key[KEY_ESC] )
{
if ( variavel_x == 1 )
{
x += variavel_x;
}
else
{
x -= variavel_x;
}
if ( variavel_y == 1 )
{
y += variavel_y;
}
else
{
y -= variavel_y;
}
// limitando o quadrado dentro da tela
if ( x < 0 )
{
variavel_x = variavel_x * (-1);
}
if ( (x+tamanho) > SCREEN_W )
{
variavel_x = variavel_x * (-1);
}
if ( y < 0 )
{
variavel_y = variavel_y * (-1);
}
if ( (y+tamanho) > SCREEN_H )
{
variavel_y = variavel_y * (-1);
}
// limpa o nosso novo buffer
clear( buffer );
// escreve a bola no buffer
ellipsefill(buffer, x, y, tamanho, tamanho, makecol(255,255,0) );
textprintf_ex( buffer, font, 10, 10, makecol(255,0,0), -1, "FPS: %d", ::fps_antigo );
blit(buffer, screen, 0,0,0,0, SCREEN_W, SCREEN_H);
vsync();
::fps++;
}
destroy_bitmap( buffer );
allegro_exit();
return 0;
}
END_OF_MAIN();
void frame_rate()
{
::fps_antigo = ::fps;
::fps = 0;
}
FIM DE CÓDIGO
No exemplo acima, criamos uma variável global fps que é incrementada sempre após que a imagem é
imprimida na tela.
Usando um timer do Allegro, sempre que passar 1 segundo, nós mostramos o total de FPS que o Allegro
conseguiu imprimir, tendo assim o total de frames impressos em 1 segundo.
Legal.. já sabemos a quantos quadros nosso programa roda, agora precisamos controlar a taxa de quadros,
sem influênciar na jogabilidade.
Nos exemplos anteriores do site, estavamos usando o comando rest() que fazia o compilador esperar.Esta
função não deve ser usanda, por que durante a execução do comando rest() as teclas do teclado não são
detectadas.
#include <allegro.h>
// global
int fps = 0;
int fps_antigo = 0;
int fps_speed = 0;
int main()
{
allegro_init();
set_color_depth(16);
install_keyboard();
set_gfx_mode(GFX_AUTODETECT_WINDOWED, 640, 480, 0, 0);
BITMAP *buffer = NULL;
int x = 0;
int y = SCREEN_H/2;
int tamanho = 20;
install_timer();
// a cada 1 segundo mostra quantas vezes a imagem foi impressa na tela
install_int( frame_rate, 1000 );
install_int_ex( incrementa_speed, BPS_TO_TIMER(60) );
bool colide = false;
int variavel_x = 10;
int variavel_y = 10;
// Criando BUFFER para double buffer
buffer = create_bitmap(800,800);
// Laço principal
while( !key[KEY_ESC] )
{
while ( ::fps_speed > 0 )
{
if ( variavel_x == 1 )
{
x += variavel_x;
}
else
{
x -= variavel_x;
}
if ( variavel_y == 1 )
{
y += variavel_y;
}
else
{
y -= variavel_y;
}
// limitando o quadrado dentro da tela
if ( x < 0 )
{
variavel_x = variavel_x * (-1);
}
if ( (x+tamanho) > SCREEN_W )
{
variavel_x = variavel_x * (-1);
}
if ( y < 0 )
{
variavel_y = variavel_y * (-1);
}
if ( (y+tamanho) > SCREEN_H )
{
variavel_y = variavel_y * (-1);
}
// limpa o nosso novo buffer
clear( buffer );
// escreve o quadrado no buffer
ellipsefill(buffer, x, y, tamanho, tamanho, makecol(255,255,0) );
textprintf_ex( buffer, font, 10, 10, makecol(255,0,0), -1, "FPS: %d",
::fps_antigo );
::fps_speed--;
::fps++;
}
blit(buffer, screen, 0,0,0,0, SCREEN_W, SCREEN_H);
vsync();
}
destroy_bitmap( buffer );
allegro_exit();
return 0;
}
END_OF_MAIN();
void frame_rate()
{
::fps_antigo = ::fps;
::fps = 0;
}
void incrementa_speed()
{
::fps_speed++;
}
FIM DE CÓDIGO
Quando acontecer os primeiros 60 clocks, o compilador vai entrar no while que atualiza a lógica do jogo.
Enquanto o fps_speed não for menor que 0 ( o while vai fazer ele chegar bem rápido ) ele vai atualizando a
lógica do jogo, mais não a tela.
Esta lógica faz com que o contador de FPS fique constante.
Execute o código acima e faça testes alterando o valor que está na macro BPS_TO_TIMER para conferir.
Para fazer com que o teclado obedeça a velocidade em FPS é preciso utilizar um Buffer de teclado, que pode
ser lido aqui:
http://www.bdjogos.com/biblioteca_conteudo.php?id=17
Com este tutorial suprimos uma das dúvidas mais cabulosas na criação de jogos, a partir deste capítulo
iremos demonstrar outras técnicas importantes na programação de jogos. Qualquer dúvida entre em contato
pelo forum.
29 - Movimentação de Sprites
Sprites é um conjunto de imagens que organizadas em uma determinada seqüência simulam o movimento de
algum objeto ou personagem. Praticamente todos os jogos 2D possuem algum tipo de sprites.
Podemos definir como sprites a movimentação do jogador principal, as folhas caindo no chão, os inimigos em
movimento e principalmente os efeitos entre os objetos do cenário.
http://tsgk.captainn.net/
http://www.panelmonkey.org/category.php?id=1&theme=1
http://www.molotov.nu/?page=graphics
dragao.bmp
Especificações
robocop.bmp
Especificações
Não se esqueça de converter as imagens acima para bitmap.
O código que iremos usar para movimentar o Robocop e o dragão é o mesmo.
Porém, antes de executar cada um é necessário fazer algumas configurações com relação às especificações
citadas acima.
CÓDIGO...
#include <allegro.h>
int main()
{
allegro_init();
install_keyboard();
set_color_depth(16);
set_gfx_mode(GFX_AUTODETECT_WINDOWED, 640, 480, 0, 0);
while (!key[KEY_ESC])
{
clear_bitmap( Buffer );
textprintf_ex(Buffer, font, 0, 0, makecol(0, 255, 255),-1,"ANIMACAO SPRITES");
buffer_animacao = iCiclo;
}
else
{
buffer_animacao--;
}
blit(Buffer,screen,0,0,0,0,SCREEN_W,SCREEN_H);
vsync();
}
destroy_bitmap(imgSprites);
destroy_bitmap(Buffer);
allegro_exit();
return 0;
}
END_OF_MAIN();
FIM DO CÓDIGO
Logo acima temos um código genérico para a movimentação dos sprites na horizontal. No processo para a
movimentação dos sprites na vertical a única coisa que muda é o eixo x para y.
Para que as imagens não fossem trocadas na velocidade de processamento de cada computador adicionamos
um buffer de animação iCiclo.
Dessa forma nossa animação terá um movimento mais pausado e uma impressão de movimento muito
melhor.
Caro leitor chegamos ao fim do entendimento da movimentação de sprites. Se houver alguma dúvida ou
dificuldade favor entrar em contato com o pessoal pelo Forum.
Nesse artigo vou ilustrar o código para movimentação do pacman em todas as direções utilizando sprites.
Antes de iniciar o código será necessário entender como estão organizados os sprites no arquivo.
A tabela abaixo mostra que na vertical estão definidas as direções dos desenhos, Direita, Esquerda, Cima e
Baixo.
Já na horizontal estão definidas as seqüências dos sprites que irão definir o movimento do pacman.
Ao selecionar a posição 0,0 você deverá desenhar na tela o pacman virado para a direita e de boca fechada.
pacpac.bmp
Especificações
OBS: Salvar a imagem acima como .BMP. O allegro não aceita o tipo jpg.
Porém, antes de executar cada um é necessário fazer algumas configurações com relação às especificações
citadas acima.
main.cpp
Jogador.h
Jogador.cpp
CÓDIGO...
#include <allegro.h>
#include "cjogador.h"
// Arquivo: main.cpp
int main()
{
allegro_init();
set_color_depth(32);
install_keyboard();
set_gfx_mode(GFX_AUTODETECT_WINDOWED, 640, 480, 0, 0);
while (!key[KEY_ESC])
{
clear_bitmap(Buffer);
textprintf_ex(Buffer, font, 0, 0, makecol(0, 255, 255),-1,"SPRITE EM MOVIMENTO");
blit(Buffer,screen,0,0,0,0,SCREEN_W,SCREEN_H);
vsync();
}
destroy_bitmap(Buffer);
delete Buffer;
allegro_exit();
return 0;
}
END_OF_MAIN();
FIM DO CÓDIGO
main.cpp
CÓDIGO...
#ifndef CJOGADOR_H
#define CJOGADOR_H
#include <allegro.h>
// Arquivo: cjogador.h
class CJogador
{
private:
BITMAP *imgJogador;
int iPosx;
int iPosy;
int iSpritex;
int iSpritey;
public:
int iLargura;
int iAltura;
CJogador();
~CJogador();
void Setax(int);
void Setay(int);
void Atualiza(BITMAP *buffer);
void Controle(int);
};
#endif
FIM DO CÓDIGO
Jocador.h
Arquivo de recurso responsável por armazenar apenas a definição da classe Jogador. Todas
as características que o pacman vai possuir estão definidas nesse arquivo. São elas:
Descrição
Variável
Descrição
Variável
CÓDIGO...
#include "cjogador.h"
// Arquivo: cjogador.cpp
CJogador::CJogador()
{
this->imgJogador = load_bitmap("pacpac.bmp",NULL); // Sprites do PacPac
this->iPosx = 0; // Posição x do pacman na tela
this->iPosy = 0; // Posição y do pacman na tela
this->iLargura = 25; // Define a largura do sprite
this->iAltura = 25; // Define a altura do sprite
this->iSpritex = 0; // Inicia animação na posição zero.
this->iSpritey = 0; // Inicia virado para a direita
}
CJogador::~CJogador()
{
delete this->imgJogador;
}
masked_blit(imgJogador,buffer,this->iSpritex,this->iSpritey,this->iPosx,this->iPosy,this-
>iLargura,this->iAltura);
iVelSprite--;
if (iVelSprite<=0)
{
this->iSpritex += 25;
iVelSprite = 4;
}
}
if (Buffer_Teclado == 0)
{
if (key[KEY_UP])
{
this->Setay(-Vel);
Buffer_Teclado = 10;
}
else if (key[KEY_DOWN])
{
this->Setay(Vel);
Buffer_Teclado = 10;
}
else if (key[KEY_LEFT])
{
this->Setax(-Vel);
Buffer_Teclado = 10;
}
else if (key[KEY_RIGHT])
{
this->Setax(Vel);
Buffer_Teclado = 10;
}
}
else
{
Buffer_Teclado--;
}
}
FIM DO CÓDIGO
Jogador.cpp
Esse arquivo faz referencia ao arquivo de recursos Jogador.h. No entanto, ele guarda apenas
o código responsável pela movimentação do pacman na tela.
Caro leitor chegamos ao fim do entendimento da troca de sprites durante a animação. Espero ter escrito um
código auto explicativo.
Se houver alguma dúvida ou dificuldade favor entrar em contato com o pessoal pelo Forum.
Scrolling falso
Um exemplo de scrolling falso é o exemplo do espaço aonde as estrelas vão passando, umas bem lentas e
outras rápidamente dando o efeito de que o cenário está andando.
Um outro exemplo é de um cenário que tem a mesma imagem de fundo, quando o personagem vai andando a
imagem vai andando também, mais ela acaba sempre se repetindo.
Vamos trabalhar com os 2 exemplos, primeiro, mostraremos nosso programa base que mostra e limita os
FPS.
CÓDIGO...
#include <allegro.h>
// PROGRAMA BASE
// variáveis globais
int fps = 0;
int fps_antigo = 0;
int fps_speed = 0;
int main()
{
allegro_init();
set_color_depth(16);
install_keyboard();
set_gfx_mode(GFX_AUTODETECT_WINDOWED, 640, 480, 0, 0);
install_timer();
install_int( frame_rate, 1000 );
install_int_ex( incrementa_speed, BPS_TO_TIMER(60) );
BITMAP *buffer = NULL;
// Criando BUFFER para double buffer
buffer = create_bitmap(800,800);
// Laço principal
while( !key[KEY_ESC] )
{
while ( ::fps_speed > 0 )
{
clear( buffer );
textprintf_ex( buffer, font, 10, 10, makecol(255,0,0), -1, "FPS: %d",
::fps_antigo );
::fps_speed--;
::fps++;
}
blit(buffer, screen, 0,0,0,0, SCREEN_W, SCREEN_H);
vsync();
}
destroy_bitmap( buffer );
allegro_exit();
return 0;
}
END_OF_MAIN();
void frame_rate()
{
::fps_antigo = ::fps;
::fps = 0;
}
void incrementa_speed()
{
::fps_speed++;
}
FIM DE CÓDIGO...
Criamos uma classe que sempre fica verificando a posição do objeto, se o objeto está fora da tela,
ele reposiciona o objeto para o começo da tela com uma nova velocidade.
Preste atenção na forma como eu aloco cada objeto do array de estrelas, como eu uso eles e como a memória
é desalocada.
CÓDIGO...
#ifndef STAR_H
#define STAR_H
// ARQUIVO: star.h
// Data: 18/08/2007
class Star {
private:
int velocidade;
int id;
public:
};
#endif
FIM DE CÓDIGO...
CÓDIGO...
#include <allegro.h>
#include <iostream>
#include "star.h"
// ARQUIVO: star.cpp
// Data: 18/08/2007
// construtor
Star::Star( int vid )
{
this->id = vid;
this->velocidade = 0;
this->x = 0;
this->y = 0;
this->tamanho = 0;
this->x = 1 + rand() % SCREEN_W;
this->y = this->id + rand() % ( this->id + 10 );
this->velocidade = ( 1 + rand() % (::velocidade_total) );
this->tamanho = 1 + rand() % (::tamanho_total);
}
// destrutor
Star::~Star()
{
this->velocidade = 0;
this->x = 0;
this->y = 0;
this->tamanho = 0;
}
// inicia o objeto
void Star::iniciar( int i )
{
if ( i == 0 )
{
i = 1;
}
this->y = i + rand() % ( i + 10 );
this->velocidade = ( 1 + rand() % (::velocidade_total) );
this->tamanho = 1 + rand() % (::tamanho_total);
this->x = SCREEN_W;
}
// verifica se não chegou no final da tela
void Star::verificar()
{
if ( this->x > 0 )
{
this->x -= this->velocidade;
}
else
{
this->iniciar( this->id );
}
}
FIM DE CÓDIGO...
CÓDIGO...
#include <allegro.h>
#include <iostream>
#include <ctime>
#include "star.h"
// ARQUIVO: main.cpp
// Data: 18/08/2007
// variáveis globais
int fps = 0;
int fps_antigo = 0;
int fps_speed = 0;
int main()
{
allegro_init();
set_color_depth(16);
install_keyboard();
set_gfx_mode(GFX_AUTODETECT_WINDOWED, 640, 480, 0, 0);
srand( time(0) ); // determina a randomização
install_timer();
install_int( frame_rate, 1000 );
install_int_ex( incrementa_speed, BPS_TO_TIMER(60) );
BITMAP *buffer = NULL;
int i = 0;
int total_estrelas = SCREEN_H;
Star *estrelas[total_estrelas];
// inicia as estrelas
for ( i=0; i< total_estrelas; i++ )
{
estrelas[ i ] = new Star( i );
}
// Criando BUFFER para double buffer
buffer = create_bitmap(800,800);
// Laço principal
while( !key[KEY_ESC] )
{
while ( ::fps_speed > 0 )
{
clear( buffer );
// imprime as estrelas na tela e verifica se ela não chegou no final da tela
for ( i=0; i< total_estrelas; i++ )
{
putpixel(buffer, estrelas[ i ]->x, estrelas[ i ]->y, makecol(255,255,255));
estrelas[ i ]->verificar();
}
textprintf_ex( buffer, font, 10, 10, makecol(255,0,0), -1, "FPS: %d",
::fps_antigo );
::fps_speed--;
::fps++;
}
blit(buffer, screen, 0,0,0,0, SCREEN_W, SCREEN_H);
vsync();
}
// apaga as estrelas da memoria
for ( i=0; i< total_estrelas; i++ )
{
delete estrelas[i];
}
destroy_bitmap( buffer );
allegro_exit();
return 0;
}
END_OF_MAIN();
void frame_rate()
{
::fps_antigo = ::fps;
::fps = 0;
}
void incrementa_speed()
{
::fps_speed++;
}
FIM DE CÓDIGO...
CÓDIGO...
#include <allegro.h>
// ARQUIVO: main.cpp
// Data: 20/08/2007
// variáveis globais
int fps = 0;
int fps_antigo = 0;
int fps_speed = 0;
int main()
{
allegro_init();
set_color_depth(16);
install_keyboard();
set_gfx_mode(GFX_AUTODETECT_WINDOWED, 640, 480, 0, 0);
install_timer();
install_int( frame_rate, 1000 );
install_int_ex( incrementa_speed, BPS_TO_TIMER(60) );
// Criando BUFFER para double buffer
BITMAP *buffer = NULL;
buffer = create_bitmap(800,800);
// Load das imagens .bmp ( converter de jpg para bmp )
BITMAP *img_fundo1 = NULL;
BITMAP *img_fundo2 = NULL;
img_fundo1 = load_bitmap("ceu1.bmp", NULL );
img_fundo2 = load_bitmap("cenario2.bmp", NULL );
// essas variáveis são variáveis auxiliares que ajudarão muito
BITMAP *fundo1 = NULL;
BITMAP *fundo2 = NULL;
fundo1 = create_bitmap( 1022, 180 );
fundo2 = create_bitmap( 780, 149 );
// estou criando o fundo em uma variavel auxiliar
blit(img_fundo1, fundo1, 0, 0, 0, 0, 511, 180 );
blit(img_fundo1, fundo1, 0, 0, 511, 0, 511, 180 );
// estou usando masked para ficar incolor a area rosa
masked_blit(img_fundo2, fundo2, 0, 0, 0, 0, 260, 149 );
masked_blit(img_fundo2, fundo2, 0, 0, 260, 0, 260, 149 );
masked_blit(img_fundo2, fundo2, 0, 0, 420, 0, 260, 149 );
// Laço principal
while( !key[KEY_ESC] )
{
while ( ::fps_speed > 0 )
{
clear( buffer );
masked_blit( fundo1, buffer, 0, 0, 0, 0, 1022, 180 );
masked_blit( fundo2, buffer, 0, 0, 0, 130, 780, 149 );
textprintf_ex( buffer, font, 10, 10, makecol(255,0,0), -1, "FPS: %d",
::fps_antigo );
::fps_speed--;
::fps++;
}
blit(buffer, screen, 0,0,0,0, SCREEN_W, SCREEN_H);
vsync();
}
destroy_bitmap( buffer );
destroy_bitmap( img_fundo1 );
destroy_bitmap( img_fundo2 );
destroy_bitmap( fundo1 );
destroy_bitmap( fundo2 );
allegro_exit();
return 0;
}
END_OF_MAIN();
void frame_rate()
{
::fps_antigo = ::fps;
::fps = 0;
}
void incrementa_speed()
{
::fps_speed++;
}
FIM DE CÓDIGO...
Enquanto nosso contador for menor que o tamanho total de 1 imagem ( a mesma imagem montada uma ao
lado da outra ),
a gente vai incrementando ela e mostrando na tela a imagem na posição do contador. Desta forma a imagem
vai andando para o lado.
Para que a imagem seja repetida corretamente, a segunda imagem é colada na posição
(total_fundo1*(-1))+x_fundo1)
Que é igual a, -1022 + x_fundo1, isso vai fazer com que nossa segunda imagem fique exatamente no final da
primeira imagem, e isso também vai previnir de que o cenário seja quebrado.
CÓDIGO...
#include <allegro.h>
// ARQUIVO: main.cpp
// Data: 20/08/2007
// variáveis globais
int fps = 0;
int fps_antigo = 0;
int fps_speed = 0;
int main()
{
allegro_init();
set_color_depth(16);
install_keyboard();
set_gfx_mode(GFX_AUTODETECT_WINDOWED, 640, 480, 0, 0);
install_timer();
install_int( frame_rate, 1000 );
install_int_ex( incrementa_speed, BPS_TO_TIMER(30) );
// Criando BUFFER para double buffer
BITMAP *buffer = NULL;
buffer = create_bitmap(800,800);
// Load das imagens .bmp ( converter de jpg para bmp )
BITMAP *img_fundo1 = NULL;
BITMAP *img_fundo2 = NULL;
img_fundo1 = load_bitmap("ceu1.bmp", NULL );
img_fundo2 = load_bitmap("cenario2.bmp", NULL );
// essas variáveis são variáveis auxiliares que ajudarão muito
BITMAP *fundo1 = NULL;
BITMAP *fundo2 = NULL;
fundo1 = create_bitmap( 1022, 180 );
fundo2 = create_bitmap( 780, 149 );
// estou criando o fundo em uma variavel auxiliar
blit(img_fundo1, fundo1, 0, 0, 0, 0, 511, 180 );
blit(img_fundo1, fundo1, 0, 0, 511, 0, 511, 180 );
// estou usando masked para ficar incolor a area rosa
masked_blit(img_fundo2, fundo2, 0, 0, 0, 0, 260, 149 );
masked_blit(img_fundo2, fundo2, 0, 0, 260, 0, 260, 149 );
masked_blit(img_fundo2, fundo2, 0, 0, 520, 0, 260, 149 );
//masked_blit(img_fundo2, fundo2, 0, 0, 420, 0, 260, 149 );
// Inicio programação da rolagem
int velocidade_fundo1 = 1;
int velocidade_fundo2 = 6;
int total_fundo1 = 1022;
int total_fundo2 = 780;
int x_fundo1 = 0;
int x_fundo2 = 0;
// Laço principal
while( !key[KEY_ESC] )
{
while ( ::fps_speed > 0 )
{
clear( buffer );
// faz com que, antes que o segundo fundo termine, começe a imprimir o primeiro fundo
if ( x_fundo1 < ( total_fundo1 ) )
{
x_fundo1 = x_fundo1 + velocidade_fundo1;
}
else
{
x_fundo1 = 0;
}
// vai passando o primeiro fundo
masked_blit( fundo1, buffer, x_fundo1, 0, 0, 0, 1022, 180 );
// imprime no final do primeiro fundo, e vai passando
masked_blit( fundo1, buffer, ((total_fundo1*(-1))+x_fundo1), 0, 0, 0, 1022, 180 );
if ( x_fundo2 < ( total_fundo2 ) )
{
x_fundo2 = x_fundo2 + velocidade_fundo2;
}
else
{
x_fundo2 = 0;
}
masked_blit( fundo2, buffer, x_fundo2, 0, 0, 130, 780, 149 );
masked_blit( fundo2, buffer, ((total_fundo2*(-1))+x_fundo2), 0, 0, 130, 780, 149 );
textprintf_ex( buffer, font, 10, 10, makecol(255,0,0), -1, "FPS: %d",
::fps_antigo );
::fps_speed--;
::fps++;
}
blit(buffer, screen, 0,0,0,0, SCREEN_W, SCREEN_H);
vsync();
}
destroy_bitmap( buffer );
destroy_bitmap( img_fundo1 );
destroy_bitmap( img_fundo2 );
destroy_bitmap( fundo1 );
destroy_bitmap( fundo2 );
allegro_exit();
return 0;
}
END_OF_MAIN();
void frame_rate()
{
::fps_antigo = ::fps;
::fps = 0;
}
void incrementa_speed()
{
::fps_speed++;
}
FIM DE CÓDIGO...
Para escrever o código, primeiro precisamos saber que o personagem é o centro da tela da nossa rolagem
( ele não precisa estar no centro da tela necessariamente ).
Quando o personagem se mover, devemos mover todos os tiles do fundo na posição oposta.
Para isso, precisamos também ter o tamanho total da tela, no caso, a fase do nosso jogo.
Iremos usar também a parte central da tela, desta forma, podemos ver o efeito de scrolling.
Você vai perceber que nossos códigos estão começando a aumentar hehehe.. Isto é normal.
A técnica utilizada abaixo pode ser usada com um construtor de cenários, basta entender o algoritmo de
rolagem que é bem simples.
CÓDIGO...
#include <allegro.h>
// ARQUIVO: main.cpp
// Data: 22/08/2007
// Exemplo de scrolling verdadeiro
// variáveis globais
int fps = 0;
int fps_antigo = 0;
int fps_speed = 0;
int main()
{
allegro_init();
set_color_depth(16);
install_keyboard();
set_gfx_mode(GFX_AUTODETECT_WINDOWED, 640, 480, 0, 0);
install_timer();
install_int( frame_rate, 1000 );
install_int_ex( incrementa_speed, BPS_TO_TIMER(30) );
// Criando BUFFER para double buffer
BITMAP *buffer = NULL;
buffer = create_bitmap(800,800);
// variáveis comentadas no escopo do programa
const int tamanho_tela = 500;
const int inicial_tela = ( (SCREEN_W/2)-(tamanho_tela/2) );
const int cenario = 250; // tiles
const int tamanho_tile = 50;
// posição de nosso personagem
int personagem_pos = inicial_tela + 10;
int x = personagem_pos;
// auxiliares
int i = 0;
int total_for = 0;
int posicao = 0;
int posicao_cenario_inicial = inicial_tela;
int posicao_cenario = 0;
// Laço principal
while( !key[KEY_ESC] )
{
while ( ::fps_speed > 0 )
{
clear( buffer );
// este for faz com que seja mostrado apenas a parte do cenario visivel para o usuário
// para isso ele só vai mostrar
// os tiles que cabem na tela ( tamanho_tela / tamanho_tile )
// mais a posição atual do cenario ( que é incrementado quando se aperta uma tecla )
// usamos o -1 para trocar o sinal da operação
total_for = ( tamanho_tela / tamanho_tile )+( posicao_cenario*(-1) );
for ( i=posicao_cenario; i<=total_for; i++ )
{
// aqui determinamos a posição correta de cada tile
// usamos a posicao_cenario_inicial para somar aonde começa o cenario
posicao = ( posicao_cenario + i ) * tamanho_tile;
posicao += posicao_cenario_inicial;
// impressão dos tiles nas posições corretas
rectfill(buffer, posicao, 230, posicao+tamanho_tile, 250, makecol(0,0,255) );
rect(buffer, posicao, 230, posicao+tamanho_tile, 250, makecol(255,0,0) );
textprintf_ex( buffer, font, posicao+1, 235, makecol(255,255,0), -1, "%d", i );
}
// esse será nosso limite de tela
rect(buffer, inicial_tela, 10, inicial_tela+tamanho_tela, 250, makecol(0,255,0) );
// esse é nosso personagem
personagem_pos = x;
rectfill(buffer, personagem_pos, 210, personagem_pos+10, 230, makecol(255,255,0) );
textprintf_ex( buffer, font, 10, 10, makecol(255,0,0), -1, "FPS: %d",
::fps_antigo );
::fps_speed--;
::fps++;
}
if ( key[KEY_RIGHT] )
{
// atualiza a posição do nosso personagem
if ( x < ( tamanho_tela + inicial_tela ) / 2 )
{
x++;
}
else
{
// do cenario
// nunca vai poder passar o limite do cenario
if ( (posicao_cenario ) > - ( ( cenario ) - (tamanho_tela / tamanho_tile) ) )
{
posicao_cenario--;
}
else
{
// atualiza a posição do nosso personagem
if ( x < (inicial_tela+tamanho_tela)-10 )
{
x++;
}
}
}
}
if ( key[KEY_LEFT] )
{
// atualiza a posição do nosso personagem
if ( x > ( tamanho_tela + inicial_tela ) / 2 )
{
x--;
}
else
{
// do cenario
// nunca vai poder passar o limite do cenario
if ( posicao_cenario < cenario && posicao_cenario < 0 )
{
posicao_cenario++;
}
else
{
// atualiza a posição do nosso personagem
if ( x > ( inicial_tela ) )
{
x--;
}
}
}
}
blit(buffer, screen, 0,0,0,0, SCREEN_W, SCREEN_H);
vsync();
}
destroy_bitmap( buffer );
allegro_exit();
return 0;
}
END_OF_MAIN();
void frame_rate()
{
::fps_antigo = ::fps;
::fps = 0;
}
void incrementa_speed()
{
::fps_speed++;
}
30 - Scrolling de Tiles
No tutorial 26 "Utilizando Tiles" já foi explicado conceitos básicos sobre tiles e sua aplicação. Nesse tutorial,
estarei mostrando uma técnica chamada SCROLLING de TILES (Não é importante que você tenha lido o
tutorial 26 antes de continuar neste tutorial, pois estarei mostrando uma abordagem diferente sobre o
assunto). Para imprimir tiles na tela, é usado um mapa para representar a posição de cada tile. Para
preenchermos uma tela de 640x480, e usando tiles de 32x32, precisaríamos de 20 tiles na horizontal e 15 na
vertical. Com SCROLLING DE TILES, podemos ter o mapa do tamanho que quisermos (não preciso nem dizer
que o mapa é representado por uma matriz multidimensional). SCROLING DE TILES é amplamente usado em
jogos 2d, como Mario ou Metroid.
Portanto, poderemos ter um mapa de tamanho qualquer, não importa a altura nem a largura, nosso algoritmo
deve imprimir os tiles na tela perfeitamente. Você pode estar pensando: "é só imprimir todos os tiles na tela".
Nada impede que você faça isso, mas seu jogo ficará extremamente lento. O correto é imprimir somente a
área correspondente ao tamanho da tela.
Básico
O tutorial 26 mostrou um jeito fácil de imprimir tiles na tela. Nessa parte básica, vou mostrar outra forma de
obter o mesmo resultado, ou seja, imprimir tiles carregando o mapa a partir de um arquivo. Primeiro,
precisamos declarar algumas constantes e estruturas básicas:
CÓDIGO...
/* Tamanho do tile */
#define TILEWH 32
typedef struct
{
BITMAP * bitmap;
} SPRITE;
typedef struct
{
SPRITE * sprite;
} TILE;
typedef struct
{
int w;
int h;
TILE ** tiles;
} MAP;
typedef struct
{
int w;
int h;
} MAPINFO;
FIM DE CÓDIGO...
A primeira estrutura, SPRITE, armazenará informações do tile atual, que no nosso caso vai ser somente
estático (imagem fixa).
A segunda estrutura, TILE, armazenará informações sobre que tipo de SPRITE iremos imprimir na tela.
A estrutura MAP armazenará o mapa, contendo a posição x e y da cada tile.
Os Tiles
Usarei tiles de 32x32px de tamanho neste tutorial. Portanto, o arquivo Bitmap deve ser múltiplo de 32.
Poderíamos carregar os tiles manualmente, mas um processo automatizado é sempre melhor, então,
criaremos uma função para carregar os tiles a partir de um arquivo Bitmap.
A função irá retornar um vetor de SPRITE.
CÓDIGO...
int x;
int y;
int i = 0;
i++;
}
}
destroy_bitmap(tmp);
return spr;
}
FIM DE CÓDIGO...
Explicando
No código acima, criamos uma rotina de carga de tiles. Um arquivo Bitmap é requerido.
Para obtermos o número de tiles na horizontal e na vertical, basta pegar a altura e largura do Bitmap e dividir
pelo tamanho do tile.
int x;
int y;
int i = 0;
i++;
}
}
Primeiro, declaramos uma variável i, que armazenará o índice no vetor de SPRITE (nosso vetor de SPRITE é
linear).
Os dois laços de repetição FOR são usados para percorrer o bitmap "tmp" de cima para baixo e da esquerda
para a direita.
Para cada vez que o laço y for executado, o laço x será executado N vezes.
Na prática ficaria:
+-------+-------+-------+
| | | |
| 1 | 2 | 3 |
+-------+-------+-------+
+-------+-------+-------+
| | | |
| 1 | 2 | 3 |
+-------+-------+-------+
| | | |
| 4 | 5 | 6 |
+-------+-------+-------+
| | | |
| 7 | 8 | 9 |
+-------+-------+-------+
Os tiles ficariam organizados nessa ordem no vetor. Para finalizar, criamos realmente o BITMAP da estrutura
SPRITE e "blitamos" o tile nele:
O Mapa
Normalmente, tudo em um jogo é colocado dentro de arquivos, no caso de mapas, é mais simples e você
ainda tem a possibilidade de criar outros mapas para seu jogo. Pode-se usar programas como o Mappy ou Tile
Studio, mas a verdade é que VOCÊ deve fazer seu próprio editor de mapas. O mapa pode ser armazenado em
arquivos texto ou binário.
Para usarmos arquivos texto, precisaríamos criar um ANALISADOR LÉXICO para ler os tokens. Escrever um
analisador léxico (descente) não é fácil, e outra coisa, o mapa ficaria exposto (com qualquer editor de texto
poderíamos editar o mapa). Com arquivos binários, é mais simples e para alterar o mapa será necessário um
editor hexadecimal.
Usaremos o Mappy para criar os mapas, mas não usaremos sua API para imprimi-lo na tela. Baixe o Mappy no
endereço:
http://www.tilemap.co.uk/mappy.php
Basta descompactá-lo e executar normalmente. Abra-o e uma tela como essa será apresentada:
Para criar um mapa, basta ir ao menu "File / New Map". Altere o tamanho do tile se quiser, mas a
configuração padrão é a de tiles de 32x32. Mais abaixo, coloque o tamanho na horizontal (tiles wide) e na
vertical (tiles high), lembrando que isso significa o tamanho da tela em tiles, não em pixels. Crie um mapa
com tiles de 32x32 e a tela com 40 tiles na horizontal e 15 na vertical.
Depois de criar o mapa, é só importar o tiles. Baixe os seguintes tiles (clique em cima da imagem para fazer o
download):
Agora, para importar, vá no menu "File / Import", e escolha um arquivo .bmp contendo os tiles que você
baixou ou os de sua escolha. Selecione o tile na janela à direita e depois pinte o mapa. Faça um mapa
parecido com este:
Depois que o mapa estiver pronto, precisaremos gerar um arquivo.
Como não usaremos a API do Mappy, precisaremos exportar para um outro formato de arquivo que seja fácil
de abrirmos.
Nela diz se você quer ajustar os valores dos tiles (pode-se ajustar tanto para cima quanto para baixo).
O valor "-1" é o padrão, mas devemos alterar para "0" pelo seguinte fato:
os tiles vazios são representados pelo número "0", e o nosso primeiro tile é representado pelo número "1", se
colocarmos o valor de ajuste como "-1", nosso primeiro tile será "0", ou seja, ele será um tile vazio!
Ao final, um arquivo .map será criado. Segue-se a estrutura desse tipo de arquivo:
+----------------------+
| Cabeçalho |
+----------------------+
| Dados sobre a fase |
| |
| |
| |
+----------------------+
As primeiras informações no arquivo referem-se ao tamanho do mapa (em tiles) na horizontal e na vertical.
Usaremos a estrutura MAPINFO para obter essas informações para depois ler o mapa. Segue a rotina de carga
de mapa:
CÓDIGO...
if (!file)
return NULL;
MAPINFO info;
map->w = info.w;
map->h = info.h;
int x;
int y;
int value;
fclose(file);
return map;
}
FIM DE CÓDIGO...
Explicando
No código acima, um arquivo de mapa é requerido e o vetor de SPRITE contendo os tiles.
Primeiramente, pegamos as informações referentes ao mapa (altura e largura em tiles). A partir daí, é só
carregar o mapa. A leitura do mapa é feita sequencialmente, quem define a posição de cada tile são os laços
de repetição FOR.
Lemos um inteiro de cada vez e verificamos se o seu valor não é 0. Se for 0, o valor NULL é atribuído, senão,
atribua o ponteiro para o vetor de SPRITE (lembrando que na rotina de carga de tiles, os SPRITEs são
carregados linearmente, começando pelo índice 0, que é o primeiro tile).
Obs.: No Mappy, o primeiro tile representa o índice 1, por isso a subtração por 1.
Imprimindo o mapa
A rotina de impressão de mapa deste tutorial difere um pouco do tutorial 26. Se você reparar bem, o código
mostrado até agora parece ser uma extensão do allegro. Eis a rotina de impressão de mapa:
CÓDIGO...
int x;
int y;
FIM DE CÓDIGO...
Explicando
A rotina acima se propõe a imprimir um MAPA no "BITMAP bitmap". As posições x e y são também requeridas.
Primeiro, pegamos as posições iniciais x e y de pintura da tela. Como dito anteriormente, a tela fica sendo
dividida em tiles, então, devemos pegar a posição do tile e não a posição na tela, portanto, devemos dividir as
posições x e y pelo tamanho do tile que estamos usando (32x32).
Se xpos for 16, dividido por 32, fica sendo 0,5. Arredondando dá 0. Então essa é a posição x inicial. O mesmo
para ypos (abs() foi usando pois não adianta imprimir antes da posição 0,0 da tela). os dois IF's verificam se
as posições xpos e ypos são maiores que 0, então, a posição inicial x e y ficam sendo 0.
Já encontramos as posições iniciais x e y, agora falta encontrar as posições finais. As posições finais são fáceis
de encontrar: basta pegar o tamanho da tela em tiles (tanto na horizontal quanto na vertical) e somar com o
x e y inicial. O "+ 1" é para que a rotina de impressão imprima até para fora da tela (somente 1 tile a mais).
Sem isso, os tiles não serão impressos até que preencham a tela toda.
Sabendo as posições iniciais e finais, basta imprimir na tela. Antes de imprimir, verificamos se o SPRITE é
nulo (NULL), e se não for, imprimimos ele na tela. Encontramos a posição correta de cada tile multiplicando
pelo tamanho do mesmo, ou seja:
Acrecentamos também xpos e ypos à posição do tile, para não dar um efeito de MOSAICO na tela.
O Código Completo
Para executar o código abaixo, baixe o seguinte arquivo : mapa.map
CÓDIGO...
#include <allegro.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define TILEWH 32
#define SPEED 1
typedef struct
{
BITMAP * bitmap;
} SPRITE;
typedef struct
{
SPRITE * sprite;
} TILE;
typedef struct
{
TILE ** tiles;
int w;
int h;
} MAP;
typedef struct
{
int w;
int h;
} MAPINFO;
BITMAP * buffer;
install_keyboard();
set_color_depth(16);
set_gfx_mode(GFX_AUTODETECT_WINDOWED, 640, 480, 0, 0);
if (!spr)
{
allegro_message("Falha ao carregar SPRITES!");
return 1;
}
if (!map)
{
allegro_message("Falha ao carregar MAPA!");
return 1;
}
int xmap = 0;
int ymap = 0;
int x = 0;
int y = 0;
while (!key[KEY_ESC])
{
if (key[KEY_LEFT])
xmap += SPEED;
if (key[KEY_RIGHT])
xmap -= SPEED;
clear(buffer);
destroy_bitmap(buffer);
allegro_exit();
return 0;
}
END_OF_MAIN();
if (!tmp)
return NULL;
int x;
int y;
int i = 0;
i++;
}
}
destroy_bitmap(tmp);
return spr;
}
if (!file)
return NULL;
MAPINFO info;
fread(& info, sizeof(MAPINFO), 1, file);
map->w = info.w;
map->h = info.h;
int x;
int y;
int value;
fclose(file);
return map;
}
int x;
int y;
Fim
Chegamos ao final deste EXTENSO tutorial. Se houver alguma dúvida, crítica ou sugestão sobre o tutorial é só
ir no fórum, ficarei muito grato. Num próximo tutorial, estarei escrevendo sobre colisão com o cenário (um
dos assuntos mais comentados sobre desenvolvimento de jogos).
Mais uma coisa: se você quer ver a rotina de impressão de mapa trabalhar, altere o código da seguinte
forma:
Você pode incrementar o cenário, imprimindo uma imagem de fundo atrás do mapa, como um céu ou uma
caverna.
Chegamos em um dos capítulos mais interessantes e também muito importante para nossa linha de
aprendizado.
Iremos aprender como fazer nosso personagem pular e subir morros.
Este capítulo é importante por que iremos introduzir a partir de agora um dos assuntos mais complicados na
programação de jogos: Matemática e Física.
Nosso objetivo é sempre deixar tudo muito claro, e por causa disso não iremos dar aulas de física avançada
aqui.
A idéia é deixar claro como certas coisas em jogos são feitas.
Muito jogos utilizam física, mais certamente, todos utilizam matemática. Nenhum dos dois assuntos é difícil,
basta que ele seja bem explicado e bem compreendido por quem lê.
O Algoritmo para fazer um personagem pular é bem simples e ao mesmo tempo sensacional.
Sensacional por que nele vemos como a física é utilizada claramento nos jogos.
Para fazer um personagem pular, primeiro precisamos pensar como no mundo real.
O que faz com que a gente não saia voando por ai ??
A gravidade.
E quando tentamos pular, ou correr, ou se movimentar, o que nosso corpo precisa fazer para sair do lugar?
Aplicar Força.
Desta forma, temos que a gravidade sempre vai agir sobre nosso personagem ( assim como no mundo real ),
e para que ele pule, basta aplicar uma força X que irá se perder aos poucos e fazer o objeto cair.
Veja o código:
Para entender o código, é importante que você já tenha lido os seguintes tutoriais:
23 - Conceito X e Y
24 - Movendo um objeto na tela
28 - FPS - Quadros por segundo ou Frame Rate
Algoritmo de Pulo
CÓDIGO...
#include <allegro.h>
// variáveis globais
int fps = 0;
int fps_antigo = 0;
int fps_speed = 0;
int main()
{
allegro_init();
set_color_depth(16);
install_keyboard();
set_gfx_mode(GFX_AUTODETECT_WINDOWED, 640, 480, 0, 0);
install_timer();
install_int( frame_rate, 1000 );
install_int_ex( incrementa_speed, BPS_TO_TIMER(30) );
BITMAP *buffer = NULL;
buffer = create_bitmap(800,800);
// Inicio do programa atual
// Esse será nosso chão
const int limite_cenario = 300;
// posição do personagem
// nosso personagem terá 10 pixels
int x = 10;
int y = limite_cenario - 10;
// valida se o personagem já está no pulo
bool pulou = false;
// Gravidade: quanto maior, mais rápida será a queda
const int gravidade = 2;
// Força do pulo
int forca = 0;
// Laço principal
while( !key[KEY_ESC] )
{
while ( ::fps_speed > 0 )
{
clear( buffer );
// Caso o personagem nao esteja pulando
// e apertou a tecla espaço
if ( key[KEY_SPACE] && pulou == false )
{
pulou = true;
// força do pulo
forca = 30;
}
// movimentação do personagem
if ( key[KEY_RIGHT] )
{
x+=5;
}
// movimentação do personagem
if ( key[KEY_LEFT] )
{
x-=5;
}
// Isto vai fazer com que o personagem vá voltado para o chão
forca = forca - gravidade;
y = y - forca;
// Esse if vai barrar nosso personagem de passar pelo chão ( limite )
if (y > limite_cenario-10)
{
y = limite_cenario-10;
pulou = false;
forca = 0;
}
textprintf_ex( buffer, font, 10, 10, makecol(255,0,0), -1, "FPS: %d",
::fps_antigo );
textprintf_ex( buffer, font, 10, 30, makecol(255,0,0), -1, "Forca: %d", forca );
rectfill( buffer, x, y, x+10, y+10, makecol(255,255,0) );
rectfill( buffer, 0, 300, SCREEN_W, 310, makecol(255,0,0) );
::fps_speed--;
::fps++;
}
blit(buffer, screen, 0,0,0,0, SCREEN_W, SCREEN_H);
vsync();
}
destroy_bitmap( buffer );
allegro_exit();
return 0;
}
END_OF_MAIN();
void frame_rate()
{
::fps_antigo = ::fps;
::fps = 0;
}
void incrementa_speed()
{
::fps_speed++;
}
FIM DE CÓDIGO...
Observe que, quanto mais força você der para o personagem, mais alto ele irá pular.
Quanto mais gravidade você colocar, menor será o tempo de pulo.
O código é alto explicativo, a gravidade sempre faz com que o personagem vai para baixo ( ela exerce na
força ).
Quando o personagem quer pular, adicionamos força, o que faz com que o personagem vá para cima.
Subindo Morros
Nós temos que responder cada uma dessas perguntas para resolver o problema.
Para isto precisamos fazer um jogo usando objetos para o chão que irão colidir com o personagem e mante-lo
na posição acima.
Este gráfico representa um plano cartesiano, nós temos um morro ( ou subida ) desenhada nele.
Nosso morro começa no ponto X( 0 ) e vai até o ponto X( 12 ).
Nosso morro também começa no ponto Y( 12 ) e vai até o ponto Y( 0 ).
Observe que o plano está como se fosse nosso monitor, quando maior o Y, mais para baixo.
( Nova posição de Y ) = (Tamanho total do morro ( no caso 12, o ponto incial Y ) ) - ( ponto atual do
personagem, no caso X(8) )
Observe que essa formula só funciona com morros de pontos exatos, no caso, de 12 para 12, de 50 para 50
ou de 45 para 45.
Agora, para escrever nosso programa, pasta posicionar nosso y descontando o chão e o tamanho do
personagem.
Veja o exemplo:
CÓDIGO...
#ifndef CHAO_H
#define CHAO_H
// Arquivo: chao.h
// Data: 29/08/2007
class Chao {
public:
Chao();
~Chao();
// verifica se colide com as coordenadas passadas
bool colide( int, int, int );
void setx( int ); // seta a posicao x
void sety( int ); // seta a posicao y
void set_tamanho( int ); // seta o tamanho do objeto
int getx(); // retorna posicao x
int gety(); // retorno posicao y
int get_tamanho(); // retorna tamanho do objeto
private:
int x;
int y;
int tamanho;
};
#endif
FIM DE CÓDIGO...
CÓDIGO...
#include "chao.h"
// Arquivo: chao.cpp
// Data: 29/08/2007
Chao::Chao()
{
setx(0);
sety(0);
set_tamanho(0);
}
Chao::~Chao()
{
setx(0);
sety(0);
set_tamanho(0);
}
// seta a posicao x
void Chao::setx( int vx )
{
this->x = vx;
}
// seta a posicao y
void Chao::sety( int vy )
{
this->y = vy;
}
// retorna posicao x
int Chao::getx()
{
return this->x;
}
// retorno posicao y
int Chao::gety()
{
return this->y;
}
FIM DE CÓDIGO...
CÓDIGO...
#include <allegro.h>
#include "chao.h"
// variáveis globais
int fps = 0;
int fps_antigo = 0;
int fps_speed = 0;
int main()
{
allegro_init();
set_color_depth(16);
install_keyboard();
set_gfx_mode(GFX_AUTODETECT_WINDOWED, 640, 480, 0, 0);
install_timer();
install_int( frame_rate, 1000 );
install_int_ex( incrementa_speed, BPS_TO_TIMER(30) );
BITMAP *buffer = NULL;
buffer = create_bitmap(800,800);
// Inicio do programa atual
// Esse será nosso chão
const int limite_cenario = 300;
// posição do personagem
// nosso personagem terá 10 pixels
const int tamanho_personagem = 10;
int x = 10;
int y = limite_cenario - 100;
// novo y para quando estiver no morro
int novo_y = 0;
// valida se o personagem já está no pulo
bool pulou = false;
// Gravidade: quanto maior, mais rápida será a queda
const int gravidade = 2;
// Força do pulo
int forca = 0;
const int forca_maxima = 30;
// Variáveis do chão
const int total_chao1 = 50;
const int total_chao2 = 5;
const int tamanho_chao = 30;
Chao *chao[total_chao1];
Chao *chao2[total_chao2];
// Criando as variaveis do morro de SUBIDA
const int morro1_altura = 100;
const int morro1_largura = 100;
const int morro_x = 200;
const int morro_x2 = 300;
const int morro_y = limite_cenario - tamanho_chao;
const int morro_y2 = ( limite_cenario - tamanho_chao ) - morro1_altura;
// criando um morro de descida
const int morro2_altura = 100;
const int morro2_largura = 100;
const int morro2_x = 550;
const int morro2_x2 = 450;
const int morro2_y = limite_cenario - tamanho_chao;
const int morro2_y2 = ( limite_cenario- tamanho_chao ) - morro2_altura;
// inicia o chao
int i = 0;
for ( i=0; i< total_chao1; i++ )
{
chao[ i ] = new Chao();
chao[i]->sety( limite_cenario - tamanho_chao );
chao[i]->setx( (i * tamanho_chao) + tamanho_chao );
chao[i]->set_tamanho( tamanho_chao );
}
// criando o chão em cima do morro
for ( i=0; i< total_chao2; i++ )
{
chao2[ i ] = new Chao();
chao2[i]->sety( (limite_cenario - tamanho_chao) - morro2_altura );
chao2[i]->setx( (i * tamanho_chao) + tamanho_chao + 270 );
chao2[i]->set_tamanho( tamanho_chao );
}
// Laço principal
while( !key[KEY_ESC] )
{
while ( ::fps_speed > 0 )
{
clear( buffer );
// Caso o personagem nao esteja pulando
// e apertou a tecla espaço
if ( key[KEY_SPACE] && pulou == false )
{
pulou = true;
// força do pulo inicial
forca = forca_maxima;
}
// movimentação do personagem
if ( key[KEY_RIGHT] )
{
x+=5;
}
// movimentação do personagem
if ( key[KEY_LEFT] )
{
x-=5;
}
textprintf_ex( buffer, font, 10, 10, makecol(255,0,0), -1, "FPS: %d",
::fps_antigo );
textprintf_ex( buffer, font, 10, 30, makecol(255,0,0), -1, "Forca: %d", forca );
textprintf_ex( buffer, font, 10, 40, makecol(255,0,0), -1, "x: %d y: %d", x, y );
// Isto vai fazer com que o personagem vá voltado para o chão
forca = forca - gravidade;
y = y - forca;
// verifica se está no morro - SUBIDA
if ( x+tamanho_personagem >= morro_x && x+tamanho_personagem <= morro_x2 )
{
novo_y = ( morro_y2 + (morro2_altura - tamanho_chao) ) - ( ( x-tamanho_personagem
) - morro_x);
if ( y > novo_y )
{
y = novo_y;
pulou = false;
forca = 0;
}
}
// verifica se está no morro - DESCIDA
if ( x >= morro2_x2 && x <= morro2_x )
{
novo_y = (morro2_y2) + ( (x-tamanho_personagem) - morro2_x2 );
if ( y > novo_y )
{
y = novo_y;
pulou = false;
forca = 0;
}
}