Jogando Flappy Bird com Arduino 25

Se olharmos para alguns anos atrás podemos lembrar do clássico jogo Flappy Bird que fez muito sucesso entre os usuários da Apple e posteriormente Android. Então surgiram muitos clones do jogo pelo fato do mesmo ter sido tirado das lojas de aplicativos. Existem inúmeras versões online desse jogo e criações usando diversas plataformas. Nesse post vamos mostrar como você pode jogar Flappy Bird com Arduino, montando um circuito com poucos componentes.

Pra quem não conhece o jogo, ele consiste em mover um passarinho entre tubos evitando a colisão. A cada passada pelos tubos o placar é incrementado. Se o passarinho bater nos tubos ou cair no chão o jogo é encerrado. Game Over. 

Lista de Componentes

Para começar a jogar o Flappy Bird com Arduino você vai precisar dos seguintes componentes:

Montagem do Circuito

O Display TFT de 1.8″ tem resolução de 160×128 pixels e é capaz de mostrar 262144 cores (18-bit). Possui também um leitor de cartão SD podendo armazenar imagens. Seu controlador é um ST7735 (datasheet). Para comunicação com o display, usa-se o protocolo SPI utilizando o Hardware SPI do Arduino e alguns pinos adicionais.

O display tem a seguinte pinagem:

Pinagem Display TFT 1.8"

Conecte o Display ao Arduino da seguinte forma:

Tabela Pinagem Circuito

Conecte também uma chave Push Button ao pino 2 do Arduino e um resistor pull-up de 1Kohm e o circuito ficará completo da seguinte forma:

Circuito Completo Flappy Bird com Arduino

 

Instalando as Bibliotecas Necessárias

Instale as bibliotecas GFX e ST7735 da Adafruit utilizando o library manager. Clique em Sketch -> Incluir Biblioteca -> Gerenciar Bibliotecas…

Menu Gerenciador de Bibliotecas IDE Arduino

 

No campo de busca procure pela biblioteca gfx, selecione a biblioteca Adafruit GFX Library e clique em Instalar.

Tela Gerenciador de Bibliotecas IDE Arduino

Procure também pela biblioteca st7735, selecione a biblioteca Adafruit ST7735 Library e clique em Instalar.

Tela Gerenciador de Bibliotecas IDE Arduino

Programa Flappy Bird com Arduino

O programa é baseado no programa original encontrado no Github escrito por Themistokle Benetatos. Foi apenas alterado o valor da constante SKIP_TICKS de 20 para 22 para tirar alguns erros quando os canos verdes são desenhados na tela. Experimente alterar outras constantes, assim você pode mudar o tamanho dos canos ou diminuir a força do pulo do passarinho por exemplo (parâmetro JUMP_FORCE).

// =============================================================================
//
// Arduino - Flappy Bird clone
// ---------------------------
// by Themistokle "mrt-prodz" Benetatos
//
// This is a Flappy Bird clone made for the ATMEGA328 and a Sainsmart 1.8" TFT
// screen (ST7735). It features an intro screen, a game over screen containing
// the player score and a similar gameplay from the original game.
//
// Developed and tested with an Arduino UNO and a Sainsmart 1.8" TFT screen.
//
// TODO: - debounce button ?
//
// Dependencies:
// - https://github.com/adafruit/Adafruit-GFX-Library
// - https://github.com/adafruit/Adafruit-ST7735-Library
//
// References:
// - http://www.tweaking4all.com/hardware/arduino/sainsmart-arduino-color-display/
// - http://www.koonsolo.com/news/dewitters-gameloop/
// - http://www.arduino.cc/en/Reference/PortManipulation
//
// --------------------
// http://mrt-prodz.com
// http://github.com/mrt-prodz/ATmega328-Flappy-Bird-Clone
//
// =============================================================================

#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>
#include <SPI.h>

