53
5. JOGOS 2D Nesse bloco vamos estudar os fundamentos de aplicações e jogos 2D. Iniciaremos explicando como configurar a janela para exibição de gráficos 2D e iremos até técnicas avançadas como parallax scrolling. Por que devemos estudar jogos 2D? - São o tipo mais jogado, pois a maioria da pessoas preferem jogos casuais. - Eles rodam em maquina de baixo desempenho, por isso possuem um mercado maior. - Vivemos uma febre de celulares e por enquanto eles só rodam jogos 2d. PRIMITIVAS Vamos fazer um breve estudo sobre os elementos mais básicos da programação 2D: a definição do modo gráfico e as primitivas gráficas. Primitivas são recursos internos do Blitz3D com os quais podemos compor imagens sem a necessidade de importar arquivos criados em outras ferramentas. Definindo o modo gráfico A primeira coisa a se fazer ao iniciar uma aplicação com gráficos 2D é dizer ao computador como desejamos que esses gráficos sejam exibidos. Podemos fazer isso com o comando Graphics. Veja a assinatura do comando: Graphics resolução x, resolução y, paleta, modo Exemplo 1 Graphics 640, 480, 16, 1 Oval 270,190, 100,100,1 WaitKey No exemplo acima estamos configurando a resolução da tela em 640, 480. Definimos a paleta de cores para 16 bits e definimos que o programa deve rodar em modo fullscreen, isso é, tela cheia "1". Veja o mesmo programa com outra resolução de tela. Exemplo 2 Graphics 800, 600, 16, 1 Oval 270,190, 100,100,1 WaitKey O próximo exemplo mostra uma adaptação do exemplo anterior, agora rodando em modo de janela. Exemplo 3 Graphics 640, 480, 16, 2 Oval 270,190, 100,100,1 WaitKey Uma opção pelo modo “3” fará com que você possua uma janela redimensionável. Rode o exemplo abaixo e teste essa possibilidade. Aumente as laterais, depois a altura e teste maximizar, restaurar e minimizar. Exemplo 4 Graphics 640, 480, 16, 3 Oval 270,190, 100,100,1 WaitKey Títulos nas Janelas Caso você faça a opção por rodar um aplicativo em modo de janela provavelmente vai querer colocar o título do jogo na janela. Isso pode ser feito com o comando AppTitle. Exemplo 5 Graphics 640, 480, 16, 2 AppTitle ":-) 2D" WaitKey Duplo Buffer A técnica de Double Buffer consiste em dividir o esforço de composição da imagem do esforço para a exibição da mesma. Quando a composição da imagem se dá diretamente no Buffer de vídeo é comum gerar “fantasmas” na imagem, aquelas falhas que as deixam meio transparentes. Isso se dá porque acabamos vendo o processo de composição da tela.

Jogos 2D

Embed Size (px)

Citation preview

Page 1: Jogos 2D

5. JOGOS 2DNesse bloco vamos estudar os fundamentos de aplicações e jogos 2D. Iniciaremos explicando como configurar a janela para exibição de gráficos 2D e iremos até técnicas avançadas como parallax scrolling.Por que devemos estudar jogos 2D?- São o tipo mais jogado, pois a maioria da pessoas preferem jogos casuais.- Eles rodam em maquina de baixo desempenho, por isso possuem um mercado maior.- Vivemos uma febre de celulares e por enquanto eles só rodam jogos 2d.PRIMITIVASVamos fazer um breve estudo sobre os elementos mais básicos da programação 2D: a definição do modo gráfico e as primitivas gráficas. Primitivas são recursos internos do Blitz3D com os quais podemos compor imagens sem a necessidade de importar arquivos criados em outras ferramentas.Definindo o modo gráficoA primeira coisa a se fazer ao iniciar uma aplicação com gráficos 2D é dizer ao computador como desejamos que esses gráficos sejam exibidos. Podemos fazer isso com o comando Graphics.Veja a assinatura do comando: Graphics resolução x, resolução y, paleta, modo

Exemplo 1Graphics 640, 480, 16, 1Oval 270,190, 100,100,1WaitKey

No exemplo acima estamos configurando a resolução da tela em 640, 480. Definimos a paleta de cores para 16 bits e definimos que o programa deve rodar em modo fullscreen, isso é, tela cheia "1".Veja o mesmo programa com outra resolução de tela.Exemplo 2Graphics 800, 600, 16, 1Oval 270,190, 100,100,1WaitKey

O próximo exemplo mostra uma adaptação do exemplo anterior, agora rodando em modo de janela.Exemplo 3Graphics 640, 480, 16, 2Oval 270,190, 100,100,1WaitKey

Uma opção pelo modo “3” fará com que você possua uma janela redimensionável. Rode o exemplo abaixo e teste essa possibilidade.

Aumente as laterais, depois a altura e teste maximizar, restaurar e minimizar.Exemplo 4Graphics 640, 480, 16, 3Oval 270,190, 100,100,1WaitKey

Títulos nas JanelasCaso você faça a opção por rodar um aplicativo em modo de janela provavelmente vai querer colocar o título do jogo na janela. Isso pode ser feito com o comando AppTitle.Exemplo 5Graphics 640, 480, 16, 2AppTitle ":-) 2D"WaitKey

Duplo BufferA técnica de Double Buffer consiste em dividir o esforço de composição da imagem do esforço para a exibição da mesma. Quando a composição da imagem se dá diretamente no Buffer de vídeo é comum gerar “fantasmas” na imagem, aquelas falhas que as deixam meio transparentes. Isso se dá porque acabamos vendo o processo de composição da tela.Com o uso de Double Buffer a imagem é composta em outra área e só é colocada na tela quando ela estiver pronta.Para usar essa técnica no Blitz3D precisamos usar dois comando: Backbuffer() e Flip. Com o primeiro comando definimos o modo de buffer para dois buffers. Caso estejamos usando o modo de double buffer deveremos usar o comando Flip para colocar a imagem do Buffer de Composição no Buffer de Vídeo. A falta desse comando pode gerar o inconveniente de as imagens simplesmente não serem exibidas.Exemplo 6Graphics 640, 480, 16, 2SetBuffer BackBuffer()Oval 270, 190, 100, 100, 1FlipWaitKey

Desenhando um pontoA primeira primitiva gráfica que iremos estudar é o comando Plot. Ele desenha um ponto na tela, que tem exatamente o tamanho de um pixel. Para ser executado ele deve receber as coordenadas x e y de onde ele deverá ser exibido.Exemplo 7Graphics 400, 300Plot 200, 150WaitKey

Como o Plot exibe um ponto do tamanho de um pixel, podemos usar vários deles para

Page 2: Jogos 2D

criar gráficos ou formas geométricas mais complexas. Mas não é recomendável fazer uso excessivo desse tipo de recurso, pois esses comandos de primitivas são um pouco lentos. No exemplo abaixo usamos laços For-Next para preencher a tela com Plot. Veja que isso demanda um esforço excessivo.Exemplo 8Graphics 800, 600While Not KeyHit(1)Color Rnd(255), Rnd(255), Rnd(255)For y = 1 To 600For x = 1 To 800 Plot x, y-recuo Next NextWend

colorJá usamos algumas vezes esse comando em programas anteriores como no exemplo que você acabou de ver. Esse comando e responsável por mudar a cor da paleta de desenho. É a cor da paleta de desenho que determina qual será a cor de textos e primitivas gráficas.Esse comando funciona por meio de três argumentos: a saturação de cor Red (vermelha); a saturação de cor Green (verde) e a saturação de cor Blue (azul). As iniciais dessas cores correspondem ao padrão de cores RGB – Red, Green e Blue. Por meio desse sistema de cores podemos construir qualquer cor pela mistura dessas três cores primitivas.O programa abaixo demonstra como são compostas as coresExemplo 9Graphics 800, 600, 16,1SetBuffer BackBuffer()R = 255G = 255B = 255While Not KeyHit(1)ClsColor 255, 255, 255Text 50, 50, "** BOTOES DIREITO E ESQUERDO DO MOUSE GRADUAM AS CORES **"Color R, G, BOval 400, 190, 300, 300, 1Color 255,0,0Rect 50,200, 50, 255Color 0,255,0Rect 150,200, 50, 255Color 0,0,255Rect 250,200, 50, 255Color 100,100,100Rect 40, 430 - R, 70, 25Text 65, 470, RRect 140, 430 - G, 70, 25Text 165, 470, G

Rect 240, 430 - B, 70, 25Text 265, 470, Bmx = MouseX()my = MouseY()Color 255,0,255Text mx-5, my-5, "+"If (mx > 40) And (mx < 110)If (my > (430 - R)) And (my < 455 - R)If MouseDown(1) And (R < 255) Then R = R + 5If MouseDown(2) And (R > 0) Then R = R - 5EndIfEndIfIf (mx > 140) And (mx < 210)If (my > (430 - G)) And (my < 455 - G)If MouseDown(1) And (G < 255) Then G = G + 5If MouseDown(2) And (G > 0) Then G = G - 5EndIfEndIfIf (mx > 240) And (mx < 310)If (my > (430 - B)) And (my < 455 - B)If MouseDown(1) And (B < 255) Then B = B + 5If MouseDown(2) And (B > 0) Then B = B - 5EndIfEndIfGetMouse()FlipWend

Programinha interessante... Entendeu Tudo? Acredito que tudo que está nele já foi discutido. Talvez você se confunda um pouco com a lógica do programa que está um pouco abstrata, mas o importante aqui é você saber como se controla a paleta de cores por meio do comando Color e ter uma ferramenta para descobrir como compor uma cor desejada.Desenhando linhasPodemos desenhar linhas no Blitz3D por meio do comando Line. Para que ele funcione devemos declarar as posições x e y de onde a linha começa e declarar a posições x e y de onde ela deve terminar. Assim, a lógica desse comando é Line x1,y1,x2,y2.Veja um exemplo prático.Exemplo 10Graphics 800, 600 Line 100,100, 700, 500 WaitKey

Desenhando retângulosPara desenhar retângulos devemos passar 5 argumentos para o comando Rect. A posição x e posição y onde ele será desenhado; largura; altura e se será preenchido ou vazado.

Page 3: Jogos 2D

O exemplo a seguir cria a retângulo na posição x = 100 e y =100 na tela de dimensões 100 x 50, vazado, pois seu ultimo argumento é 0.Exemplo 11Graphics 800, 600 Rect 100, 100, 100, 50, 0 WaitKey

Para criar retângulos preenchidos devemos usar o valor 1.Exemplo 12Graphics 800, 600 Rect 100, 100, 100, 50, 1 WaitKey

Desenhando círculos e ovaisPara desenhar circunferências usamos o comando Oval. Ele tem a mesma assinatura do comando de desenho de retângulos.Veja um exemplo onde desenhamos uma oval vazada.Exemplo 13Graphics 800, 600 Oval 200, 200, 100, 50, 0 WaitKey

Nesse próximo exemplo desenhamos um círculo preenchido.Exemplo 14Graphics 800, 600 Oval 200, 200, 100, 100, 1 WaitKey

SonsO que seria de um jogo sem os efeitos sonoros... O Blitz3D possui comandos específicos para sons, musicas e som 3d. Os formatos suportados originalmente são: raw, wav, mp3 e ogg; mas isso não impede que você faça uso de uma Dll fará ampliar para outros tipos de sons.Como estamos tratando aqui de jogos 2d, vamos abordar nesse primeiro momento especificamente efeitos sonoros para esse tipo de jogo. Quando chegarmos na parte de jogos 3d, nós explicaremos o funcionamento de som 3d.Carregando e executando um somPara carregar um som para a memória usamos o comando LoadSound e passamos como referência a esse comando o nome do arquivo e, se ele estiver em uma pasta diferente de onde está o seu código executável, você também precisará indicar o local onde ele está.Por exemplo, se o arquivo de som ficará na mesma pasta do seu executável e do seu código fonte, podemos carregar um arquivo de som chamado tiro.wav assim:meusom = LoadSound(“tiro.wav”)

Perceba que colocamos o som em uma variável meusom para podermos manipulá-lo depois. O normal é que, em um jogo, exista uma pasta só para mídias. O comando abaixo carrega o som a partir de uma pasta mídia dentro da nossa pasta do projeto, onde fica o código fonte.meusom = LoadSound(“mídia\tiro.wav”)

Veja que fizemos isso por meio de endereçamento relativo, isso é, não demos a localização a partir da raiz, do disco rígido, mas a partir da pasta do arquivo principal, isso é, relativo ao arquivo principal. Em tempo de projeto o arquivo principal é o código fonte e depois de compilado, o arquivo principal é o seu executável.Veja abaixo um exemplo de endereçamento absoluto, isso é, a partir da raiz. meusom = LoadSound(“c:\jogo\mídia\tiro.wav”)

Veja que esse tipo de endereçamento é desaconselhado, pois se você mudar o local da pasta do jogo, ele não vai funcionar mais.Para executar um som usamos o comando PlaySound e damos como parâmetro para esse comando a variável na qual carregamos o som.PlaySound meusom

O som será executado apenas uma vez, do seu início até o seu fim. Se ele tiver um segundo de duração então ele executará por esse tempo e irá parar. Vamos a um exemplo prático sobre sons. Veja que colocamos nosso som dentro de uma pasta de mídia.Exemplo 15meusom = LoadSound("midia\tiro.wav")PlaySound meusomWaitKey

Tocar um som em loopSe desejarmos, podemos tocar o som em loop. Para isso devemos configurar o som para ser executado dessa forma por meio do comando LoopSound. Veja que esse comando é aplicado à variável do som e não no comando de play.Exemplo 16meusom = LoadSound("midia\sapo.wav")LoopSound meusomPlaySound meusomWaitKey

Parar a execuçãoPara ter um controle maior sobre as propriedades do som como, por exemplo, para a execução deles, devemos selecionar os canais de som. Para selecionar o canal de som, basta pegar esse canal em uma

Page 4: Jogos 2D

variável quando mandamos o som ser executado.meucanal = PlaySound(meusom)

Daí podemos usar o comando StopChannel sobre o canal de som para parar sua execução. StopChannel meucanal

Vamos a um exemplo executávelExemplo 17meusom = LoadSound("midia\sapo.wav")LoopSound meusommeucanal = PlaySound (meusom)paralisa()StopChannel meucanalparalisa()meucanal = PlaySound (meusom)paralisa()EndFunction paralisa()While Not KeyHit(57)Print "pressione space"WendEnd Function

Alterar VolumePodemos alterar o volume de um som por meio do comando SoundVolume. Se você vai abaixar o volume de um som que já está em execução, vai precisar usar o canal de som para parar esse som, abaixar o volume, e reativar o canal. Se você apenas abaixar o som e aplicar o comando PlaySound, vai criar um segundo canal de som. Exemplo 18meusom = LoadSound("midia\sapo.wav")LoopSound meusommeucanal = PlaySound (meusom)While Not KeyHit(1)x = x + 1If x = 215StopChannel meucanalSoundVolume meusom, 0.6meucanal = PlaySound (meusom) EndIfIf x = 500StopChannel meucanalSoundVolume meusom, 0.2meucanal = PlaySound (meusom) EndIfFlip Wend

Descarregar um somJogos normalmente são constituídos de várias fazes e elas normalmente possuem uma grande quantidade de informação para ser armazenada na memória; coisas como sons, imagens e modelos 3d. O normal é que cada fase seja carregada apenas momentos antes de ser iniciada e seja descarregada da memória depois de ser encerrada. Assim temos apenas os dados da

fase que está sendo jogada na memória e não sobrecarregamos o sistema.Como carregar um som você já sabe, é só dar o comando LoadSound. Para descarregar um som da memória usamos o comando FreeSound.

Exemplo19meusom = LoadSound("midia\sapo.wav")LoopSound meusomPlaySound meusomWhile Not KeyHit(1)Text 50,10, "Tecle Space para Descarregar o Som"If KeyHit(57)FreeSound meusomEndIfFlip Wend

ImagensJogos 2d e jogos isométricos são fundamentalmente constituídos por imagens do tipo bitmaps, como arquivos “.bmp”, “.png” ou “.jpg”. O cenário, os objetos, a vegetação, os personagens, tudo é feito de imagens.Veja abaixo um arquivo de dados de imagem de um jogo.

Carregando e exibindoPara esse exemplo estamos usando a imagem abaixo. Ela está na pasta de mídia dos códigos fontes deste capítulo.

Page 5: Jogos 2D

Para carregar uma imagem usamos o comando LoadImage. Seu funcionamento é semelhante ao comando de carregamento de som. Veja abaixo como se usa esse comando.minhaimagem = LoadImage(“midia\arvore.bmp”)

A segundo coisa necessária é ordenar que o Blitz3D exiba a imagem na tela. Isso é feito por meio do comando DrawImage. Passamos para esse comando a variável onde a imagem foi carregada e a posição x, y onde ela deverá ser exibida.DrawImage minhaimagem, 100, 100

Vamos agora a um exemplo completo Exemplo 20Graphics 800, 600minhaimagem = LoadImage("midia\arvore.bmp")DrawImage minhaimagem, 100, 100WaitKey

Se você é humano, deve estar espantado com a qualidade gráfica dessa demonstração e nesse momento deve estar imaginando o que você não poderia fazer com esse poder do Blitz3D...Um problema básicoExecute o programa abaixo e veja o que acontece.Exemplo 21Graphics 800, 600arvore= LoadImage("midia\arvore.bmp")arbusto = LoadImage("midia\arbusto.bmp")DrawImage arvore, 100, 100DrawImage arbusto, 200, 220WaitKey

Quando carregamos uma imagem o Blitz3D já define uma mascara padrão para imagem. Essa mascara é uma cor escolhida como fundo da imagem, então ela é desconsiderada para que o fundo da imagem possa ser apagado. Assim só ficamos com a imagem principal.

