Jogando no Arduino | Star Wars Deixe um comentário

Você já pensou em como criar um jogo com Arduino? Neste post vamos ver como ligar a um Arduino alguns botões, um display OLED e um buzzer e construir um jogo de ação.

Foto do jogo de Star Wars motnado com Arduino Nano
Jogo rodando com o Arduino Nano

Material Necessário

Para montar o jogo de Star Wars com Arduino você vai precisar de:

Vale ressaltar que se você já possuir o Kit Arduino Iniciante da MakerHero, você só vai precisar adquirir o display OLED 128×64 com interface I2C para montar esse projeto!

Montagem

A figura abaixo mostra a montagem com o Arduino Nano (que aparece nas fotos):

Esquemático do projeto de star wars arduinono software Fritzing
Esquemático do projeto no software Fritzing usando um Arduino Nano

Abaixo, a montagem com o Arduino Uno:

Esquemático do projeto star wars arduino no software Fritzing usando um Arduino Uno
Esquemático do projeto no software Fritzing usando um Arduino Uno

Instalando as Bibliotecas Necessárias

O programa necessita de duas bibliotecas, uma para controlar o display e outra para efetuar os desenhos. Essas bibliotecas podem ser instaladas diretamente na IDE do Arduino. Dispare a IDE, selecione no menu “Ferramentas”, “Gerenciar Bibliotecas”, aguarde atualizar o índice e instale as bibliotecas indicadas abaixo.

Captura de tela da instalação da biblioteca Adafruit SSD1306
Instalação da biblioteca Adafruit SSD1306

 

Instalação da biblioteca Adafruit GFX Library
Instalação da biblioteca Adafruit GFX Library

Importante: Se durante a instalação de uma biblioteca pedir para instalar alguma outra da qual ela dependa, autorize a instalação.

O Programa

Abaixo o programa completo:

/*********************************************************************
 * Arduino Star Wars - xWing vs Death Star
 * 
 * Adaptado por Daniel Quadros a partir das versões em
 * 
 * - https://create.arduino.cc/projecthub/12345hoxdipan/
 *   how-to-make-a-star-wars-game-using-arduino-and-oled-retro-g-c137bd
 * - https://www.youtube.com/watch?v=gIdPQISg5f8
 * - https://www.youtube.com/watch?v=lOz_GuME63E
 * 
 * Versão original baseada em exemplo das bibliotecas da Adafruit:
 *
 * This example is for a 128x32 size display using I2C to communicate
 * 3 pins are required to interface (2 I2C and one reset)
 *
 * Adafruit invests time and resources providing this open source code,
 * please support Adafruit and open-source hardware by purchasing
 * products from Adafruit!
 *
 * Written by Limor Fried/Ladyada  for Adafruit Industries.
 * BSD license, check license.txt for more information
 * All text above, and the splash screen must be included in any redistribution
 *********************************************************************/

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Fonts/FreeSans9pt7b.h>

// Definição do display
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET 4
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// Conexoes
#define BUZZER 9
#define BOTAO_ATIRA 3
#define BOTAO_DESCE 11
#define BOTAO_SOBE  12

// Notas musicais
const int c = 261;
const int d = 294;
const int e = 329;
const int f = 349;
const int g = 391;
const int gS = 415;
const int a = 440;
const int aS = 455;
const int b = 466;
const int cH = 523;
const int cSH = 554;
const int dH = 587;
const int dSH = 622;
const int eH = 659;
const int fH = 698;
const int fSH = 740;
const int gH = 784;
const int gSH = 830;
const int aH = 880;

// Imagem do xWing
const unsigned char PROGMEM xwing [] = {
  0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x3F, 0xF0, 0x3C, 0x00, 0x3C, 0x00, 0xFF, 0x00, 0x7F, 0xFF,
  0x7F, 0xFF, 0xFF, 0x00, 0x3C, 0x00, 0x3C, 0x00, 0x1F, 0xF0, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00
};

