Você está na página 1de 88

Marvson Allan

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.

Todas as linguagens usam IF, FOR, arrays e ponteiros.. principalmente funções.


Por isso não interessa muito em qual linguagem você está programando, e sim, o que você sabe fazer com
ela.

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.

VOCÊ PRECISA SABER...


- A organização de código é necessária para que você possa entender o que escreveu.

- A organização de código é obrigatória para que você possa reutilizar seu código
fonte.

Para organizar o código você precisa seguir regras fundamentais e obrigatórias.

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 totalmente errado que não deve ser usado:


int a, b, c, valor, numero, xab, contador, teste, coisa;
int a = 10, b, c, d=15, valor, coisa, variavel21, contador=0; 

int numero_jogadores, posicao_peca_x, vari;

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.

3 - Sempre inicie as variáveis.

4 - Sempre deixe espaços ao iniciar as variáveis

Exemplo totalmente errado que não deve ser usado:


int a=0;b=30;

Exemplo correto:

int a = 0;
int b = 30;

5 - Ao iniciar as variáveis, mantenha uma ordem nas atribuições;

Exemplo totalmente errado que não deve ser usado:


int aviao = 0;
int posicao_x = 0;
int posi = 0;
Exemplo correto:

int aviao      = 0;


int posicao_x  = 0;
int posi       = 0;

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 totalmente errado que não deve ser usado:


int aviao = 0;
int b=0; 
int posicao_x = 0;
int x=0; 
int posi = 0;

Exemplo correto:

int aviao      = 0;


int posicao_x  = 0;
int posi       = 0;

int b = 0;
int a = 0;

6 - Tente sempre escrever o nome da variável por exemplo, usando o separador _.


Se não for possível, digite um comentário sobre o que a variável faz;

Exemplo totalmente errado que não deve ser usado:


int pos_per;
int px;
int py;

Exemplo correto:

int posicao_personagem;
int personagem_px; // posição x do personagem; 
int personagem_py; // posicao y do personagem;

VOCÊ PRECISA SABER...


Abreviar nome das variáveis é preguiça.. se você tem preguiça ao programar, então
desista de programar jogos.

7 - Mantenha distancia em números na hora das operações matemáticas.


Utilize mais variáveis para ajudar nas operações, de forma que a operação fique fácil de ler 

Exemplo totalmente errado que não deve ser usado:


int num=0; 
num = 10*(4/2*(3-1));

Exemplo correto:
int num      = 0;
int auxiliar = 0;

auxiliar = ( ( 4 / 2 ) * ( 3- 1 ) );
num      = 10 * auxiliar;

8 - IMPORTANTE: Indentar sempre os códigos com extruturas de controle.

Exemplo totalmente errado que não deve ser usado:


if(a==1){ ....... }