No caso acima, o Blitz já tinha definido como máscara a cor preta, isso é 0,0,0. Como a segunda imagem tem o fundo cinza com saturação 170,170,170 ele não é apagado e então ele é exibido na tela gerando esse péssimo problema. Para evitar isso temos que configura a mascara de cada imagem manualmente.MáscarasPodemos ajustar a máscara para cada imagem por meio do comando MaskImage, assim excluímos manualmente o fundo de cada imagem. Para trabalhar com esse comando devemos passar o nome da imagem e a cor no padrão RGB que usaremos como máscara.Exemplo 22Graphics 800, 600arvore= LoadImage("midia\arvore.bmp")arbusto = LoadImage("midia\arbusto.bmp")MaskImage arbusto, 170,170,170DrawImage arvore, 100, 100DrawImage arbusto, 200, 220WaitKey

Imagens animadasImagens animadas são na verdade uma seqüência de quadros de animação dentro de uma única imagem. Assim carregamos todos os dados da animação a partir de um único arquivo para a memória e o Blitz3D cria apontadores para cada frame da animação.Veja abaixo uma imagem com estrutura animada.

Veja que é uma imagem comum e pode ser carregada e exibida como uma imagem comum.Exemplo 23Graphics 800, 600bule= LoadImage("midia\tea.bmp")DrawImage bule, 200, 220WaitKey

A imagem acima foi construída dentro de rígida disposição geométrica. Cada frame tem 50 x 50 pixels, totalizando uma imagem de 250 x 100. É essa estrutura de disposição de cada imagem que permite que o Blitz3D crie um sistema de apontadores para cada frame.Para usar uma imagem animada dinamicamente essa deve ser carregada

Page 6: Jogos 2D

como uma imagem animada por meio do comando LoadAnimImage e no momento de sua exibição, com o comando DrawImage, devemos informar qual frame da animação deveremos exibir.Veja como é a estrutura do comando LoadAnimImage:LoadAnimImage (“nome.tipo”, Largura do frame, Altura do frame, primeira, quantidade)Veja que primeiramente devemos informar o nome da imagem. O segundo argumento é a largura de cada frame, que no exemplo da nossa imagem será 50. O terceiro argumento é a altura de cada frame, que também é 50. No quanto argumento dizemos qual é a imagem inicial, nesse caso vamos carregar a partir da primeira, então nossa imagem inicial será 0. No ultimo argumento dizemos quantos frames vamos carregar; como o ultimo frame da imagem é igual ao primeiro, vamos carregar apenas 9. Então nosso comando fica assim:chaleira= LoadAnimImage("midia\tea.bmp", 50, 50, 0, 9)

Para exibir um frame da imagem animada usamos o comando DrawImage e colocamos um ultimo parâmetro que diz qual o frame a ser exibido. No exemplo abaixo exibimos o primeiro frame da animação, o frame 0.DrawImage chaleira, 200, 220, 0

O primeiro parâmetro do comando chaleira é a imagem animada carregada. O segundo parâmetro 200 se refere à posição x onde a imagem será exibida. O terceiro parâmetro 220 é a posição y onde a imagem aparecerá. Por fim, o 0 é o frame da imagem que vai ser mostrado.Veja um exemplo completo que carrega e exibe todos os frames de uma imagem animada.Exemplo 24Graphics 800, 600chaleira= LoadAnimImage("midia\tea.bmp", 50, 50, 0, 9)DrawImage chaleira, 100, 120, 0DrawImage chaleira, 150, 170, 1DrawImage chaleira, 200, 220, 2DrawImage chaleira, 250, 270, 3DrawImage chaleira, 300, 320, 4DrawImage chaleira, 350, 370, 5DrawImage chaleira, 400, 420, 6DrawImage chaleira, 450, 470, 7DrawImage chaleira, 500, 520, 8WaitKey

Nesse exemplo você pode perceber que podemos usar cada imagem individualmente. É por isso que, como você viu no início, um arquivo pode conter diversas coisas

diferentes. É até comum que alguns jogos tenham praticamente todos os tiles que compõe o cenário de uma fase inteira em apenas um único arquivo.O próximo exemplo demonstra que uma mesma imagem pode ser exibida quantas vezes você desejar, por isso não é necessário criar uma variável para cada vez que a imagem vai ser repetida.Exemplo 25Graphics 800, 600chaleira= LoadAnimImage("midia\tea.bmp", 50, 50, 5, 1)For x = 0 To 750 Step 50For y = 0 To 650 Step 50DrawImage chaleira, x, y, 0NextNextWaitKey

Temos algumas novidades nesse exemplo.chaleira= LoadAnimImage("midia\tea.bmp", 50, 50, 5, 1)

Veja que estamos carregando apenas um frame e estamos carregando a partir do frame 5, que é o sexto frame. Portanto essa imagem animada terá apenas um frame que é na verdade o sexto frame da imagem.DrawImage chaleira, x, y, 0

Veja que estamos exibindo o frame 0. Não confunda os frame do arquivo da imagem com os frame carregados. O comando LoadImage carrega frames de um arquivo e quando apontamos para ele o frame 5 foi como se disséssemos “Carregue o sexto frame desse arquivo”. Mas a partir desse momento, esse arquivo não faz parte da lógica, e como carregamos apenas 1 frame, esse frame que era o 5º do arquivo agora é o frame “0” da variável de imagem animada.For x = 0 To 750 Step 50For y = 0 To 650 Step 50DrawImage chaleira, x, y, 0NextNext

Como nossa imagem tem 50 x 50, também incrementamos os loops em 50 unidades por meio de Step 50. Assim as imagens já se enquadram diretamente no local que deverão se exibidas. Veja que estamos usando as variáveis do loop como posição x e y para exibição das imagens.E a animação?Para criar um sistema de animação basta criarmos uma variável contador e uma variável frame. Usaremos o comando Flip no modo gráfico assim a velocidade de execução será sincronizada com o retraço do

Page 7: Jogos 2D

monitor, o que é mais ou menos 60 ciclos por segundo.Vamos primeiro criar uma animação de 12 quadros por segundo. O primeiro passo é achar qual deverá ser o valor limite do contador. 60/12 = 5, assim, para conseguirmos uma animação de 12 quadros por segundo, deveremos incrementar um frame a cada 5 unidade do contador. A estrutura seria assim.contador = contador + 1If contador = 5contador = 0frame = frame + 1End If

Veja que para cada vez que o contador atingir o valor limite “5” devemos passar a animação para o próximo frame, assim teremos a velocidade desejada. Mas ainda falta colocar o controle para que animação não passe no ultimo quadro, senão dará erro:If frame = 9 Then frame = 0

Isso reinicia a animação para o primeiro frame, colocando-a em loop.Vamos ao exemplo!Exemplo 26Graphics 800, 600chaleira= LoadAnimImage("midia\tea.bmp", 50, 50, 0, 9)frame = 0contador = 0While Not KeyHit(1)contador = contador + 1If contador = 5contador = 0frame = frame + 1End IfIf frame = 9 Then frame = 0DrawImage chaleira, 375, 275, frameFlipClsWend

No próximo exemplo demos uma turbinada na animação, colocamos em 30 quadros por segundo.Exemplo 27Graphics 800, 600chaleira= LoadAnimImage("midia\tea.bmp", 50, 50, 0, 9)frame = 0contador = 0While Not KeyHit(1)contador = contador + 1If contador = 2contador = 0frame = frame + 1End IfIf frame = 9 Then frame = 0DrawImage chaleira, 375, 275, frameFlipClsWend

No exemplo a seguir, demonstramos o poder de animação enchendo a tela de imagens animadas.Exemplo 28Graphics 800, 600chaleira= LoadAnimImage("midia\tea.bmp", 50, 50, 0, 9)frame = 0contador = 0While Not KeyHit(1)contador = contador + 1If contador = 3contador = 0frame = frame + 1End IfIf frame = 9 Then frame = 0For x = 0 To 750 Step 50For y = 0 To 650 Step 50DrawImage chaleira, x, y, frameNextNextFlipClsWend

Um ultimo detalhe:FlipCls

Como você se lembra, o comando Flip tem duas finalidades: colocar a imagem no buffer de vídeo e controlar a velocidade de execução do loop do jogo. Abaixo dele está o comando Cls, que é composto pelas iniciais de Clear Screen, isso é, limpar a tela. O que esse comando faz é limpar as imagens anteriores da tela para que elas não vão se sobrepondo umas as outras.Experimente tirar o Cls do programa e veja o que acontece.FreeImagePara descarregar uma imagem da memória usamos o comando FreeImage sobre a variável na qual a imagem foi carregada.FreeImage chaleira

Exemplo 29Graphics 800, 600chaleira= LoadAnimImage("midia\tea.bmp", 50, 50, 0, 9) DrawImage chaleira, 200, 200, 1 Print "Pressione uma tecla para descarregar"WaitKeyFreeImage chaleira DrawImage chaleira, 200, 200, 1WaitKey

Veja nesse exemplo que o primeiro comando de exibição funciona normalmente, mas a segunda vez que o comando é usado vem depois de descarregar a imagem. Isso causa um erro, pois a imagem não existe mais.Animações

Page 8: Jogos 2D

Como você já percebeu as animações de um jogo 2D são uma série de imagens estáticas que, devido ao fato de serem elaboradas numa seqüência de momentos, criam a ilusão de o personagem estar em movimento.Você tem a liberdade de fazer essas imagens da forma que desejar, mas a técnica para conseguir o melhor resultado com menor esforço é criar essas imagens a partir de um modelador 3D com render, como Blender, 3DS Max, Maya, Cinema4D, etc. Criar as imagens em um modelar da melhor qualidade de iluminação e deixa a mesma mais realista, além do que você só tem o trabalho de fazer o modelo uma vez e depois a ferramenta gera automaticamente várias posições e perspectivas diferentes do mesmo trabalho.Animação com duas câmerasEssa é a maneira mais primitiva de animação. Ela é o estilo de animação das plataformas mais antigas, onde os processadores não tinham um grande poder de processamento. Animações nesse estilo possuem uma perspectiva lateral do personagem que só se move para frente ou para trás. São exemplos desse tipo de animação o Jogo Sonic, os primeiros jogos do Mario e a grande maioria dos jogos de plataformas até 16 bits.Veja abaixo um exemplo de animação em duas câmeras.

Esse arquivo de imagem animado foi feito a partir de um modelo 3d que vem junto com o Blitz3D. Foi importado para o 3ds Max, posicionamos uma câmera na lateral esquerda do robô e outras na lateral direita e renderizamos alguns quadros da animação original do modelo. O passo seguinte foi montar todos os quadros em uma só imagem por meio de um programa de tratamento de imagens.Vamos ver o resultado disso? O exemplo abaixo exibe todas as animações presentes na imagem animada.Exemplo 30Graphics 800, 600

robo = LoadAnimImage("midia\robo2c.bmp", 50, 80, 0, 16)frame = 0contador = 0px = 200py = 200While Not KeyHit(1)contador = contador + 1If contador = 5contador = 0frame = frame + 1End IfIf frame = 16 Then frame = 0DrawImage robo, px, py, frameFlipClsWend

Vamos agora aprender a fazer controle de animações de duas câmeras para personagens do jogo. O que temos a fazer é criar 4 séries de animações diferente: uma parada olhando a direita, outra andando para a direita, a terceira parado olhando a esquerda e por último andando para a esquerda. Essas animações vão ser controladas por chaves que serão acionadas por meio das teclas de setas.Exemplo 31Graphics 800, 600SetBuffer BackBuffer()robo = LoadAnimImage("midia\robo2c.bmp", 50, 80, 0, 16)frame = 0contador = 0anima = 1 ; 1 = parado direita, ; 2 = andando direita; 3 = parado esquerda; 4 = andando esquerdapx = 375py = 260While Not KeyHit(1) ;------------------ input -------------------- If KeyDown(205)If anima <> 2anima = 2contador = 0Frame = 0EndIfElseIf anima = 2 Then anima = 1 EndIfIf KeyDown(203)If anima <> (4)anima = 4contador = 0Frame = 8EndIfElseIf anima = 4 Then anima = 3 EndIf;------------------ logica ------------------ If anima = 2contador = contador + 1

Page 9: Jogos 2D

If contador = 5contador = 0frame = frame + 1EndIfIf frame = 8 Then frame = 0EndIfIf anima = 4contador = contador + 1If contador = 5contador = 0frame = frame + 1EndIfIf frame = 16 Then frame = 8EndIf;-------------------Exibe ------------------- If anima = 1 DrawImage robo, px, py, 0If anima = 3 DrawImage robo, px, py, 8If anima = 2 DrawImage robo, px, py, frameIf anima = 4 DrawImage robo, px, py, frameFlipClsWend

Explicando...frame = 0contador = 0

Essas variáveis já são nossas conhecidas não é mesmo? Se você na se lembra, de uma olhada nos exemplos anteriores.anima = 1 ; 1 = parado direita, ; 2 = andando direita; 3 = parado esquerda; 4 = andando esquerda

Criamos uma variável para ser a chave da animação, isso é: ela é responsável por ativar as animações e informar o estado atual delas. Ao lado fizemos anotações para não esquecermos o que cada estado representa.px = 375py = 260

Essas duas variáveis representam a posição x e y onde a animação vai aparecer.If KeyDown(205)If anima <> 2anima = 2contador = 0Frame = 0EndIf

Se a tecla seta a direita estiver pressionada iremos verificar se o estado da animação não é o andando para a direita. Isso é necessário, pois se já estivermos andando para a direita não podemos reiniciar a animação. Sem essa chave a animação não sairia do primeiro quadro. Se a animação atual é diferente de 2 então vamos iniciar a animação 2 anima=2 e reiniciar os estados do contador contador=0 e do frame atual Frame=0. ElseIf anima = 2 Then anima = 1 EndIf

Caso a tecla de seta a direita não esteja pressionada, devemos verificar se a animação que está ativa é a animação para a direita para que possamos pará-la se isso for verdadeiro. Assim, se ela ainda não foi parada, colocamos ela no estado 1 que é olhando para a direita. Essa estrutura lógica causa o efeito imediato de paralisar a animação e fazer o robô olhar para o lado que estava andando.If KeyDown(203)If anima <> (4)anima = 4contador = 0Frame = 8EndIfElseIf anima = 4 Then anima = 3 EndIf

Esse trecho de código é idêntico ao explicado anteriormente, mas para o outro lado da animação. Bem por isso ao reiniciar os estados da animação o frame inicial é o 8 Frame=8, pois agora o robô vai andar para o lado contrário.If anima = 2contador = contador + 1If contador = 5contador = 0frame = frame + 1EndIfIf frame = 8 Then frame = 0EndIfIf anima = 4contador = contador + 1If contador = 5contador = 0frame = frame + 1EndIfIf frame = 16 Then frame = 8EndIf

O código acima já foi estudado em programas anteriores. Ele é o responsável por fazer o controle dos frames que devem ser exibidos. O diferencial que apresenta aqui é que agora são duas animações separadas e por isso tivemos que separar o código em dois blocos controlado pela chave de animação. Se a chave é igual a 2 If anima=2 a animação vai de 0 a 7 If frame=8 Then frame=0. E se for igual a 4 If anima=4 a animação vai de 8 a 15 If frame=16 Then frame=8.;-------------------Exibe ------------------- If anima = 1 DrawImage robo, px, py, 0If anima = 3 DrawImage robo, px, py, 8If anima = 2 DrawImage robo, px, py, frameIf anima = 4 DrawImage robo, px, py, frame

Page 10: Jogos 2D

Por fim a estrutura de exibição das imagens. Se a chave está nas imagens estáticas, sem mistério, exibem só um frame. Caso ela esteja em um dos estados animados, usamos a variável frame para exibir o frame atual da animação.FlipClsWend

Não vamos esquecer o comando de atualização do buffer e de apagar as imagens antigas.Colocando um pouco de vidaVamos colocar algumas árvores e arbustos no programa anterior para dar uma sensação de maior realismo no movimento. Afinal, sem alguns objetos se movendo na tela, temos apenas a impressão de animação, mas não temos uma sensação real de movimento.Uma animação em um jogo 2D é ao contrário da vida real. Enquanto na vida real o personagem anda e o cenário fica estático, em um jogo 2D todo o cenário se move e o personagem é o único a ficar estático.Para criar esse efeito usamos uma matemática muito simples onde a posição x de cada objeto do cenário é dada por uma função F(x) = posição + x. Cada objeto do cenário tem uma posição relativa inicial e sua posição x real será igual a essa posição mais o fator x. Quando apertarmos os botões direcionais o fator x vai ser aumentando ou diminuído e como a posição o objeto é sua posição mais o fator x, ela vai ser alterada.O exemplo abaixo mostra esse efeito.Exemplo 32Graphics 800, 600SetBuffer BackBuffer()arvore = LoadImage("midia\Arvore.bmp")CONTROLE_FX = 0While Not KeyHit(1) If KeyDown(205) Then CONTROLE_FX = CONTROLE_FX - 2 If KeyDown(203) Then CONTROLE_FX = CONTROLE_FX + 2 DrawImage arvore, 170 + CONTROLE_FX, 140DrawImage arvore, 700 + CONTROLE_FX, 120FlipClsWend

Como você pode ver, iniciamos nossa variável de controle F(x) em zero:CONTROLE_FX = 0

E, com a apertar das setas direcionais, essa variável vai sendo incrementada ou decrementada.If KeyDown(205) Then CONTROLE_FX = CONTROLE_FX - 2

If KeyDown(203) Then CONTROLE_FX = CONTROLE_FX + 2

O que cria a ilusão de movimento são as linhas de código abaixo, pois as imagens não são exibidas em um local fixo, mas tem seu local de exibição derivado da soma da posição inicial + a variável FX que varia de valor. Assim a posição em que será exibida varia junto com o valor dessa variável.DrawImage arvore, 170 + CONTROLE_FX, 140DrawImage arvore, 700 + CONTROLE_FX, 120

Agora que você já sabe como funciona, vamos colocar umas árvores e arbustos no programa de animação do robô para dar um pouco mais de realismo.