// Imagem do Stormtrooper na tela inicial
const unsigned char PROGMEM storm [] = {
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x7F, 0xFE, 0x00, 0x00, 0x00, 0x07, 0x80, 0x01, 0xE0, 0x00, 0x00, 0x0C,
  0x00, 0x00, 0x20, 0x00, 0x00, 0x18, 0x00, 0x00, 0x18, 0x00, 0x00, 0x30, 0x00, 0x00, 0x04, 0x00,
  0x00, 0x20, 0x00, 0x00, 0x04, 0x00, 0x00, 0x20, 0x00, 0x00, 0x04, 0x00, 0x00, 0x60, 0x00, 0x00,
  0x02, 0x00, 0x00, 0x40, 0x00, 0x00, 0x02, 0x00, 0x00, 0x40, 0x00, 0x00, 0x01, 0x00, 0x00, 0x40,
  0x00, 0x00, 0x01, 0x00, 0x00, 0x40, 0x00, 0x00, 0x01, 0x00, 0x00, 0x7F, 0xE0, 0x00, 0x01, 0x00,
  0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xD7, 0xFF, 0xFF,
  0xE1, 0x00, 0x01, 0xBF, 0xFC, 0x1F, 0xFA, 0x80, 0x01, 0xBF, 0xF1, 0xCF, 0xFA, 0x80, 0x01, 0x3F,
  0xC2, 0x37, 0xF7, 0x80, 0x01, 0xEF, 0x9C, 0x01, 0xE7, 0xC0, 0x01, 0xE0, 0x70, 0x06, 0x06, 0x80,
  0x01, 0xE0, 0xC0, 0x03, 0x06, 0x80, 0x01, 0xFF, 0x80, 0x01, 0xFF, 0x80, 0x01, 0xF8, 0x00, 0x00,
  0x1D, 0xC0, 0x03, 0x70, 0x00, 0x80, 0x0C, 0x60, 0x05, 0xB0, 0x07, 0xF0, 0x08, 0x90, 0x09, 0x10,
  0x1F, 0xF8, 0x09, 0xD0, 0x0B, 0x90, 0x1F, 0x7C, 0x03, 0xF0, 0x0F, 0xC0, 0xFC, 0x0F, 0x07, 0x90,
  0x0D, 0x43, 0xC0, 0x03, 0x07, 0x90, 0x05, 0x64, 0x00, 0x00, 0xCF, 0x10, 0x07, 0xFC, 0x00, 0x00,
  0x26, 0x10, 0x01, 0x80, 0x00, 0x00, 0x10, 0x20, 0x01, 0x00, 0x00, 0x00, 0x0E, 0x40, 0x01, 0x80,
  0x07, 0xF0, 0x01, 0x80, 0x00, 0x80, 0x07, 0xC8, 0x00, 0x80, 0x00, 0x80, 0x0B, 0xE8, 0x00, 0x80,
  0x00, 0x87, 0x97, 0xE9, 0xE0, 0x80, 0x00, 0x87, 0xDF, 0xEF, 0xA0, 0x80, 0x00, 0x4B, 0xFF, 0xFF,
  0xA0, 0x80, 0x00, 0x6B, 0xDF, 0xFB, 0xA3, 0x00, 0x00, 0x24, 0x97, 0xE8, 0x24, 0x00, 0x00, 0x1E,
  0x1F, 0xC0, 0x2C, 0x00, 0x00, 0x07, 0xF8, 0x1F, 0xF0, 0x00, 0x00, 0x00, 0x0F, 0xF8, 0x00, 0x00
};

// Variaveis de controle do jogo
int pontos = 0;
int vidas = 5;
int nivel = 1;
unsigned long inicio = 0;   // início do jogo atual
unsigned long tempo = 0;    // instante atual
unsigned long tininiv = 0;  // quando o nível atual começou

// tiros do xWing
// obs: no display o eixo x é vertical e o eixo y é horizontal
int xw_tirox = 0;          // posição vertical do tiro do xwing
int xw_tiroy = 0;          // posição horizontal do tiro do xwing
bool xw_atirando = false;  // true se tiro do xwing no display