while (ab!=true) { 
....

for (i=0;i<10;i++) {
....
}

Todas as 3 formas acima estão erradas

Exemplo correto:

// Observe os espaços no parenteses, e use sempre a quebra de linha com a identação 


if ( a == 1 ) 
{
      ........
}

while ( ab != true )
{
       // nunca abra as chaves na linha de cima 
       ......
}

// observe os espaços entre os parametros do for 


for ( i=0; i<10; i++ )
{
      .....
}

9 - Cuidar da identação quando o código for muito grande

Exemplo totalmente errado que não deve ser usado:  


( exemplo com código maior ilustrativo, você nao precisa entender o que está escrito )

bool selecao_direta( fila *pprimeiro ) {


fila *proximo,*atual,*menor;
int chave;
while (pprimeiro != NULL) {
   proximo=(*pprimeiro).proximo;
   atual=pprimeiro;
   menor=atual;

  while ( proximo != NULL )


  {
     if ( (*proximo).numero<(*menor).numero)      
     menor=proximo;
      
     proximo=proximo->proximo;
  }
  
  chave = atual->numero;
  atual->numero = menor->numero;
  menor->numero = chave; 
  pprimeiro = pprimeiro->proximo;
}
return true;
}

Exemplo correto:

bool selecao_direta( fila *pprimeiro )


{

  fila *proximo, *atual, *menor;

  int chave;

  while ( pprimeiro != NULL )


  {
      proximo  = (*pprimeiro).proximo;
      atual    = pprimeiro;
      menor    = atual;

      while ( proximo != NULL )


      {
        
          // nesse caso é muito importante usar as chaves " { } " e identar o if 
          if ( (*proximo).numero < (*menor).numero )
          {
              menor = proximo;
          } 

         proximo = proximo->proximo;


      }

      chave = atual->numero;


      atual->numero = menor->numero;
      menor->numero = chave;

      pprimeiro = pprimeiro->proximo;
  }

  return true;
}

10 - Não utilizar o comando Do While


O comando do while deixa as linguas de programação confusas, por isso, não é interessante utilizar

Qualquer código em qualquer linguagem pode ser escrito seguido essas observações importantes.

21 - Adicional 1 - Organização de Código 

Este capítulo é para quem já possuí experiência em programação.


Recentemente fora levantado um tópico no nosso forum sobre a organização do código.
No capítulo 21 nós aprendemos a melhor forma de se programar um jogo, organizando o código como
programação e não como um projeto.

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.

Eu vou abordar aqui a criação de um jogo no estilo Arkanoid.

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 especificamos detalhes do jogo, sua descrição e regras.


A descrição inicia da idéia não deveria ser mudada durante a execução do projeto.

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 historia ?

- 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 ?

- Quais serão as telas do jogo ? - Descrever as telas do jogo..

- Descrição das regras para o Jogador - Descrição das regras para o Jogador para cada tela ou cenário

- Descrição das regras do jogo para cada tela ou cenario 


- Descrição das regras para upgrades, inimigos, jogabilidade

 
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.

3 - Identificando atores e tarefas


Esta aqui é uma das partes mais importantes ao se fazer um projeto pois ela define como será feita a
programação.
Você deve identificar atores de ações nas regras e idéias descritas acima.

Nada muito técnico ainda.

Exemplo:

- Bola

A bola pode se mover pelo cenario


A bola pode colidir com uma peça e destrui-la
A bola pode colidir com a raquete do jogador, que vai rebatela para outra direção

- Raquete do jogador

A raquete do jogador pode se mover apenas na horizontal para os lados


A raquete do jogador pode se mover mais rapido ou mais devagar
A raquete do jogador pode ter tamanhos diferentes
A raquete do jogador pode rebater a bola

- Cenario

O cenario pode ter tamanhos diferentes


O cenario pode ter várias peças
O cenario pode ter uma imagem de fundo
O cenario pode ter apenas um jogador
O cenario pode ter várias bolas
O cenario deve controlar para que as bolas não saiam de seus limites

Eu identifiquei alguns atores e tarefas deles acima.


Nem sempre se consegue identificar todos os atores, por isso talvez você vá precisar alterar este parte mais
tarde.

4 - Criando Classes e métodos


Este método de desenvolvimento não é igual ao UML, Por favor, não confunda! 
Os atores no UML são usuários e/ou outros meios externos que desenvolvem algum papel em relação ao
sistema.

Você pode usar a UML para ajudar no seu projeto também. 


Além de definir os atores e possíveis tarefas, você vai definir diagramas que irão ajudar você a identificar
quem serão as classes.

Estude UML também! 


http://pt.wikipedia.org/wiki/UML

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.

Agora é hora de descidir a linguagem de programação e como será programado o jogo.


Agora também é hora de decidir quais classes se comunicarão com outras classes.

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:

   // contadores auxiliares


   int i;
   int c;

public:

   int posicao_x;
   int posicao_y;

   int tamanho; 
   int cor;

   // controlam a velocidade da bola em x e em y


   // desta forma ela pode ir para lugares diferentes na tela
   int velocidade_x; 
   int velocidade_y;
   // informam a direção de movimentação da bola
   // pelo cenario

   // para esquerda bola->direcao_x = -1;


   // para cima bola->direcao_y = -1; 
   int direcao_x;
   int direcao_y;

   Bolas(); // função construtor


   ~Bolas(); // função destrutor

   void iniciar( int pos_x, int pos_y ); // inicia objeto


   void destrutor(); // reinicia variáveis do objeto

   void mostrar( BITMAP * fundo ); // imprime a bola no vetor fundo


   void refresh(); // atualiza posição da bola

   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.

Então vou mostrar como isto é feito na prática! 

- 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

Eu criei um jogo no estilo do Arkanoid para você baixar.


O código fonte está incluído e você poderá alterar como quiser.

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.

Clique aqui para fazer o download do código fonte

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. 

É bom sempre fazer alguns rascunhos antes de organizar as idéias oficiais. 


Dessa forma teremos uma visão clara do que iremos precisar durante o desenvolvimento.

Nesse caso, as características desse jogo devem ser as seguintes:

1. Teremos um baralho de 32 cartas. 16 pares de desenhos.

2. A pontuação base inicial é 100.

3. O Jogador inicia com 0 pontos.

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.

O jogo terá 3 níveis de dificuldade. 


Em cada nível teremos as seguintes alterações:

 
Nível 1:

1. Pontuação base deve ser 100.


2. Tempo para mostrar as cartas 15 segundos.

Nível 2:

1. Pontuação base deve ser 150.


2. Tempo para mostrar as cartas 10 segundos.

Nível 3:

1. Pontuação base deve ser 200.


2. Tempo para mostrar as cartas 5 segundos.

o A cada troca de nível devem ser acrescentadas mais duas chances ao jogador.

o Cada nível de dificuldade terá um fundo e os desenhos das cartas alterados.

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.

Clique aqui para fazer o download do código fonte

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. 

Algumas características chaves incluem:

Facilidade de utilização - Vem com documentação e exemplos detalhados.


Portátil - Se a funcionalidade interna não for bastante, há muitos add-ons disponíveis em todas plataformas -
sem mudar uma única linha do código, você pode criar versões para Windows, OS X, Linux, DOS e outros!
Trabalhe com muitos compiladores, incluindo DJGPP e VC++ Fonte aberta - qualquer um pode contribuir,
inclusive você! 

Livre - não lhe custará uma moeda de dez centavos, e não há nenhuma limitação em seu uso.

Para mais informações referente a biblioteca allegro visite o site: 


http://www.allegro.cc

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.

VOCÊ PRECISA SABER...


Em outras palavras Allegro é uma biblioteca desenvolvida para ajudar os
programadores iniciantes no desenvolvimento de jogos sem que seja necessário ler
toneladas de livros referentes o assunto.

Basicamente, para que você possa desenvolver um jogo simples e divertido é


necessário ter um bom conhecimento de qualquer linguagem de programação,
criatividade e um bom conhecimento em bibliotecas gráficas como Allegro, Open GL,
Direct x entre outros.

ENTENDO O LAÇO PRINCIPAL DO JOGO


Logo acima vimos que a biblioteca Allegro parece ser uma ferramenta com várias funcionalidades no processo
de desenvolvimento de um jogo. Só o fato de não precisarmos ler toneladas de livros sobre matemática, física
e APIs já poupa um tempo muito precioso. 

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

//Exemplo de código em Allegro


#include <allegro.h>

int main()
{
   allegro_init();
   install_keyboard();
   set_color_depth(16);
   set_gfx_mode( GFX_AUTODETECT_WINDOWED, 640, 480, 0, 0 );

   // Laço principal


   while( !key[KEY_ESC] )
   {
      //Código
   }
   allegro_exit();
   return 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. 

VOCÊ PRECISA SABER...


Esse comando indica ao compilador onde ele deve encontrar as funções do
allegro.

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. 

Mais adiante vamos ver um topico de como manipular as interrupções do teclado. 

void set_color_depth(int depth);


Essa função seta a profundidade das cores de todas as imagens que você vai carregar durante o
projeto. O allegro suporta:

8 bits 256 cores


16 bits 65.536 mil
24 bits 16,7 milhões
32 bits 16,7 milhões

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.

int set_gfx_mode(int card, int w, int h, int v_w, int v_h);


O comando acima é responsável por detectar a placa de vídeo, setar o tamanho da tela em pixel e
o posicionamento inicial x,y. 

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:

set_gfx_mode(GFX_AUTODETECT_WINDOWED,640, 480, 0, 0); 

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). 

O Allegro esperará automaticamente um retrace antes de alterar o palette ou de fazer algum


desdobramento da ferragem, embora, assim que você não necessita normalmente se incomodar
com esta função.

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. 

Dessa forma ele executa o código do jogo em velocidade de processamento. 


Mais adiante veremos tópicos relacionados ao timer para controlar o loop do jogo.

Caro leitor, chegamos ao fim do entendimento do laço principal de um jogo. 

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

Antes de ler este tutorial, leve em consideração de que você já leu:

- 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" ).

int numero_natural      =  6;


int array_numeros[3]  =  { 5, 10, 15 };

 
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, - ...

Podemos representa-los em nossa programação atravez do tipo inteiro tbm.

int numero_negativo     =  -6;


int array_negativos[3]   = { -5, -10, -15 };

Os números inteiros, são somente constituídos dos números naturais {0, 1, 2, ...} e dos seus opostos {-1,
-2, -3, ...}.

Os números racionais e frações


Em alguma determinada época, também fomos obrigados a dividir comida com os companheiros das
cavernas, e foi dai que apareceram as nossas amigas frações. Onde um valor é divido por outro.

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.

doube valor = 1.666;

Um clássico exemplo de número racional é o PI, muito utilizado na programação de jogos.


No jogo Worms ele é utilizado para saber aonde um projetil vai cair dependendo de um angulo e força de
lançamento.

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.

Resumindo, os pontos X e Y são coordenadas do plano cartesiano, e é com essas


coordenadas que iremos escrever gráficos na tela do computador ou do video-game.

Todo ponto é sempre representado por ( X, Y ), exemplo: ( 2, 3 )

Exemplos de vários pontos X,Y em um plano cartesiano que poderia ser a tela de um jogo:
 

Agora vamos brincar de escrever na tela usando os pontos 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;

   textout_ex(screen, font, "Mensagem em 0,0 da tela", x, y, makecol(255, 0, 0), -1); 

   // Laço principal


    while( !key[KEY_ESC] )
   {

   }

   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..

VOCÊ PRECISA SABER...


Executando o exemplo acima, percebemos que no allegro, a coordenada
inicial do monitor é bem em cima da tela na esquerda. 
Isto é muito importante, no open gl por exemplo, a coordenada inicial é
bem no meio da tela.

O que acontece se almentar a variável X ou Y ? Veja o exemplo:

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();

24 - Movendo um objeto na tela


Neste tutorial iremos ver como mover um objeto na tela usando as coordenadas x e y.

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...

// Movendo um objeto nas coordenadas x e y


#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();

25 - Double Buffer - Técnica de desenhar na tela


Quando começamos a desenhar vários objetos primitivos na tela, e movemos ele atraves do teclado ( na
execução do jogo ) ou mesmo animamos figuras .bmp, muitas vezes percebemos que a imagem do jogo fica
piscando.

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 )