Exemplo 33Graphics 800, 600SetBuffer BackBuffer()robo = LoadAnimImage("midia\robo2c.bmp", 50, 80, 0, 16)arvore = LoadImage("midia\Arvore.bmp")arbusto = LoadImage("midia\arbusto.bmp")MaskImage arbusto, 170,170, 170frame = 0contador = 0anima = 1 ; 1 = parado direita, ; 2 = andando direita; 3 = parado esquerda; 4 = amdando esquerdapx = 375py = 260CONTROLE_FX = 0While Not KeyHit(1) ;------------------ input -------------------- If KeyDown(205)If anima <> 2anima = 2contador = 0Frame = 0EndIfCONTROLE_FX = CONTROLE_FX - 2 ElseIf anima = 2 Then anima = 1 EndIfIf KeyDown(203)If anima <> (4)

Page 11: Jogos 2D

anima = 4contador = 0Frame = 8EndIfCONTROLE_FX = CONTROLE_FX + 2 ElseIf anima = 4 Then anima = 3 EndIf;------------------ logica ------------------ If anima = 2contador = contador + 1If contador = 5contador = 0frame = frame + 1EndIfIf frame = 8 Then frame = 0EndIfIf anima = 4contador = contador + 1If contador = 5contador = 0frame = frame + 1EndIfIf frame = 16 Then frame = 8EndIf;-------------------Exibe -------------------;arvoresDrawImage arvore, -300 + CONTROLE_FX, 100DrawImage arvore, 70 + CONTROLE_FX, 140DrawImage arvore, 700 + CONTROLE_FX,120DrawImage arbusto, -100 + CONTROLE_FX, 210DrawImage arbusto, 300 + CONTROLE_FX, 190;roboIf anima = 1 DrawImage robo, px, py, 0If anima = 3 DrawImage robo, px, py, 8If anima = 2 DrawImage robo, px, py, frameIf anima = 4 DrawImage robo, px, py, frame FlipClsWend

Vamos ver o que mudou nesse programa.arvore = LoadImage("midia\Arvore.bmp")arbusto = LoadImage("midia\arbusto.bmp")MaskImage arbusto 170 170, 170

Agora estamos carregando arvores e arbustos também. Aplicamos uma máscara na imagem do arbusto que possui um fundo cinza.CONTROLE_FX = 0

Agora o programa também tem uma variável F(x).CONTROLE_FX = CONTROLE_FX + 2 CONTROLE_FX = CONTROLE_FX - 2

Dentro dos controles de input de teclado estamos alterando o valor da variável de controle de rolagem de objetos F(x) para criar a sensação de movimento.

;-------------------Exibe -------------------;arvoresDrawImage arvore, -300 + CONTROLE_FX, 100DrawImage arvore, 70 + CONTROLE_FX, 140DrawImage arvore, 700 + CONTROLE_FX, 120DrawImage arbusto, -100 + CONTROLE_FX, 210DrawImage arbusto, 300 + CONTROLE_FX, 190

Como a vegetação é uma composição de fundo, devemos exibi-las antes do robô, senão elas ficariam sobre ele na ordem de empilhamento. Animação com 4 câmeras As animações em quatro câmeras são mais comuns em jogos no estilo rpg, onde existe uma certa liberdade em explorar o território. As animações são criadas a partir de quatro ângulos de visão que normalmente são para direita, para a esquerda, para cima e para baixo. As câmeras normalmente também possuem uma certa inclinação, pegando uma visão a partir de cima do personagem, algo em torno de um ângulo de 30 a 45 graus, pois esses jogos possuem perspectiva, já que o personagem se move para todas as regiões de um mapa.

Essa imagem acima representa os 4 eixos de animação de um jogo com animação em quatro câmeras. Um exemplo recente desse tipo de jogo é o Magnífico RPG Fire Emblem: The Sacred Stones, da Nintendo, desenvolvido para o Game Boy Advanced.Jogos em 4 câmeras também podem ser feitos com a técnica de isometria e nesse caso as câmeras e os movimentos são diagonais. Um exemplo desse caso é o famoso jogo Final Fantasy Tatics.Veja abaixo um arquivo animado com 4 câmeras para um personagem.

Page 12: Jogos 2D

No exemplo abaixo, usamos a técnica para animar um personagem em 4 câmeras.Exemplo 34Graphics 800, 600SetBuffer BackBuffer()robo = LoadAnimImage("midia\robo4c.png", 50, 50, 0, 32)MaskImage robo, 255, 255, 255frame = 0contador = 0anima = 1 ; 1 = parado direita ; 2 = andando direita; 3 = parado esquerda ; 4 = andando esquerda ; 5 = parado acima ; 6 = andando acima; 7 = parado abaixo ; 8 = andando abaixopx = 375py = 260While Not KeyHit(1) ;------------------ input -------------------- If KeyDown(205)If anima <> 2anima = 2contador = 0Frame = 0EndIfElseIf anima = 2 Then anima = 1 EndIf If KeyDown(203)If anima <> (4)anima = 4contador = 0Frame = 8EndIfElseIf anima = 4 Then anima = 3 EndIfIf KeyDown(200)If anima <> (6)anima = 6contador = 0Frame = 16EndIfElseIf anima = 6 Then anima = 5 EndIfIf KeyDown(208)If anima <> (8)anima = 8contador = 0Frame = 24

EndIfElseIf anima = 8 Then anima = 7 EndIf;------------------ logica ------------------ If anima = 2contador = contador + 1If contador = 5contador = 0frame = frame + 1EndIfIf frame = 8 Then frame = 0EndIfIf anima = 4contador = contador + 1If contador = 5contador = 0frame = frame + 1EndIfIf frame = 16 Then frame = 8EndIfIf anima = 6contador = contador + 1If contador = 5contador = 0frame = frame + 1EndIfIf frame = 24 Then frame = 16EndIfIf anima = 8contador = contador + 1If contador = 5contador = 0frame = frame + 1EndIfIf frame = 32 Then frame = 24EndIf;-------------------Exibe ------------------- If anima = 1 DrawImage robo, px, py, 0If anima = 3 DrawImage robo, px, py, 8If anima = 5 DrawImage robo, px, py, 16If anima = 7 DrawImage robo, px, py, 24If anima = 2 DrawImage robo, px, py, frameIf anima = 4 DrawImage robo, px, py, frameIf anima = 6 DrawImage robo, px, py, frameIf anima = 8 DrawImage robo, px, py, frameFlipCls Wend

Perceba que esse programa não possui nenhuma novidade em relação ao programa que manipula a animação em duas câmeras. Só adicionamos novos controles semelhantes aos anteriores, para manipular as animações adicionadas.Como essa animação é maior, estamos usando o formato de arquivo .png que é compactado. Para trabalhar com esse arquivo temos que definir a área de

Page 13: Jogos 2D

transparência e salvar sem suavizações, para que a imagem não seja alterada, senão não é possível fazer a mascara de imagem (o colorkey). Animações com 8 câmerasOs melhores jogos 2D são criados baseados em um sistema de animação com 8 câmeras. Alguns deles são tão bem desenvolvidos, como Age Of Empires e Diablo II, que a maioria das pessoas não sabem distinguir se é um jogo 2D ou se é um jogo 3D. Você saberia distinguir? É bem simples, em um jogo 2D só personagens possuem efeito de animação em vários ângulos. Em um jogo 3D todo o cenário é capaz de rotacionar e não apenas os personagens.O gráfico abaixo mostra os ângulos de animação em um sistema com 8 câmeras, ou 8 direções.

Um jogo nesse estilo tem uma grande demanda de memória, pois cada personagem tem todas as suas animações em oito perspectivas diferentes. Veja abaixo um arquivo com animações em 8 câmeras.

O exemplo abaixo roda todas as animações dessa imagem animada.

Exemplo 35Graphics 800, 600robo= LoadAnimImage("midia\robo8c.png", 50, 50, 0, 64)MaskImage robo, 255, 255, 255frame = 0contador = 0While Not KeyHit(1)contador = contador + 1If contador = 5contador = 0frame = frame + 1End IfIf frame = 64 Then frame = 0DrawImage robo, 375, 275, frameFlipClsWend

Para fazer o controle da animação via teclado devemos implementar uma técnica diferente, pois as animações diagonais dependem de duas teclas e as animações dos quatro eixos principais, apenas de uma tecla. Mas todas as animações vão possuir pelo menos 1 tecla em comum com outras duas animação, por exemplo, tanto o movimentar a direita, como o movimentar a direita e acima e o movimentar a direita e abaixo dependem da tecla de seta à direita. Isso pode causar um colapso na animação se não for bem administrado.Vamos abaixo dar um exemplo de uma técnica errada, causando um colapso na animação. Esse exemplo tenta implementar as três animações descritas acima. Exemplo 36 (Erro Lógico)Graphics 800, 600robo= LoadAnimImage("midia\robo8c.png", 50, 50, 0, 64)MaskImage robo, 255, 255, 255frame = 0contador = 0anima = 0While Not KeyHit(1)contador = contador + 1If contador = 5contador = 0frame = frame + 1End IfIf KeyDown(205)If anima <> (1)anima = 1contador = 0Frame = 0EndIf If frame > 7 Then frame = 0 ElseIf anima = 1 Then anima = 0 EndIfIf KeyDown(205) And KeyDown(208)If anima <> (2)anima = 2contador = 0

Page 14: Jogos 2D

Frame = 8EndIf If frame > 15 Then frame = 8 ElseIf anima = 2 Then anima = 0 EndIfIf KeyDown(205) And KeyDown(200)If anima <> (8)anima = 8contador = 0Frame = 56EndIf If frame > 63 Then frame = 56 ElseIf anima = 8 Then anima = 0 EndIfIf anima = 0 Then DrawImage robo, 375, 275, 0If anima = 1 Then DrawImage robo, 375, 275, frameIf anima = 2 Then DrawImage robo, 375, 275, frameIf anima = 8 Then DrawImage robo, 375, 275, frameFlipClsWend

Como você já deve ter percebido, só a animação para a direita funciona. O problema dessa implementação é que verificamos se a tecla 205 está pressionada em 3 locais. Quando pressionamos as teclas para direita e para cima, é liberado o movimento diagonal nesse sentido, mas também é liberado o movimento para a direita, assim a animação nunca passa do primeiro frame de cada animação. No entanto, quando só pressionamos a tecla para direita, só a animação para a direita é liberada, por isso ela é a única que funciona.Veja como foi feita essa lógica erradaCondição1;Condição2;Condição3;If Condição1 => ação1If Condição2 e Condição1 => ação2If Condição3 e Condição1 => ação3Veja como seria o CorretoIf Condição1If Condição2=> ação2Else If Condição3=> ação3Else=> ação1EndIfEndIfNesse sistema acima cada condição será verificada apenas 1 vez e não irá causar

colapso na animação. Quer a prova? Vamos lá! Execute o próximo exemplo.Exemplo 37Graphics 800, 600SetBuffer BackBuffer()robo= LoadAnimImage("midia\robo8c.png", 50, 50, 0, 64)MaskImage robo, 255, 255, 255frame = 0contador = 0anima = 0While Not KeyHit(1);--------------- CONTROLADOR ----------------contador = contador + 1If contador = 5contador = 0frame = frame + 1End If;------------------INPUTS--------------------If KeyDown(205)If KeyDown(208); DIREITA E ABAIXOIf anima <> (2)anima = 2contador = 0Frame = 8EndIfIf frame > 15 Then frame = 8 Else If KeyDown(200);DIREITA E ACIMAIf anima <> (8)anima = 8contador = 0Frame = 56EndIfIf frame > 63 Then frame = 56 Else;DIREITAIf anima <> (1)anima = 1contador = 0Frame = 0EndIfIf frame > 7 Then frame = 0 EndIfElseanima = 0EndIf;------------------------EXIBE---------------------- If anima = 0 Then DrawImage robo, 375, 275, 0If anima > 0 Then DrawImage robo, 375, 275, frameFlipClsWend

Agora funcionou direitinho... Que tal mais um exemplo com as oito animações?Exemplo 38Graphics 640, 480, 16, 1SetBuffer BackBuffer()robo= LoadAnimImage("midia\robo8c.png", 50, 50, 0, 64)

Page 15: Jogos 2D

MaskImage robo, 255, 255, 255frame = 0contador = 0anima = 0parado = 0While Not KeyHit(1);--------------- CONTROLADOR ----------------contador = contador + 1If contador = 5contador = 0frame = frame + 1 End If;------------------INPUTS-------------------- TECLA_DIREITA = KeyDown(205)TECLA_ESQUERDA = KeyDown(203)TECLA_ACIMA = KeyDown(200)TECLA_ABAIXO = KeyDown(208)If TECLA_DIREITA If TECLA_ABAIXO ; DIREITA E ABAIXOIf anima <> (2)anima = 2contador = 0Frame = 8EndIfIf frame > 15 Then frame = 8 Else If TECLA_ACIMA ;DIREITA E ACIMAIf anima <> (8)anima = 8contador = 0Frame = 56EndIfIf frame > 63 Then frame = 56 Else;DIREITAIf anima <> (1)anima = 1contador = 0Frame = 0EndIfIf frame > 7 Then frame = 0 EndIfEndIf; ACIMAIf TECLA_ACIMA If Not TECLA_ESQUERDAIf Not TECLA_DIREITAIf anima <> (7)anima = 7contador = 0Frame = 48EndIfIf frame > 55 Then frame = 48EndIfEndIfEndIf ; ABAIXOIf TECLA_ABAIXO If Not TECLA_ESQUERDAIf Not TECLA_DIREITAIf anima <> (3)anima = 3contador = 0Frame = 16

EndIfIf frame > 23 Then frame = 16EndIfEndIfEndIf If TECLA_ESQUERDAIf TECLA_ABAIXO ;ESQUERDA E ABAIXOIf anima <> (4)anima = 4contador = 0Frame = 24EndIfIf frame > 31 Then frame = 24 Else If TECLA_ACIMA ;ESQUERDA E ACIMAIf anima <> (6)anima = 6contador = 0Frame = 40EndIfIf frame > 47 Then frame = 40 Else;ESQUERDAIf anima <> (5)anima = 5contador = 0Frame = 32EndIfIf frame > 39 Then frame = 32 EndIf EndIf;SE NENHUMA TECLA FOI PRESSIONADAIf Not TECLA_ACIMAIf Not TECLA_ABAIXO If Not TECLA_ESQUERDAIf Not TECLA_DIREITAIf anima > 0parado = (anima * 8) - 8 EndIfAnima = 0EndIfEndIfEndIfEndIf;------------------------EXIBE---------------------- If anima = 0 Then DrawImage robo, 300, 200, paradoIf anima > 0 Then DrawImage robo, 300, 200, frameFlipClsWend

Explicando as novidades...parado = 0

Criamos uma variável para controlar o frame do estado de animação parado (anima=0) dinamicamente. TECLA_DIREITA = KeyDown(205)TECLA_ESQUERDA = KeyDown(203)TECLA_ACIMA = KeyDown(200)TECLA_ABAIXO = KeyDown(208)

Ao invés de ficar testando várias vezes o buffer de teclado, o que consumiria recursos de processamento, fazemos apenas um teste

Page 16: Jogos 2D

por ciclo para cada tecla e colocamos o resultado em variáveis.If TECLA_DIREITA If TECLA_ABAIXO

Para controlar as animações, agora testamos essas variáveis para teclas e não o buffer do teclado.; ACIMAIf TECLA_ACIMA If Not TECLA_ESQUERDAIf Not TECLA_DIREITAIf anima <> (7)anima = 7contador = 0Frame = 48EndIfIf frame > 55 Then frame = 48EndIfEndIfEndIf

Para acionarmos a animação para cima, a tecla de seta para cima deve estar pressionada e as teclas para esquerda e para direita não podem estar, senão teríamos aquele velho problema de colapso de animação. Por isso tivemos que fazer esse conjunto de verificações if aninhados.; ABAIXOIf TECLA_ABAIXO If Not TECLA_ESQUERDAIf Not TECLA_DIREITAIf anima <> (3)anima = 3contador = 0Frame = 16EndIfIf frame > 23 Then frame = 16EndIfEndIfEndIf

O mesmo é verdadeiro para animação para baixo.;SE NENHUMA TECLA FOI PRESSIONADAIf Not TECLA_ACIMAIf Not TECLA_ABAIXO If Not TECLA_ESQUERDAIf Not TECLA_DIREITAIf anima > 0parado = (anima * 8) - 8 EndIfAnima = 0EndIfEndIfEndIfEndIf

O ultimo problemas a ser vencido é a implementação do estado parado. Para ativarmos este estado tivemos que fazer 4 verificações if para saber se nenhuma das quatro teclas de movimento estavam sendo pressionadas. O ultimo desafio foi colocar a imagem parada no mesmo sentido que estava andando. Para isso colocamos uma

verificação se é o primeiro momento como parada “If anima > 0”, isso é, se antes de agora, ela estava sendo animada. Se isso é verdadeiro fazemos o frame de animação parado, que se chama “parado” igual a uma fórmula que fornece o primeiro frame da animação anterior “parado = (anima * 8) - 8”. Isso faz com que o robô fique parado no mesmo sentido que estava andando.Com isso aprendemos praticamente todos os fundamentos de técnicas de animações de personagens 2D. Mais pela frente, nós vamos estudar de maneira mais profunda a animação de cenários com técnicas de scrolling e mapas de tiles. COLISÔESA detecção de colisões é algo essencial na maioria dos jogos. É por meio dela que um personagem é impedido de atravessar paredes, perde vida ao receber um tiro, salta, pega itens, etc.Em imagens mais simples, como um cubo ou um círculo as colisões podem ser verificadas matematicamente, mas nas mais complexas, com formas mais singulares, fica inviável aplicar esse tipo de técnica com precisão.O Blitz3D possui um poderoso e veloz sistema de colisões que funciona por meio de verificação de pixels das imagens. Assim ele vai fazer a verificação de colisões apenas da área da imagem que está sendo exibida na tela, desprezando a área invisível da máscara.Colisões SimplesPor meio do comando ImagesOverlap o Blitz3D fornece um sistema de colisões de imagens muito rápido, mas sem precisão de transparência. Para que esse comando funcione devemos passar para ele 6 parâmetros: a primeira imagem a ser verificada, sua posição x, sua posição y, a segunda imagem, a sua posição x e sua posição y. Portanto, esse comando possui a seguinte assinatura:ImagesOverlap( imagem1, x1, y1, imagem2, x2, y2)Veja no exemplo abaixo o uso que ImagesOverlap. Use o mouse para controlar a posição da bola. Como você poderá perceber, esse comando não leva em consideração a transparência da imagem e por isso não oferece colisões precisas.Exemplo 39Graphics 800, 600SetBuffer BackBuffer()arvore = LoadImage("midia\Arvore.bmp")