// tiros da estrela da morte
bool ds_armartiro = true;       // estrela da morte deve programar atirar
unsigned long ds_horatiro = 0;  // quando vai atirar
int ds_veltiros = 3;            // velocidade do tiro da estrela da morte
int ds_tiros = 0;               // numero de tiros no display
int ds_tirox[4] = { 95, 95, 95, 95 };
int ds_tiroy[4] = { 0, 0, 0, 0 };

// posição (vertical) do xwing
int xw_x = 30;   

// posição da estrela da morte
int ds_dir = 0;                 // direção da movimentação
int ds_y = 95;                  // posição horizontal da estrela da morte
int ds_x = 8;                   // posição vertical da estrela da morte
int ds_velx = 1;                // velocidade vertical da estrela da morte
int ds_diametro = 10;           // diametro da estrela da morte

// Iniciação
void setup()   {

  // Colocar pullup nos botões
  pinMode(BOTAO_SOBE, INPUT_PULLUP); //UP BUTTON
  pinMode(BOTAO_DESCE, INPUT_PULLUP); //DOWN BUTTON
  pinMode(BOTAO_ATIRA, INPUT_PULLUP); //TRIGGER BUTTON

  // Iniciar o display
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.display();

  // Tela inicial
  display.clearDisplay();
  display.setTextSize(0);
  display.drawBitmap(6, 0, storm, 48, 48, 1);
  display.setFont(&FreeSans9pt7b);
  display.setTextColor(WHITE);
  display.setCursor(65, 14);
  display.println("xWing");
  display.setFont();
  display.setCursor(65, 17);
  display.setTextSize(0);
  display.println("vs");
  display.setFont(&FreeSans9pt7b);
  display.setCursor(65, 39);
  display.println("Death");
  display.setFont();
  display.setCursor(65, 42);
  display.println("star ");
  display.display();

  // Marcha imperial
  delay(300);
  beep(a, 500);
  beep(a, 500);
  beep(a, 500);
  beep(f, 350);
  beep(cH, 150);
  beep(a, 500);
  beep(f, 350);
  beep(cH, 150);
  beep(a, 650);
  delay(1000);

  inicio = millis();
}