Exemplo sem Double Buffer:

Possivelmente o quadrado vermelho e os textos ficarão piscando em sua 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++;
      }
      
      // 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...

Exemplo com Double Buffer

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.

VOCÊ PRECISA SABER...


É importante que você tenha lido nosso artigo sobre imagens antes de continuar. 
Caso contrário você pode se ter dificuldades no entendimento de algumas funções de
manipulação de imagens que iremos utilizar ao longo do código.

- Desenhando imagens BMP na tela 

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

Converta as imagens de .jpg para .bmp atravez de algum programa de editoração

Com base na ilustração acima devemos esclarecer os seguintes itens:

 O tamanho do cenário é 640x480. Totalizando 307.200 pixels.


 Todas as imagens foram salvas com 24bits de profundidade de cores. No entanto configuramos o
compilador para enxergar até 32bits.
 O tamanho de cada Tile é 32x32 pixels.

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”. 

Se você estiver fazendo a textura de uma grama ou um chão,por exemplo, quando


você terminar poderá tornar essa textura encaixavel em todos os sentidos. Dessa
forma você não terá problema na hora de visualizar os tiles um do lado do outro.

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);

   // COMENTADO NO FINAL - 1 


   int i=0;
   int x=0;
   int y=0;

   const int xTile = 32; // Largura do Tile


   const int yTile = 32; // Altura do Tile

     // COMENTADO NO FINAL - 2 

   // Posições dos tiles no mapa bidimencional.


   int Mapa[15][20] = {
   3,3,3,3,4,5,5,5,5,6,2,2,1,10,10,8,9,9,9,9,
   3,3,3,3,4,5,5,5,5,6,2,2,1,7,7,7,9,9,9,9,
   3,3,3,3,4,5,5,5,5,6,2,2,1,7,7,7,9,9,9,9,
   3,3,3,3,4,5,5,5,5,6,2,2,1,7,7,8,9,9,9,9,
   3,3,3,3,4,5,5,5,5,6,2,2,1,7,7,8,8,8,8,8,
   3,3,3,3,4,5,5,5,5,6,2,2,1,7,7,7,7,7,7,7,
   3,3,3,3,4,5,5,5,5,6,2,2,1,7,7,7,7,7,7,7,
   3,3,3,3,4,5,5,5,5,6,2,2,1,7,0,10,7,7,10,0,
   3,3,3,3,4,5,5,5,5,6,2,2,1,7,10,0,7,7,10,0,
   3,3,3,3,3,4,5,5,5,6,2,2,1,7,0,10,7,7,10,0,
   3,3,3,3,3,3,4,5,5,6,2,2,1,7,10,0,7,7,10,0,
   3,3,3,3,3,3,4,5,5,6,2,2,1,7,0,10,7,7,10,0,
   3,3,3,3,3,3,4,5,5,6,2,2,1,7,10,0,7,7,10,0,
   3,3,3,3,3,3,4,5,5,6,2,2,1,7,0,10,7,7,10,0,
   3,3,3,3,3,3,4,5,5,6,2,2,1,7,10,0,7,7,10,0};

     // COMENTADO NO FINAL - 3 


   
   // Carregando o ponteiro com a imagem que possui todos os tiles
   BITMAP *bmpTiles = NULL;
   bmpTiles = create_bitmap(224,64);
   bmpTiles = load_bitmap("tiles.bmp",NULL);
   
   // Array que guarda as texturas separadas
   BITMAP *bmpTexturas[11];
   
   // Definindo o tamanho 32x32 para cada tile
   for (i=0;i<11;i++)
   {
      bmpTexturas[i] = create_bitmap(xTile,yTile);
   }

   // A rotina abaixo separa cada tile e armazena no array.


   blit(bmpTiles, bmpTexturas[0],  64, 0, 0, 0, xTile, yTile );   // Grama normal
   blit(bmpTiles, bmpTexturas[1],  32, 0, 0, 0, xTile, yTile );   // Grama com areia
   blit(bmpTiles, bmpTexturas[2],  0, 0, 0, 0, xTile, yTile );     // Areia
   blit(bmpTiles, bmpTexturas[3],  96, 0, 0, 0, xTile, yTile );   // Agua funda   
   blit(bmpTiles, bmpTexturas[4],  128, 0, 0, 0, xTile, yTile ); // Agua funda com agua clara
   blit(bmpTiles, bmpTexturas[5],  160, 0, 0, 0, xTile, yTile ); // Agua clara
   blit(bmpTiles, bmpTexturas[6],  192, 0, 0, 0, xTile, yTile ); // Agua clara com areia
   blit(bmpTiles, bmpTexturas[7],  0, 32, 0, 0, xTile, yTile );  // Pedras
   blit(bmpTiles, bmpTexturas[8],  32, 32, 0, 0, xTile, yTile ); // Parede
   blit(bmpTiles, bmpTexturas[9],  64, 32, 0, 0, xTile, yTile ); // Piso
   blit(bmpTiles, bmpTexturas[10], 96, 32, 0, 0, xTile, yTile );  // Flores

     // COMENTADO NO FINAL - 4 

   //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 );
      }
   }

     // COMENTADO NO FINAL - 5 

   //Escreve mercado na parede.


   textout_ex(screen, font, "MERCADO", 540, 135, makecol(255,255,0), -1);

   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

