Flappy Bird no Mini M5StickC

Neste artigo, iremos introduzi-lo ao controlador Mini M5StickC instalando o famoso jogo Flappy Bird neste controlador.

Placa M5StickC

Para acompanhar este tutorial pode adquirir o seu M5StickC aqui,

O M5Stick-C é um mini M5Stack, alimentado por ESP32. É uma placa de desenvolvimento de IoT portátil, fácil de usar e de código aberto para que possa efectuar todas as alterações que pretender.

Características do Controlador:

• Fonte de Alimentação 5V DC – USB Tipo C;
• Baseado em ESP32;
• 4 MByte Flash + 520K RAM;
• IMU de 6 Eixos MPU6886;
• LED Vermelho;
• Transmissor IR;
• Microfone;
• 2 Botões, LCD (0,96 Polegadas), 1 Redefinição;
• Antena 2.4G: Proant 440;
• Bateria de 80mAh Lipo;
• Soquete Extensível;
• Porta Grove;
• Plataforma de Desenvolvimento UlFlow, MicroPython, Arduino.

Recursos do ESP32:

• Microcontrolador Tensilica LX6 de Núcleo Duplo de 240MHz com 600DMIPS;
• SRAM Integrada de 520KB;
• Transceptor WiFi 802.11b/g/n HT40 WiFi Integrado, Banda Base, Pilha e LWIP;
• Bluetooth de Modo Duplo Integrado (Clássico e BLE);
• Sensor Hall;
• Interface de Toque Capativo de 10x;
• Oscilador de Cristal de 32kHz;
• Entrada / Saída PWM / Timer Disponível em Todos os Pinos GPIO;
• Mestre SDIO / Pomada 50MHz;
• Suporte à Interface do Cartão SD.

Preparar IDE

Sendo que o M5StickC não é uma placa nativa ao IDE do arduino temos que a adiciona-la manualmente, para isso:

  1. Dentro do Arduino, Ficheiro > Preferências.

    Preferências Arduino
  2. Adicionar o seguinte link: https://dl.espressif.com/dl/package_esp32_index.json no campo URL Adicionais de Gestor de Placas
  3. Aceder Ferramentas > Placa > Gestor de Placas e pesquisar por ESP32
  4. Instalar a versão oferecida pela Espressif Systems

Carregar o Código

  1. Conectar o M5StickC ao seu computador e aguardar que os drivers necessários sejam instalados;
  2. No separador de Ferramentas seleccionar a Porta COM correspondente ao seu Controlador e a placa correta;
    Ferramentas Arduino

     

  3. Aceder a Rascunho > Incluir Biblioteca > Gerir Bibliotecas > Pesquisar por M5StickC e todas as suas dependências;

    M5StickC Biblioteca
  4. Pode encontrar o código em Ficheiro > Exemplos > M5StickC > Games > Flappy Birds.

 

Código do Jogo

// By Ponticelli Domenico.
// 12NOV2020 EEPROM Working now, Modified by Zontex
// https://github.com/pcelli85/M5Stack_FlappyBird_game

#include <M5StickC.h>
#include <EEPROM.h>

#define TFTW 80 // screen width
#define TFTH 160 // screen height
#define TFTW2 40 // 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 20.0 // 1000 / 50fps
#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 15 // pipe width
#define GAPHEIGHT 30 // 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)

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

// bird sprite
// bird sprite colors (Cx name for values to keep the array readable)
#define C0 BCKGRDCOL
#define C1 M5.Lcd.color565(195,165,75)
#define C2 BIRDCOL
#define C3 TFT_WHITE
#define C4 TFT_RED
#define C5 M5.Lcd.color565(251,216,114)