// Loop principal
void loop() {

  if (vidas > 0) {
    display.clearDisplay();
    desenha_estrelas();

    // trata mudança de nível
    tempo = millis();
    if ((tempo - tininiv) > 50000) {
      tininiv = tempo;
      nivel = nivel + 1;
      ds_veltiros = ds_veltiros + 1; // tiros mais rápidos
      if (nivel % 2 == 0) {
        ds_velx = ds_velx + 1;
        ds_diametro = ds_diametro - 1;
      }
      tone(BUZZER, 500, 300);
      delay(300);
      tone(BUZZER, 1000, 200);
      delay(200);
    }

    // trata tiros da estrela da morte
    if (ds_armartiro) {
      ds_horatiro = millis() + random(400, 1200);
      ds_armartiro = false;
    }
    if (ds_horatiro < tempo)
    {
      ds_armartiro = true;
      ds_atira();
    }
    desenha_dstiros();

    // trata botão sobe
    if (digitalRead(BOTAO_SOBE) == 0 && xw_x >= 2) {
      xw_x = xw_x - 2;
    }

    // trata botão desce
    if (digitalRead(BOTAO_DESCE) == 0 && xw_x <= 46) {
      xw_x = xw_x + 2;
    }

    // trata botão atira
    if (digitalRead(BOTAO_ATIRA) == 0 && !xw_atirando) {
      xw_atirando = true;
      xw_tiroy = 6;
      xw_tirox = xw_x + 8;
      tone(9, 1200, 20);
    }
    if (xw_atirando == 1) {
      // Move e desenha o tiro
      xw_tiroy = xw_tiroy + 8 ;
      display.drawLine(xw_tiroy, xw_tirox, xw_tiroy + 4, xw_tirox, 1);
    }
    if (xw_tiroy > 128) {
      xw_atirando = false;  // tiro saiu da tela
    }

    // desenha o xwing
    display.drawBitmap(4, xw_x, xwing, 16, 16, 1);
    
    // desenha a estrela da morte
    display.fillCircle(ds_y, ds_x, ds_diametro, 1);
    display.fillCircle(ds_y + 2, ds_x + 3, ds_diametro / 3, 0);
    
    // informações
    display.setTextSize(1);
    display.setTextColor(WHITE);
    display.setCursor(33, 57);
    display.println("pontos:");
    display.setCursor(74, 57);
    display.println(pontos);
    display.setCursor(33, 0);
    display.println("vidas:");
    display.setCursor(68, 0);
    display.println(vidas);
    display.setCursor(110, 0);
    display.println("N:");
    display.setCursor(122, 0);
    display.println(nivel);
    display.setCursor(108, 57);
    display.println((tempo-inicio) / 1000);
    display.display();

    // move a estrela da morte
    if (ds_dir == 0) {
      ds_x = ds_x + ds_velx;
    } else {
      ds_x = ds_x - ds_velx;
    }
    if (ds_x >= (64 - ds_diametro)) {
      ds_dir = 1;
    }
    if (ds_x <= ds_diametro) {
      ds_dir = 0;
    }

    // verifica se tiro acertou a estrela da morte
    if ((xw_tirox >= (ds_x - ds_diametro)) && (xw_tirox <= (ds_x + ds_diametro))) {
      if ((xw_tiroy > (ds_y - ds_diametro)) && (xw_tiroy < (ds_y + ds_diametro)))
      {
        xw_tiroy = -20;
        tone(9, 500, 20);
        pontos = pontos + 1;
        xw_atirando = false;
      }
    }

    // verifica se algum tiro da estrela da morte acertou o xwing
    int pos = xw_x + 8;
    for (int i = 0; i < 4; i++) {
      if ((ds_tiroy[i] >= (pos - 8)) && (ds_tiroy[i] <= (pos + 8))) {
        if ((ds_tirox[i] < 12) && (ds_tirox[i] > 4)) {
          ds_tirox[i] = -50;
          ds_tiroy[i] = -50;
          tone(9, 100, 100);
          vidas = vidas - 1;
          if (i == 3) {
            ds_tiros = 0;
          }
          if (vidas == 0) {
            break;
          }
        }
      }
    }

    if (ds_tirox[3] < 1) {
      ds_tiros = 0;
      ds_tirox[3] = 200;
    }

    if (vidas == 0) {
      game_over();
    }
  } else {
    
    // aguarda apertar ATIRA para começar outro jogo
    if (digitalRead(BOTAO_ATIRA) == 0)
    {
      tone(BUZZER, 280, 300);
      delay(300);
      tone(BUZZER, 250, 200);
      delay(200);
      tone(BUZZER, 370, 300);
      delay(300);
      reinicia();
      inicio = millis();
    }
  }
}

// Reinicia o jogo
void reinicia() {
  xw_tiroy = 0;
  xw_tirox = 0;
  xw_atirando = false;
  ds_x = 8;
  ds_dir = 0;
  pontos = 0;

  ds_veltiros = 3;
  ds_velx = 1;
  ds_diametro = 12;

  ds_armartiro = true;
  ds_horatiro = 0;
  ds_tiros = 0;
  for (int i = 0; i < 4; i++) {
    ds_tirox[i] = 95;
    ds_tiroy[i] = 0;
  }

  vidas = 5;
  nivel = 1;
  tempo = 0;
  tininiv = 0;
}

// Toca uma nota
void beep(int note, int duration) {
  tone(BUZZER, note, duration);
  delay(duration);
  noTone(BUZZER);
  delay(50);
}

// Desenha as estrelas ao fundo
void desenha_estrelas() {
  display.drawPixel(50, 30, 1);
  display.drawPixel(30, 17, 1);
  display.drawPixel(60, 18, 1);
  display.drawPixel(55, 16, 1);
  display.drawPixel(25, 43, 1);
  display.drawPixel(100, 43, 1);
  display.drawPixel(117, 52, 1);
  display.drawPixel(14, 49, 1);
  display.drawPixel(24, 24, 1);
  display.drawPixel(78, 36, 1);
  display.drawPixel(80, 57, 1);
  display.drawPixel(107, 11, 1);
  display.drawPixel(150, 11, 1);
  display.drawPixel(5, 5, 1);
  display.drawPixel(8, 7, 1);
  display.drawPixel(70, 12, 1);
  display.drawPixel(10, 56, 1);
  display.drawPixel(70, 25, 1);
}