Passos para a preparação dos tiles com base nesse exemplo:

 Definição do tamanho da tela de 640x480 pixels.


 Profundidade de cores de 32 bits.
 Criação dos tiles no tamanho de 32x32 pixels – 24bits. Dessa forma temos espaço para 20 tiles na
horizontal e 15 tiles na vertical.
 Organizar todos os tiles em apenas um arquivo de imagem com fundo lilás conforme ilustrado acima.
Dessa forma podemos extrair todas as texturas através da função blit.

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

Bom, vamos pular as configurações da tela e ver as 3 variáveis criadas. 

A variávei “i” é apenas um contador auxiliar.


A variável ‘x” e “y” vão ser utilizadas para percorrer o array bidimensional do mapa. 

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.

Primeiro, vamos fazer a colisão de um objeto com a tela.


Sabemos que no Allegro, a posição 0,0 é o limite esquerdo e do topo.

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. 

- A posição Y do objeto não pode ser menor que zero 


- A posição de Y + o tamanho do objeto ( em Y ) não pode ser maior que o limite da
tela. Se for maior, o objeto vai sair da tela.

Tome cuidado quando o objeto tiver um tamanho X diferente do tamanho Y

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:

Imagine que as linhas pretas são os limites da tela.


Temos que, o x de nosso quadrado nunca pode ser menor que o x limite da tela.
Para resolver isso fizemos:

      if ( x < 0 )
      {
         x = 0;
      }

O mesmo para o y.
Agora, para testar os limites finais da tela:

Imagine que em x=100 o quadrado já está tocando no final da tela.


Para testar isso, fizemos o seguinte:

      if ( (x+tamanho) > SCREEN_W )


      {
         x = SCREEN_W - tamanho;
      }

Se imaginarmos que o SCREEN_W seja de 200, podemos dizer que


x ( 100 ) + tamanho ( 100 ) nunca pode ser maior que o SCREEN_W.

O mesmo acontece para y.

 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.

Já temos este teste: 

 if ( (x+tamanho) >= obj_x && x <= obj_x+obj_tam )

 
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.

Neste caso, já temos este teste: 

 if ( (y+tamanho) >= obj_y && y <= obj_y+obj_tam )

 
E no último exemplo, temos a colisão em X e Y dentro dos limites nas imagem azul.
Juntando os 2 testes, temos:

     // 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);
         }
      }

27 - Detectando colisões com uma função


Usando o capítulo 27, vamos praticar detectar colisão, usando uma função e não deixando que o objeto que
está se movendo, 
consiga se mover enquanto estiver colidindo.

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>

// função que verifica colisão


bool colide( int, int, int, int, int, int );

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.

Os exercícios acima são bastante interessantes e também difíceis. 


Você pode olhar as respostas caso não conseguir fazer, mais tentar resolver os exercícios vai aumentar seu
raciocínio lógico.

Para resolver o primeiro exercício:

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

Respostas dos Exercícios


- 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.

Preste atenção na forma que a classe foi criada.


Quanto mais prática a classe ficar, melhor para seu jogo. Você poderá usar ela muito mais fácil.
Evite complicações.

CÓDIGO...
#ifndef PAREDE_H
#define PAREDE_H

// ARQUIVO: parede.h
// Data: 01/08/2007

class Parede {
public:

Parede( int, int, int, int );