// initialize Sainsmart 1.8" TFT screen
// (connect pins accordingly or change these values)
#define TFT_DC            9     // Sainsmart RS/DC
#define TFT_RST           8     // Sainsmart RES
#define TFT_CS           10     // Sainsmart CS
// initialize screen with pins
static Adafruit_ST7735 TFT = Adafruit_ST7735(TFT_CS,  TFT_DC, TFT_RST);
// instead of using TFT.width() and TFT.height() set constant values
// (we can change the size of the game easily that way)
#define TFTW            128     // screen width
#define TFTH            160     // screen height
#define TFTW2            64     // half screen width
#define TFTH2            80     // half screen height
// game constant
#define SPEED             1
#define GRAVITY         9.8
#define JUMP_FORCE     2.15
#define SKIP_TICKS     22.0     // 1000 / 50fps // foi mudado de 20 para 22, linhas não sobram mais na tela
#define MAX_FRAMESKIP     5
// bird size
#define BIRDW             8     // bird width
#define BIRDH             8     // bird height
#define BIRDW2            4     // half width
#define BIRDH2            4     // half height
// pipe size
#define PIPEW            12     // pipe width
#define GAPHEIGHT        36     // pipe gap height
// floor size
#define FLOORH           20     // floor height (from bottom of the screen)
// grass size
#define GRASSH            4     // grass height (inside floor, starts at floor y)

// background
const unsigned int BCKGRDCOL = TFT.Color565(138,235,244);
// bird
const unsigned int BIRDCOL = TFT.Color565(255,254,174);
// pipe
const unsigned int PIPECOL  = TFT.Color565(99,255,78);
// pipe highlight
const unsigned int PIPEHIGHCOL  = TFT.Color565(250,255,250);
// pipe seam
const unsigned int PIPESEAMCOL  = TFT.Color565(0,0,0);
// floor
const unsigned int FLOORCOL = TFT.Color565(246,240,163);
// grass (col2 is the stripe color)
const unsigned int GRASSCOL  = TFT.Color565(141,225,87);
const unsigned int GRASSCOL2 = TFT.Color565(156,239,88);

// bird sprite
// bird sprite colors (Cx name for values to keep the array readable)
#define C0 BCKGRDCOL
#define C1 TFT.Color565(195,165,75)
#define C2 BIRDCOL
#define C3 ST7735_WHITE
#define C4 ST7735_RED
#define C5 TFT.Color565(251,216,114)
static unsigned int birdcol[] =
{ C0, C0, C1, C1, C1, C1, C1, C0,
  C0, C1, C2, C2, C2, C1, C3, C1,
  C0, C2, C2, C2, C2, C1, C3, C1,
  C1, C1, C1, C2, C2, C3, C1, C1,
  C1, C2, C2, C2, C2, C2, C4, C4,
  C1, C2, C2, C2, C1, C5, C4, C0,
  C0, C1, C2, C1, C5, C5, C5, C0,
  C0, C0, C1, C5, C5, C5, C0, C0};

// bird structure
static struct BIRD {
  unsigned char x, y, old_y;
  unsigned int col;
  float vel_y;
} bird;
// pipe structure
static struct PIPE {
  char x, gap_y;
  unsigned int col;
} pipe;

// score
static short score;
// temporary x and y var
static short tmpx, tmpy;

// ---------------
// draw pixel
// ---------------
// faster drawPixel method by inlining calls and using setAddrWindow and pushColor
// using macro to force inlining
#define drawPixel(a, b, c) TFT.setAddrWindow(a, b, a, b); TFT.pushColor(c)

// ---------------
// initial setup
// ---------------
void setup() {
  // initialize the push button on pin 2 as an input
  DDRD &= ~(1<<PD2);
  // initialize a ST7735S chip, black tab
  TFT.initR(INITR_BLACKTAB);
}

// ---------------
// main loop
// ---------------
void loop() {
  game_start();
  game_loop();
  game_over();
}