Page 17: Jogos 2D

bola = LoadImage("midia\bola.png")While Not KeyHit(1) px = MouseX()py = MouseY()If ImagesOverlap(bola, px, py, arvore, 100, 100) Text 300, 20, "-X- COLIDINDO -X-"EndIfDrawImage arvore, 100, 100DrawImage bola, px, py Flip Cls Wend

Colisões PrecisasSe desejarmos um perfeito controle de colisões então devemos fazer uso do comando ImagesCollide. Esse comando oferece uma precisão por pixels e leva em consideração a transparência da imagem.A assinatura desse comando é um pouco mais complexa, pois também devemos colocar o frame da animação atual da imagem. Se a imagem não é animada, deixe como “0”. Veja:ImagensCollide(imagem1, x1, y1, frame, imagem2, x2, y2, frame)Como cada frame de uma imagem animada pode possuir objetos totalmente diferentes, é necessário fornecer esse parâmetro adicional.Veja o exemplo anterior, mas agora com maior precisão graças ao uso de ImagesCollide.Exemplo 40Graphics 800, 600SetBuffer BackBuffer()arvore = LoadImage("midia\Arvore.bmp")bola = LoadImage("midia\bola.png")While Not KeyHit(1) px = MouseX()py = MouseY()If ImagesCollide(bola, px, py, 0, arvore, 100, 100, 0) Text 300, 20, "-X- COLIDINDO -X-"EndIfDrawImage arvore, 100, 100DrawImage bola, px, py Flip Cls Wend

Veja que usamos o frame “O” de cada imagem:If ImagesCollide(bola, px, py, 0, arvore, 100, 100, 0) Agora sim as colisões estão perfeitas...Colisões com imagens animadasPara conseguirmos um efeito perfeito de colisão com imagens animadas basta usarmos o mesmo frame da animação para a verificação da colisão.

O exemplo abaixo faz isso dinamicamente. Como temos uma imagem com animação e possuímos uma variável “frame” que contem o frame atual da animação, é só usar essa variável como parâmetro, tanto para exibir a imagem, como para dizer com qual frame se dará a colisão.Exemplo 41 Graphics 800, 600SetBuffer BackBuffer()bola = LoadImage("midia\bola.png")ponta = LoadAnimImage("midia\ponta.png", 100,40,0,4)frame = 0controle = 0While Not KeyHit(1) controle = controle + 1If controle = 20frame = frame + 1controle = 0EndIfIf frame = 4 Then frame = 0 If ImagesCollide(bola, 300, 250, 0, ponta, 221, 253, frame) Text 300, 20, "-X- COLIDINDO -X-"EndIfDrawImage ponta, 221, 253, frameDrawImage bola, 300, 250 Flip Cls Wend

Veja a baixo as linhas correspondentes a essa tecnica:ImagesCollide(bola, 300, 250, 0, ponta, 221, 253, frame)Aqui usamos à variável frame para testar dinamicamente o frame atual da animação.DrawImage ponta, 221, 253, frameA mesma variável é usada para exibir a animação, por isso temos um sincronismo da exibição coma a física de colisão.Detectando tirosO que é realmente importante saber em técnicas de tiro e suas relações com colisões é que ao ser detectada uma colisão de um tiro com um personagem, devem ser disparados eventos relativos a esse fato. Por exemplo, um tiro normalmente destrói, mata um personagem, explode um objeto, contabiliza pontos...Veja o exemplo abaixo.Exemplo 42Graphics 800, 600SetBuffer BackBuffer()tiro = LoadImage("midia\foguete.png")barril = LoadAnimImage("midia\barril.png", 50,40,0,4)MaskImage barril, 255, 255, 255frame = 0controle = 0STATE = 0

Page 18: Jogos 2D

PONTOS = 0TOTAL_TIROS = 0TIROS_CERTOS = 0px = 400py = 500barrilx = Rnd(50, 750)barrily = Rnd(50, 200)While Not KeyHit(1) ;STATE = 0 normal;STATE = 1 atirando;STATE = 2 explodindoIf STATE = 0px = MouseX()EndIfIf STATE = 1py = py - 2If py < 0 py = 500STATE = 0EndIfEndIfIf STATE = 2controle = controle + 1If controle = 5frame = frame + 1controle = 0EndIfIf frame = 4 frame = 0 STATE = 0barrilx = Rnd(50, 750)barrily = Rnd(20, 200)EndIfEndIf If ImagesCollide(tiro, px, py, 0, barril, barrilx, barrily, 0) STATE = 2py = 500TIROS_CERTOS = TIROS_CERTOS + 1PONTOS = PONTOS + (205 - barrily)EndIf If MouseHit(1) If STATE = 0STATE = 1TOTAL_TIROS = TOTAL_TIROS + 1EndIfEndIfDrawImage barril, barrilx, barrily, frameDrawImage tiro, px, py Text 100, 0, "PONTOS: " + PONTOSText 350, 0, "TOTAL DE TIROS: " + TOTAL_TIROSText 540, 0, "TIROS NO ALVO: " + TIROS_CERTOSFlip Cls Wend

Explicando...STATE = 0PONTOS = 0TOTAL_TIROS = 0TIROS_CERTOS = 0px = 400py = 500

Criamos várias variáveis de controle. A variável STATE dirá qual é o estado atual do

jogo, se esta em estado normal, com tiro em ação ou explodido. Nesse exemplo, colocamos todos esses eventos como sincrônicos, mas em um jogo real eles são assíncronos, tendo suas linhas de eventos independentes e variáveis próprias para gerenciar esses eventos.Também criamos variáveis para pontos, total de tiros disparados, tiros certos e para a posição atual do tiro.barrilx = Rnd(50, 750)barrily = Rnd(50, 200)

A posição do barril será aleatória, dentro de certos limites.If STATE = 0px = MouseX()EndIf

E estivermos em estado normal do jogo, a posição x do tiro, isso é, do nosso foguete é a posição x do mouse. Assim, enquanto não está em estado de disparado, o foguete obedece ao mouse.If STATE = 1py = py - 2If py < 0 py = 500STATE = 0EndIfEndIf

O estado 1 é de tiro se movendo. Por isso a variável da posição y do tiro está sendo decrementada “py = py - 2”, isso é, o tiro se move para cima. Se o tiro chegou até em cima da tela “If py < 0”, então é sinal que ele errou o alvo, assim colocamos ele de volta a posição inicial “py = 500” e mudamos o status do jogo para normal “STATE = 0” para que o mouse volte a controlar o mesmo.If STATE = 2controle = controle + 1If controle = 5frame = frame + 1controle = 0EndIfIf frame = 4 frame = 0 STATE = 0barrilx = Rnd(50, 750)barrily = Rnd(20, 200)EndIfEndIf

Esse bloco de código é o controle do estado 2 isso é, da animação da explosão. Veja as pastes mais importantes desse bloco:controle = controle + 1

Esse é o contador do tempo da animação.If controle = 5frame = frame + 1controle = 0EndIf

Page 19: Jogos 2D

Se o contador chegar a 5 avança uma frame da animação e reinicia o contadorIf frame = 4 frame = 0 STATE = 0barrilx = Rnd(50, 750)barrily = Rnd(20, 200)EndIf

Se a animação chegou até o ultimo frame reiniciamos o frame “frame = 0” para que a imagem a ser exibida seja a imagem inicial. Colocamos o estado em 0 “STATE = 0”, pois volta ao estado inicial. Como o barril foi destruído, criamos um novo barril, nesse caso apenas sorteamos novas posições para ele “barrilx=Rnd(50,750)” e “barrily=Rnd(20,200)”.If ImagesCollide(tiro, px, py, 0, barril, barrilx, barrily, 0) STATE = 2py = 500TIROS_CERTOS = TIROS_CERTOS + 1PONTOS = PONTOS + (205 - barrily)EndIf