   void imprimir( BITMAP * );
   bool colide( int, int, int, int );

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

// Este é o construtor do objeto


Parede::Parede( int x, int y, int tam_x, int tam_y )
{
   this->x = x;
   this->y = y;
   this->tamanho_x = tam_x;
   this->tamanho_y = tam_y;
}

// Essa função imprime o objeto na tela,


// passamos o local aonde queremos imprimir
void Parede::imprimir( BITMAP *screen )
{
   rectfill( screen, x, y, x+tamanho_x, y+tamanho_y, makecol(255, 255, 0) );
}

// 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...

Preste muita atenção a forma que as paredes foram criadas.


Também preste atenção em como fazemos a colisão, olhando um objeto de cada vez.

Será que em um jogo grande isso seria válido?? 


Imagine testar a colisão desta forma em toda uma fase do Super Mario Bros ?
Quantos testes seriam necessários?

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();

28 - FPS - Quadros por segundo, Frames per second ou Frame Rate

Todo jogo possui animação, isto é fato. 


Animações são geralmente quadros de desenhos que vão se mudando ( sobrepondo a antiga ) gerando assim
uma animação.

Este é o exemplo de uma animação e alguns de seus quadros, que juntos formam a animação.

Esta animação originalmente tem 12 quadros e ela é executada a 100 milisegundos.


Se aumentarmos a taxa de milisegundos de cada quadro em separado, a animação vai ficar ainda mais lenta. 
Se diminuirmos a taxa de 100 milisegundos de cada quadro, a animação vai ficar bem mais rápida.

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.

Isso influência não só as animações como também a jogabilidade.

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.

Em DirectX podemos fazer a mesma coisa usando a função do windows


GetTickCount(); que pega o total de ticks.
Em OpenGL podemos fazer a mesma coisa usando as funções do GLUT .

VOCÊ PRECISA SABER...


É importante que você tenha lido nosso artigo sobre como instalar timers no Allegro. 

- Controle de tempo 
CÓDIGO...
#include <allegro.h>

// Exemplo da bolinha com contador de Frames por segundos

// global
int fps = 0;
int fps_antigo = 0;

// prototipo do contador de frames


void frame_rate();

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.

Agora precisamos controlar a taxa de frames de nosso programa.


A descisão da quantidade de frames por segundo o jogo vai rodar é sua, você pode usar 30 FPS que é o limite
mínimo para que o olho detecte transação da animação, ou 60 que é o valor mostrado no manual da Allegro.
CÓDIGO...

#include <allegro.h>

// Exemplo da bolinha com limitador de FPS

// global
int fps = 0;

int fps_antigo = 0;
int fps_speed = 0;

// prototipo do contador de frames


void frame_rate();

// prototipo do contador de velocidade 


void incrementa_speed();

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

O programa acima pode parecer confuso.


Nós criamos um timer para que, a cada 60 clock tick ( usando a macro BPS_TO_TIMER ) o contador de speed
seja incrementado.

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.

A seguir iremos selecionar alguns sprites prontos de jogos e colocá-los em movimento.


Se você proferir escolher algum outro sprite segue uma seqüência de sites abaixo com uma infinidade de
sprites de jogos.

Links para Sprites

http://tsgk.captainn.net/
http://www.panelmonkey.org/category.php?id=1&theme=1
http://www.molotov.nu/?page=graphics

dragao.bmp

Especificações

Qtde de Desenhos em x[0]: 6 


Qtde de Desenhos em y[0]: 0 
Largura total: 360 pixels 
Largura individual: 60 pixels 
Altura: 55 pixels

robocop.bmp

Especificações

Qtde de Desenhos em x[0]: 6 


Qtde de Desenhos em y[0]: 0 
Largura total: 415 pixels 
Largura individual: 69 pixels 
Altura: 80 pixels

 
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);

   BITMAP *imgSprites = NULL;


   imgSprites = load_bitmap("robocop.bmp",NULL);

   BITMAP *Buffer = NULL;


   Buffer = create_bitmap(SCREEN_W,SCREEN_H);

   const int iSprite_Largura = 69;   // Largura de um sprite


   const int iSprite_Altura = 80;     // Altura de um sprite
   const int Qtde_Spritesx = 6;      // Qtde de Desenhos em x[0] - Horizontal
   const int Qtde_Spritesy = 0;      // Qtde de Desenhos em y[0] - Vertical

   const int iCiclo = 30; // Ciclo de troca de sprite

   int iPosx = 0; // Posição x do corte.


   int iPosy = 0; // Posição y do corte. Como é apenas uma fileira então y sempre vai ser 0

   int buffer_animacao = 0; // Buffer para controle do ciclo

   while (!key[KEY_ESC])
   {
      clear_bitmap( Buffer );
      textprintf_ex(Buffer, font, 0, 0, makecol(0, 255, 255),-1,"ANIMACAO SPRITES");

      // Controle para mudar o sprite 


      if(buffer_animacao == 0)
      {
            if (iPosx > 0 && iPosx == Qtde_Spritesx-1)
            {
               iPosx = 0;
            }

            if (Qtde_Spritesx > 0) 


            {
               iPosx++;
            }

            buffer_animacao = iCiclo;

      } 
      else 
      {
         buffer_animacao--;
      }

      // Corta e desenha o sprite


      masked_blit(imgSprites,Buffer,iPosx*iSprite_Largura,iPosy,SCREEN_W/2, SCREEN_H/2,iSprit
e_Largura,iSprite_Altura);

      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.

Basicamente, o código possui a informação da largura, altura e quantidade de sprites na horizontal. 


Com essas informações é possível fazer um corte nas imagens com a função blit vista em outro artigo.

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.

Trocar de direção usando Sprite

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.

Posições dos Sprites


Animação (x), Direção (y).
Sprite Sprite Sprite Sprite Sprite
  1 2 3 4 5

Direita 0,0 25,0 50,0 75,0 100,0

Esquerda 0,25 25,25 50,25 75,25 100,25

Cima 0,50 25,50 50,50 75,50 100,50

Baixo 0,75 25,75 50,75 75,75 100,75

Ao selecionar a posição 0,0 você deverá desenhar na tela o pacman virado para a direita e de boca fechada. 

Já na posição 100,0 o pacman está virado para a direita e de boca aberta. 


Para fazer a simulação de movimento e de troca de direção é necessário juntar a tabela acima com os
direcionais do teclado.

Abaixo segue as especificações dos sprites.

pacpac.bmp
Especificações

Qtde de Desenhos em x[0]: 5 


Qtde de Desenhos em y[0]: 4 
Largura total: 125 pixels 
Largura individual: 25 pixels 
Altura: 25 pixels

OBS: Salvar a imagem acima como .BMP. O allegro não aceita o tipo jpg.

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. 

O código será dividido em 3 arquivos. São eles:

 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);

   CJogador Jogador; // Instacia o Pacman

   BITMAP *Buffer = NULL;


   Buffer = create_bitmap(SCREEN_W,SCREEN_H);

   while (!key[KEY_ESC])
   {
      clear_bitmap(Buffer);
      textprintf_ex(Buffer, font, 0, 0, makecol(0, 255, 255),-1,"SPRITE EM MOVIMENTO");

      //Atualiza o Pacman na tela


      Jogador.Atualiza(Buffer);

      //Controle para Movimentação do Pacman na tela


      Jogador.Controle(25);

      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

Dentro desse arquivo é possível encontrar:

 Referência aos arquivos de recursos.


 Inicialização da biblioteca allegro.
 Referência a classe Jogador (Pacman)
 Loop principal do jogo.
 Liberação dos ponteiros.
 Encerramento do programa.

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

BITMAP *imgJogador Guarda imagem do Pacmam

int iPosx Posição x do pacman na tela

int iPosy Posição y do pacman na tela

int iSpritex Posição da animação do sprite

int iSpritey Posição da direção do sprite

int iLargura Armazena a largura de um sprite

int iAltura Armazena a altura de um sprite

Descrição
Variável

CJogador(); Construtor para iniciar as variáveis da classe.

~CJogador(); Destrutor responsável por liberar a imagem do


pacman da memória.

void Setax(int); Seta a posição x do pacman a partir de uma


velocidade passada por argumento.

void Setay(int); Seta a posição y do pacman a partir de uma


velocidade passada por argumento.

void Atualiza(BITMAP *buffer); Renderiza o pacman na tela.

void Controle(int); Controla a direção do pacman através das setas


do teclado.

CÓDIGO...

#include "cjogador.h"

// Arquivo: cjogador.cpp

static int iVelSprite = 4;

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;
}

