Construire un Clone Flappy Bird Arduino : Jeu Étape par Étape avec Écran Tactile TFT
Dans ce projet Arduino, nous allons réaliser un jeu Arduino sympa, en fait une réplique du célèbre jeu Flappy Bird pour smartphones, en utilisant un Arduino et un écran tactile TFT. Vous pouvez apprendre comment cela fonctionne en regardant la vidéo suivante ou en lisant le texte ci-dessous.
Vue d'Ensemble
Le jeu est assez simple mais intéressant et addictif. À l'aide de l'écran tactile, on contrôle l'oiseau et on essaie d'éviter les piliers mobiles dont la vitesse augmente au fur et à mesure. Le jeu peut aussi mémoriser votre meilleur score même si vous débranchez l'alimentation.
Dans le tutoriel précédent (Tutoriel Arduino TFT), nous avons appris en détail comment utiliser les écrans tactiles TFT avec un Arduino et nous avions laissé l'exemple du jeu pour ce tutoriel. Maintenant, tout comme dans le tutoriel précédent, nous allons expliquer étape par étape le code derrière ce jeu Arduino.
Code Source
Comme le code est un peu long et pour une meilleure compréhension, je posterai le code source du programme par sections avec description pour chaque section. À la fin de cet article, je posterai le code source complet.
Nous utiliserons les bibliothèques UTFT et URTouch d'Henning Karlsen. Vous pouvez les télécharger depuis son site www.RinkyDinkElectronics.com. Nous utiliserons aussi la bibliothèque EEPROM pour stocker le meilleur score dans l'EEPROM. L'EEPROM est une mémoire qui conserve les données même quand la carte est éteinte.
Après avoir inclus les bibliothèques, nous devons créer les objets UTFT et URTouch ainsi que définir les variables nécessaires au jeu. Dans la section setup, nous initialisons l'affichage et le tactile, lisons le meilleur score depuis l'EEPROM et lançons le jeu avec la fonction personnalisée initiateGame().
#include
#include
#include
//==== Création des Objets
UTFT myGLCD(SSD1289,38,39,40,41); // Paramètres à adapter à votre modèle d'écran/shield
URTouch myTouch(6, 5, 4, 3, 2);
//==== Définition des Polices
extern uint8_t SmallFont[];
extern uint8_t BigFont[];
extern uint8_t SevenSegNumFont[];
extern unsigned int bird01[0x41A]; // Bitmap de l'oiseau
int x, y; // Variables pour les coordonnées où l'écran a été pressé
// 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() {
// Initialisation de l'affichage
myGLCD.InitLCD();
myGLCD.clrScr();
myTouch.InitTouch();
myTouch.setPrecision(PREC_MEDIUM);
highestScore = EEPROM.read(0); // Lecture du meilleur score depuis l'EEPROM
initiateGame(); // Lancement du jeu
}
Avec la fonction personnalisée initiateGame(), nous allons dessiner l'état initial du jeu. Voici comment : on efface l'écran, on dessine le fond bleu, la section du bas, on ajoute le texte et on appelle drawBird() pour dessiner l'oiseau. Ensuite, on a besoin d'une boucle while qui empêchera le jeu de démarrer jusqu'à ce qu'on tape sur l'écran. Tant qu'on est dans cet état, si on appuie sur le coin supérieur droit, on peut réinitialiser le meilleur score à zéro, et si on appuie n'importe où ailleurs sur l'écran, on sort de la boucle while et on entre dans la boucle principale qui démarre le jeu.
// ===== initiateGame - Fonction Personnalisée
void initiateGame() {
myGLCD.clrScr();
// Fond bleu
myGLCD.setColor(114, 198, 206);
myGLCD.fillRect(0,0,319,239);
// Sol
myGLCD.setColor(221,216,148);
myGLCD.fillRect(0, 215, 319, 239);
myGLCD.setColor(47,175,68);
myGLCD.fillRect(0, 205, 319, 214);
// Texte
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("Meilleur 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); // Dessine l'oiseau
// Attend qu'on tape sur l'écran
while (!gameStarted) {
if (myTouch.dataAvailable()) {
myTouch.read();
x = myTouch.getX();
y = myTouch.getY();
// Réinitialiser le meilleur score
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);
}
}
}
// Efface le texte "TAP TO START" avant le début du jeu
myGLCD.setColor(114, 198, 206);
myGLCD.fillRect(85, 100, 235, 116);
}
Dans la section loop principale, nous avons la variable xP pour dessiner les piliers, ainsi que yP. Au début, xP vaut 319 (taille de l'écran) et yP vaut 100 (hauteur du premier pilier). À chaque itération, xP diminue de la valeur de movingRate (3 au début, puis augmente au fil du jeu).
// La Section Boucle Principale
void loop() {
xP = xP - movingRate; // xP - coordonnée x des piliers; plage: 319 - (-51)
drawPilars(xP, yP); // Dessine les piliers
// yB - coordonnée y de l'oiseau selon fallRate
yB += fallRateInt;
fallRate = fallRate + 0.4; // À chaque itération, fallRate augmente pour effet gravité
fallRateInt = int(fallRate);
// Vérification des collisions
if(yB >= 180 || yB <= 0) { // haut et bas
gameOver();
}
if((xP <= 85) && (xP >= 5) && (yB <= yP - 2)) { // pilier supérieur
gameOver();
}
if((xP <= 85) && (xP >= 5) && (yB >= yP + 60)) { // pilier inférieur
gameOver();
}
// Dessine l'oiseau
drawBird(yB);
// Après que le pilier a traversé l'écran
if (xP <= -51) {
xP = 319; // Réinitialise xP à 319
yP = rand() % 100 + 20; // Nombre aléatoire pour la hauteur des piliers
score++; // Augmente le score de 1
}
//==== Contrôle de l'oiseau
if (myTouch.dataAvailable() && !screenPressed) {
fallRate = -6; // fallRate négatif fait sauter l'oiseau
screenPressed = true;
}
// N'autorise pas à maintenir l'écran / il faut taper
else if (!myTouch.dataAvailable() && screenPressed) {
screenPressed = false;
}
// Après chaque 5 points, augmente la vitesse des piliers
if ((score - lastSpeedUpScore) == 5) {
lastSpeedUpScore = score;
movingRate++;
}
}
Voici le principe du jeu : nous avons des piliers de 50 pixels de large qui se déplacent de droite à gauche, chaque pilier suivant ayant une hauteur aléatoire différente. Pour les faire bouger, logiquement, après chaque itération, il faudrait effacer l'écran et redessiner les graphiques avec les piliers à leur nouvelle position. Cependant, on ne peut pas faire cela à cause du faible taux de rafraîchissement de l'écran, ce qui provoquerait un scintillement. L'écran a besoin de plus de temps pour activer tous ses pixels, donc nous devons improviser et redessiner seulement les éléments qui bougent.
Voyons comment la fonction personnalisée drawPilars() fait cela. Elle prend les variables xP et yP et, en les utilisant avec la fonction fillRect(), dessine les piliers. À chaque itération, elle dessine les piliers à leur nouvelle position avec des rectangles bleus supplémentaires sur leurs côtés gauche et droit qui effacent le pilier précédemment dessiné, réalisant ainsi l'improvisation de ne redessiner que les piliers en mouvement. Les instructions if ici sont une improvisation supplémentaire car pour une raison quelconque, fillRect() ne fonctionnait pas si son paramètre 'x2' avait une valeur hors de la taille de l'écran. À la fin de cette fonction, on affiche aussi la valeur du score atteint.
// ===== drawPilars - Fonction Personnalisée
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) {
// Dessine rectangle bleu à droite du pilier
myGLCD.setColor(114, 198, 206);
myGLCD.fillRect(x+51, 0, x+60, y);
// Dessine le pilier
myGLCD.setColor(0, 200, 20);
myGLCD.fillRect(x+49, 1, x+1, y-1);
// Dessine le cadre noir du pilier
myGLCD.setColor(0, 0, 0);
myGLCD.drawRect(x+50, 0, x, y);
// Dessine rectangle bleu à gauche du pilier
myGLCD.setColor(114, 198, 206);
myGLCD.fillRect(x-1, 0, x-3, y);
// Le pilier du bas
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);
}
// Affiche le score
myGLCD.setColor(0, 0, 0);
myGLCD.setBackColor(221, 216, 148);
myGLCD.setFont(BigFont);
myGLCD.printNumI(score, 100, 220);
}
De retour dans la boucle loop, nous avons la variable yB qui est la position y de l'oiseau et dépend du taux de chute qui augmente à chaque itération, créant l'effet d'accélération ou de gravité. On vérifie aussi les collisions et on utilise des instructions if pour limiter l'oiseau : s'il touche le haut, le sol ou les piliers, c'est game over.
Ensuite, la fonction personnalisée drawBird(). L'oiseau est en fait une photo convertie en bitmap avec l'outil ImageConverter565 d'Henning Karlsen. Le fichier ".c" créé doit être inclus dans le répertoire pour se charger au lancement du sketch. On définit le bitmap et on utilise drawBitmap() pour dessiner la photo. L'oiseau a une coordonnée X fixe et yB comme coordonnée Y. Comme pour les piliers, on efface l'état précédent de l'oiseau en dessinant deux rectangles bleus au-dessus et en dessous.
//====== drawBird() - Fonction Personnalisée
void drawBird(int y) {
// Dessine l'oiseau - bitmap
myGLCD.drawBitmap(50, y, 35, 30, bird01);
// Dessine rectangles bleus au-dessus et en dessous pour effacer l'état précédent
myGLCD.setColor(114, 198, 206);
myGLCD.fillRoundRect(50, y, 85, y-6);
myGLCD.fillRoundRect(50, y+30, 85, y+36);
}
De retour dans la boucle, après que le pilier a traversé l'écran, xP est réinitialisé à 319, yP prend une nouvelle valeur aléatoire de 20 à 100 pour la hauteur des piliers et le score augmente de 1. La prochaine instruction if contrôle l'oiseau : si on tape sur l'écran, on met le taux de chute à négatif ce qui fait sauter l'oiseau, et le else if empêche de maintenir l'écran appuyé. La dernière instruction if gère la difficulté et augmente la vitesse des piliers tous les 5 points.
Il reste à voir comment fonctionne la fonction gameOver(). Après un délai d'une seconde, elle efface l'écran, affiche le score et du texte, si le score est supérieur au meilleur score, l'écrit dans l'EEPROM, réinitialise toutes les variables à leurs valeurs de départ et appelle initiateGame() pour redémarrer le jeu.
//======== gameOver() - Fonction Personnalisée
void gameOver() {
delay(1000); // 1 seconde
// Efface l'écran et affiche le texte
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);
// Écrit le meilleur score dans l'EEPROM
if (score > highestScore) {
highestScore = score;
EEPROM.write(0, highestScore);
}
// Réinitialise les variables aux valeurs de départ
xP = 319;
yB = 50;
fallRate = 0;
score = 0;
lastSpeedUpScore = 0;
movingRate = 3;
gameStarted = false;
// Redémarre le jeu
initiateGame();
}
C'est tout ! J'espère que l'explication du code était assez claire. Si vous avez des questions, n'hésitez pas à les poser dans les commentaires ci-dessous.
Voici le code complet du jeu :
/* Projet Jeu Arduino
* Programme par Dejan Nedelkovski,
* www.HowToMechatronics.com
*/
/* Ce programme utilise les bibliothèques UTFT et URTouch
* d'Henning Karlsen.
* Vous pouvez les trouver et télécharger sur :
* www.RinkyDinkElectronics.com
*/
#include
#include
#include
//==== Création des Objets
UTFT myGLCD(SSD1289,38,39,40,41); // Paramètres à adapter à votre modèle
URTouch myTouch(6, 5, 4, 3, 2);
//==== Définition des Polices
extern uint8_t SmallFont[];
extern uint8_t BigFont[];
extern uint8_t SevenSegNumFont[];
extern unsigned int bird01[0x41A]; // Bitmap de l'oiseau
int x, y;
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() {
myGLCD.InitLCD();
myGLCD.clrScr();
myTouch.InitTouch();
myTouch.setPrecision(PREC_MEDIUM);
highestScore = EEPROM.read(0);
initiateGame();
}
void loop() {
xP = xP - movingRate;
drawPilars(xP, yP);
yB += fallRateInt;
fallRate = fallRate + 0.4;
fallRateInt = int(fallRate);
if(yB >= 180 || yB <= 0) { gameOver(); }
if((xP <= 85) && (xP >= 5) && (yB <= yP - 2)) { gameOver(); }
if((xP <= 85) && (xP >= 5) && (yB >= yP + 60)) { gameOver(); }
drawBird(yB);
if (xP <= -51) {
xP = 319;
yP = rand() % 100 + 20;
score++;
}
if (myTouch.dataAvailable() && !screenPressed) {
fallRate = -6;
screenPressed = true;
}
else if (!myTouch.dataAvailable() && screenPressed) {
screenPressed = false;
}
if ((score - lastSpeedUpScore) == 5) {
lastSpeedUpScore = score;
movingRate++;
}
}
// ===== initiateGame - Fonction Personnalisée
void initiateGame() {
myGLCD.clrScr();
myGLCD.setColor(114, 198, 206);
myGLCD.fillRect(0,0,319,239);
myGLCD.setColor(221,216,148);
myGLCD.fillRect(0, 215, 319, 239);
myGLCD.setColor(47,175,68);
myGLCD.fillRect(0, 205, 319, 214);
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("Meilleur 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);
while (!gameStarted) {
if (myTouch.dataAvailable()) {
myTouch.read();
x = myTouch.getX();
y = myTouch.getY();
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);
}
}
}
myGLCD.setColor(114, 198, 206);
myGLCD.fillRect(85, 100, 235, 116);
}
// ===== drawPilars - Fonction Personnalisée
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) {
myGLCD.setColor(114, 198, 206);
myGLCD.fillRect(x+51, 0, x+60, y);
myGLCD.setColor(0, 200, 20);
myGLCD.fillRect(x+49, 1, x+1, y-1);
myGLCD.setColor(0, 0, 0);
myGLCD.drawRect(x+50, 0, x, y);
myGLCD.setColor(114, 198, 206);
myGLCD.fillRect(x-1, 0, x-3, y);
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);
}
myGLCD.setColor(0, 0, 0);
myGLCD.setBackColor(221, 216, 148);
myGLCD.setFont(BigFont);
myGLCD.printNumI(score, 100, 220);
}
//====== drawBird() - Fonction Personnalisée
void drawBird(int y) {
myGLCD.drawBitmap(50, y, 35, 30, bird01);
myGLCD.setColor(114, 198, 206);
myGLCD.fillRoundRect(50, y, 85, y-6);
myGLCD.fillRoundRect(50, y+30, 85, y+36);
}
//======== gameOver() - Fonction Personnalisée
void gameOver() {
delay(3000);
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);
if (score > highestScore) {
highestScore = score;
EEPROM.write(0, highestScore);
}
xP = 319;
yB = 50;
fallRate = 0;
score = 0;
lastSpeedUpScore = 0;
movingRate = 3;
gameStarted = false;
initiateGame();
}
Voici un fichier de téléchargement avec le sketch Arduino, l'image de l'oiseau et le fichier bitmap.
Processus de fabrication
- Créer un système Ambilight spectaculaire pour moniteur avec Arduino
- Créer un jeu Pong classique sur Arduino UNO avec écran OLED – Tutoriel étape par étape
- Manette de jeu USB Arduino DIY – Construisez votre propre gamepad haute performance
- Jeu Breakout tactile Arduino : recréez l'arcade classique sur écran TFT 2,4″
- Construire un jeu « Opération » géant animé type Lego Minifigure avec Arduino
- Détecteur de fréquence audio Arduino — Mesurer le pic sonore avec module haute sensibilité
- Morpion sur matrice LED MAX7219 avec boîtier en carton
- Construire un minuteur à rebours Arduino fiable avec afficheur 7 segments SparkFun
- Pixel Chaser : Jeu LED interactif à une touche avec Arduino Nano