// ---------------
// game loop
// ---------------
void game_loop() {
  // ===============
  // prepare game variables
  // draw floor
  // ===============
  // instead of calculating the distance of the floor from the screen height each time store it in a variable
  unsigned char GAMEH = TFTH - FLOORH;
  // draw the floor once, we will not overwrite on this area in-game
  // black line
  TFT.drawFastHLine(0, GAMEH, TFTW, ST7735_BLACK);
  // grass and stripe
  TFT.fillRect(0, GAMEH+1, TFTW2, GRASSH, GRASSCOL);
  TFT.fillRect(TFTW2, GAMEH+1, TFTW2, GRASSH, GRASSCOL2);
  // black line
  TFT.drawFastHLine(0, GAMEH+GRASSH, TFTW, ST7735_BLACK);
  // mud
  TFT.fillRect(0, GAMEH+GRASSH+1, TFTW, FLOORH-GRASSH, FLOORCOL);
  // grass x position (for stripe animation)
  char grassx = TFTW;
  // game loop time variables
  double delta, old_time, next_game_tick, current_time;
  next_game_tick = current_time = millis();
  int loops;
  // passed pipe flag to count score
  bool passed_pipe = false;
  // temp var for setAddrWindow
  unsigned char px;
  
  while (1) {
    loops = 0;
    while( millis() > next_game_tick && loops < MAX_FRAMESKIP) {
      // ===============
      // input
      // ===============
      if ( !(PIND & (1<<PD2)) ) {
        // if the bird is not too close to the top of the screen apply jump force
        if (bird.y > BIRDH2*0.5) bird.vel_y = -JUMP_FORCE;
        // else zero velocity
        else bird.vel_y = 0;
      }
      
      // ===============
      // update
      // ===============
      // calculate delta time
      // ---------------
      old_time = current_time;
      current_time = millis();
      delta = (current_time-old_time)/1000;

      // bird
      // ---------------
      bird.vel_y += GRAVITY * delta;
      bird.y += bird.vel_y;

      // pipe
      // ---------------
      pipe.x -= SPEED;
      // if pipe reached edge of the screen reset its position and gap
      if (pipe.x < -PIPEW) {
        pipe.x = TFTW;
        pipe.gap_y = random(10, GAMEH-(10+GAPHEIGHT));
      }

      // ---------------
      next_game_tick += SKIP_TICKS;
      loops++;
    }

    // ===============
    // draw
    // ===============
    // pipe
    // ---------------
    // we save cycles if we avoid drawing the pipe when outside the screen
    if (pipe.x >= 0 && pipe.x < TFTW) {
      // pipe color
      TFT.drawFastVLine(pipe.x+3, 0, pipe.gap_y, PIPECOL);
      TFT.drawFastVLine(pipe.x+3, pipe.gap_y+GAPHEIGHT+1, GAMEH-(pipe.gap_y+GAPHEIGHT+1), PIPECOL);
      // highlight
      TFT.drawFastVLine(pipe.x, 0, pipe.gap_y, PIPEHIGHCOL);
      TFT.drawFastVLine(pipe.x, pipe.gap_y+GAPHEIGHT+1, GAMEH-(pipe.gap_y+GAPHEIGHT+1), PIPEHIGHCOL);
      // bottom and top border of pipe
      drawPixel(pipe.x, pipe.gap_y, PIPESEAMCOL);
      drawPixel(pipe.x, pipe.gap_y+GAPHEIGHT, PIPESEAMCOL);
      // pipe seam
      drawPixel(pipe.x, pipe.gap_y-6, PIPESEAMCOL);
      drawPixel(pipe.x, pipe.gap_y+GAPHEIGHT+6, PIPESEAMCOL);
      drawPixel(pipe.x+3, pipe.gap_y-6, PIPESEAMCOL);
      drawPixel(pipe.x+3, pipe.gap_y+GAPHEIGHT+6, PIPESEAMCOL);
    }
    // erase behind pipe
    if (pipe.x <= TFTW) TFT.drawFastVLine(pipe.x+PIPEW, 0, GAMEH, BCKGRDCOL);

    // bird
    // ---------------
    tmpx = BIRDW-1;
    do {
          px = bird.x+tmpx+BIRDW;
          // clear bird at previous position stored in old_y
          // we can't just erase the pixels before and after current position
          // because of the non-linear bird movement (it would leave 'dirty' pixels)
          tmpy = BIRDH - 1;
          do {
            drawPixel(px, bird.old_y + tmpy, BCKGRDCOL);
          } while (tmpy--);
          // draw bird sprite at new position
          tmpy = BIRDH - 1;
          do {
            drawPixel(px, bird.y + tmpy, birdcol[tmpx + (tmpy * BIRDW)]);
          } while (tmpy--);
    } while (tmpx--);
    // save position to erase bird on next draw
    bird.old_y = bird.y;

    // grass stripes
    // ---------------
    grassx -= SPEED;
    if (grassx < 0) grassx = TFTW;
    TFT.drawFastVLine( grassx    %TFTW, GAMEH+1, GRASSH-1, GRASSCOL);
    TFT.drawFastVLine((grassx+64)%TFTW, GAMEH+1, GRASSH-1, GRASSCOL2);

    // ===============
    // collision
    // ===============
    // if the bird hit the ground game over
    if (bird.y > GAMEH-BIRDH) break;
    // checking for bird collision with pipe
    if (bird.x+BIRDW >= pipe.x-BIRDW2 && bird.x <= pipe.x+PIPEW-BIRDW) {
      // bird entered a pipe, check for collision
      if (bird.y < pipe.gap_y || bird.y+BIRDH > pipe.gap_y+GAPHEIGHT) break;
      else passed_pipe = true;
    }
    // if bird has passed the pipe increase score
    else if (bird.x > pipe.x+PIPEW-BIRDW && passed_pipe) {
      passed_pipe = false;
      // erase score with background color
      TFT.setTextColor(BCKGRDCOL);
      TFT.setCursor( TFTW2, 4);
      TFT.print(score);
      // set text color back to white for new score
      TFT.setTextColor(ST7735_WHITE);
      // increase score since we successfully passed a pipe
      score++;
    }

    // update score
    // ---------------
    TFT.setCursor( TFTW2, 4);
    TFT.print(score);
  }
  
  // add a small delay to show how the player lost
  delay(1200);
}