static unsigned int birdcol[] =
{ C0, C0, C1, C1, C1, C1, C1, C0, C0, C0, C1, C1, C1, C1, C1, C0,
C0, C1, C2, C2, C2, C1, C3, C1, C0, C1, C2, C2, C2, C1, C3, C1,
C0, C2, C2, C2, C2, C1, C3, C1, C0, C2, C2, C2, C2, C1, C3, C1,
C1, C1, C1, C2, C2, C3, C1, C1, C1, C1, C1, C2, C2, C3, C1, C1,
C1, C2, C2, C2, C2, C2, C4, C4, C1, C2, C2, C2, C2, C2, C4, C4,
C1, C2, C2, C2, C1, C5, C4, C0, C1, C2, C2, C2, C1, C5, C4, C0,
C0, C1, C2, C1, C5, C5, C5, C0, C0, C1, C2, C1, C5, C5, C5, C0,
C0, C0, C1, C5, C5, C5, C0, C0, C0, C0, C1, C5, C5, C5, C0, C0};

// bird structure
static struct BIRD {
long x, y, old_y;
long col;
float vel_y;
} bird;

// pipe structure
static struct PIPES {
long x, gap_y;
long col;
} pipes;

// score
int 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) M5.Lcd.setAddrWindow(a, b, a, b); M5.Lcd.pushColor(c)
// ---------------
// 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
M5.Lcd.drawFastHLine(0, GAMEH, TFTW, TFT_BLACK);
// grass and stripe
M5.Lcd.fillRect(0, GAMEH+1, TFTW2, GRASSH, GRASSCOL);
M5.Lcd.fillRect(TFTW2, GAMEH+1, TFTW2, GRASSH, GRASSCOL2);
// black line
M5.Lcd.drawFastHLine(0, GAMEH+GRASSH, TFTW, TFT_BLACK);
// mud
M5.Lcd.fillRect(0, GAMEH+GRASSH+1, TFTW, FLOORH-GRASSH, FLOORCOL);
// grass x position (for stripe animation)
long 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) {
if(digitalRead(M5_BUTTON_HOME) == LOW){
//while(digitalRead(M5_BUTTON_HOME) == LOW);
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
// ---------------

pipes.x -= SPEED;
// if pipe reached edge of the screen reset its position and gap
if (pipes.x < -PIPEW) {
pipes.x = TFTW;
pipes.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 (pipes.x >= 0 && pipes.x < TFTW) {
// pipe color
M5.Lcd.drawFastVLine(pipes.x+3, 0, pipes.gap_y, PIPECOL);
M5.Lcd.drawFastVLine(pipes.x+3, pipes.gap_y+GAPHEIGHT+1, GAMEH-(pipes.gap_y+GAPHEIGHT+1), PIPECOL);
// highlight
M5.Lcd.drawFastVLine(pipes.x, 0, pipes.gap_y, PIPEHIGHCOL);
M5.Lcd.drawFastVLine(pipes.x, pipes.gap_y+GAPHEIGHT+1, GAMEH-(pipes.gap_y+GAPHEIGHT+1), PIPEHIGHCOL);
// bottom and top border of pipe
drawPixel(pipes.x, pipes.gap_y, PIPESEAMCOL);
drawPixel(pipes.x, pipes.gap_y+GAPHEIGHT, PIPESEAMCOL);
// pipe seam
drawPixel(pipes.x, pipes.gap_y-6, PIPESEAMCOL);
drawPixel(pipes.x, pipes.gap_y+GAPHEIGHT+6, PIPESEAMCOL);
drawPixel(pipes.x+3, pipes.gap_y-6, PIPESEAMCOL);
drawPixel(pipes.x+3, pipes.gap_y+GAPHEIGHT+6, PIPESEAMCOL);
}
#if 1
// erase behind pipe
if (pipes.x <= TFTW)
M5.Lcd.drawFastVLine(pipes.x+PIPEW, 0, GAMEH, BCKGRDCOL);
//M5.Lcd.drawFastVLine(pipes.x, 0, GAMEH, BCKGRDCOL);
// PIPECOL
#endif
// 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;
M5.Lcd.drawFastVLine( grassx %TFTW, GAMEH+1, GRASSH-1, GRASSCOL);
M5.Lcd.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 >= pipes.x-BIRDW2 && bird.x <= pipes.x+PIPEW-BIRDW) {
// bird entered a pipe, check for collision
if (bird.y < pipes.gap_y || bird.y+BIRDH > pipes.gap_y+GAPHEIGHT) break;
else passed_pipe = true;
}
// if bird has passed the pipe increase score
else if (bird.x > pipes.x+PIPEW-BIRDW && passed_pipe) {
passed_pipe = false;
// erase score with background color
M5.Lcd.setTextColor(BCKGRDCOL);
M5.Lcd.setCursor( TFTW2, 4);
M5.Lcd.print(score);
// set text color back to white for new score
M5.Lcd.setTextColor(TFT_WHITE);
// increase score since we successfully passed a pipe
score++;
}

// update score
// ---------------
M5.Lcd.setCursor( 2, 4);
M5.Lcd.print(score);
}

// add a small delay to show how the player lost
delay(1200);
}


