Este é um jogo de nave espacial para 1 jogador, onde você controla um OVNI que deve eliminar tudo o que estiver em seu caminho. Você pode movê-lo para cima, para baixo e atirar. Não pense que será uma tarefa fácil, pois diversas forças de defesa tentarão destruir você.
Neste post, você vai aprender como montar e programar este jogo, com saída de imagem para TV e utilizando apenas alguns componentes eletrônicos básicos: uma placa Arduino UNO e um Módulo Joystick para trabalhar como controle.
O videogame construído neste projeto gera imagens para TV. Portanto, estaremos utilizando a biblioteca TVout para a plataforma Arduino.
A biblioteca TVout permite a geração de um sinal de imagem de vídeo composto no padrão NTSC ou PAL, a uma resolução de 128×96 píxeis. Esta resolução pode ser definida para um valor diferente, maior ou menor, dependendo dos recursos de memória da placa Arduino que for utilizada.
A TVout pode ser utilizada em diferentes modelos de placas Arduino, mas, dependendo do modelo da placa, as conexões de saída de áudio e vídeo podem ser diferentes. Neste projeto, estaremos utilizando uma placa Arduino UNO, portanto as conexões demonstradas são válidas apenas para esta.
Para instalar a biblioteca TVout, acesse o menu Sketch ► Incluir Biblioteca ► Gerenciar Bibliotecas…
Na tela do Gerenciador de Bibliotecas procure pelo nome “TVout” e clique no botão INSTALAR. A biblioteca será instalada dentro deste caminho: “\Documents\Arduino\libraries\TVout”
IMPORTANTE: Assim que a instalação terminar, abra o Windows Explorer e acesse o local onde foi instalada a biblioteca. Como você pode observar, a pasta “TVoutfonts” se encontra dentro da pasta “TVout” e, desta forma, o Arduino IDE não vai conseguir encontrá-la para compilar o código. Para corrigir isto, você precisa recortar esta pasta “TVoutfonts” e colocá-la em um nível anterior, ou seja, dentro da pasta “libraries”, de forma que, quando você estiver dentro da pasta Libraries, você possa ver tanto a pasta “TVout” quanto a pasta “TVoutfonts”.
Para este tutorial, você vai precisar dos seguintes componentes eletrônicos:
Para facilitar a conexão entre a mini protoboard e o conector jack RCA fêmea, recomenda-se o uso deste conector, que possui uma ponta com garra jacaré e a outra ponta um jumper macho:
Para o desenvolvimento deste projeto siga o diagrama abaixo:
(Abra a imagem em uma nova guia para visualizar com maior resolução)
O hardware é composto de duas partes: a primeira é utilizada pela biblioteca TVout para a emissão do sinal de vídeo, a segunda é utilizada pelo jogo para receber os sinais do joystick utilizado pelo jogador.
A biblioteca TVout utiliza dois resistores para a composição de seu sinal de vídeo, um resistor de 1k Ohms e outro de 470 Ohms.
Para que o jogo saiba se a nave do jogador deve subir, descer ou atirar ele utiliza o pino direcional e o botão SET do módulo Joystick 5-direções, que conecta o pino COM ao pino GND, e os pinos UP, DWN e SET respectivamente às portas digitais 2, 3 e 4.
O fornecimento de energia do videogame deve ser de 5V.
Você pode conectar a Arduino UNO a um cabo USB e ligar este cabo à porta USB do computador ou a um carregador de celular.
O jogo inicia mostrando a nave do jogador (OVNI) no canto esquerdo da tela. A quantidade de vidas e a pontuação do jogador são exibidas na parte inferior da tela.
O chão se mostra em movimento para passar a ideia de que a nave está constantemente seguindo para a direita.
Após alguns segundos um determinado “Alvo” irá surgir na parte direita da tela e começará a seguir para a esquerda. Dependendo do tipo de alvo este começará a atirar contra o jogador.
Neste jogo temos 7 tipos diferentes de alvos: Casa, Árvore, Balão, Tanque, Helicóptero, Teco-teco e Avião. Cada um destes alvos vale uma determinada pontuação para o score do jogador.
A casa, a árvore e o balão são alvos que não atiram contra o jogador.
O tanque, o helicóptero, o teco-teco e o avião são alvos hostis e vão atirar para tentar acertar a nave.
Você movimenta a sua nave empurrando o direcional do módulo joystick 5-direções para cima ou para baixo. Para atirar você deve apertar o botão “SET”. Se você preferir atirar utilizando o botão “RST” ou “MID” você deve trocar o jumper de lugar, no módulo joystick, colocando-o ou no primeiro pino ou no terceiro.
O jogo se encerra quando todas as vidas se esgotarem. A palavra “GAME-OVER” ficará escrita na tela até que o jogador aperte o botão de tiro novamente, fazendo com que o jogo seja reiniciado.
Para fazer o download do código do jogo, clique neste link: Eletrogate_SpaceJockey.zip
Assim que você já tiver montado o circuito e enviado o código do jogo para sua Arduino, você já pode começar a jogar:
O projeto Jogo de Nave na TV com módulo Joystick é composto por 2 arquivos:
“Eletrogate_SpaceJockey.ino” e “sprites.h”
A programação do jogo é simples de entender. Vamos conferir o funcionamento de cada uma das partes através da explicação que segue logo abaixo:
Neste arquivo nós encontramos as referências de imagens que correspondem aos elementos visuais do jogo. Alguns sprites são formados apenas por uma única imagem, e por isso são objetos estáticos e sem animação.
Repare que todas estas imagens gráficas são declaradas como arrays com os modificadores const unsigned char <nome da variável> PROGMEM. Isso deve ser feito desta forma para que estes bytes das imagens sejam armazenados somente na memória de programa e não na memória RAM do Arduino, fazendo com que a memória RAM fique com mais espaço livre para as variáveis e para a memória gráfica que a biblioteca TVout aloca para o seu uso.
. . . // Sprite da Árvore (id = 4) const unsigned char imgArvore[] PROGMEM = { /* 1 imagem */ 8,12, 0x3C, 0x7E, 0xFF, 0xFF, 0xFF, 0x7F, 0x32, 0x1C, 0x58, 0x38, 0x18, 0xDB, }; // Sprite da Casa (id = 5) const unsigned char imgCasa[] PROGMEM = { /* 1 imagem */ 10,11, 0x0C, 0xC0, 0x1E, 0xC0, 0x3F, 0x40, 0x7F, 0x80, 0xFF, 0xC0, 0x5E, 0x80, 0x52, 0x80, 0x73, 0x80, 0x00, 0x00, 0x7F, 0x80, 0xFF, 0xC0, }; . . .
Outros sprites são formados por duas imagens ou mais, e estas ficam se alternando ao longo do jogo para causar um efeito de animação do objeto.
. . . // Imagens referentes à animação do Helicóptero (id = 0) const unsigned char imgHelicoptero[][11] PROGMEM = { /* 2 imagens */ { 8,9, 0xF0, 0x10, 0x70, 0xF9, 0xFF, 0x78, 0x00, 0x28, 0xFC, }, { 8,9, 0x1E, 0x10, 0x70, 0xF8, 0xFF, 0x79, 0x00, 0x28, 0xFC, } }; // Imagens referentes à animação do Teco-Teco (id = 1) const unsigned char imgTecoTeco[][8] PROGMEM = { /* 2 imagens */ { 8,6, 0x18, 0x3D, 0xFF, 0xA7, 0x98, 0x1C, }, { 8,6, 0x98, 0xBD, 0xFF, 0x27, 0x18, 0x1C, } }; . . .
typedef struct { bool ativo; bool atira; int camada; int pontos; int tipo; float vel_x; float x; int y; int h; } recAlvo; recAlvo alvo[3];
Aqui nós definimos o corpo da estrutura que irá conter todas as características referentes aos Alvos.
Desta forma podemos definir características específicas para cada um dos Alvos que forem criados, de acordo com o seu tipo:
– no momento, ele está ativo ou inativo?
– ele é um Alvo que atira contra o jogador?
– em qual das três camadas ele pertence?
– quantos pontos o jogador vai ganhar se destruir um Alvo deste tipo?
– que tipo de Alvo é este? (avião, teco-teco, balão, helicóptero, tanque, casa ou árvore?)
– este Alvo se movimenta horizontalmente em qual velocidade?
– qual é a sua posição X e Y na tela?
– qual é a sua altura? (define o tamanho da área que o tiro do jogador pode acertar)
#include <TVout.h> #include <fontALL.h> #include "sprites.h" TVout TV; #define __PIN_UP 2 #define __PIN_DOWN 3 #define __PIN_FIRE 4 bool btnUP, btnDOWN, btnFIRE;
Aqui nós incluímos as bibliotecas TVout.h e fontALL.h, que já estão instaladas no Arduino.
Também incluímos o arquivo local sprites.h, que contém as imagens gráficas utilizadas no jogo.
Mantemos as imagens em um arquivo separado para simplificar a troca e inclusão de novas imagens e também para facilitar a leitura do código principal do jogo.
Instanciamos um objeto TV do tipo TVout para utilizar os recursos gráficos da biblioteca; definimos os pinos referentes às portas digitais em que o módulo Joystick 5-direções está conectado, e também definimos suas respectivas variáveis booleanas para identificarmos quando cada um dos botões estiver em estado Verdadeiro ou Falso.
int frameId; float frameId2; float frameId_OVNI; float frameId_Explosao; float solo_vel; float posX_espaco; int alvoFire_X; int alvoFire_Y; int player_X; int player_Y; int playerFire_X; int playerFire_Y; bool player_Ativo; int player_Camada; int player_Vidas; unsigned long player_Score; unsigned long previousMillis = 0;
Neste ponto nós definimos as variáveis que serão utilizadas como índice para especificar qual imagem, do array de imagens, deverá ser utilizada durante as animações.
Definimos também as variáveis posX_espaco e solo_vel para determinarmos a posição e a velocidade de movimento do solo para a esquerda.
Depois colocamos algumas variáveis referentes à posição X,Y do tiro do Alvo, posição X,Y do jogador, posição X,Y do tiro do jogador, se o jogador está ativo ou não, qual a sua pontuação, em que camada ele está, e a quantidade de vidas que ele ainda possui.
void reset_game() { randomSeed(analogRead(0)); player_Score = 0; player_Ativo = true; player_Vidas = 2; player_X = 0; player_Y = 50; playerFire_X = 60; playerFire_Y = -10; btnFIRE = false; frameId = 0; frameId2 = 0; frameId_OVNI = 0; posX_espaco = 0; frameId_Explosao = 6; solo_vel = 0.5; alvoFire_X = -10; for(int i=0; i<3; i++) { alvo[i].ativo = false; } }
Esta é a função reset_game().
Nós chamamos esta função sempre que o jogo for se iniciar: no começo da função setup ou quando o jogo termina e o jogador aperta o botão de tiro para reiniciar a partida.
Ela serve para reiniciar algumas variáveis como zero e colocar outras variáveis com alguns valores inicias específicos para que o jogo mantenha sempre as mesmas características sempre que for iniciado.
void setup() { reset_game(); TV.begin(NTSC,120,96); TV.select_font(font8x8); pinMode(__PIN_UP, INPUT_PULLUP); pinMode(__PIN_DOWN, INPUT_PULLUP); pinMode(__PIN_FIRE, INPUT_PULLUP); }
Esta é a função setup().
Esta função é executada sempre que o Arduino é iniciado. Aproveitamos para colocar aqui algumas coisas importantes que devem ser executadas logo no início:
– chamar a rotina que reseta o jogo (reseta os valores de algumas variáveis)
– iniciar o objeto TV com uma resolução de 120×96 pixels
– setar a fonte de texto como ‘font8x8’
– definir as portas digitais, utilizadas pelo módulo joystick 5-direcoes, como INPUT_PULLUP
void criarNovoAlvo() { if((player_Vidas == -1) || (player_Ativo == false)) { return; } bool camada_1 = true; bool camada_2 = true; bool camada_3 = true; int camada = -1; int indice_livre = -1; for(int i=0; i<3; i++) { if( (alvo[i].ativo) && (alvo[i].camada == 1) ) camada_1 = false; if( (alvo[i].ativo) && (alvo[i].camada == 2) ) camada_2 = false; if( (alvo[i].ativo) && (alvo[i].camada == 3) ) camada_3 = false; if( (alvo[i].ativo == false) && (indice_livre == -1) ) indice_livre = i; } if(camada_1) { camada = 1; } else if(camada_2) { camada = 2; } else if(camada_3) { camada = 3; } if((camada == -1) || (indice_livre == -1)) { return; } int posY; int tipo; if(camada == 1) { tipo = random(4); posY = 2; } else if(camada == 2) { tipo = random(4); posY = 26; } else if(camada == 3) { tipo = random(7); posY = 51; }; int i = indice_livre; int posX = 120 + 30; if(tipo == 0) { /* Helicóptero */ alvo[i].pontos = 50; alvo[i].atira = true; alvo[i].vel_x = 0.35; alvo[i].x = posX; alvo[i].y = posY; alvo[i].h = 9; } else if(tipo == 1) { /* Teco-teco */ alvo[i].pontos = 100; alvo[i].atira = true; alvo[i].vel_x = 0.5; alvo[i].x = posX; alvo[i].y = posY; alvo[i].h = 6; } else if(tipo == 2) { /* Avião */ alvo[i].pontos = 100; alvo[i].atira = true; alvo[i].vel_x = 1; alvo[i].x = posX; alvo[i].y = posY; alvo[i].h = 6; } else if(tipo == 3) { /* Balão */ alvo[i].pontos = 25; alvo[i].atira = false; alvo[i].vel_x = 0.25; alvo[i].x = posX; alvo[i].y = posY; alvo[i].h = 10; } else if(tipo == 4) { /* Árvore */ alvo[i].pontos = 20; alvo[i].atira = false; alvo[i].vel_x = 0.5; alvo[i].x = posX; alvo[i].y = 63; alvo[i].h = 12; } else if(tipo == 5) { /* Casa */ alvo[i].pontos = 20; alvo[i].atira = false; alvo[i].vel_x = 0.5; alvo[i].x = posX; alvo[i].y = 65; alvo[i].h = 10; // 12 } else if(tipo == 6) { /* Tanque */ alvo[i].pontos = 100; alvo[i].atira = true; alvo[i].vel_x = 0.6; alvo[i].x = posX; alvo[i].y = 65; alvo[i].h = 10; }; alvo[i].tipo = tipo; alvo[i].ativo = true; alvo[i].camada = camada; }
Esta é a função criarNovoAlvo().
Logo no início nós colocamos uma condição de que ela só vai prosseguir com o código se a nave do jogador estiver em modo ‘ativo’ e se a quantidade de vidas do jogador estiver maior ou igual a zero.
Iniciamos o código desta função procurando dentro de todos os alvos que estão ativos, para saber em quais camadas cada um deles está para podermos encontrar se há alguma camada que ainda não está sendo ocupada por um Alvo ativo.
Se houver alguma camada livre e também houver algum índice livre para criar um novo Alvo, então o código prossegue.
Ele seleciona randomicamente algum dos tipos de Alvos disponíveis, seta a sua posição horizontal um pouco mais à direita do limite da tela para que ele leve algum tempo para aparecer, e seta sua posição vertical de acordo com a camada a que ele pertence.
Agora nós especificamos as características que serão atribuídas ao novo Alvo de acordo com o tipo de Alvo que foi escolhido para.
void desenharAlvos() { for(int i=0; i<3; i++) { if(alvo[i].ativo && (alvo[i].x < 120-10)) { int posX = alvo[i].x; int posY = alvo[i].y; int tipo = alvo[i].tipo; if(tipo == 0) { /* Helicóptero */ TV.bitmap(posX,posY, imgHelicoptero[(int)frameId]); } else if(tipo == 1) { /* Teco-teco */ TV.bitmap(posX,posY, imgTecoTeco[(int)frameId]); } else if(tipo == 2) { /* Avião */ TV.bitmap(posX,posY, imgAviao); } else if(tipo == 3) { /* Balão */ TV.bitmap(posX,posY, imgBalao); } else if(tipo == 4) { /* Árvore */ TV.bitmap(posX,posY, imgArvore); } else if(tipo == 5) { /* Casa */ TV.bitmap(posX,posY, imgCasa); } else if(tipo == 6) { /* Tanque */ TV.bitmap(posX,posY, imgTanque[(int)frameId2]); }; } } }
Esta é a função desenharAlvos().
Esta função é chamada de dentro da função loop e serve para desenhar na tela todos os Alvos que estiverem ativos.
Ela acessa todos os três alvos e verifica quais deles estão ativos. À medida em que for encontrando um Alvo ativo ele desenha na tela a imagem correspondente a ele de acordo com o tipo a que ele pertence.
void movimentarAlvos() { for(int i=0; i<3; i++) { if(alvo[i].ativo) { alvo[i].x -= alvo[i].vel_x; if(alvo[i].x < 0) alvo[i].ativo = false; } } }
A função movimentarAlvos() é chamada de dentro da função loop e serve para movimentar pela tela os Alvos que estiverem ativos, de acordo com a velocidade de movimento atribuída a cada um deles.
void linha(int x,int y, int x2, int y2, int cor) { bool desenhar = true; if((x < 0 ) && (x2 < 0)) { desenhar = false; } if(x < 0) x = 0; if(x2 < 0) x2 = 0; if(x > 120) x = 120; if(x2 > 120) x2 = 120; if(desenhar) { TV.draw_line(x,y, x2,y2, cor); } }
O processo de desenhar linhas, da biblioteca TVout, somente desenha uma linha se o seu ponto de início e o ponto final estiverem dentro da área visível da tela.
A função linha() é utilizada para corrigir este processo verificando se o ponto inicial e o ponto final estão dentro da área visível. Se algum deles estiver fora desta área ele corrige a posição horizontal fazendo com que o pondo inicial e o ponto final assumam valores que estejam dentro da área da tela.
int explosao_X; int explosao_Y; bool explosaoGrande; void iniciarExplosao(int x, int y, bool expGrande = false) { explosao_X = x; explosao_Y = y; frameId_Explosao = 0; explosaoGrande = expGrande; } void desenharExplosao() { if((frameId_Explosao >= 0) && (frameId_Explosao < 6)) { if(explosaoGrande) { TV.bitmap(explosao_X,explosao_Y, imgExplosaoOVNI[(int)frameId_Explosao]); } else { TV.bitmap(explosao_X,explosao_Y, imgExplosao[(int)frameId_Explosao]); } } }
A função desenharExplosao() serve para desenhar a animação da explosão em uma região específica da tela.
Ela desenha a imagem específica do array de imagens de explosão de acordo com o índice frameId_Explosao.
Ela desenha as imagens correspondentes ao tamanho normal ou ao tamanho grande, de acordo com o que foi especificado na chamada da função iniciarExplosao().
A função iniciarExplosao() serve para dar início ao processo da explosão. Ela especifica a posição da explosão e também se será uma explosão de tamanho normal ou grande. Após especificar estas informações a função coloca o frameId_Explosao igual a zero para que a animação da explosão seja iniciada.
void incrementarFrameIDs() { frameId = 1-frameId; frameId_OVNI += 0.5; if(frameId_OVNI == 8) { frameId_OVNI = 0; criarNovoAlvo(); } frameId2 += 0.125; if(frameId2 == 2) { frameId2 = 0; } if(frameId_Explosao < 6) { frameId_Explosao += 0.5; } }
A função incrementarFrameIDs() é chamada de dentro da função loop e serve para incrementar os índices das variáveis que controlam os frames das animações.
Quando o ciclo de frameId_OVNI chegar ao fim a variável se reinicia e é feita uma tentativa de criar um novo Alvo na tela.
void loop() { if (millis() - previousMillis >= 10) { previousMillis = millis(); incrementarFrameIDs(); } TV.clear_screen(); TV.print(0,96-9, "SCORE:"); TV.print(50,96-9, player_Score); TV.draw_rect(0,76,119,9, WHITE,WHITE); for(int i=0; i<player_Vidas; i++) { TV.bitmap(2 + i*10,80, imgVida); } movimentarAlvos(); posX_espaco -= solo_vel; if(posX_espaco <= -32) { posX_espaco = 0; } . . .
Função LOOP – parte 1
A cada 10 milissegundos chama a função incrementarFrameIDs.
Limpa a tela e escreve a pontuação do jogador no canto inferior esquerdo da tela. Um pouco acima do texto SCORE, desenha os ícones que representam a quantidade de vidas do jogador.
Chama a função movimentarAlvos, e também altera o valor da variável posX_espaco de acordo com a velocidade de movimento especificada em solo_vel. Quando posX_espaco chegar a -32 ela se reinicia para zero. Utilizamos solo_vel para determinar a velocidade de movimento do solo pois esta velocidade acelera quando o jogador é destruído. Quando todos os Alvos somem da tela esta velocidade volta ao seu valor normal.
. . . btnUP = !digitalRead(__PIN_UP); btnDOWN = !digitalRead(__PIN_DOWN); btnFIRE = !digitalRead(__PIN_FIRE); if((player_Ativo == false) && (player_Vidas >= 0)) { btnUP = false; btnDOWN = false; btnFIRE = false; } if(btnUP) { player_Y -= 2; if(player_Y < 1) player_Y = 1; } if(btnDOWN) { player_Y += 2; if(player_Y > 66) player_Y = 66; } if(btnFIRE && (playerFire_X >= 120)) { playerFire_X = player_X + 16; playerFire_Y = player_Y+2; } . . .
Função LOOP – parte 2
Faz a leitura dos estados das porta digitais e os atribui às suas respectivas variáveis. Se a nave do jogador estiver inativa então trava todos os controles para que não possa se mover ou lançar tiros.
Se o jogador empurrar o direcional para cima ou empurrar para baixo ele vai alterar a posição vertical do OVNI para movê-lo para cima ou para baixo, respeitando sempre os seus limites superior e inferior.
Se o jogador apertar o botão SET ele coloca o tiro em uma posição X, Y logo em frente à nave, lembrando que esta ação só será possível se, neste momento, o tiro do OVNI estiver fora da área visível da tela, para que este possa realizar somente um único tiro de cada vez.
. . . player_Camada = -1; if(player_Y <= 12) { player_Camada = 1; } else if((player_Y >= 20) && (player_Y <= 36)) { player_Camada = 2; } else if(player_Y >= 45) { player_Camada = 3; } if((player_Camada >= 1) && (alvoFire_X <= 0) && (frameId_OVNI == 4) && (random(100) > 50) ) { for(int i=0; i<3; i++) { if(alvo[i].ativo && alvo[i].atira && (alvo[i].x < 110) && (alvo[i].camada == player_Camada)) { alvoFire_X = alvo[i].x; alvoFire_Y = alvo[i].y + alvo[i].h/2; if(alvo[i].tipo == 6) alvoFire_Y = alvo[i].y + 1; break; } } } if(alvoFire_X > 0) { alvoFire_X -= 4; TV.bitmap(alvoFire_X,alvoFire_Y, imgTiro); } if(player_Ativo && (alvoFire_X <= 16) && (alvoFire_X > 0) && (alvoFire_Y >= player_Y) && (alvoFire_Y <= player_Y+5)) { for(int i=0; i<3; i++) { alvo[i].vel_x += 0.5; } solo_vel = 1; player_Ativo = false; iniciarExplosao(player_X, player_Y-1, true); } . . .
Função LOOP – parte 3
Verifica a posição vertical do OVNI para determinar em qual região das três camadas ele se encontra.
Após encontrar a camada em que o OVNI está o programa verifica todos os Alvos ativos para ver se algum deles está na mesma camada que o OVNI. Se algum deles estiver na mesma camada o programa posiciona o tiro do Alvo logo à sua frente, lembrando que esta ação só é possível se o tiro do Alvo, neste momento, estiver fora da área visível da tela, para que o Alvo também possa atirar um único tiro de cada vez.
Verifica se o tiro do Alvo está dentro da área visível da tela e o movimenta da direita para a esquerda até que o tiro não esteja mais visível na tela.
Verifica constantemente se o tiro está dentro da área ocupada pelo OVNI para identificar se houve uma colisão do tiro com o OVNI.
Se sim, então executa as seguintes ações:
OBS: Lembrando que, quando o OVNI é destruído e está em estado inativo, ele só se tornará ativo novamente depois que todos os Alvos da tela saírem da área visível.
. . . if((player_Ativo == false) && (player_Vidas >= 0)) { bool nenhumAlvoAtivo = true; for(int i=0; i<3; i++) { if(alvo[i].ativo) nenhumAlvoAtivo = false; } if(nenhumAlvoAtivo) { player_Ativo = true; solo_vel = 0.5; } } if((player_Vidas == -1) && (frameId_Explosao >= 5)) { TV.print(8*3,25-8, "GAME-OVER"); if(btnFIRE) { reset_game(); } } . . .
Função LOOP – parte 4
Quando o OVNI estiver inativo, mas o jogador ainda possuir vidas, então o programa verifica se ainda existem Alvos ativos na área visível da tela.
Quando não houver mais Alvos ativos o programa então executa as seguintes ações:
A tela de GAME-OVER
A mensagem “GAME-OVER” ficará escrita no meio da tela quando o jogador não possuir mais nenhuma vida e todos os Alvos estiverem fora da área visível da tela. Durante este período o programa aguarda até que o jogador aperte o botão SET para que o jogo chame a função reset_game() e inicie uma nova partida.
. . . if(playerFire_X <= 120) { playerFire_X += 4; for(int i=0; i<3; i++) { if(alvo[i].ativo) if((playerFire_X-2 >= alvo[i].x) && (playerFire_Y >= alvo[i].y) && (playerFire_Y <= alvo[i].y+alvo[i].h)) { player_Score += alvo[i].pontos; alvo[i].ativo = false; playerFire_X = 200; iniciarExplosao(alvo[i].x, alvo[i].y); break; } } } if((playerFire_X < 120-2) && (playerFire_Y >= 0)) { TV.bitmap(playerFire_X,playerFire_Y, imgTiro); } desenharAlvos(); if(player_Ativo) { TV.bitmap(player_X,player_Y, imgOVNI[(int)frameId_OVNI]); } desenharExplosao(); TV.delay_frame(2); }
Função LOOP – parte final
Chegamos à parte final do código interno da função LOOP. Nesta parte o programa movimenta constantemente o tiro do OVNI da esquerda para a direita até que ele não esteja mais visível na tela.
Verifica constantemente se o tiro está dentro da área visível, então confere se ele entrou em colisão com algum dos Alvos ativos. Se o tiro atingiu algum deles então o programa executa as seguintes ações:
Após este processo o programa executa algumas outas funções para finalizar:
Desenha o tiro do OVNI na tela, se ele estiver dentro da área visível.
Chama a função desenharAlvos() para que todos os Alvos ativos sejam desenhados na tela.
Desenha o OVNI na tela, se ele estiver ativo.
Chama constantemente a função desenharExplosao() para que os frames de animação da explosão sejam desenhados. Lembrando que as imagens da explosão só serão exibidas se o frameId_Explosao for menor do que 6.
E, por fim, o programa faz uma chamada ao método delayframe do objeto TV, passando o valor 2 como parâmetro, para que a TVout aguarde 2 ciclos antes de renderizar a imagem. Isso diminui um pouco a velocidade do jogo, mas permite que todos os elementos da tela tenham tempo suficiente para serem desenhados sem que eles fiquem piscando na tela da TV.
Bom, chegamos ao final do post!
Aprendemos como criar uma versão simplificada do jogo Space Jockey para o Arduino utilizando a biblioteca TVout.
Os recursos de memória e a velocidade de processamento de uma placa Arduino UNO são bem limitados e isso nos impede de criar jogos mais elaborados com maior resolução gráfica e muitos elementos gráficos na tela mas, apesar disso, podemos usar nossa criatividade e exercitar nossa capacidade de programação para criar diversos tipos de jogos simples e divertidos com a biblioteca TVout.
Se você gostou desta matéria veja também os meus outros posts sobre programação de jogos aqui na Eletrogate:
Jogo da Velha na TV!
Criando um Videogame PONG com Arduino
Até a próxima !
|
A Eletrogate é uma loja virtual de componentes eletrônicos do Brasil e possui diversos produtos relacionados à Arduino, Automação, Robótica e Eletrônica em geral.
Tenha a Metodologia Eletrogate dentro da sua Escola! Conheça nosso Programa de Robótica nas Escolas!