// ---------------
// game start
// ---------------
void game_start() {
  TFT.fillScreen(ST7735_BLACK);
  TFT.fillRect(10, TFTH2 - 20, TFTW-20, 1, ST7735_WHITE);
  TFT.fillRect(10, TFTH2 + 32, TFTW-20, 1, ST7735_WHITE);
  TFT.setTextColor(ST7735_WHITE);
  TFT.setTextSize(3);
  // half width - num char * char width in pixels
  TFT.setCursor( TFTW2-(6*9), TFTH2 - 16);
  TFT.println("FLAPPY");
  TFT.setTextSize(3);
  TFT.setCursor( TFTW2-(6*9), TFTH2 + 8);
  TFT.println("-BIRD-");
  TFT.setTextSize(0);
  TFT.setCursor( 10, TFTH2 - 28);
  TFT.println("ATMEGA328");
  TFT.setCursor( TFTW2 - (12*3) - 1, TFTH2 + 34);
  TFT.println("press button");
  while (1) {
    // wait for push button
    if ( !(PIND & (1<<PD2)) ) break;
  }
  
  // init game settings
  game_init();
}

// ---------------
// game init
// ---------------
void game_init() {
  // clear screen
  TFT.fillScreen(BCKGRDCOL);
  // reset score
  score = 0;
  // init bird
  bird.x = 20;
  bird.y = bird.old_y = TFTH2 - BIRDH;
  bird.vel_y = -JUMP_FORCE;
  tmpx = tmpy = 0;
  // generate new random seed for the pipe gape
  randomSeed(analogRead(0));
  // init pipe
  pipe.x = TFTW;
  pipe.gap_y = random(20, TFTH-60);
}

// ---------------
// game over
// ---------------
void game_over() {
  TFT.fillScreen(ST7735_BLACK);
  TFT.setTextColor(ST7735_WHITE);
  TFT.setTextSize(2);
  // half width - num char * char width in pixels
  TFT.setCursor( TFTW2 - (9*6), TFTH2 - 4);
  TFT.println("GAME OVER");
  TFT.setTextSize(0);
  TFT.setCursor( 10, TFTH2 - 14);
  TFT.print("score: ");
  TFT.print(score);
  TFT.setCursor( TFTW2 - (12*3), TFTH2 + 12);
  TFT.println("press button");
  while (1) {
    // wait for push button
    if ( !(PIND & (1<<PD2)) ) break;
  }
}

Mais detalhes sobre o projeto original podem ser encontrados no Blog MRT-PRODZ.

Gostou do post Jogando Flappy Bird com Arduino? Deixe seu comentário logo abaixo. 

Faça seu comentário

Acesse sua conta e participe