// ---------------
// game start
// ---------------
void game_start() {
M5.Lcd.fillScreen(TFT_BLACK);
M5.Lcd.fillRect(0, TFTH2 - 10, TFTW, 1, TFT_WHITE);
M5.Lcd.fillRect(0, TFTH2 + 15, TFTW, 1, TFT_WHITE);
M5.Lcd.setTextColor(TFT_WHITE);
M5.Lcd.setTextSize(1);
// half width - num char * char width in pixels
M5.Lcd.setCursor( TFTW2-15, TFTH2 - 6);
M5.Lcd.println("FLAPPY");
M5.Lcd.setTextSize(1);
M5.Lcd.setCursor( TFTW2-15, TFTH2 + 6);
M5.Lcd.println("-BIRD-");
M5.Lcd.setTextSize(1);
M5.Lcd.setCursor( 15, TFTH2 - 21);
M5.Lcd.println("M5StickC");
M5.Lcd.setCursor( TFTW2 - 40, TFTH2 + 21);
M5.Lcd.println("please press home");
while (1) {
// wait for push button
if(digitalRead(M5_BUTTON_HOME) == LOW){
while(digitalRead(M5_BUTTON_HOME) == LOW);
break;
}

}
// init game settings
game_init();
}

void game_init() {
// clear screen
M5.Lcd.fillScreen(BCKGRDCOL);
// reset score
score = 0;
// init bird
bird.x = 30;
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
pipes.x = 0;
pipes.gap_y = random(20, TFTH-60);
}


// ---------------
// game over
// ---------------
void game_over() {
M5.Lcd.fillScreen(TFT_BLACK);
maxScore = EEPROM.readInt(address);

if(score>maxScore)
{
EEPROM.writeInt(address, score);
EEPROM.commit();
maxScore = score;
M5.Lcd.setTextColor(TFT_RED);
M5.Lcd.setTextSize(1); 
M5.Lcd.setCursor( 0, TFTH2 - 16);
M5.Lcd.println("NEW HIGHSCORE");
}

M5.Lcd.setTextColor(TFT_WHITE);
M5.Lcd.setTextSize(1);
// half width - num char * char width in pixels
M5.Lcd.setCursor( TFTW2 - 25, TFTH2 - 6);
M5.Lcd.println("GAME OVER");
M5.Lcd.setTextSize(1);
M5.Lcd.setCursor( 1, 10);
M5.Lcd.print("score: ");
M5.Lcd.print(score);
M5.Lcd.setCursor( 5, TFTH2 + 6);
M5.Lcd.println("press button");
M5.Lcd.setCursor( 1, 21);
M5.Lcd.print("Max Score:");
M5.Lcd.print(maxScore);
while(1) {
// wait for push button
if(digitalRead(M5_BUTTON_HOME) == LOW){
while(digitalRead(M5_BUTTON_HOME) == LOW);
break;
}
}
}

void resetMaxScore()
{
EEPROM.writeInt(address, 0);
EEPROM.commit();
}

void setup() {
// put your setup code here, to run once:
M5.begin();
EEPROM.begin(1000);
pinMode(M5_BUTTON_HOME, INPUT);
//resetMaxScore();
Serial.println("last score:");
Serial.println(EEPROM.readInt(address));

}

void loop() {
// put your main code here, to run repeatedly:
game_start();
game_loop();
game_over();
}

Resultado Final

Para mais projetos, percorram o nosso blog, onde podem encontrar vários artigos interessantes relacionados com eletrónica, robótica e muito mais! Visitem também o nosso site, onde encontram tudo para eletrónica e robótica!