void CJogador::Setax(int vx)


{
   this->iPosx += vx;

   if (vx<0) this->iSpritey = 25; // Vira Sprite para esquerda


   if (vx>0) this->iSpritey = 0; // Vira o Sprite para a direita
}
void CJogador::Setay(int vy)
{
   this->iPosy += vy;

   if (vy<0) this->iSpritey = 50; // Vira o Sprite para Cima


   if (vy>0) this->iSpritey = 75; // Vira o Sprite para Baixo
}

void CJogador::Atualiza(BITMAP *buffer)


{
   if (iSpritex >= 100) iSpritex = 0;

   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;
   }
}

void CJogador::Controle(int Vel)


{
   static int Buffer_Teclado =0;
   // A cada movimento reinicia o buffer do teclado.

   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.

30 - Rolagem de Cenário ( Scroll ou Scrolling )

Existem várias formas de se fazer um efeito de Scrolling.


Vamos aprender a fazer 3 tipos de Efeitos.

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;

// prototipo do contador de frames


void frame_rate();

// prototipo do contador de velocidade


void incrementa_speed();

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...

Exemplo do espaço estrelado.


O exemplo não tem muita complicação. O resultado se parece muito com os créditos de vários jogos ( Mega
Man ).

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

const int tamanho_total = 3;


const int velocidade_total = 15;

class Star {

private:

   int velocidade;
   int id;

public:

   Star( int ); // construtor


   ~Star(); // destrutor
   
   void iniciar( int ); // inicia o objeto
   void verificar(); // verifica se não chegou no final da tela
   
   int x;
   int y;
   int tamanho;

};

#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

// Exemplo de scrolling falso

// variáveis globais
int fps = 0;
int fps_antigo = 0;
int fps_speed = 0;

// prototipo do contador de frames


void frame_rate();

// prototipo do contador de velocidade


void incrementa_speed();

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...

Exemplo cenário com imagem de fundo repetida.


Este é um exemplo bastante utilizado em jogos de scroll vertical, como o Ninja Gaiden.
Faremos uma imagem de fundo passar lentamente, e uma imagem mais perto passar bem rápido, passando a
impressão de velocidade e movimentação do cenário.

Vamos usar as seguintes imagens retiradas do Ninja Gaiden 3.


Este será o céu, é uma imagem que andará bem lentamente. 
Agora vamos pegar a segunda paisagem que andará rapidamente, dando o efeito de scroll.

Novamente iremos utilizar nosso travador de FPS.


A lógica do programa é fazer copias das imagens, para que o usuário não perceba que elas tem um inicio e
um fim.
Iremos concatenar a imagem para fazer isso.

CÓDIGO...
#include <allegro.h>

// ARQUIVO: main.cpp
// Data: 20/08/2007

// Exemplo de scrolling falso

// variáveis globais
int fps = 0;
int fps_antigo = 0;
int fps_speed = 0;

// prototipo do contador de frames


void frame_rate();

// prototipo do contador de velocidade


void incrementa_speed();

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...

Agora vamos implementar no código acima, a rolagem do cenário. 


Observe no código abaixo, que colamos 2 imagens iguais de fundo no buffer.. uma ao lado da outra.
A lógica funciona da seguinte maneira.

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

// Exemplo de scrolling falso

// variáveis globais
int fps = 0;
int fps_antigo = 0;
int fps_speed = 0;

// prototipo do contador de frames


void frame_rate();

// prototipo do contador de velocidade


void incrementa_speed();

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...

Exemplo de scrolling verdadeiro


Imagine uma fase do Super Mario, quando o personagem chega no limite da tela, a tela começa a andar, e os
sprites de fundo começam a se mover.

É esse tipo de simulação que iremos mostrar agora.


O código abaixo é controlado pelo teclado. Utilize as setas para fazer o cenário andar. 

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.

Nossa tela medirá 500 px.


Nós vamos especificar o tamanho do cenario em 250 tiles.
Cada tile terá 50 px de tamanho total;

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;

// prototipo do contador de frames


void frame_rate();

// prototipo do contador de velocidade


void incrementa_speed();

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. 

MAPINFO é um estrutura adicional e será explicada mais à frente.

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...

/* Função para carga de tiles */

SPRITE * load_tiles(const char * filename)


{
     /* Armazenando o bitmap em uma variável temporária */

    BITMAP * tmp = load_bitmap(filename, NULL);

    /* Verificando se o bitmap foi carregado */

    if (tmp == NULL)


        return NULL;

     /* Pegando o número de tiles na horizontal e na vertical */

    int xtiles = tmp->w / TILEWH;


    int ytiles = tmp->h / TILEWH;
    /* Total de tiles no bitmap */

    int total = xtiles * ytiles;

    /* Criando o vetor de SPRITE */

    SPRITE * spr = (SPRITE *) malloc(sizeof(SPRITE) * total);

    int x;
    int y;

    /* Índice para o vetor de SPRITE */

    int i = 0;

    /* Faça enquanto houver tiles na horizontal e na vertical */

    for (y = 0; y < ytiles; y++)


    {
        for (x = 0; x < xtiles; x++)
        {
             /* Criando o BITMAP que vai armazenar o tile */

            spr[i].bitmap = create_bitmap(TILEWH, TILEWH);

             /* Copiando o pedaço da imagem para o SPRITE */

            blit(tmp, spr[i].bitmap, x * TILE, y * TILE, 0, 0, TILE, TILE);

             /* Próximo índice */

            i++;
        }
    }

     /* Libere a memória alocada para o bitmap */

    destroy_bitmap(tmp);

    /* Retorne o vetor de SPRITE */

    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 xtiles = tmp->w / TILEWH;


int ytiles = tmp->h / TILEWH;

Calculamos o total de tiles no bitmap

int total = xtiles * ytiles;


Já temos condição de criar o vetor de SPRITE, que armazenará os tiles:

SPRITE * spr = (SPRITE *) malloc(sizeof(SPRITE) * total);

E o final: carregar os tiles do bitmap "tmp" no vetor.

int x;
int y;

int i = 0;

for (y = 0; y < ytiles; y++)


{
    for (x = 0; x < xtiles; x++)
    {
        spr[i]->bitmap = create_bitmap(TILE, TILE);

        blit(tmp, spr[i]->bitmap, x * TILE, y * TILE, 0, 0, TILE, TILE);

        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   |
+-------+-------+-------+

Isso se tivéssemos um BITMAP de 96x32. Se tivéssemos um BITMAP de 96x96:

+-------+-------+-------+
|       |       |       |
|   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:

spr[i]->bitmap = create_bitmap(TILEWH, TILEWH);

blit(tmp, spr[i]->bitmap, x * TILEWH, y * TILEWH, 0, 0, TILEWH, TILEWH);

Lembrando que a posição do tile é definida pelos parâmetros x e y dos laços. 


Como nossos TILES tem 32x32 de tamanho, fica fácil encontrar sua posição no BITMAP tmp, pois é só
multiplicar por 32.

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. 

Vá no menu "Custom / Export binary file". 

Uma janela abrirá, basta clicar em OK e outra janela abrirá. 

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...

/* Rotina para carga de mapa */

MAP * load_map(const char * filename, SPRITE * spr)


{
    /* Abrindo o arquivo de mapa */

    FILE * file = fopen(filename, "rb");

    /* Verificando se ele existe */

    if (!file)
        return NULL;

     /* Alocando memória para o mapa */


    MAP * map = (MAP *) malloc(sizeof(MAP));

    /* Pegando informaçõe sobre o mapa */

    MAPINFO info;

    fread(& info, sizeof(MAPINFO), 1, file);

    /* Altura e largura do mapa */

    map->w = info.w;
    map->h = info.h;

    /* Crie a matriz na vertical */

    map->tiles = (TILE **) malloc(sizeof(TILE) * map->h);

    int x;
    int y;

    int value;

    for (y = 0; y < map->h; y++)


    {
        /* Crie a matriz na horizontal */

        map->tiles[y] = (TILE *) malloc(sizeof(TILE) * map->w);

        for (x = 0; x < map->w; x++)


        {
            /* Lendo o tipo de tile */

            fread(& value, sizeof(int), 1, file);

             /* Se o valor do tile for diferente de 0, atribua o SPRITE, senão, NULL */

            map->tiles[y][x].sprite = value != 0 ? & spr[value - 1] : NULL;


        }
    }

    /* Feche o handle de arquivo */

    fclose(file);

    /* Retorne o mapa */

    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...

/* Rotina de impressão de mapa */

void draw_map(BITMAP * bitmap, MAP * map, int xpos, int ypos)


{
     /* Posição inicial */

    int xbegin = abs(xpos / TILEWH);


    int ybegin = abs(ypos / TILEWH);

     /* Verificando se não ultrapassa o mapa */

    if (xpos > 0)


        xbegin = 0;

    if (ypos > 0)


        ybegin = 0;

     /* Posição final */

    int xend = SCREEN_W / TILEWH + xbegin + 1;


    int yend = SCREEN_H / TILEWH + ybegin + 1;

     /* Verificando se não ultrapassa o mapa */

    if (xend > map->w)


        xend = map->w;

    if (yend > map->h)


        yend = map->h;

    int x;
    int y;

    /* Imprima o mapa! */

    for (y = ybegin; y < yend; y++)


        for (x = xbegin; x < xend; x++)
            if (map->tiles[y][x].sprite)
                draw_sprite(bitmap, map->tiles[y][x].sprite->bitmap, x * TILEWH +
xpos, y * TILEWH + ypos);
}

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:

Tile: 0,0 - Posição: 0, 0


Tile: 0,1 - Posição: 0, 32
...
Tile: 1,1 - Posição: 32, 32
...

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;

SPRITE * load_tiles(const char *);

MAP * load_map(const char *, SPRITE *);


void draw_map(BITMAP *, MAP *, int, int);

int main(int argc, char ** argv)


{
    allegro_init();

    install_keyboard();

    set_color_depth(16);
    set_gfx_mode(GFX_AUTODETECT_WINDOWED, 640, 480, 0, 0);

    buffer = create_bitmap(SCREEN_W, SCREEN_H);

    SPRITE * spr = load_tiles("tiles.bmp");

    if (!spr)
    {
        allegro_message("Falha ao carregar SPRITES!");

        return 1;
    }

    MAP * map = load_map("mapa.map", spr);

    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);

        draw_map(buffer, map, xmap, 0);

        blit(buffer, screen, 0, 0, 0, 0, SCREEN_W, SCREEN_H);


    }

    destroy_bitmap(buffer);

    allegro_exit();

    return 0;
}
END_OF_MAIN();

SPRITE * load_tiles(const char * filename)


{
    BITMAP * tmp = load_bitmap(filename, NULL);

    if (!tmp)
        return NULL;

    int xtiles = tmp->w / TILEWH;


    int ytiles = tmp->h / TILEWH;

    int total = xtiles * ytiles;

    SPRITE * spr = (SPRITE *) malloc(sizeof(SPRITE *) * total);

    int x;
    int y;

    int i = 0;

    for (y = 0; y < ytiles; y++)


    {
        for (x = 0; x < xtiles; x++)
        {
            spr[i].bitmap = create_bitmap(TILEWH, TILEWH);

            blit(tmp, spr[i].bitmap, x * TILEWH, y * TILEWH, 0, 0, TILEWH, TILEWH);

            i++;
        }
    }

    destroy_bitmap(tmp);

    return spr;
}

MAP * load_map(const char * filename, SPRITE * spr)


{
    FILE * file = fopen(filename, "rb");

    if (!file)
        return NULL;

    MAP * map = (MAP *) malloc(sizeof(MAP));

    MAPINFO info;
    fread(& info, sizeof(MAPINFO), 1, file);

    map->w = info.w;
    map->h = info.h;

    map->tiles = (TILE **) malloc(sizeof(TILE **) * map->h);

    int x;
    int y;

    int value;

    for (y = 0; y < map->h; y++)


    {
        map->tiles[y] = (TILE *) malloc(sizeof(TILE *) * map->w);

        for (x = 0; x < map->w; x++)


        {
            fread(& value, sizeof(int), 1, file);

            map->tiles[y][x].sprite = value != 0 ? & spr[value - 1] : NULL;


        }
    }

    fclose(file);

    return map;
}

void draw_map(BITMAP * bitmap, MAP * map, int xpos, int ypos)


{
    int xbegin = abs(xpos / TILEWH);
    int ybegin = abs(ypos / TILEWH);

    if (xpos > 0)


        xbegin = 0;

    if (ypos > 0)


        ybegin = 0;

    int xend = SCREEN_W / TILEWH + xbegin + 1;


    int yend = SCREEN_H / TILEWH + ybegin + 1;

    if (xend > map->w)


        xend = map->w;

    if (yend > map->h)


        yend = map->h;

    int x;
    int y;

    for (y = ybegin; y < yend; y++)


        for (x = xbegin; x < xend; x++)
            if (map->tiles[y][x].sprite)
                draw_sprite(bitmap, map->tiles[y][x].sprite->bitmap, x * TILEWH +
xpos, y * TILEWH + ypos);
}

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:

for (y = ybegin + 1; y < yend - 1; y++)


    for (x = xbegin + 1; x < xend - 1; x++)
        if (map->tiles[y][x].sprite)
            draw_sprite(bitmap, map->tiles[y][x].sprite->bitmap, x * TILEWH + xpos,
y * TILEWH + ypos);

Você pode incrementar o cenário, imprimindo uma imagem de fundo atrás do mapa, como um céu ou uma
caverna.

Espero que tenham gostado do tutorial e até mais...

31 - Pulando e Subindo Morros

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>

// Programa que simula pulos de um personagem 

// variáveis globais
int fps = 0;
int fps_antigo = 0;
int fps_speed = 0;

// prototipo do contador de frames


void frame_rate();

// prototipo do contador de velocidade


void incrementa_speed();

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

Para fazer um personagem subir um morro, vamos utilizar o exemplo acima.

Aqui a idéia já começa a complicar um pouco mais.


Quando o personagem vai subir um morro, primeiro temos que pensar em como vamos fazer o morro.
Qual vai ser a colisão com o cenário e de que forma o personagem vai aparecer ao subir o morro.

Nós temos que responder cada uma dessas perguntas para resolver o problema.

Primeiro, no algoritmo de pulo, definimos um limite de cenário, que no caso é o chão.


Neste exemplo não poderemos mais fazer isto, simplesmente por que agora não teremos mais um limite, já
que agora o chão pode ser um morro de limite um pouco maior ( para cima ou para baixo ).

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.

Vamos analisar uma solução, observe o gráfico:

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.

Agora imagine que nosso personagem andou até o ponto X(8).


A formula para a gente posicionar o personagem no ponto correto da subida é:

( 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.

Para fazer uma descida, basta inverter o sinal.

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);
}

// verifica se colide com as coordenadas passadas


bool Chao::colide( int x, int y, int tamanho )
{
   if ( 
   this->x + this->tamanho > x && 
   this->x < x + tamanho && 
   this->y + this->tamanho > y && 
   this->y < y + tamanho
   )
   {
      return true;
   }
   
   return false;
}

// seta a posicao x
void Chao::setx( int vx )
{
   this->x = vx;
}

// seta a posicao y
void Chao::sety( int vy )
{
   this->y = vy;
}

// seta o tamanho do objeto


void Chao::set_tamanho( int vtamanho )
{
   this->tamanho = vtamanho;

// retorna posicao x
int Chao::getx()
{
   return this->x;

// retorno posicao y
int Chao::gety()
{
   return this->y;

// retorna tamanho do objeto


int Chao::get_tamanho()
{
   return this->tamanho;

FIM DE CÓDIGO...

CÓDIGO...
#include <allegro.h>
#include "chao.h"

// PROGRAMA que simula subida e pulo na subida.


// Adriano Waltrick
// Arquivo: main.cpp
// Data: 06/09/2007 

// variáveis globais
int fps = 0;
int fps_antigo = 0;
int fps_speed = 0;

// prototipo do contador de frames


void frame_rate();

// prototipo do contador de velocidade


void incrementa_speed();

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;
            }
         }
   

         // imprime chao e verifica colisao


         for ( i=0; i< total_chao1; i++ )
         {
            
            rectfill( buffer, chao[i]->getx(), chao[i]->gety(), chao[i]->getx()+chao[i]-
>get_tamanho(), chao[i]->gety()+chao[i]->get_tamanho(), makecol(0,255,0) );
            
            if ( i < total_chao2 )
            {
               rectfill( buffer, chao2[i]->getx(), chao2[i]->gety(), chao2[i]->getx()
+chao2[i]->get_tamanho(), chao2[i]->gety()+chao2[i]->get_tamanho(), makecol(0,255,0) );
            }
            
            if ( chao[i]->colide(x, y, tamanho_personagem) == true )
            {
               y = chao[i]->gety() - tamanho_personagem;
               pulou = false; 
               forca = 0; 
            }
         
            if ( i < total_chao2 )
            {
               if ( chao2[i]->colide(x, y, tamanho_personagem) == true )
               {
                     y = chao2[i]->gety() - tamanho_personagem;
                     pulou = false;
                     forca = 0;
               }
            }
            
         }
      
         // Esse if vai barrar nosso personagem de passar pelo chão ( limite )
         if (y > limite_cenario - tamanho_personagem)
         {
            y = limite_cenario - tamanho_personagem;
            
            pulou = false;
            forca = 0;
         }
      
      
         rectfill( buffer, x, y, x+tamanho_personagem, y+tamanho_personagem,
makecol(255,255,0) );
         
         line(buffer, morro_x, morro_y, morro_x2, morro_y2, makecol(255, 0, 0 ));
         line(buffer, morro2_x, morro2_y, morro2_x2, morro2_y2, makecol(255, 0, 0 ));
         
         
         ::fps_speed--;
         ::fps++;
      }
      
      blit(buffer, screen, 0,0,0,0, SCREEN_W, SCREEN_H);
      vsync();
      
   }
      
   destroy_bitmap( buffer );
      
      
   // destroi chão 1 e 2
   for ( i=0; i< total_chao1; i++ )
   {
      delete chao[ i ];
      
      if ( i < total_chao2 )
      {
         delete chao2[ i ];
      }
   }
      
   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 no código acima que na lógica aprensentada:

   novo_y = ( morro_y2 + (morro2_altura - tamanho_chao) ) - ( ( x-tamanho_personagem ) - morro_x);

Nós executamas o ( x-tamanho_personagem ) - morro_x para ter o X relativo a posição do morro.

E também executamos   morro_y2 + (morro2_altura - tamanho_chao)


para ter o Y relativo ao Y do personagem.

Você também pode gostar