25 Comentários

  1. oi, fiz carrega tudo mas não vejo o pássaro sei que esta la pois consigo jogar uns segundos mas não o vejo

    1. Olá Paulo,

      Você está usando o mesmos componentes do post?

      Abraço!
      Rosana – Equipe MakerHero

  2. Qual a versão da bibliotecas devo instalar para eliminar o erro => class Adafruit_ST7735′ has no member named ‘Color565’

    1. Olá Daniel,

      Tente a biblioteca 1.0.3.

      Abraço!
      Rosana – Equipe MakerHero

  3. Comprei uns displays desses do MakerHero há algum tempo e o chipset não era o ST7735 como apregoado nesta matéria sobre o jogo Flappy Bird. O chipset era ILI9341. Ao trocar para esse chipset, o display passou a funcionar corretamente.

  4. Boa noite, estou usando um TFT LCD 2.0″ com o driver IC: LGDP4524, tentei usar ele para executar o código, mas não consegui executar nele.

    1. Olá Gustavo,

      Acredito que não seja possível executar exatamente esse código em outro display. Você precisa usar a biblioteca exata do seu display para que o mesmo funcione corretamente.

      Abraço!
      Rosana – Equipe MakerHero

  5. Como ficam as ligações entre esse display e uma raspberry pi?

    1. Olá Diego!

      O display usa protocolo SPI para comunicação.

      Veja nos seguintes links que encontrei no Google para você como utilizar SPI com Raspberry Pi:
      https://learn.sparkfun.com/tutorials/raspberry-pi-spi-and-i2c-tutorial/allhttps://www.raspberrypi.org/documentation/hardware/raspberrypi/spi/README.md

      Espero ter ajudado!

  6. Boa noite. Comprei os componentes sugeridos pelo site bem como segui as instruções do mesmo e ao embarcar o código gera a seguinte mensagem: error: ‘class Adafruit_ST7735’ has no member named ‘Color565’.
    Como resolver? Poderiam me ajudar?

    1. Olá Matheus!

      Bibliotecas de software geralmente são atualizadas muito rapidamente e podem sofrer drásticas mudanças. O legal é que o GitHub tem um sistema de versões que torna possível baixar uma versão mais antiga de uma biblioteca.

      Analisando a versão mais recente da biblioteca Adafruit_ST7735.h realmente não podemos mais encontrar a função membro Color565.

      Tentei achar o último commit antigo que ainda possuia a função Color565. Veja se ele funciona para vocẽ.

      https://github.com/adafruit/Adafruit-ST7735-Library/tree/591fa4165d95c33ba13bfad2f09758852fe1282a

      Lembre-se antes de excluir a biblioteca que vocẽ tem atualmente.

      Abraço!

  7. O meu LCD não acende o que será que é:

  8. Funciona sem o resistor, e pra que servê o resistor neste projeto?

    1. Funciona sim Estevam. Esse é um resistor de pull-up. Se não quiser usar o resistor, basta configurar o pull-up interno do Atmega. Leia mais sobre essa técnica aqui: https://playground.arduino.cc/CommonTopics/PullUpDownResistor

      Abraço!

  9. Eu gostaria de saber se a tela LCD TFT poderia ser ligado no Raspberry pi zero,se sim,como eu faço,vocês poderiam me auxiliar?Desde já agradeço.

  10. Como eu faço para conectar esse display com um PIC?

  11. Olá.

    Gostaria de saber se o Display na simulação com o Fritzing é uma edição ou existe mesmo como importá-lo? Gostaria de usá-lo para meu projeto, porém não encontro o arquivo na internet, teria como passá-lo?

    Agradeço.

    1. Olá Gustavo,

      Fiz uma pequena modificação de um display azul da Adafruit para que ficasse parecido com nosso display.
      Usei o Inkscape para fazer as modificações.
      Envie um e-mail para [email protected] requisitando o componente do Fritzing.
      Pelo e-mail consigo te mandar o arquivo do componente em anexo.

  12. No lugar do “Push Button”, posso utilizar um “Mini Push Button”?

    1. Olá Bruno,

      Obrigado pela leitura!

      Sim, você pode usar qualquer tipo de botão desde que feche contato com GND.

  13. Gostaria de saber o que devo alterar no codgo para utilizar o display 2.4 tft lcd shield

  14. a proposito, creio que o mesmo rascunho possa ser utillizado um ema tela oled 128×64, não?

  15. Puxa, nà0 acreditava ser possível rodar um game em um equipo tão restrito, mas hoje tive uma aula de logica e hardware. Muito obrigado.

  16. Bom dia. Tenho o display TFT 2.4 mas não estou conseguindo fazê-lo funcionar utilizando esse código. Devo fazer alguma alteração no código? Pq quando carrego esse ai pro arduino a tela do display fica toda branca. Agradeço muito se me responder.

    1. Boa tarde Jonathan,

      O seu display é mais antigo ? Talvez ele utilize outro tipo de driver. Verifique no post qual o driver apropriado para o seu display e tente outras opções, por favor.

      Abraço!

      Adilson – Equipe MakerHero