// Estrela da morte atira
void ds_atira() {
  if (ds_tiros < 4) {
    ds_tirox[ds_tiros] = 95;
    ds_tiroy[ds_tiros] = ds_x;
    ds_tiros = ds_tiros + 1;
  }
}

// Desenha tiros da Estrela da morte
void desenha_dstiros() {
  for (int i = 0; i < ds_tiros; i++) {
    if (ds_tirox[i] >= 0) {
      display.drawCircle(ds_tirox[i], ds_tiroy[i], 2, 1);
      ds_tirox[i] = ds_tirox[i] - ds_veltiros;
    }
  }
}

// Desenha a tela de fim do jogo
void game_over() {
  tone(BUZZER, 200, 300);
  delay(300);
  tone(BUZZER, 250, 200);
  delay(200);
  tone(BUZZER, 300, 300);
  delay(300);
      
  display.clearDisplay();
  display.setFont();
  display.setTextSize(2);
  display.setTextColor(WHITE);
  display.setCursor(7, 10);
  display.println("GAME OVER!");
  display.setTextSize(1);
  display.setCursor(7, 30);
  display.println("pontos:");
  display.setCursor(50, 30);
  display.println(pontos);
  display.setCursor(7, 40);
  display.println("nivel:");
  display.setCursor(44, 40);
  display.println(nivel);
  display.setCursor(7, 50);
  display.println("tempo(s):");
  display.setCursor(66, 50);
  display.println((tempo - inicio) / 1000);
  display.display();
}

Abra a IDE do Arduino, confira que está selecionada a sua placa e a porta correspondente, use Arquivo Novo para abrir uma nova janela, copie nela o programa, salve no micro e carregue na sua placa.

Captura de tela da Arduino IDE
Arduino IDE

Como Jogar Star Wars

No início do jogo é apresentada a tela abaixo e tocada uma música. Após alguns instantes o jogo começa.

Tela de abertura do jogo star wars arduino
Tela de abertura do jogo

A tela abaixo mostra o jogo em andamento.

Jogo em funcionamento
Jogo em funcionamento

À esquerda você tem a sua nave (o X-Wing). Você pode mover a nave para cima e para baixo com dois dos botões.

O terceiro botão dispara um tiro (linha horizontal próxima ao X-Wing). Você ganha um ponto a cada tiro que acertar a Estrela da Morte (o círculo no alto da tela do lado esquerdo, ela fica se movimentando para cima e para baixo).

A Estrela da Morte fica atirando de volta (pequenos círculos no alto da tela). A cada tiro que atinge a sua nave, você perde uma vida.

Se você sobreviver por 50 segundos, passa para o nível seguinte (o nível é indicado no alto da tela, “N:2”). A cada nível a Estrela da Morte fica menor e atira mais rápido.
Quando as suas vidas acabarem é apresentada a tela de fim de jogo. Aperte o botão de tiro para começar um novo jogo.

Tela de Game Over
Tela de Game Over

O vídeo abaixo mostra o jogo de Star Wars com Arduino em andamento:

YouTube video

Conclusão e Comentários

Neste artigo você viu como montar um jogo de ação com um Arduino. Depois que você montar e brincar bastante, dê uma estudada no código (está bem comentado) e veja como ele foi feito.

Que tal você usar este projeto como ponto de partida para os seus próximos jogos? Não deixe de comentar abaixo as suas experiências!

E se não quiser perder nenhuma novidade do nosso Blog, nos siga no Instagram.


Este projeto é uma adaptação de um projeto disponível na internet (aqui, aqui e aqui). Dei uma revisada no código e acrescentei comentário para ficar mais fácil o seu entendimento.

Faça seu comentário

Acesse sua conta e participe