Construye un Clon de Flappy Bird con Arduino: Juego Paso a Paso con Pantalla TFT Táctil
En este proyecto de Arduino haremos un genial juego de Arduino, en realidad una réplica del popular juego Flappy Bird para smartphones, usando un Arduino y una pantalla TFT táctil. Puedes aprender cómo funciona viendo el siguiente vídeo o leyendo el texto escrito a continuación. El juego es bastante simple pero interesante y adictivo. Usando la pantalla táctil controlamos el pájaro e intentamos evitar los pilares móviles cuya velocidad aumenta a medida que avanzamos. Además, el juego puede almacenar tu puntuación más alta incluso si desconectas la alimentación. En el tutorial anterior (Tutorial Arduino TFT) aprendimos en detalle cómo usar pantallas TFT táctiles con un Arduino y dejamos el ejemplo del juego para ser explicado en este tutorial. Así que ahora, al igual que en el tutorial anterior, explicaremos paso a paso el código detrás de este juego de Arduino. Como el código es un poco largo y para mejor comprensión publicaré el código fuente del programa en secciones con descripción para cada sección. Y al final de este artículo publicaré el código fuente completo. Usaremos las librerías UTFT y URTouch hechas por Henning Karlsen. Puedes descargar estas librerías de su sitio web, www.RinkyDinkElectronics.com. También usaremos la librería EEPROM para almacenar la puntuación más alta en la EEPROM. La EEPROM es una memoria que puede almacenar datos incluso cuando la placa está apagada. Después de incluir las librerías, necesitamos crear los objetos UTFT y URTouch, así como definir las variables necesarias para el juego. En la sección setup necesitamos iniciar la pantalla y el tacto, leer la puntuación más alta de la EEPROM e iniciar el juego usando la función personalizada initiateGame(). Con la función personalizada initiateGame() dibujaremos el estado inicial del juego y aquí está cómo lo haremos. Primero necesitamos limpiar la pantalla, luego dibujar el fondo azul, dibujar la sección inferior, agregar el texto y llamar a la función personalizada drawBird() para dibujar el pájaro. Después de esto, necesitamos un bucle while que evitará que el juego comience hasta que toquemos la pantalla. Así que mientras estemos en este estado, si presionamos la esquina superior derecha podemos reiniciar la puntuación más alta a cero y si presionamos en cualquier otra parte de la pantalla saldremos del bucle while y entraremos en el loop principal del código que iniciará el juego. En la sección principal del loop tenemos la variable xP que se usa para dibujar los pilares, así como la variable yP. Al principio, la variable xP tiene el valor de 319 como el tamaño de la pantalla y la variable yP tiene el valor de 100 que es la altura del primer pilar. Cada iteración, el valor de la variable xP disminuye por el valor de la variable movingRate que al principio tiene el valor de 3 y a medida que avanzamos en el juego aumenta. Aquí está el principio de funcionamiento del juego: tenemos pilares de 50 píxeles de ancho que se mueven de derecha a izquierda y cada siguiente pilar tiene una altura aleatoria diferente. Para hacerlos mover, lógicamente, después de cada iteración necesitaríamos limpiar la pantalla y redibujar los gráficos con los pilares en su nueva posición. Sin embargo, no podemos hacer eso debido a la baja tasa de refresco de la pantalla, lo que causaría parpadeo de los gráficos. Para activar todos sus píxeles, la pantalla necesita un poco más de tiempo, por lo tanto, tendremos que improvisar y redibujar solo aquellas cosas que se mueven. Veamos cómo la función personalizada drawPilars() hará eso. Toma las variables xP e yP y, usándolas y la función fillRect(), dibujará los pilares. Así, cada iteración dibuja los pilares en su nueva ubicación con rectángulos azules adicionales desde su lado izquierdo y derecho que borran el pilar dibujado anteriormente y de esa manera hacemos la improvisación de solo redibujar los pilares en movimiento. Las sentencias if aquí son una improvisación adicional porque por alguna razón la función fillRect() no funcionaba si su parámetro 'x2' tenía un valor fuera del tamaño de la pantalla. Además, al final de esta función personalizada necesitamos imprimir el valor de la puntuación alcanzada. Volviendo a la sección del loop, tenemos la variable yB que es la posición y del pájaro y depende de la tasa de caída que después de cada iteración aumenta y de esa manera obtenemos el efecto de aceleración o gravedad. También, aquí comprobamos colisiones y usamos sentencias if para confinar el pájaro de modo que si golpea la parte superior, el suelo o los pilares, el juego termina. Luego está la función personalizada drawBird() y veamos cómo funciona. El pájaro es en realidad una foto que se convierte en un bitmap usando la herramienta ImageConverter565 hecha por Henning Karlsen. El archivo ".c" que se crea usando la herramienta necesita incluirse en el directorio para que se cargue al iniciar el sketch. También tenemos que definir el bitmap así y usando la función drawBitmap() dibujaremos la foto en la pantalla. El pájaro tiene una coordenada X fija y la variable yB como coordenada Y. Similar a los pilares, limpiaremos el estado anterior del pájaro dibujando dos rectángulos azules encima y debajo del pájaro. Volviendo al loop, podemos ver que después de que el pilar ha pasado por la pantalla, la variable xP se reiniciará a 319, yP obtendrá un nuevo valor aleatorio de 20 a 100 para la altura de los pilares y la puntuación aumentará en uno. Con la siguiente sentencia if controlamos el pájaro. Si tocamos la pantalla, estableceremos la tasa de caída a negativa lo que hará que el pájaro salte y la sentencia else if no permitirá que ocurra si solo mantenemos la pantalla presionada. La última sentencia if es para la dificultad del juego y aumenta la tasa de movimiento de los pilares después de cada cinco puntos. Ok, lo que queda ahora es ver cómo funciona la función personalizada gameOver(). Después de un delay de un segundo, limpiará la pantalla, imprimirá la puntuación y algún texto, si la puntuación es mayor que la más alta la escribirá en la EEPROM, reiniciará todas las variables a sus valores de posición inicial y al final llamará a la función personalizada initiateGame() para reiniciar el juego. Eso es todo y espero que la explicación del código haya sido lo suficientemente clara. Si tienes alguna pregunta, siéntete libre de preguntar en la sección de comentarios a continuación. Aquí tienes un archivo de descarga con el sketch de Arduino, la imagen del pájaro y el archivo bitmap para el pájaro.Visión General