Aqui está o núcleo principal dos eventos, a detecção da colisão. Veja que a posição da imagem tiro é dada pelas variáveis de controle da sua posição “(tiro, px, py, 0 ...”, pois ele está em movimento. Se a colisão foi detectada primeiro colocamos o estado do jogo para o estado de explodir o barril “STATE = 2” e reiniciamos a posição do tiro “py=500”. Também contabilizamos os tiros certos “TIROS_CERTOS=TIROS_CERTOS+1” e em seguida os pontos “PONTOS=PONTOS+(205-barrily)”. Veja que os pontos são dados por uma fórmula onde quanto mais acima na tela estiver o barril, maior será a pontuação.If MouseHit(1) If STATE = 0STATE = 1TOTAL_TIROS = TOTAL_TIROS + 1EndIfEndIf

Aqui disparamos o tiro. Para que ele seja disparado são necessárias 2 condições: a primeira é que o botão esquerdo do mouse seja pressionado “If MouseHit(1)” e que o jogo esteja no estado inicial de espera “If STATE = 0”. Sendo essas condições vencidas, então colocamos o tiro em movimento acionando o estado 1 “STATE = 1” e contabilizamos mais um tiro disparado “TOTAL_TIROS = TOTAL_TIROS + 1”.DrawImage barril, barrilx, barrily, frameDrawImage tiro, px, py

Nessas linhas exibimos as imagens.Text 100, 0, "PONTOS: " + PONTOS

Text 350, 0, "TOTAL DE TIROS: " + TOTAL_TIROSText 540, 0, "TIROS NO ALVO: " + TIROS_CERTOS

E aqui as informações do jogo.Restringindo o movimentoOutra funcionalidade importante de um sistema de colisões e restringir os movimentos de um personagem. Quando o personagem se choca com a parede ele deve parar de se mover, não pode atravessar a mesma.Isso parece simples de ser criado, seria só verificar a colisão e se estiver colidindo, bloquear o movimento... Mas não é bem assim. Se fizéssemos dessa forma o personagem jamais andaria novamente, ficaria colado na parede. A restrição deve ser feita de forma que ele seja impedido de andar para onde existe colisão.Agora parece complexo... Mas calma existe um algoritmo simples para implementar isso. Devemos ter duas variáveis de posição x e y. Uma variável marcará sempre a posição anterior do personagem a outra guarda a posição depois de ele se mover. Se houve colisão, dizemos que a posição atual é igual a posição antes do movimento, assim a imagem fica parada na posição anterior. Se não existe colisão, ela se move normalmente.Veja a lógica:ultimapx = pxultimapy = pymovepx()movepy()If colide()px = ultimapxpy = ultimapy End if ExibirImagens()A primeira coisa a se fazer nessa lógica é guardar uma cópia da posição do objeto antes de ele mover.ultimapx = pxultimapy = pydepois disso podemos fazer os inputs de teclado e mover o mesmo.movepx()movepy()O passo seguinte é verificar há uma colisão.If colide()E se colidiu, volta o mesmo à sua posição antes de ser movido.px = ultimapxpy = ultimapy

Page 20: Jogos 2D

Simples não é mesmo? O exemplo abaixo mostra como isso é feito na prática.Exemplo 43Graphics 800, 600SetBuffer BackBuffer()laberinto = LoadImage("midia\laberinto.png")bola = LoadImage("midia\bola2.png")px = 400py = 300While Not KeyHit(1) ultimapx = pxultimapy = pyIf KeyDown(200) Then py = py - 2If KeyDown(208) Then py = py + 2If KeyDown(203) Then px = px - 2If KeyDown(205) Then px = px + 2If ImagesCollide(laberinto,150,50,0, bola,px,py,0)px = ultimapxpy = ultimapyEndIfDrawImage laberinto, 150, 50DrawImage bola, px, py Flip Cls Wend

SCROLLINGA técnica de scrolling nada mais é do que criar um efeito de continuidade do cenário por meio de rolagem e repetição. Podemos criar esse efeito por meio da repetição de uma imagem que seja o fundo do cenário ou por meio de tiles que vão compor esse cenário.Na maioria das engines e programas, a montagem da tela se dá de forma manual, mas o Blitz3D possui o comando TileImage que faz esse trabalho duro pra você de composição do fundo. Dessa forma você só precisa se preocupar em fazer a rolagem.Veja abaixo um exemplo onde o Blitz3D cria um fundo de cenário a partir de um tile de apenas 17 x 17.Exemplo 44Graphics 640,480,16SetBuffer BackBuffer()tile=LoadImage("midia\tile.png")While Not KeyHit(1)TileImage tile, 0, 0Flip Wend

Veja que apenas um comando preencheu a tela toda.TileImage tile, 0, 0

Esse comando preenche toda a tela a partir da imagem passada como parâmetro. Sua assinatura é:TileImage imagem, posiçãox, posiçãoy, frame

As posições x e y não são de onde a exibição será iniciada, mas de offset da imagem, isso é, de encaixe, como elas se posicionam na tela, pois sempre a tela toda será preenchida por esse comando.O parâmetro de frame é opcional, e deve ser usado para imagens animadas.Horizontal scrollingHorizontal scrolling é a técnica de rolagem do cenário no eixo horizontal. Isso é muito usando em jogos 2D de aventura como Mario ou Sonic, onde o personagem só se movimenta para frente ou para traz e os cenários são grandes.No próximo exemplo, fazemos um Scrolling Horizontal a partir de tiles.Exemplo 45Graphics 640,480,16SetBuffer BackBuffer()tile=LoadImage("midia\tile.png")While Not KeyHit(1)x = x + 1TileImage tile, 0 + x, 0Flip Wend

Mas o caso mais típico de uso desse tipo de rolagem é com cenários como o do exemplo abaixo.Exemplo 46Graphics 640,480,16SetBuffer BackBuffer()tile=LoadImage("midia\tela.jpg")While Not KeyHit(1)x = x + 1TileImage tile, 0 - x, 0Flip Wend

Se adicionarmos uma imagem de um personagem ai fica muito bom o aspecto...Exemplo 47Graphics 640,480,16SetBuffer BackBuffer()tile = LoadImage("midia\tela.jpg")robo = LoadAnimImage("midia\robo2c.bmp", 50, 80, 0, 8)While Not KeyHit(1)contador = contador + 1If contador = 5contador = 0frame = frame + 1If frame = 8 Then frame = 0EndIffx = fx - 2TileImage tile, fx, 0DrawImage robo, 200, 325, frame Flip Cls Wend

Vertical ScrollingA técnica de Vertical Scrolling normalmente é usada para criar jogos de nave ou de carros

Page 21: Jogos 2D

que se movimentam no sentido de baixo para cima da tela. Para criar esse efeito é só deixar a posição y de exibição do tile como dinâmica.Veja o exemplo abaixo.Exemplo 48Graphics 640,480,16SetBuffer BackBuffer()tile = LoadImage("midia\tile.png")missil = LoadImage("midia\f15.bmp")MaskImage missil, 255, 255, 255MoveMouse(320,400)While Not KeyHit(1) FY = FY + 2TileImage tile, 0, FYpx = MouseX()DrawImage missil, px, 350Flip Cls Wend

Double ScrollingPara fazer um Double Scrolling basta colocar os dois parâmetros de localização do tile como dinâmicos: Fx e Fy. Essa técnica é usada em jogos que possuem liberdade total de movimento e usam um tile único como fundo básico de cenário.Exemplo 49Graphics 640,480,16SetBuffer BackBuffer()tile = LoadImage("midia\tile.png")MoveMouse(320,400)While Not KeyHit(1) If KeyDown(200) Then Fy = Fy + 1If KeyDown(208) Then Fy = Fy - 1If KeyDown(203) Then Fx = Fx + 1If KeyDown(205) Then Fx = Fx - 1TileImage tile, Fx, FyColor 0,0,0Text 180, 20, "USE TECLAS DIRECIONAIS PARA MOVER"Color 255, 0, 0Text 181, 21, "USE TECLAS DIRECIONAIS PARA MOVER"Flip Cls Wend

Os demais elementos que irão compor o cenário deverão ser posicionados manualmente e ter sua posição de exibição ajustada por Fx e Fy. Veja, no exemplo abaixo, como compor e controlar objetos do cenário.Exemplo 50Graphics 640,480,16SetBuffer BackBuffer()tile = LoadImage("midia\tile.png")arvore = LoadImage("midia\arvore.bmp")MoveMouse(320,400)While Not KeyHit(1) If KeyDown(200) Then Fy = Fy + 1If KeyDown(208) Then Fy = Fy - 1If KeyDown(203) Then Fx = Fx + 1

If KeyDown(205) Then Fx = Fx - 1TileImage tile, Fx, FyDrawImage arvore, 20 + Fx, -50 + FyDrawImage arvore, 550 + Fx, 200 + FyDrawImage arvore, 100 + Fx, 400 + FyColor 0,0,0Text 180, 20, "USE TECLAS DIRECIONAIS PARA MOVER"Color 255, 0, 0Text 181, 21, "USE TECLAS DIRECIONAIS PARA MOVER"Flip Cls Wend

Parallax ScrollingPor meio dessa técnica conseguimos colocar efeito de perspectiva em um jogo 2D. Não é uma técnica muito comum de ser vista em jogos, mas ela cria um efeito muito interessante, gerando um bom visual. Para fazer um Parallax Scrolling, devemos ter dois, ou mais, objetos de fundo se movendo em velocidades diferentes, gerando uma sensação de profundidade, de um estar em maior distância que o outro.Para gerar esse efeito temos que ter um controle Fx para cada objeto de fundo de distância diferente. Os mais distantes se movem em velocidade menor e os mais próximos em velocidade maior.No exemplo que vamos apresentar usamos o TileImage apenas para compor o fundo da imagem, por se a única coisa que se repete por todo o cenário. Os demais elementos foram exibidos por meio do comando DrawImage.Exemplo 51Graphics 640,480,16SetBuffer BackBuffer()fundo_parado = LoadAnimImage("midia\paralax.png", 50, 50, 0, 1)fundo_paralax1 = LoadImage("midia\paralax.png")Tile = LoadAnimImage("midia\tile2.png", 11, 11, 0, 9)MaskImage tile, 255, 255, 255While Not KeyHit(1) If KeyDown(203) Then Fx# = Fx# + 1If KeyDown(205) Then Fx# = Fx# - 1; FUNDO AZUL - estáticoTileImage fundo_parado, 0, 0,0;MONTANHAS - MOVE 1 DrawImage fundo_paralax1, -400 + Fx#, 340DrawImage fundo_paralax1, 0 + Fx#, 340DrawImage fundo_paralax1, 400 + Fx#, 340DrawImage fundo_paralax1, 800 + Fx#, 340;PISO - MOVE 4 For x = - 4000 To 4200 Step 11px = x + (fx# * 4)If px > -11 And px < 801 DrawImage Tile, px, 440, 0

Page 22: Jogos 2D

DrawImage Tile, px, 451, 8DrawImage Tile, px, 462, 8DrawImage Tile, px, 473, 8EndIf Next;VIGAS - MOVE 2For x = - 1000 To 1200 Step 190px = x + (fx# * 2)If px > -11 And px < 801 DrawImage Tile, px, 418, 7DrawImage Tile, px, 429, 6 EndIf NextFor x = - 1000 To 1200 Step 140px = x + (fx# * 2)If px > -11 And px < 801 DrawImage Tile, px, 418, 7DrawImage Tile, px, 429, 6 EndIf Next;FLOR - MOVE 3For x = - 2000 To 2200 Step 190px = x + (fx# * 3)If px > -11 And px < 801 DrawImage Tile, px, 429, 4 EndIf Next;ARBUSTO - MOVE 4For x = - 2000 To 2200 Step 140px = x + (fx# * 4)If px > -11 And px < 801 DrawImage Tile, px, 429, 5 EndIf NextColor 0,0,0Text 180, 20, "USE TECLAS DIRECIONAIS PARA MOVER"Color 255, 0, 0Text 181, 21, "USE TECLAS DIRECIONAIS PARA MOVER"Flip Cls Wend

Perceba que não existe praticamente nada de novo nesse programa. Todos os comandos e técnicas já foram estudados; é só uma combinação de técnicas. Vejas essas linhas abaixo:DrawImage Tile, px, 440, 0DrawImage Tile, px, 451, 8DrawImage Tile, px, 418, 7DrawImage Tile, px, 429, 6DrawImage Tile, px, 429, 4 DrawImage Tile, px, 429, 5

Veja que todas as imagens têm uma posição x dinâmica “PX”, e todos esses “PX” são dados por uma relação com “FX” que é controlado pelo teclado.px = x + (fx# * 4)px = x + (fx# * 3)px = x + (fx# * 2)

Só o bloco de exibição das montanhas não possui essa fórmula, pois ele usa o FX diretamente. Os demais são movimentados através de uma proporcionalidade em

relação ao FX. O piso, em primeiro plano, move a “fx# * 4”, isso é, quatro vezes mais que as montanhas e possui a mesma velocidade do arbusto. Já as flores, têm sua posição dada pela equação “fx# * 3”, assim ela é três vezes mais velos que as montanhas, mas é mais lenta que o piso e o arbusto.No meio de tudo estão as pilastras, que se movem por “fx# * 2”, sendo o dobro da velocidade das montanhas e a metade da velocidade do piso em primeiro plano.Usamos também um sistema de seleção de exibição de imagem. Como nosso cenário é muito grande não devemos mandar o programa compor imagem que estão fora da área de visão. Por isso verificamos se a imagem está localizada na área do monitor antes de exibir:If px > -11 And px < 801 DrawImage Tile, px, 429, 5 EndIf

6. DeskTroyO objetivo desse módulo é criar um jogo 2d completo. Vamos criar uma versão de breakout. A escolha desse jogo se dá por ele possuir uma lógica simples, ai você poderá ter seu foco mais voltado para compreender a arquitetura do jogo, que é o nosso objetivo.Por meio desse exercício, iremos compreender como estruturar um jogo lógica e fisicamente; como dividir o código fonte em módulos e em arquivos com responsabilidades próprias; e o mais importante: como estruturar logicamente os controles e eventos de um jogo.Primeiro passo: requisitos funcionaisO primeiro passo no desenvolvimento de um software é fazer um levantamento de seus requisitos, isso é, as características que o programa deverá possuir. É nesse momento que se dá a contratação de um projeto de jogo. Decidimos se é viável ou não, e qual será o custo da aplicação. Se nós estamos desenvolvendo um trabalho para um cliente, esse documento de requisitos deverá servir como um contrato de trabalho onde são especificados o que deverá ser desenvolvido e qual será o custo disso.Abaixo temos uma lista não muito rigorosa para esse projeto.Projeto: DeskTroy

Page 23: Jogos 2D

Objetivo: criar um jogo semelhante ao clássico Breakout.1. O jogo consiste de quatro elementos físicos: uma bolinha, um batedor, vários blocos e paredes.2. O jogador terá 3 vidas, ou três bolas de chance.3. Quando a bola passar da linha limite inferior da tela, o jogador perde uma vida(bola).4. As partes superior e lateral da tela são protegidas pela parede, não havendo perda de bolas.5. Na parte superior da tela existem vários bloco que serão destruídos ou perderão vida ao se chocar com a bola. 6. Os blocos serão de três tipos: vermelho, com 3 unidades de vida; azul, com 2 de vida e verde, com 1 de vida.7. Cada vez que a bola se chocar com um bloco, esse perderá uma unidade de vida.8. Quando todos os blocos forem destruídos, passamos de fase.9. Se perdermos as três bolas, o jogo acaba.10. Ao chocar com as paredes e com os blocos, a bola terá sua direção alterada, com um efeito de rebater de bola.11. Ao se chocar com o meio do batedor, a bola também será apenas rebatida. Se a bola se chocar com os pontos mais laterais do batedor, a bola terá seu sentido levemente alterando para o sentido da lateral que se chocou.12. A pontuação se dará por sistema de combos. A primeira vez que a bola se chocar com um bloco renderá 1 ponto, nas vezes seguintes que se chocar, sem ter colidido com o batedor, os pontos serão aumentados progressivamente em uma unidade. Toda vez que a bola se chocar com o batedor, o combo será reiniciado para 1 ponto.13. O jogador controlará o batedor por meio do mouse, em seu eixo x.14. Quando inicia-se uma partida, uma fase ou uma nova bola, a bola fica em estado de espera, acompanhando a posição do batedor, até que o jogador a libere ao clicar o botão esquerdo do mouse.15. Se o jogador apertar o botão “esc” durante a partida, a partida será encerrada e irá para a tela de menu.16. No menu temos as opções de iniciar um jogo e sair da aplicação.17. Quando iniciamos o programa, essa aplicação cairá na tela de menu.

Segundo passo: requisitos de mídiasAgora de nosso cliente já nos passou as características funcionais da aplicação, deverá também nos passar os requisitos artísticos, o storyboard dessa aplicação. No caso, somos nós mesmos que estamos construindo a aplicação por nossa conta, portanto já sabemos o que vamos fazer e isso pode ser dispensável.Se nossos requisitos artísticos forem elaborados por terceiros, também seria necessário elaborar um storyboard para passar para o artista que vai criar as Midas.Terceiro passo: elaboração das mídiaAntes de por as mãos na massa, no software em si, devemos criar as mídias (ou pelo menos algumas delas se estamos desenvolvendo em um processo iterativo com vários integrantes na equipe).Se o jogo é desenvolvido para um cliente deverão ser feitas validações das mídiasAbaixo temos as mídias para esse jogo:Som1.wavSom2.wavSom3.wav

OBS: as imagens não estão no tamanho original, foram ajustadas para dar melhor aspecto visual para o projeto.Quarto passo: designer do projeto de software.Nesse momento fazemos diagramas UML para documentar a estrutura do software. Esses diagramas foram desenvolvidos originalmente para o projeto de software orientado a objetos, mas quem pode o mais, pode o menos, portanto eles servem para representar qualquer coisa, até uma receita de bolo, ou as suas tarefas cotidianas de compras.Como esse projeto é muito simples, usaremos apenas o diagrama de classes. Isso vai ser muito importante para podermos projetar a estrutura do software de uma forma mais simples e rápida. Não recomendo a ninguém a técnica de tirar um software da “cabeça direto para o código” sem ter um projeto antes, isso sempre gera muitos

Page 24: Jogos 2D

problemas e acaba demorando 5 vezes mais fazer. Com um diagrama como o de classes fica muito fácil projetar como o software deverá funcionar, como as peças se encaixam e você terá em apenas uma página a representação de milhares de linhas de código. Isso é impossível de conseguir com o código fonte. Além do mais com isso teremos uma planta de como ele funciona, nos orientado na hora de efetuar a sua implementação. Assim dificilmente ocorrerão erros.O que um diagrama de classes mostra?Um diagrama de classes mostra a estrutura estática de um software, como suas unidade são constituídas e como elas se relacionam. Classes são estruturas de software independentes que possuem em si dados e métodos e os diagramas de classes mostram essas classes com as características de seus dados e métodos e como eles se relacionam com outras classes.O Blitz3d não suporta classes, mas estamos desenvolvendo esse software com uma estrutura lógica semelhante à de classes, onde dividimos o código em arquivos, como se fossem classes, isso é, os dados e os métodos de um mesmo objeto estão em um só arquivo, por isso ele pode ser representado por esse diagrama. Veja a estrutura de representação de uma Classe:

Veja que acima temos o nome da classe, no nosso caso, o nome do arquivo. Logo a baixo, no segundo compartimento, temos as variáveis da classe, em nosso caso, as variáveis do arquivo. E por último, em baixo as funções (métodos) da classe, em nosso caso, as funções do arquivo.DependênciasNo diagrama temos vários tipos de relações entre uma classe e outra. Como o Blitz3D não suporta essas relações por não suportar POO (programação orientada a objetos), em

nossos diagramas teremos apenas uma seta pontilhada, que representa dependência de uma classe em relação à outra. Essa dependência normalmente significa que aquela classe está usando uma função da outra e por isso depende dela. Isso é muito importante para mapear relacionamentos entre os módulos do projeto.Assim, nosso projeto para esse jogo fica com está abaixo:

TiposVeja que os dados e métodos possuem qualificadores como “int”, “void”, “boolean”, “String”. Eles se referem a qual é o tipo dos dados ou a qual é o tipo de valor que uma função aceita ou então retorna. Perceba que para sons e imagens deixei esses qualificadores de tipos como int. Em diagrama de classes de uma linguagem orientadas a objetos normalmente isso seria um objeto, mas como no Blitz3d não possuímos objetos e ele cria ponteiros internos para essas mídias que são

Page 25: Jogos 2D

referenciados por uma variável do tipo int, achamos melhor deixa seus tipos como int.Classe (arquivo) Game.Veja que esse é o nosso arquivo principal que controla todos os demais. Isso é fácil de perceber pois saem setas dele para todos os outros. Isso significa que ele usa funções de todos. Veja que ela possui variáveis internas para controlar Vidas, Scores e Fase e ela controla a Física, Lógica, Colisões e Desenho do programa.Dividindo por objetos e responsabilidadesA melhor maneira de projetar um software é por responsabilidades e objetos. Cada tipo de objeto do jogo deverá ser uma classe assim como entidades lógicas com responsabilidades específicas.ObjetosOs objetos físicos que viraram classes são: bola, bloco, parede e batedor. Os dados desses objetos, assim como as suas funções de controle devem estar em um arquivo para cada um deles.ResponsabilidadesEntidades como Game, menu, Fase1, etc, não são coisas físicas, objetos em si, mas denotam relações complexas com lógica e responsabilidades próprias, por isso devem ser organizadas em um arquivo próprio para cada um. Por exemplo, no controle de fase, cada fase possui uma estrutura de criação, de controle, de descarregar, de verificação, etc, por isso, como existem diversas responsabilidades do mesmo tipo, criamos um arquivo para cada fase onde temos uma concentração lógica e física dos mesmos processos. Isso facilita muito, pois “Sabemos onde cada coisa está e o que cada coisa faz”. Fica mais fácil desenvolver, alterar e corrigir eventuais erros.Quinto Passo: implementação.A nossa penúltima fase é a de implementação da aplicação, a digitação do código do Blitz3D segundo o projeto desenvolvido pelo diagrama UML. O ideal é ter uma cópia desse diagrama em mão, pois ele será o seu mapa, seu guia de bordo, a planta a partir da qual você constrói seu software. Arquivo “Creditos.bb”Perceba que a classe que representa esse arquivo não possui dados, mas apenas dois métodos:Abrir():voidSair():void

O método Abrir() será usado para iniciar a parte visual da aplicação, vai apresentar o projeto. Deverá mostrar na tela, por 5 segundos, a mensagem “Cusos UDCO” e “Blitz3”.O método Sair() é o que irá encerrar a aplicação, dando retorno ao Windows. Ele exibirá os créditos do jogo, isso é sua autoria: nesse projeto o meu nome em outros o seu ou o de sua empresa.Vamos ao código fonte explicado.Veja o código da função de apresentação do programa.Function Creditos_Abrir()tela = LoadImage("midia\udco.jpg")t1 = MilliSecs()While(t2-t1 < 5000)DrawImage tela, 150, 200 ; imagem de 500x400t2 = MilliSecs()FlipClsWendEnd Function

Veja que a função se inicia com o mesmo nome do arquivo “Creditos”, assim sabemos onde ela está fisicamente.tela = LoadImage("midia\udco.jpg")

Carregamos a imagem da apresentação, mas isso não pode ser considerado um dado, por ser uma variável volátil, será destruída no final da função.t1 = MilliSecs()

Essa imagem deverá ser exibida por 5 segundos, portanto pegamos o tempo inicial para calcular esses 5 segundos. O comando Millisecs() dá o tempo do sistema em milésimos de segundos.While(t2-t1 < 5000)

Faremos o sistema de tempo por meio de um loop. A condição é: enquanto tempo 2 “T2” menos tempo 1 “T1” for menor que 5.000, isso é, cinco segundos, pois a função retorna o tempo em milésimos de segundos.DrawImage tela, 150, 200 ; imagem de 500x400

Enquanto não der os 5.000 milésimos de segundos, exibimos a imagem na tela.t2 = MilliSecs()

Atualizamos o tempo 2 a todo momento para ver se chegou aos 5 segundos.Vamos agora ao código da segunda função.Function Creditos_Sair()tela = LoadImage("midia\credito.jpg")t1 = MilliSecs()While(t2-t1 < 5000)DrawImage tela, 150, 270 ; imagem de 500x60t2 = MilliSecs()Flip

Page 26: Jogos 2D

ClsWendEndEnd Function

Veja que essa função é praticamente igual a anterior, exceto nesse trecho:WendEnd

Depois de vencidos os 5 segundos essa função não retorna o controle para a aplicação que a invoca, ela encerra a aplicação. Isso se dá por se tratar da função de sair.CODIGO COMPLETO DE “CREDITOS.BB” Function Creditos_Abrir()tela = LoadImage("midia\udco.jpg")t1 = MilliSecs()While(t2-t1 < 5000)DrawImage tela, 150, 200 ; imagem de 500x400t2 = MilliSecs()FlipClsWend End FunctionFunction Creditos_Sair()tela = LoadImage("midia\credito.jpg")t1 = MilliSecs()While(t2-t1 < 5000)DrawImage tela, 150, 270 ; imagem de 500x60t2 = MilliSecs()FlipClsWendEndEnd Function

Arquivo “Menu.bb”Esse arquivo de código é responsável pela exibição e controle do menu. Vamos a sua explicação.Global menu_IMGlogo Global menu_IMGsair Global menu_IMGjogar Global menu_IMGmousemenu_IMGlogo = LoadImage("midia\logo.jpg")menu_IMGsair = LoadImage("midia\btn_sair.jpg")menu_IMGjogar = LoadImage("midia\btn_jogar.jpg")menu_IMGmouse = LoadImage("midia\mouse.png")

Veja que estamos declarando 4 variáveis globais para receber imagens. Fizemos isso pelo fato de elas serem acessadas dentro de funções, assim só com a definição de global elas seriam acessíveis.Function menu_exibir()iniciar = 0While Not (iniciar)

Essa é nossa função de exibição do menu. Veja que ela será implementada por um loop

While e esse loop será controlado pela chave “iniciar”, que se inicia com o valor “0”.;detectando a posição do mousemx = MouseX()my = MouseY()

Estamos capturando a posição do mouse para exibirmos o cursor do mouse no local correto e detectar sua posição para o controle de botões do menu.;exibindo imagensDrawImage menu_IMGlogo, 150, 200DrawImage menu_IMGsair, 165, 300DrawImage menu_IMGjogar,440, 300

Aqui estamos exibindo as imagens do logo do jogo e dos dois botões.If mx > 165 And mx < 365 If my > 300 And my < 367 Rect 165,300, 200, 65, 0If MouseHit(1) Then Creditos_Sair()EndIfEndIf

Esse trecho de código controla o primeiro botão, o botão sair. A primeira coisa que devemos fazer para isso é verificar se o cursor do mouse está sobre o botão. O primeiro passo é verificarmos a posição x do mouse, se ela está sobre o botão sair “If mx > 165 And mx < 365” o segundo passo é verificar se a posição y dele também coincide “If my > 300 And my < 367”. Se as duas verificações forem verdadeiras, exibimos um retângulo em volta do botão para dar a sensação de foco no botão “Rect 165, 300, 200, 65, 0” e verificamos se o botão esquerdo do mouse é apertado “If MouseHit(1)”. Caso isso seja verdadeiro, invocamos a função para encerrar a aplicação “Creditos_Sair()”.If mx > 440 And mx < 640 If my > 300 And my < 367 Rect 440,300, 200, 65, 0If MouseHit(1) Then iniciar = 1EndIfEndIf

A segunda função de controle de botão tem a mesma estrutura da anterior. Mas no caso desse botão, caso ele seja pressionado, vamos mudar o estado da chave de 0 para 1. Isso faz com que o loop do menu seja encerrado e a execução do código siga o fluxo adiante.DrawImage menu_IMGmouse, mx, my

Ainda dentro do loop, esse comando exibe o cursor do mouse. Deixamos ele por último pelo fato de o mouse ter que ser exibido acima de qualquer outra imagem e a ordem de empilhamento depende da seqüência na qual ordenamos as exibições de imagens.Fase1_carregar()

Page 27: Jogos 2D

Veja que no final da função carregamos a Fase 1 do jogo. É por isso que em nosso diagrama, temos uma dependência de “Menu” em relação a “Fase1”.CODIGO COMPLETO DE “MENU.BB” Global menu_IMGlogo Global menu_IMGsair Global menu_IMGjogar Global menu_IMGmousemenu_IMGlogo = LoadImage("midia\logo.jpg")menu_IMGsair = LoadImage("midia\btn_sair.jpg")menu_IMGjogar = LoadImage("midia\btn_jogar.jpg")menu_IMGmouse = LoadImage("midia\mouse.png")Function menu_exibir()iniciar = 0While Not (iniciar);detectando a posição do mousemx = MouseX()my = MouseY();exibindo imagensDrawImage menu_IMGlogo, 150, 200DrawImage menu_IMGsair, 165, 300DrawImage menu_IMGjogar,440, 300;verificandos se está sobre os botões ; e se clicou neles If mx > 165 And mx < 365 If my > 300 And my < 367 Rect 165,300, 200, 65, 0If MouseHit(1) Then Creditos_Sair()EndIfEndIfIf mx > 440 And mx < 640 If my > 300 And my < 367 Rect 440,300, 200, 65, 0If MouseHit(1) Then iniciar = 1EndIfEndIf; exibir o mouse por ultimoDrawImage menu_IMGmouse, mx, my FlipClsWend Fase1_carregar()End Function

Arquivo “Batedor.bb”Esse arquivo de código é responsável pela exibição e controle do menu. Vamos a sua explicação.Type TBATEDORField imageField pxField pyEnd Type

O batedor é uma entidade física do jogo, por isso criamos um Type para conter suas propriedades. O campo image conterá a imagem do batedor. Os campos px e py a sua posição na tela.Global Batedor.TBATEDOR = New TBATEDOR

Batedor\image = LoadImage("midia\batedor.png")Batedor\px = 350Batedor\py = 520

Acima estamos criando uma instância da estrutura do Type. Veja que a declaramos como Global, pois será acessada de dentro de funções. Estamos também carregando a imagem, e definindo a posição inicial dele.Function Batedor_draw()DrawImage Batedor\image, Batedor\px, Batedor\pyEnd Function

Essa função exibe a imagem do batedor na tela. Veja que usamos os dados internos da instância do Type para exibir a imagem.Function Batedor_colide(obj, px, py)If ImagesCollide(Batedor\image, Batedor\px, Batedor\py, 0, obj, px, py, 0)If (PX - Batedor\px < 20) Then Return 1If (PX - Batedor\px > 60) Then Return 3Return 2EndIfReturn 0End Function

Essa função é responsável pelas colisões do batedor. Embora ele só irá colidir com a bola, criamos uma função genérica que aceita verificar se ocorreram colisões com qualquer imagem passada como parâmetro. Fizemos assim por dois motivos: evitar aumento de dependência no código e por motivo didático, pois isso deixa você mais habituado com programação genérica.Veja que se houver colisão essa função retornará um dos seguintes valores: 1, 2 e 3. Se não houver colisão, então retornará o valor 0. O valor retornado será 1 se a colisão se der na borda esquerda do batedor e 3 se ocorrer na borda direita. Se colidir no meio dele irá retornar 2. É por meio desses valores retornados que a bola irá mudar ou não sua rota, e saberá para onde deverá se deslocar.Function Batedor_Mover(px, py)Batedor\px = pxIf Batedor\px < 50 Then Batedor\px = 50If Batedor\px > 650 Then Batedor\px = 650End Function

Nossa última função desse arquivo é responsável por controlar o movimento do batedor. Ela recebe dois parâmetros que serão passados pela posição atual do mouse (a posição x) e por um valor fixo, no caso do eixo y.Veja também que colocamos um sistema para impedir que o batedor atravesse a parede de tijolos, limitando sua posição

Page 28: Jogos 2D

máxima e mínima. Assim ele deverá estar entre 50 e 650, pois as paredes tem 50 pixels de largura e são exibidas a partir do ponto 0 e o batedor tem 100 de comprimento.CODIGO COMPLETO DE “BATEDOR.BB” Type TBATEDORField imageField pxField pyEnd TypeGlobal Batedor.TBATEDOR = New TBATEDORBatedor\image = LoadImage("midia\batedor.png")Batedor\px = 350Batedor\py = 520Function Batedor_draw()DrawImage Batedor\image, Batedor\px, Batedor\pyEnd FunctionFunction Batedor_colide(obj, px, py)If ImagesCollide(Batedor\image, Batedor\px, Batedor\py, 0, obj, px, py, 0)If (PX - Batedor\px < 20) Then Return 1If (PX - Batedor\px > 60) Then Return 3Return 2EndIfReturn 0End FunctionFunction Batedor_Mover(px, py)Batedor\px = pxIf Batedor\px < 50 Then Batedor\px = 50If Batedor\px > 650 Then Batedor\px = 650End Function

Arquivo “Parede.bb”Esse arquivo é responsável pelo gerenciamento dos objetos da parede do jogo. Veja abaixo a explicação de como ele foi elaborado.Global PAREDE_IMGPAREDE_IMG = LoadImage("midia\parede.bmp")

Como teremos vários objetos parede, optamos por deixar a imagem numa variável a parte, para não criarmos várias cópias da imagem de forma desnecessária.Type TPAREDEField id$Field pxField pyEnd Type

Aqui temos o protótipo das entidades que formarão a parede. Function PAREDE_New(id$, px, py)PAREDE.TPAREDE = New TPAREDEPAREDE\id$ = id$PAREDE\px = pxPAREDE\py = pyEnd Function

Nossa primeira função é uma construtora de objetos. Veja que ela recebe uma identidade e as posições x e y do bloco de tijolos. A identidade servirá para diferenciar os efeitos

de colisões com a bola, pois as paredes da esquerda produzem efeitos diferentes das paredes da direita e também das de cima. Por isso a função de colisões deverá retornar valores diferentes de acordo com o tipo da parede. Isso poderia ser feito matematicamente também, mas preferimos essa opção mais simples.Function PAREDE_Delete()For This.TPAREDE = Each TPAREDEDelete ThisNextEnd Function

Essa é nossa função destrutora de objetos, ela será invocada ao final de cada fase para destruir os objetos da parede. Como na função construtora não declaramos os objetos como Globais, não podemos invocar os objetos pelo próprio nome para destruí-los por isso, criamos um novo índice “This” para todos os objetos do tipo “TPAREDE” que estão em uma lista oculta do Blitz, e assim conseguimos acessar os objetos declarados anteriormente. Assim deletamos todos eles.Function Parede_draw()For this.TPAREDE = Each TPAREDEDrawImage PAREDE_IMG, this\px, this\pyNextEnd Function

Esse método vai exibir todos os objetos do tipo TPAREDE na tela, veja que ele usa o mesmo sistema de acesso da função anterior. Nós usamos “this” por ser algo já consagrado em programação, mas o nome aqui poderia ser qualquer um, pois qualquer objeto de um tipo pode ter acesso à lista daquele tipo por meio da estrutura For-Each.Function Parede_Colide(obj, px, py)For this.TPAREDE = Each TPAREDEIf ImagesCollide(PAREDE_IMG, this\px, this\py, 0, obj, px, py, 0)If this\id$ = "esquerda" Then Return 1If this\id$ = "acima" Then Return 2If this\id$ = "direita" Then Return 3EndIfNextReturn 0End Function

Por fim, nossa função de colisões. Como as demais, ela recebe uma imagem e suas posições x e y como parâmetros, isso é, ela foi construída de forma genérica. Também usamos a estrutura For-Each para acessar os dados dos objetos do tipo TPAREDE. Perceba que usamos a Id$ de cada objeto para retornar um valor por meio do comando Return. Esse valor retornado que determinar para onde a bola vai rebater.Se não colidiu com nenhum objeto, retorna 0.

Page 29: Jogos 2D

CODIGO COMPLETO DE “PAREDE.BB” Global PAREDE_IMGPAREDE_IMG = LoadImage("midia\parede.bmp")Type TPAREDEField id$Field pxField pyEnd TypeFunction PAREDE_New(id$, px, py)PAREDE.TPAREDE = New TPAREDEPAREDE\id$ = id$PAREDE\px = pxPAREDE\py = pyEnd FunctionFunction PAREDE_Delete()For this.TPAREDE = Each TPAREDEDelete ThisNextEnd FunctionFunction Parede_draw()For this.TPAREDE = Each TPAREDEDrawImage PAREDE_IMG, this\px, this\pyNextEnd FunctionFunction Parede_Colide(obj, px, py)For this.TPAREDE = Each TPAREDEIf ImagesCollide(PAREDE_IMG, this\px, this\py, 0, obj, px, py, 0)If this\id$ = "esquerda" Then Return 1If this\id$ = "acima" Then Return 2If this\id$ = "direita" Then Return 3EndIfNextReturn 0End Function

Arquivo “Bloco.bb”Esse arquivo controla os blocos coloridos da tela. Eles que são o alvo do jogo. Veja abaixo a explicação de como esse arquivo foi elaborado.Global Bloco_IMG1Global Bloco_IMG2Global Bloco_IMG3Bloco_IMG1 = LoadImage("midia\verde.bmp")Bloco_IMG2 = LoadImage("midia\azul.bmp")Bloco_IMG3 = LoadImage("midia\vermelho.bmp")

Estamos construindo variáveis à parte para armazenar as imagens. Assim não teremos várias cópias desse dado.Type TBLOCO Field vidaField pxField pyEnd Type

Esse é o nosso protótipo dos blocos. Possui um campo de vida, um da posição x e um para a posição y. Não necessitamos colocar uma imagem para eles, pois a imagem será definida pela vida, pois para cada vitalidade existe uma imagem.Function Bloco_New(vida, px, py)

Bloco.TBLOCO = New TBLOCOBloco\vida = vidaBloco\px = pxBloco\py = pyEnd Function

Essa é nossa função construtora, criadora de instâncias de blocos. Veja que devemos passar os dados de vida, posição x e posição y para criar um bloco.Function Bloco_Delete()For this.TBLOCO = Each TBLOCODelete ThisNextEnd Function

A função destrutora é semelhante à do arquivo anterior, afinal também usamos a mesma arquitetura nesse arquivo.Function Bloco_Existe()For this.TBLOCO = Each TBLOCOIf this\vida > 0Return 1EndIfNextEnd Function

Essa função tem a finalidade de verificar o fim da fase. Ela faz isso passando por todos os blocos existentes em busca de algum que possua uma vida maior que o valor zero, isso é, algum bloco ativo “If this\vida > 0”. Ao encontrar o primeiro ela encerra sua execução retornando o valor 1 “Return 1”.Se a função passar por todos os elementos e não encontrar nenhum com vida maior que zero, não vai retornar valor algum, o que boleanamente significa 0, ou falso, e indica que a fase não está ativa.Function Bloco_draw()For this.TBLOCO = Each TBLOCOIf this\vida = 1 Then DrawImage Bloco_IMG1, this\px, this\pyIf this\vida = 2 Then DrawImage Bloco_IMG2, this\px, this\pyIf this\vida = 3 Then DrawImage Bloco_IMG3, this\px, this\pyNextEnd Function

Essa é nossa função de desenho, onde exibe cada imagem dependendo da vida de cada bloco. Se não tem vida, não exibe nada.Function Bloco_colide(obj, px, py)For this.TBLOCO = Each TBLOCOIf this\vida > 0If ImagesCollide(Bloco_IMG1, this\px, this\py, 0, obj, px, py, 0)this\vida = this\vida - 1 Return 1 EndIfEndIfNextReturn 0End Function

Page 30: Jogos 2D

Por fim a função de colisões. O teste de colisão só ocorre se a vida do bloco é maior que 0 “If this\vida > 0”, isso é, se o bloco está ativo, sendo exibido. Ao colidir, temos que tirar um ponto de vida do bloco “this\vida = this\vida - 1 ” e devemos informar a quem invocou a função que houve uma colisão “Return 1”.Caso não aconteça uma colisão, retornamos o valor 0 “Return 0”.CODIGO COMPLETO DE “BLOCO.BB” Global Bloco_IMG1Global Bloco_IMG2Global Bloco_IMG3Bloco_IMG1 = LoadImage("midia\verde.bmp")Bloco_IMG2 = LoadImage("midia\azul.bmp")Bloco_IMG3 = LoadImage("midia\vermelho.bmp")Type TBLOCO Field vidaField pxField pyEnd TypeFunction Bloco_New(vida, px, py)Bloco.TBLOCO = New TBLOCOBloco\vida = vidaBloco\px = pxBloco\py = pyEnd FunctionFunction Bloco_Delete()For this.TBLOCO = Each TBLOCODelete ThisNextEnd FunctionFunction Bloco_Existe()For this.TBLOCO = Each TBLOCOIf this\vida > 0Return 1EndIfNextEnd FunctionFunction Bloco_draw()For this.TBLOCO = Each TBLOCOIf this\vida = 1 Then DrawImage Bloco_IMG1, this\px, this\pyIf this\vida = 2 Then DrawImage Bloco_IMG2, this\px, this\pyIf this\vida = 3 Then DrawImage Bloco_IMG3, this\px, this\pyNextEnd FunctionFunction Bloco_colide(obj, px, py)For this.TBLOCO = Each TBLOCOIf this\vida > 0If ImagesCollide(Bloco_IMG1, this\px, this\py, 0, obj, px, py, 0)this\vida = this\vida - 1 Return 1 EndIfEndIfNextReturn 0End Function

Arquivo “Bola.bb”

Vamos fazer um estudo do arquivo de dados responsável pelo controle da bola. Esse é um dos mais complexos do projeto e merece especial atenção. Ele tem relações especiais de dependência, pois usa vários métodos de outros arquivos para implementar o sistema de colisões e a dinâmica do jogo.Type TBOLAField imagemField pxField pyField vxField vy End Type

O protótipo para a bola possui campos para imagem, posição x e y da bola e velocidades x e y. As velocidades se devem ao fato de que a bola não ser controlada pelo jogador, mas sua movimentação depende de um sistema de movimentação baseado em física vetorial.Global Bola.TBOLA = New TBOLA Bola\imagem = LoadImage("midia\bola.png")Bola\px = 400Bola\py = 520Bola\vx = 0Bola\vy = 5

Nessas linhas estamos instanciando o nosso objeto Bola e definido os seus valores iniciais padrão.Function Bola_fisica()Bola\px = Bola\px + Bola\vxBola\py = Bola\py + Bola\vyEnd Function

Essa é a nossa função de física. O que ela faz é calcular a nova posição da bola de acordo com a velocidade x e y dela. A nova posição é igual à posição atual mais a velocidade.Function Bola_colide();--------------------COLISOES COM PAREDE -----------------------------tipo = Parede_Colide(Bola\imagem, Bola\px, Bola\py)

Aqui está o inicio de nossa grande função de colisões. A primeira verificação é se está colidindo com a parede. Veja que invoca a função de colisões do arquivo parede e passa a ela como parâmetro os dados da bola: imagem, posição x e posição y.Veja que essa invocação terá seu resultado guardado na variável “tipo” que irá possuir qual é o tipo de parede que colidiu.; SE BATEU NA PAREDE DA ESQUERDAIf tipo = 1; garantindo que vai para a direitaBola\vX = Abs(Bola\vX) EndIf

Page 31: Jogos 2D

Se o valor retornado pela função de colisão com parede é 1 significa que a bola colidiu com a parede esquerda. Nesse caso vamos fazer com que a velocidade x da bola seja rebatida, isso é, fique positiva. Para isso usamos a função ABS() que retorna o valor absoluto de um número, isso é seu estado positivo, quer seja ele positivou ou negativo. Assim, se a bola se chocou com a parede esquerda é sinal que a sua velocidade x era negativa, agora ela se torna positiva, invertendo o seu sentido de movimento.; SE BATEU NA PAREDE DE CIMAIf tipo = 2; garantindo que vai para baixoBola\vy = Abs(Bola\vy) EndIf

Se o valor retornado é 2, a bola se chocou com a parede superior, então devemos garantir que sua velocidade y seja positiva. Novamente usamos ABS().; SE BATEU NA PAREDE DA DIRETAIf tipo = 3; garantindo que vai para a esquerdaBola\vX = Abs(Bola\vX) * (-1)EndIf

Se o choque se deu com a parede da direita, a velocidade x da bola terá que passar a ser negativa. Para isso primeiro aplicamos o ABS a velocidade atual garantindo que ela se se torne positiva, depois multiplicamos esse valor por (-1) para que ele se torne negativo.;------------------------COLISOES COM BATEDOR--------------------------tipo = Batedor_colide(Bola\imagem, Bola\px, Bola\py)

A partir do presente momento passamos a verificas as colisões com o batedor. Veja que invocamos a função de colisões desse objeto e passamos para ele a imagem e os dados posicionais da bolinha.If tipo > 0 Then PlaySound game\som_batedor

Caso ocorra esse tipo de colisão, então iremos executar um efeito sonoro para criar um ambiente mais realista de jogo.;se bateu a esquerda do batedorIf tipo = 1;calcula força lateralBola\vx = Bola\vx - 1;limita a velocidadeIf Bola\vx < -4 Then Bola\vx = -4;calcula a força para cima por vetorBola\vy = 5 - Abs(Bola\vx) ;garante que vai para cima Bola\vy = Abs(Bola\vy) * (-1) game\combo = 0 EndIf

Esse bloco de código é executado se a colisão com o batedor se deu em suas

extremidade esquerda, causando um efeito de deslocamento de velocidade para esse lado. Como faremos um deslocamento de velocidade para a esquerda, temos que tornar a velocidade x uma unidade menor.Bola\vx = Bola\vx – 1

Mas essa velocidade lateral tem um limite, não poderá passar de (-4), por isso fazemos os ajuste abaixo: If Bola\vx < -4 Then Bola\vx = -4

Como nosso calculo de velocidade se baseia em vetor e esse possui o valor 5, podemos achar a velocidade y por meio da velocidade x:Bola\vy = 5 - Abs(Bola\vx)

Como a bola se chocou com o batedor, a partir de agora ela deverá ir para cima, por isso temos que garantir que sua velocidade é negativa, assim usamos a função ABS abaixo.Bola\vy = Abs(Bola\vy) * (-1)

O nosso sistema de pontos é por combos e toda as vezes que a bola bate no batedor, reiniciamos os combos acumulados.game\combo = 0

O trecho a seguir é executado caso a colisão se dê na parte central do batedor.;se bateu ao centro do batedorIf tipo = 2 Bola\vy = Abs(Bola\vy) * (-1) game\combo = 0 EndIf

O que fazemos então é garantir que a bolinha vá para cima e reiniciar os combos.;se bateu a direita do batedorIf tipo = 3;calcula força lateralBola\vx = Bola\vx + 1;limita a velocidadeIf Bola\vx > 4 Then Bola\vx = 4;calcula a força para cima por vetorBola\vy = 5 - Abs(Bola\vx) ;garante que vai para cima Bola\vy = Abs(Bola\vy) * (-1)game\combo = 0 EndIf

O código acima é executado caso haja uma colisão com a parte lateral do batedor. O efeito disso é desviar a velocidade da bola um ponto mais à direita, isso é, aumentar a velocidade x em uma unidade.Bola\vx = Bola\vx + 1

Devemos garantir que a velocidade lateral dela não ultrapasse 4 unidades.If Bola\vx > 4 Then Bola\vx = 4

Daí então recalculamos a velocidade y da bolinha baseado no vetor 5.Bola\vy = 5 - Abs(Bola\vx)

Page 32: Jogos 2D

E garantimos que a velocidade y seja negativa e a bolinha vá para cima.Bola\vy = Abs(Bola\vy) * (-1)

Por último as colisões com os blocos:; COLISOES COM BLOCOSIf Bloco_colide(Bola\imagem, Bola\px, Bola\py)

Como você se lembra, essa função só retorna um tipo de dado, se colidiu ou não.PlaySound game\som_bloco

Caso tenha havido a colisão, executamos um efeito sonoro.;inverte velocidade verticalBola\vy = Bola\vy * (-1)

Invertemos a velocidade y da bola. Isso é necessário por não sabermos se ela vai bater na parte superior ou inferior, pois não implementamos um sistema matemático de detecção como para o batedor.game\combo = game\combo + 1game\scores = game\scores + game\combo

Atualizamos os combos e contabilizamos os novos pontos.A próxima função é responsável por verificar se a bolinha caiu fora da tela, isso é, passou pelo batedor e chegou à parte inferior da tela.Function Bola_morreu()If bola\py > 600 Then Return 1End Function

Caso isso seja verdadeiro, retornará o valor 1.Function Bola_reiniciar()Bola\px = 400Bola\py = 520Bola\vx = 0Bola\vy = 5End Function

Criamos essa função apenas para reiniciar os valores da bola. Isso será muito útil quando perdemos uma bolinha e a nova deve ser colocada na posição inicial ou quando mudamos de fase.Function Bola_draw()DrawImage Bola\imagem, Bola\px, Bola\pyEnd Function

Nossa ultima função é responsável por exibir a imagem do batedor.CODIGO COMPLETO DE “BOLA.BB” Type TBOLAField imagemField pxField pyField vxField vy End TypeGlobal Bola.TBOLA = New TBOLA Bola\imagem = LoadImage("midia\bola.png")Bola\px = 400Bola\py = 520Bola\vx = 0Bola\vy = 5

Function Bola_fisica()Bola\px = Bola\px + Bola\vxBola\py = Bola\py + Bola\vyEnd FunctionFunction Bola_colide();--------------------COLISOES COM PAREDE -----------------------------tipo = Parede_Colide(Bola\imagem, Bola\px, Bola\py); SE BATEU NA PAREDE DA ESQUERDAIf tipo = 1; garantindo que vai para a direitaBola\vX = Abs(Bola\vX) EndIf; SE BATEU NA PAREDE DE CIMAIf tipo = 2; garantindo que vai para baixoBola\vy = Abs(Bola\vy) EndIf; SE BATEU NA PAREDE DA DIRETAIf tipo = 3; garantindo que vai para a esquerdaBola\vX = Abs(Bola\vX) * (-1)EndIf;------------------------COLISOES COM BATEDOR--------------------------tipo = Batedor_colide(Bola\imagem, Bola\px, Bola\py)If tipo > 0 Then PlaySound game\som_batedor;se bateu a esquerda do batedorIf tipo = 1;calcula força lateralBola\vx = Bola\vx - 1;limita a velocidadeIf Bola\vx < -4 Then Bola\vx = -4;calcula a força para cima por vetorBola\vy = 5 - Abs(Bola\vx) ;garante que vai para cima Bola\vy = Abs(Bola\vy) * (-1) game\combo = 0 EndIf;se bateu ao centro do batedorIf tipo = 2 Bola\vy = Abs(Bola\vy) * (-1) game\combo = 0 EndIf;se bateu a direita do batedorIf tipo = 3;calcula força lateralBola\vx = Bola\vx + 1;limita a velocidadeIf Bola\vx > 4 Then Bola\vx = 4;calcula a força para cima por vetorBola\vy = 5 - Abs(Bola\vx) ;garante que vai para cima Bola\vy = Abs(Bola\vy) * (-1)game\combo = 0 EndIf; COLISOES COM BLOCOSIf Bloco_colide(Bola\imagem, Bola\px, Bola\py)PlaySound game\som_bloco;inverte velocidade verticalBola\vy = Bola\vy * (-1)game\combo = game\combo + 1game\scores = game\scores + game\combo

Page 33: Jogos 2D

EndIfEnd FunctionFunction Bola_morreu()If bola\py > 600 Then Return 1End FunctionFunction Bola_reiniciar()Bola\px = 400Bola\py = 520Bola\vx = 0Bola\vy = 5End FunctionFunction Bola_draw()DrawImage Bola\imagem, Bola\px, Bola\pyEnd Function

Arquivo “Fase1.bb”Esse arquivo é responsável pelo carregamento da fase 1 e por seu descarregamento quando ela acabar. Ela também provê um método para informar se a fase está ativa.Function Fase1_carregar();----------paredes-----------;DE CIMAParede_New("acima", 0, 0)Parede_New("acima", 50, 0)Parede_New("acima", 100, 0)

Ai está o inicio da função de carregamento. Em sua primeira parte estamos construindo as paredes da fase. Para isso invocamos repetidamente o método “Parede_New” e passamos a ele a identidade do bloco da parede e sua posição x e y na tela.;---------------blocos-----------Bloco_New(3, 110, 100)Bloco_New(3, 175, 100)

Em sua segunda parte, a função de construção vai criar os blocos. Para isso passa 3 parâmetros: a vida do bloco e suas posições x e y.game\fase = 1game\vidas = 2game\scores = 0game\combo = 0Bola_reiniciar()MoveMouse(350,500)

No final da função vamos reiniciar todo o sistema do jogo, afinal estamos na fase 1, que é o inicio do jogo e isso é importante, pois poderíamos estar jogando uma segunda partida.Vamos agora para nossa segunda função, a de descarregamento da fase.Function Fase1_descarregar()Bloco_Delete()PAREDE_Delete()End Function

Veja que ela consiste simplesmente da invocação da função de descarregamento dos blocos e das parede.Function Fase1_Ativa()If Bloco_Existe() Return 1End Function

Por fim a função de verificação se a fase ainda possui blocos ativos, que na verdade é um re-direcionamento para a função “Bloco_Existe()”.CODIGO COMPLETO DE “FASE1.BB” Function Fase1_carregar();----------paredes-----------;DE CIMAParede_New("acima", 0, 0)Parede_New("acima", 50, 0)Parede_New("acima", 100, 0)Parede_New("acima", 150, 0)Parede_New("acima", 200, 0)Parede_New("acima", 250, 0)Parede_New("acima", 300, 0)Parede_New("acima", 350, 0)Parede_New("acima", 400, 0)Parede_New("acima", 450, 0)Parede_New("acima", 500, 0)Parede_New("acima", 550, 0)Parede_New("acima", 600, 0)Parede_New("acima", 650, 0)Parede_New("acima", 700, 0)Parede_New("acima", 750, 0);ESQUERDAParede_New("esquerda", 0, 0)Parede_New("esquerda", 0, 43)Parede_New("esquerda", 0, 86)Parede_New("esquerda", 0, 129)Parede_New("esquerda", 0, 172)Parede_New("esquerda", 0, 215)Parede_New("esquerda", 0, 258)Parede_New("esquerda", 0, 301)Parede_New("esquerda", 0, 344)Parede_New("esquerda", 0, 387)Parede_New("esquerda", 0, 430)Parede_New("esquerda", 0, 473)Parede_New("esquerda", 0, 516);ESQUERDAParede_New("direita", 750, 0)Parede_New("direita", 750, 43)Parede_New("direita", 750, 86)Parede_New("direita", 750, 129)Parede_New("direita", 750, 172)Parede_New("direita", 750, 215)Parede_New("direita", 750, 258)Parede_New("direita", 750, 301)Parede_New("direita", 750, 344)Parede_New("direita", 750, 387)Parede_New("direita", 750, 430)Parede_New("direita", 750, 473)Parede_New("direita", 750, 516);---------------blocos-----------Bloco_New(3, 110, 100)Bloco_New(3, 175, 100)Bloco_New(3, 240, 100)Bloco_New(3, 305, 100)Bloco_New(3, 370, 100)Bloco_New(3, 435, 100)Bloco_New(3, 500, 100)Bloco_New(3, 565, 100)Bloco_New(3, 630, 100)Bloco_New(3, 110, 125)Bloco_New(1, 175, 125)Bloco_New(1, 240, 125)Bloco_New(1, 305, 125)

Page 34: Jogos 2D

Bloco_New(1, 370, 125)Bloco_New(1, 435, 125)Bloco_New(1, 500, 125)Bloco_New(1, 565, 125)Bloco_New(3, 630, 125)Bloco_New(3, 110, 150)Bloco_New(2, 175, 150)Bloco_New(2, 240, 150)Bloco_New(2, 305, 150)Bloco_New(2, 370, 150)Bloco_New(2, 435, 150)Bloco_New(2, 500, 150)Bloco_New(2, 565, 150)Bloco_New(3, 630, 150)Bloco_New(3, 110, 175)Bloco_New(1, 175, 175)Bloco_New(1, 240, 175)Bloco_New(1, 305, 175)Bloco_New(1, 370, 175)Bloco_New(1, 435, 175)Bloco_New(1, 500, 175)Bloco_New(1, 565, 175)Bloco_New(3, 630, 175)Bloco_New(3, 110, 200)Bloco_New(3, 175, 200)Bloco_New(3, 240, 200)Bloco_New(3, 305, 200)Bloco_New(3, 370, 200)Bloco_New(3, 435, 200)Bloco_New(3, 500, 200)Bloco_New(3, 565, 200)Bloco_New(3, 630, 200)game\fase = 1game\vidas = 2game\scores = 0game\combo = 0Bola_reiniciar()MoveMouse(350,500)End FunctionFunction Fase1_descarregar()Bloco_Delete()PAREDE_Delete()End FunctionFunction Fase1_Ativa()If Bloco_Existe() Return 1End Function

Arquivo “Fase2.bb”Vamos agora ao arquivo de código responsável pelo gerenciamento da segunda fase do jogo, que é bastante semelhante ao anterior.Function Fase2_carregar();----------paredes-----------;DE CIMAParede_New("acima", 0, 0)Parede_New("acima", 50, 0)

Primeiro carregamos as paredes;---------------blocos-----------Bloco_New(3, 110, 100)Bloco_New(3, 175, 100)

Depois carregamos os blocos do jogo.MoveMouse(350,500)game\fase = 2Bola_reiniciar()

Devemos reiniciar alguns estados relativos a posição do batedor e da bola. Para isso colocamos o mouse na posição inicial, mudamos a variável de controle de fase do jogo para a fase apropriada e reiniciamos os dados da bola: sua posição e velocidade.Function Fase2_Descarregar()Bloco_Delete() PAREDE_Delete()End Function

Acima nossa função para descarregar a fase.Function Fase2_Ativa()If Bloco_Existe() Return 1End Function

Essa função verifica se existe algum bloco ativo na fase.CODIGO COMPLETO DE “FASE2.BB” Function Fase2_carregar();----------paredes-----------;DE CIMAParede_New("acima", 0, 0)Parede_New("acima", 50, 0)Parede_New("acima", 100, 0)Parede_New("acima", 150, 0)Parede_New("acima", 200, 0)Parede_New("acima", 250, 0)Parede_New("acima", 300, 0)Parede_New("acima", 350, 0)Parede_New("acima", 400, 0)Parede_New("acima", 450, 0)Parede_New("acima", 500, 0)Parede_New("acima", 550, 0)Parede_New("acima", 600, 0)Parede_New("acima", 650, 0)Parede_New("acima", 700, 0)Parede_New("acima", 750, 0);ESQUERDAParede_New("esquerda", 0, 0)Parede_New("esquerda", 0, 43)Parede_New("esquerda", 0, 86)Parede_New("esquerda", 0, 129)Parede_New("esquerda", 0, 172)Parede_New("esquerda", 0, 215)Parede_New("esquerda", 0, 258)Parede_New("esquerda", 0, 301)Parede_New("esquerda", 0, 344)Parede_New("esquerda", 0, 387)Parede_New("esquerda", 0, 430)Parede_New("esquerda", 0, 473)Parede_New("esquerda", 0, 516);ESQUERDAParede_New("direita", 750, 0)Parede_New("direita", 750, 43)Parede_New("direita", 750, 86)Parede_New("direita", 750, 129)Parede_New("direita", 750, 172)Parede_New("direita", 750, 215)Parede_New("direita", 750, 258)Parede_New("direita", 750, 301)Parede_New("direita", 750, 344)Parede_New("direita", 750, 387)Parede_New("direita", 750, 430)Parede_New("direita", 750, 473)Parede_New("direita", 750, 516);---------------blocos-----------

Page 35: Jogos 2D

Bloco_New(3, 110, 100)Bloco_New(3, 175, 100)Bloco_New(3, 240, 100)Bloco_New(3, 305, 100)Bloco_New(3, 370, 100)Bloco_New(3, 435, 100)Bloco_New(3, 500, 100)Bloco_New(3, 565, 100)Bloco_New(3, 630, 100)Bloco_New(2, 110, 125)Bloco_New(2, 175, 125)Bloco_New(1, 240, 125)Bloco_New(1, 305, 125)Bloco_New(1, 370, 125)Bloco_New(1, 435, 125)Bloco_New(1, 500, 125)Bloco_New(2, 565, 125)Bloco_New(2, 630, 125)Bloco_New(2, 110, 150)Bloco_New(2, 175, 150)Bloco_New(1, 240, 150)Bloco_New(1, 305, 150)Bloco_New(1, 370, 150)Bloco_New(1, 435, 150)Bloco_New(1, 500, 150)Bloco_New(2, 565, 150)Bloco_New(2, 630, 150)Bloco_New(2, 110, 175)Bloco_New(2, 175, 175)Bloco_New(1, 240, 175)Bloco_New(1, 305, 175)Bloco_New(1, 370, 175)Bloco_New(1, 435, 175)Bloco_New(1, 500, 175)Bloco_New(2, 565, 175)Bloco_New(2, 630, 175)Bloco_New(3, 110, 200)Bloco_New(3, 175, 200)Bloco_New(3, 240, 200)Bloco_New(3, 305, 200)Bloco_New(3, 370, 200)Bloco_New(3, 435, 200)Bloco_New(3, 500, 200)Bloco_New(3, 565, 200)Bloco_New(3, 630, 200)MoveMouse(350,500)game\fase = 2Bola_reiniciar()End FunctionFunction Fase2_Descarregar()Bloco_Delete() PAREDE_Delete()End FunctionFunction Fase2_Ativa()If Bloco_Existe() Return 1End Function

Arquivo “Game.bb”Esse é o nosso arquivo principal; ele é o responsável pela combinação e ordenação de todos os elementos de forma lógica resultando em um jogo. Vamos estudá-lo passo a passo.Graphics 800, 600SetBuffer BackBuffer()

Aqui estamos configurando o modo gráfico do jogo. A resolução será de 800x600 e

iremos usar o sistema de dois buffers para imagem.Include "menu.bb"Include "bloco.bb"Include "batedor.bb"Include "bola.bb"Include "parede.bb"Include "fase1.bb"Include "fase2.bb"Include "Creditos.bb"

Para que os códigos que estão nos arquivos auxiliares do projeto possam ter validade no nosso jogo eles têm que ser incluídos no nosso código principal por meio do comando “Include”.;declarando a estrutura do jogoType TgameField fundo Field som_batedorField som_blocoField som_morreField scoresField vidasField faseField comboField stateEnd Type

Aqui estamos declarando a estrutura de dados do nosso jogo. Esses dados irão controlar o aspecto geral do jogo, como pontuação, fase atual, vidas, sons.;configurando estados iniciaisGlobal game.Tgame = New Tgamegame\fundo = LoadSound ("midia\background.jpg")game\som_batedor = LoadSound ("midia\som2.wav")game\som_bloco = LoadSound ("midia\som1.wav")game\som_morre = LoadSound ("midia\som3.wav")game\scores = 0game\vidas = 2game\fase = 0game\combo = 0game\state = 0

Aqui estamos definindo o estado inicial do jogo, dando os parâmetros iniciais para as variáveis de controle.Creditos_Abrir()

Fazemos a invocação da tela de apresentação do jogomenu_exibir()

E transferimos o controle do programa para o loop do menu.;XXXXXXXXXXXXXXXXXX GAME LOOP XXXXXXXXXXXXXXXXXWhile (1)Controle()Logica()Fisica()Colisao()Desenha()

Page 36: Jogos 2D

FlipClsWend;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Esse é o loop de controle do jogo, ele possui toda a lógica de processamento e controle do jogo. Ele só é executado quando o jogador no menu pressiona a tecla iniciar. Veja que o loop faz a invocação de 5 funções do próprio arquivo Game.bb. Essas funções possuem toda a lógica do jogo e controlam todos os demais elementos envolvidos. Abaixo temos o código de todas as funções.;--------------------LOGICA---------------------Function Logica()

Aqui iniciamos a nossa função de lógica do jogo;Fase1 ENDIf game\fase = 1 If Fase1_Ativa()ElseFase1_Descarregar()Fase2_Carregar()game\state = 0EndIf EndIf

A primeira parte dessa função verifica se a fase em jogo é a fase 1 “If game\fase = 2” e se isso for verdadeiro, se ela está ativa “If Fase2_Ativa()”. Caso não esteja, invoca a função para descarregar a fase da memória “Fase1_Descarregar()”, carrega a fase 2 “Fase2_Carregar()” e coloca a bola em estado de espera “game\state = 0”.;Fase2 ENDIf game\fase = 2 If Fase2_Ativa()ElseFase2_Descarregar()menu_exibir()EndIf EndIf

A segunda parte da função verifica se a fase atual é a fase 2 “If game\fase = 2” e se possui ou não blocos com vida “If Fase2_Ativa()”. Caso isso seja falso, descarrega os dados da fase “Fase2_Descarregar()” e entra no menu “menu_exibir()” pois acabaram as fases do jogo.;bola mortaIf Bola_Morreu()PlaySound game\som_morregame\vidas = game\vidas - 1game\state = 0Bola_reiniciar()If game\vidas < 0 ; CHAMAR SCORES

If game\fase = 1 Then Fase1_Descarregar()If game\fase = 2 Then Fase2_Descarregar()menu_exibir()EndIfEndIf

Todo esse bloco de código executa se perdermos uma bola “If Bola_Morreu()”, isso é, se a bola passar da linha inferior da tela. Veja que esse teste é feito por uma função do arquivo Bola.bb.Já que perdemos uma bola, vamos acionar um efeito sonoro para enfatizar que isso aconteceu.PlaySound game\som_morre

O passo seguinte é tirar uma vida da nossa variável de vidas, colocarmos o jogo em estado de espera e reiniciar os dados de controle da bola.game\vidas = game\vidas - 1game\state = 0Bola_reiniciar()

O último passo dessa função é mais complicado veja abaixo:If game\vidas < 0 If game\fase = 1 Then Fase1_Descarregar()If game\fase = 2 Then Fase2_Descarregar()menu_exibir()EndIf

Já que tiramos uma vida, também devemos verificar se ainda existem vidas. Se a nossa variável é menor que zero é sinal que não nos resta vida e, portanto, estamos mortos. Esse é o momento de chamarmos funções para descarregar o jogo, veja que descarregamos a fase ativa.;--------------------FISICA---------------------Function Fisica()If game\state = 1Bola_fisica()Elsebola\px = batedor\px + 40bola\py = 500End IfEnd Function

Nossa função de controle de física é bem simples, afinal o grosso da física está no arquivo Bola.bb.If game\state = 1Bola_fisica()

O que fizemos acima é condicionar a ativação da física ao estado do jogo. Se o estado for de espera, (0) a física não executa.Elsebola\px = batedor\px + 40bola\py = 500

Page 37: Jogos 2D

End If

Se estivermos no estado de espera (0) então esse trecho acima é executado. Esse código garante que a bola fique estática, seguindo o batedor.;--------------------COLISAO---------------------Function Colisao()Bola_colide()End Function

Nossa função principal de colisão só tem o trabalho de gerenciar as colisões da bolinha, pois nesse jogo só ele se movimenta. Por isso a única que coisa que ela faz é invocar esse controle.;--------------------DESENHA---------------------Function Desenha()DrawImage game\fundo, 0, 0 Bola_draw()Batedor_draw()Bloco_draw()Parede_draw()Text 50,10, "PONTOS: " + game\scoresText 685,10,"VIDAS: " + game\vidas End Function

Acima a nossa ultima função de controle, responsável por gerencia a exibição de todas as imagens. Veja que ela invoca uma por uma as funções de exibição de cada objeto que possui uma imagem. Por fim, essa função exibe os dados de vida e pontos na tela por meio do comando text.CODIGO COMPLETO DE “GAME.BB” Graphics 800, 600SetBuffer BackBuffer()Include "menu.bb"Include "bloco.bb"Include "batedor.bb"Include "bola.bb"Include "parede.bb"Include "fase1.bb"Include "fase2.bb"Include "Creditos.bb";declarando a estrutura do jogoType TgameField fundo Field som_batedorField som_blocoField som_morreField scoresField vidasField faseField comboField stateEnd Type;configurando estados iniciaisGlobal game.Tgame = New Tgamegame\fundo = LoadSound ("midia\background.jpg")game\som_batedor = LoadSound ("midia\som2.wav")game\som_bloco = LoadSound ("midia\som1.wav")

game\som_morre = LoadSound ("midia\som3.wav")game\scores = 0game\vidas = 2game\fase = 0game\combo = 0game\state = 0Creditos_Abrir()menu_exibir();XXXXXXXXXXXXXXXXXX GAME LOOP XXXXXXXXXXXXXXXXXWhile (1)Controle()Logica()Fisica()Colisao()Desenha()FlipClsWend;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX;-------------------CONTROLE--------------------Function Controle()mx = MouseX()my = MouseY()Batedor_Mover(mx, my) click = MouseHit(1)If game\state = 0If click Then game\state = 1 EndIfIf KeyHit(1) Then menu_exibir()End Function;--------------------LOGICA---------------------Function Logica();Fase1 ENDIf game\fase = 1 If Fase1_Ativa()ElseFase1_Descarregar()Fase2_Carregar()game\state = 0EndIf EndIf;Fase2 ENDIf game\fase = 2 If Fase2_Ativa()ElseFase2_Descarregar()menu_exibir()EndIf EndIf;bola mortaIf Bola_Morreu()PlaySound game\som_morregame\vidas = game\vidas - 1game\state = 0Bola_reiniciar()If game\vidas < 0 If game\fase = 1 Then Fase1_Descarregar()If game\fase = 2 Then Fase2_Descarregar()menu_exibir()EndIf

Page 38: Jogos 2D

EndIfEnd Function;--------------------FISICA---------------------Function Fisica()If game\state = 1Bola_fisica()Elsebola\px = batedor\px + 40bola\py = 500End IfEnd Function;--------------------COLISAO---------------------Function Colisao()Bola_colide()End Function;--------------------DESENHA---------------------Function Desenha()DrawImage game\fundo, 0, 0 Bola_draw()Batedor_draw()Bloco_draw()Parede_draw()Text 50,10, "PONTOS: " + game\scoresText 685,10,"VIDAS: " + game\vidas End Function

Ultimo passo: testesO ultimo passo de um processo de desenvolvimento de jogos é testar, isso é jogar em busca de encontrar eventuais erros. Duvido muito que alguém discorde que essa é a melhor fase, mas ela pode ser bastante cansativa em alguns jogos, afinal temos que testar todas as possibilidades e combinações de possibilidades.Minhas fontesComo você pode perceber, em um jogo temos textos estáticos como, por exemplo, o título do jogo, os créditos, a palavra “vidas” ou “scores” ou textos dinâmicos, como por exemplo nome das pessoas que integram um rank de scores ou mensagens enviadas em um jogo multiplay.Usar o comando Text para isso fica muito deselegante pois esse comando só existe para usarmos em tempo de desenvolvimento, como ferramenta de exibição de dados atuais para debugar o jogo.Os textos estáticos, como nunca mudam, nós podemos os fazer com uma só imagem, mas os textos dinâmicos não podem ser feitos assim, pois seria impossível prever quais imagens criar.Para isso devemos criar nossas próprias fontes de bitmaps e uma função que permita converter textos para essas imagens. É isso que vamos aprender agora.

O primeiro passo é criar um arquivo de imagem animada com os caracteres que desejamos exibir.

<![endif]>Criar imagens de letras pequenas com boa qualidade é algo muito difícil de ser conseguido. Uma estratégia muito boa para obter um resultado melhor é usar o fundo para o texto com a mesma cor do fundo onde o texto vai ser exibido. Assim podemos deixar a suavização acionada no momento de exportar a imagem e conseguiremos um ótimo resultado na aplicação final.A imagem acima foi concebida nessa filosofia ela possui apenas 20x20 em cada letra. Veja que com o fundo branco ela fica com uma aparência ruim, mas ao exibi-la em um fundo preto no Blitz3D ela vai ter outra aparência, pois ela foi criada com suavização com um fundo preto e então só deve ser usada com esse tipo de fundo.Para imagens de letras maiores, a partir de 50 pontos, já dá para ter uma boa qualidade sem suavização, portanto para fontes grandes seria melhor usar essa outra técnica, para possibilitar maior flexibilidade de uso.Vamos à parte prática...Exemplo 1Graphics 800, 600Global fonte = LoadAnimImage("midia\fonte.png", 20, 20, 0, 36)MaskImage fonte, 255, 255, 255wrmtexto ("Pontos 999123567", 100, 100)wrmtexto ("Wrmsoft is the best", 100, 150)WaitKeyFunction wrmtexto( msg$, px, py)msg$ = Lower$(msg$)tamanho = Len(msg$)For x = 1 To tamanholetra$ = Mid$(msg$, x, 1)pxf = px + ((x * 15) - 15)If letra$ = "a" Then DrawImage fonte, pxf, py, 0If letra$ = "b" Then DrawImage fonte, pxf, py, 1If letra$ = "c" Then DrawImage fonte, pxf, py, 2If letra$ = "d" Then DrawImage fonte, pxf, py, 3If letra$ = "e" Then DrawImage fonte, pxf, py, 4If letra$ = "f" Then DrawImage fonte, pxf, py, 5

Page 39: Jogos 2D

If letra$ = "g" Then DrawImage fonte, pxf, py, 6If letra$ = "h" Then DrawImage fonte, pxf, py, 7If letra$ = "i" Then DrawImage fonte, pxf, py, 8If letra$ = "j" Then DrawImage fonte, pxf, py, 9If letra$ = "k" Then DrawImage fonte, pxf, py, 10If letra$ = "l" Then DrawImage fonte, pxf, py, 11If letra$ = "m" Then DrawImage fonte, pxf, py, 12If letra$ = "n" Then DrawImage fonte, pxf, py, 13If letra$ = "o" Then DrawImage fonte, pxf, py, 14If letra$ = "p" Then DrawImage fonte, pxf, py, 15If letra$ = "q" Then DrawImage fonte, pxf, py, 16If letra$ = "r" Then DrawImage fonte, pxf, py, 17If letra$ = "s" Then DrawImage fonte, pxf, py, 18If letra$ = "t" Then DrawImage fonte, pxf, py, 19If letra$ = "u" Then DrawImage fonte, pxf, py, 20If letra$ = "v" Then DrawImage fonte, pxf, py, 21If letra$ = "w" Then DrawImage fonte, pxf, py, 22If letra$ = "x" Then DrawImage fonte, pxf, py, 23If letra$ = "y" Then DrawImage fonte, pxf, py, 24If letra$ = "z" Then DrawImage fonte, pxf, py, 25If letra$ = "0" Then DrawImage fonte, pxf, py, 26If letra$ = "1" Then DrawImage fonte, pxf, py, 27If letra$ = "2" Then DrawImage fonte, pxf, py, 28If letra$ = "3" Then DrawImage fonte, pxf, py, 29If letra$ = "4" Then DrawImage fonte, pxf, py, 30If letra$ = "5" Then DrawImage fonte, pxf, py, 31If letra$ = "6" Then DrawImage fonte, pxf, py, 32If letra$ = "7" Then DrawImage fonte, pxf, py, 33If letra$ = "8" Then DrawImage fonte, pxf, py, 34If letra$ = "9" Then DrawImage fonte, pxf, py, 35NextEnd Function

A primeira coisa a fazer foi acionar o modo gráficoGraphics 800, 600

Vamos agora carregar a imagem e aplicar uma máscara de cor.Global fonte = LoadAnimImage("midia\fonte.png", 20, 20, 0, 36)MaskImage fonte, 255, 255, 255

Nas linhas abaixo estamos usado a função para exibir os textoswrmtexto ("Pontos 999123567", 100, 100)wrmtexto ("Wrmsoft is the best", 100, 150)

Vejamos o cabeçalho de nossa funçãoFunction wrmtexto( msg$, px, py)

Veja que ela aceita uma string e a posição na qual ela deverá ser exibida.msg$ = Lower$(msg$)O passo seguinte foi deixar todas os caracteres como minúsculos por meio do comando Lower.tamanho = Len(msg$)

Aqui estamos descobrindo o tamanho da string, isso é, quantos caracteres ela têm.For x = 1 To tamanho

Usamos essa informação do tamanho da string para criar um loop que passe por todas as letras uma por uma. Daí o loop começa de 1 e vai até o tamanho da string.letra$ = Mid$(msg$, x, 1)

Aqui estamos usando o comando Mid$ para extrair as letras da string, uma de cada vez. A posição da letra a ser removida é dada de acordo com o valor atual de x.pxf = px + ((x * 15) - 15)

O que fazemos aqui é definirmos dinamicamente onde as letras deverão aparecer. Como elas tem uma área útil de mais ou menos 15 pontos, estou multiplicando esse valor pela variável x e somando a posição inicial.If letra$ = "a" Then DrawImage fonte, pxf, py, 0

O passo seguinte é exibir a imagem de acordo com a letra atual no ciclo do loop. Isso foi feito manualmente para cada caractere existente no arquivo, mas poderíamos ter feito essa etapa dinamicamente também, se usássemos os números ASC de cada caractere. Preferimos assim por é mais fácil de visualizar o que está acontecendo.Como você pode perceber, as imagens são exibidas com altíssima perfeição em um fundo preto. Mas faça um teste. Execute o próximo exemplo, onde exibimos um retângulo branco no fundo das letras.Exemplo 2Graphics 800, 600Global fonte = LoadAnimImage("midia\fonte.png", 20, 20, 0, 36)

Page 40: Jogos 2D

MaskImage fonte, 255, 255, 255Rect 50, 50, 500, 200,1wrmtexto ("Numeros 0123456789", 100, 100)wrmtexto ("Letras abcdefghijklmnopqrstuvwxyz", 100, 150)WaitKeyFunction wrmtexto( msg$, px, py)msg$ = Lower$(msg$)tamanho = Len(msg$)For x = 1 To tamanholetra$ = Mid$(msg$, x, 1)pxf = px + ((x * 15) - 15)If letra$ = "a" Then DrawImage fonte, pxf, py, 0If letra$ = "b" Then DrawImage fonte, pxf, py, 1If letra$ = "c" Then DrawImage fonte, pxf, py, 2If letra$ = "d" Then DrawImage fonte, pxf, py, 3If letra$ = "e" Then DrawImage fonte, pxf, py, 4If letra$ = "f" Then DrawImage fonte, pxf, py, 5If letra$ = "g" Then DrawImage fonte, pxf, py, 6If letra$ = "h" Then DrawImage fonte, pxf, py, 7If letra$ = "i" Then DrawImage fonte, pxf, py, 8If letra$ = "j" Then DrawImage fonte, pxf, py, 9If letra$ = "k" Then DrawImage fonte, pxf, py, 10If letra$ = "l" Then DrawImage fonte, pxf, py, 11If letra$ = "m" Then DrawImage fonte, pxf, py, 12If letra$ = "n" Then DrawImage fonte, pxf, py, 13If letra$ = "o" Then DrawImage fonte, pxf, py, 14If letra$ = "p" Then DrawImage fonte, pxf, py, 15If letra$ = "q" Then DrawImage fonte, pxf, py, 16If letra$ = "r" Then DrawImage fonte, pxf, py, 17If letra$ = "s" Then DrawImage fonte, pxf, py, 18If letra$ = "t" Then DrawImage fonte, pxf, py, 19If letra$ = "u" Then DrawImage fonte, pxf, py, 20If letra$ = "v" Then DrawImage fonte, pxf, py, 21If letra$ = "w" Then DrawImage fonte, pxf, py, 22If letra$ = "x" Then DrawImage fonte, pxf, py, 23If letra$ = "y" Then DrawImage fonte, pxf, py, 24If letra$ = "z" Then DrawImage fonte, pxf, py, 25If letra$ = "0" Then DrawImage fonte, pxf, py, 26

If letra$ = "1" Then DrawImage fonte, pxf, py, 27If letra$ = "2" Then DrawImage fonte, pxf, py, 28If letra$ = "3" Then DrawImage fonte, pxf, py, 29If letra$ = "4" Then DrawImage fonte, pxf, py, 30If letra$ = "5" Then DrawImage fonte, pxf, py, 31If letra$ = "6" Then DrawImage fonte, pxf, py, 32If letra$ = "7" Then DrawImage fonte, pxf, py, 33If letra$ = "8" Then DrawImage fonte, pxf, py, 34If letra$ = "9" Then DrawImage fonte, pxf, py, 35 NextEnd Function

Exercícios1. Crie um arquivo animado com fontes para seus projetos. Coloque nele outros caracters que também são muito usados, como pontuação, maiúsculas, etc.2. Volte ao projeto do jogo DeskTroy e substitua os textos por textos em imagens.3. Crie a funcionalidade de melhores escores para o jogo DeskTroy. Esses dados deverão ser salvos em arquivo de dados para que os placares não sejam perdidos.4. Crie mais fases para o jogo.