Código Fuente
#include <UTFT.h>
#include <URTouch.h>
#include <EEPROM.h>
//==== Creando Objetos
UTFT myGLCD(SSD1289,38,39,40,41); //Parámetros ajustados a tu modelo de Display/Shield
URTouch myTouch( 6, 5, 4, 3, 2);
//==== Definiendo Fuentes
extern uint8_t SmallFont[];
extern uint8_t BigFont[];
extern uint8_t SevenSegNumFont[];
extern unsigned int bird01[0x41A]; // Bitmap del pájaro
int x, y; // Variables para coordenadas donde se presionó la pantalla
// Floppy Bird
int xP = 319;
int yP = 100;
int yB = 50;
int movingRate = 3;
int fallRateInt = 0;
float fallRate = 0;
int score = 0;
int lastSpeedUpScore = 0;
int highestScore;
boolean screenPressed = false;
boolean gameStarted = false;
void setup() {
// Iniciar display
myGLCD.InitLCD();
myGLCD.clrScr();
myTouch.InitTouch();
myTouch.setPrecision(PREC_MEDIUM);
highestScore = EEPROM.read(0); // Leer puntuación más alta de EEPROM
initiateGame(); // Iniciar el juego
}Lenguaje: Arduino (arduino)
// ===== initiateGame - Función Personalizada
void initiateGame() {
myGLCD.clrScr();
// Fondo azul
myGLCD.setColor(114, 198, 206);
myGLCD.fillRect(0,0,319,239);
// Suelo
myGLCD.setColor(221,216,148);
myGLCD.fillRect(0, 215, 319, 239);
myGLCD.setColor(47,175,68);
myGLCD.fillRect(0, 205, 319, 214);
// Texto
myGLCD.setColor(0, 0, 0);
myGLCD.setBackColor(221, 216, 148);
myGLCD.setFont(BigFont);
myGLCD.print("Score:",5,220);
myGLCD.setFont(SmallFont);
myGLCD.print("HowToMechatronics.com", 140, 220);
myGLCD.setColor(0, 0, 0);
myGLCD.setBackColor(114, 198, 206);
myGLCD.print("Highest Score: ",5,5);
myGLCD.printNumI(highestScore, 120, 6);
myGLCD.print(">RESET<",255,5);
myGLCD.drawLine(0,23,319,23);
myGLCD.print("TAP TO START",CENTER,100);
drawBird(yB); // Dibuja el pájaro
// Espera hasta que toquemos la pantalla
while (!gameStarted) {
if (myTouch.dataAvailable()) {
myTouch.read();
x=myTouch.getX();
y=myTouch.getY();
// Reiniciar puntuación más alta
if ((x>=250) && (x<=319) &&(y>=0) && (y<=28)) {
highestScore = 0;
myGLCD.setColor(114, 198, 206);
myGLCD.fillRect(120, 0, 150, 22);
myGLCD.setColor(0, 0, 0);
myGLCD.printNumI(highestScore, 120, 5);
}
if ((x>=0) && (x<=319) &&(y>=30) && (y<=239)) {
gameStarted = true;
myGLCD.setColor(114, 198, 206);
myGLCD.fillRect(0, 0, 319, 32);
}
}
}
// Limpia el texto "TAP TO START" antes de iniciar el juego
myGLCD.setColor(114, 198, 206);
myGLCD.fillRect(85, 100, 235, 116);
}Lenguaje: Arduino (arduino)// La Sección Principal del Loop
void loop() {
xP=xP-movingRate; // xP - coordenada x de los pilares; rango: 319 - (-51)
drawPilars(xP, yP); // Dibuja los pilares
// yB - coordenada y del pájaro que depende del valor de la variable fallingRate
yB+=fallRateInt;
fallRate=fallRate+0.4; // Cada iteración la tasa de caída aumenta para efecto de aceleración/gravedad
fallRateInt= int(fallRate);
// Comprueba colisiones
if(yB>=180 || yB<=0){ // arriba y abajo
gameOver();
}
if((xP<=85) && (xP>=5) && (yB<=yP-2)){ // pilar superior
gameOver();
}
if((xP<=85) && (xP>=5) && (yB>=yP+60)){ // pilar inferior
gameOver();
}
// Dibuja el pájaro
drawBird(yB);
// Después de que el pilar ha pasado por la pantalla
if (xP<=-51){
xP=319; // Reinicia xP a 319
yP = rand() % 100+20; // Número aleatorio para la altura de los pilares
score++; // Aumenta puntuación en uno
}
//==== Controlando el pájaro
if (myTouch.dataAvailable()&& !screenPressed) {
fallRate=-6; // Poner fallingRate negativo hará que el pájaro salte
screenPressed = true;
}
// No permite mantener la pantalla / debes tocarla
else if ( !myTouch.dataAvailable() && screenPressed){
screenPressed = false;
}
// Después de cada cinco puntos, aumenta la tasa de movimiento de los pilares
if ((score - lastSpeedUpScore) == 5) {
lastSpeedUpScore = score;
movingRate++;
}
}Lenguaje: Arduino (arduino)
// ===== drawPilars - Función Personalizada
void drawPilars(int x, int y) {
if (x>=270){
myGLCD.setColor(0, 200, 20);
myGLCD.fillRect(318, 0, x, y-1);
myGLCD.setColor(0, 0, 0);
myGLCD.drawRect(319, 0, x-1, y);
myGLCD.setColor(0, 200, 20);
myGLCD.fillRect(318, y+81, x, 203);
myGLCD.setColor(0, 0, 0);
myGLCD.drawRect(319, y+80, x-1, 204);
} else if( x<=268) {
// Dibuja rectángulo azul a la derecha del pilar
myGLCD.setColor(114, 198, 206);
myGLCD.fillRect(x+51, 0, x+60, y);
// Dibuja el pilar
myGLCD.setColor(0, 200, 20);
myGLCD.fillRect(x+49, 1, x+1, y-1);
// Dibuja el marco negro del pilar
myGLCD.setColor(0, 0, 0);
myGLCD.drawRect(x+50, 0, x, y);
// Dibuja rectángulo azul a la izquierda del pilar
myGLCD.setColor(114, 198, 206);
myGLCD.fillRect(x-1, 0, x-3, y);
// El pilar inferior
myGLCD.setColor(114, 198, 206);
myGLCD.fillRect(x+51, y+80, x+60, 204);
myGLCD.setColor(0, 200, 20);
myGLCD.fillRect(x+49, y+81, x+1, 203);
myGLCD.setColor(0, 0, 0);
myGLCD.drawRect(x+50, y+80, x, 204);
myGLCD.setColor(114, 198, 206);
myGLCD.fillRect(x-1, y+80, x-3, 204);
}
// Dibuja la puntuación
myGLCD.setColor(0, 0, 0);
myGLCD.setBackColor(221, 216, 148);
myGLCD.setFont(BigFont);
myGLCD.printNumI(score, 100, 220);
}Lenguaje: Arduino (arduino)
//====== drawBird() - Función Personalizada
void drawBird(int y) {
// Dibuja el pájaro - bitmap
myGLCD.drawBitmap (50, y, 35, 30, bird01);
// Dibuja rectángulos azules arriba y abajo del pájaro para limpiar su estado anterior
myGLCD.setColor(114, 198, 206);
myGLCD.fillRoundRect(50,y,85,y-6);
myGLCD.fillRoundRect(50,y+30,85,y+36);
}Lenguaje: Arduino (arduino)
//======== gameOver() - Función Personalizada
void gameOver() {
delay(1000); // 1 segundo
// Limpia la pantalla e imprime el texto
myGLCD.clrScr();
myGLCD.setColor(255, 255, 255);
myGLCD.setBackColor(0, 0, 0);
myGLCD.setFont(BigFont);
myGLCD.print("GAME OVER", CENTER, 40);
myGLCD.print("Score:", 100, 80);
myGLCD.printNumI(score,200, 80);
myGLCD.print("Restarting...", CENTER, 120);
myGLCD.setFont(SevenSegNumFont);
myGLCD.printNumI(2,CENTER, 150);
delay(1000);
myGLCD.printNumI(1,CENTER, 150);
delay(1000);
// Escribe la puntuación más alta en la EEPROM
if (score > highestScore) {
highestScore = score;
EEPROM.write(0,highestScore);
}
// Reinicia las variables a valores de posición inicial
xP=319;
yB=50;
fallRate=0;
score = 0;
lastSpeedUpScore = 0;
movingRate = 3;
gameStarted = false;
// Reiniciar juego
initiateGame();
}Lenguaje: Arduino (arduino)Aquí está el código completo del juego:
/* Proyecto Juego Arduino
* Programa hecho por Dejan Nedelkovski,
* www.HowToMechatronics.com */
/* Este programa usa las librerías UTFT y URTouch
* hechas por Henning Karlsen.
* Puedes encontrarlas y descargarlas en:
* www.RinkyDinkElectronics.com */
#include <UTFT.h>
#include <URTouch.h>
#include <EEPROM.h>
//==== Creando Objetos
UTFT myGLCD(SSD1289,38,39,40,41); //Parámetros ajustados a tu modelo de Display/Shield
URTouch myTouch( 6, 5, 4, 3, 2);
//==== Definiendo Fuentes
extern uint8_t SmallFont[];
extern uint8_t BigFont[];
extern uint8_t SevenSegNumFont[];
extern unsigned int bird01[0x41A]; // Bitmap del pájaro
int x, y; // Variables para coordenadas donde se presionó la pantalla
// Floppy Bird
int xP = 319;
int yP = 100;
int yB = 50;
int movingRate = 3;
int fallRateInt = 0;
float fallRate = 0;
int score = 0;
int lastSpeedUpScore = 0;
int highestScore;
boolean screenPressed = false;
boolean gameStarted = false;
void setup() {
// Iniciar display
myGLCD.InitLCD();
myGLCD.clrScr();
myTouch.InitTouch();
myTouch.setPrecision(PREC_MEDIUM);
highestScore = EEPROM.read(0); // Leer puntuación más alta de EEPROM
initiateGame(); // Iniciar el juego
}
void loop() {
xP=xP-movingRate; // xP - coordenada x de los pilares; rango: 319 - (-51)
drawPilars(xP, yP); // Dibuja los pilares
// yB - coordenada y del pájaro que depende del valor de la variable fallingRate
yB+=fallRateInt;
fallRate=fallRate+0.4; // Cada iteración la tasa de caída aumenta para efecto de aceleración/gravedad
fallRateInt= int(fallRate);
// Comprueba colisiones
if(yB>=180 || yB<=0){ // arriba y abajo
gameOver();
}
if((xP<=85) && (xP>=5) && (yB<=yP-2)){ // pilar superior
gameOver();
}
if((xP<=85) && (xP>=5) && (yB>=yP+60)){ // pilar inferior
gameOver();
}
// Dibuja el pájaro
drawBird(yB);
// Después de que el pilar ha pasado por la pantalla
if (xP<=-51){
xP=319; // Reinicia xP a 319
yP = rand() % 100+20; // Número aleatorio para la altura de los pilares
score++; // Aumenta puntuación en uno
}
//==== Controlando el pájaro
if (myTouch.dataAvailable()&& !screenPressed) {
fallRate=-6; // Poner fallingRate negativo hará que el pájaro salte
screenPressed = true;
}
// No permite mantener la pantalla / debes tocarla
else if ( !myTouch.dataAvailable() && screenPressed){
screenPressed = false;
}
// Después de cada cinco puntos, aumenta la tasa de movimiento de los pilares
if ((score - lastSpeedUpScore) == 5) {
lastSpeedUpScore = score;
movingRate++;
}
}
// ===== initiateGame - Función Personalizada
void initiateGame() {
myGLCD.clrScr();
// Fondo azul
myGLCD.setColor(114, 198, 206);
myGLCD.fillRect(0,0,319,239);
// Suelo
myGLCD.setColor(221,216,148);
myGLCD.fillRect(0, 215, 319, 239);
myGLCD.setColor(47,175,68);
myGLCD.fillRect(0, 205, 319, 214);
// Texto
myGLCD.setColor(0, 0, 0);
myGLCD.setBackColor(221, 216, 148);
myGLCD.setFont(BigFont);
myGLCD.print("Score:",5,220);
myGLCD.setFont(SmallFont);
myGLCD.print("HowToMechatronics.com", 140, 220);
myGLCD.setColor(0, 0, 0);
myGLCD.setBackColor(114, 198, 206);
myGLCD.print("Highest Score: ",5,5);
myGLCD.printNumI(highestScore, 120, 6);
myGLCD.print(">RESET<",255,5);
myGLCD.drawLine(0,23,319,23);
myGLCD.print("TAP TO START",CENTER,100);
drawBird(yB); // Dibuja el pájaro
// Espera hasta que toquemos la pantalla
while (!gameStarted) {
if (myTouch.dataAvailable()) {
myTouch.read();
x=myTouch.getX();
y=myTouch.getY();
// Reiniciar puntuación más alta
if ((x>=250) && (x<=319) &&(y>=0) && (y<=28)) {
highestScore = 0;
myGLCD.setColor(114, 198, 206);
myGLCD.fillRect(120, 0, 150, 22);
myGLCD.setColor(0, 0, 0);
myGLCD.printNumI(highestScore, 120, 5);
}
if ((x>=0) && (x<=319) &&(y>=30) && (y<=239)) {
gameStarted = true;
myGLCD.setColor(114, 198, 206);
myGLCD.fillRect(0, 0, 319, 32);
}
}
}
// Limpia el texto "TAP TO START" antes de iniciar el juego
myGLCD.setColor(114, 198, 206);
myGLCD.fillRect(85, 100, 235, 116);
}
// ===== drawPilars - Función Personalizada
void drawPilars(int x, int y) {
if (x>=270){
myGLCD.setColor(0, 200, 20);
myGLCD.fillRect(318, 0, x, y-1);
myGLCD.setColor(0, 0, 0);
myGLCD.drawRect(319, 0, x-1, y);
myGLCD.setColor(0, 200, 20);
myGLCD.fillRect(318, y+81, x, 203);
myGLCD.setColor(0, 0, 0);
myGLCD.drawRect(319, y+80, x-1, 204);
} else if( x<=268) {
// Dibuja rectángulo azul a la derecha del pilar
myGLCD.setColor(114, 198, 206);
myGLCD.fillRect(x+51, 0, x+60, y);
// Dibuja el pilar
myGLCD.setColor(0, 200, 20);
myGLCD.fillRect(x+49, 1, x+1, y-1);
// Dibuja el marco negro del pilar
myGLCD.setColor(0, 0, 0);
myGLCD.drawRect(x+50, 0, x, y);
// Dibuja rectángulo azul a la izquierda del pilar
myGLCD.setColor(114, 198, 206);
myGLCD.fillRect(x-1, 0, x-3, y);
// El pilar inferior
myGLCD.setColor(114, 198, 206);
myGLCD.fillRect(x+51, y+80, x+60, 204);
myGLCD.setColor(0, 200, 20);
myGLCD.fillRect(x+49, y+81, x+1, 203);
myGLCD.setColor(0, 0, 0);
myGLCD.drawRect(x+50, y+80, x, 204);
myGLCD.setColor(114, 198, 206);
myGLCD.fillRect(x-1, y+80, x-3, 204);
}
// Dibuja la puntuación
myGLCD.setColor(0, 0, 0);
myGLCD.setBackColor(221, 216, 148);
myGLCD.setFont(BigFont);
myGLCD.printNumI(score, 100, 220);
}
//====== drawBird() - Función Personalizada
void drawBird(int y) {
// Dibuja el pájaro - bitmap
myGLCD.drawBitmap (50, y, 35, 30, bird01);
// Dibuja rectángulos azules arriba y abajo del pájaro para limpiar su estado anterior
myGLCD.setColor(114, 198, 206);
myGLCD.fillRoundRect(50,y,85,y-6);
myGLCD.fillRoundRect(50,y+30,85,y+36);
}
//======== gameOver() - Función Personalizada
void gameOver() {
delay(3000); // 1 segundo
// Limpia la pantalla e imprime el texto
myGLCD.clrScr();
myGLCD.setColor(255, 255, 255);
myGLCD.setBackColor(0, 0, 0);
myGLCD.setFont(BigFont);
myGLCD.print("GAME OVER", CENTER, 40);
myGLCD.print("Score:", 100, 80);
myGLCD.printNumI(score,200, 80);
myGLCD.print("Restarting...", CENTER, 120);
myGLCD.setFont(SevenSegNumFont);
myGLCD.printNumI(2,CENTER, 150);
delay(1000);
myGLCD.printNumI(1,CENTER, 150);
delay(1000);
// Escribe la puntuación más alta en la EEPROM
if (score > highestScore) {
highestScore = score;
EEPROM.write(0,highestScore);
}
// Reinicia las variables a valores de posición inicial
xP=319;
yB=50;
fallRate=0;
score = 0;
lastSpeedUpScore = 0;
movingRate = 3;
gameStarted = false;
// Reiniciar juego
initiateGame();
}Lenguaje: Arduino (arduino)
Proceso de manufactura
- Crea un Impresionante Sistema Ambilight para Monitor con Arduino
- Construya el clásico juego Pong en Arduino UNO con pantalla OLED: Tutorial paso a paso
- Mando USB para juegos DIY con Arduino – Construye tu propio gamepad de alto rendimiento
- Juego Breakout táctil Arduino: Arcade clásico en pantalla TFT 2,4″
- Construye un juego «Operación» animatrónico con minifigura LEGO gigante y Arduino
- Detector de frecuencia de audio con Arduino – Mide los picos de sonido más intensos
- Tres en raya con Arduino, matriz LED MAX7219 y carcasa de cartón
- Construye un Temporizador Regresivo Fiable con Arduino y Pantalla de 7 Segmentos SparkFun
- Pixel Chaser: Juego LED Interactivo de Un Toque con Arduino Nano
- Construye tu Propio Coche RC Porsche con Arduino: Guía Paso a Paso
- Juego Ruleta LED DIY: Crea un Arcade para Una Persona con Arduino Nano