123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391 |
- // By Ponticelli Domenico.
- // https://github.com/pcelli85/M5Stack_FlappyBird_game
- #include <M5Stack.h>
- #include <EEPROM.h>
- #define TFTW 320 // screen width
- #define TFTH 240 // screen height
- #define TFTW2 160 // half screen width
- #define TFTH2 120 // 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 16 // bird width
- #define BIRDH 16 // bird height
- #define BIRDW2 8 // half width
- #define BIRDH2 8 // half height
- // pipe size
- #define PIPEW 24 // pipe width
- #define GAPHEIGHT 42 // pipe gap height
- // floor size
- #define FLOORH 30 // floor height (from bottom of the screen)
- // grass size
- #define GRASSH 4 // grass height (inside floor, starts at floor y)
- int maxScore = 0;
- 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)
- void setup() {
- // put your setup code here, to run once:
- M5.begin();
- M5.Power.begin();
- resetMaxScore();
- }
- void loop() {
- // put your main code here, to run repeatedly:
- 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
- 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) {
- // ===============
- // input
- // ===============
- if (M5.BtnB.wasPressed()) {
- // 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;
- }
- M5.update();
-
- // ===============
- // 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);
- }
- // erase behind pipe
- if (pipes.x <= TFTW) M5.Lcd.drawFastVLine(pipes.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;
- 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( TFTW2, 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(10, TFTH2 - 20, TFTW-20, 1, TFT_WHITE);
- M5.Lcd.fillRect(10, TFTH2 + 32, TFTW-20, 1, TFT_WHITE);
- M5.Lcd.setTextColor(TFT_WHITE);
- M5.Lcd.setTextSize(3);
- // half width - num char * char width in pixels
- M5.Lcd.setCursor( TFTW2-(6*9), TFTH2 - 16);
- M5.Lcd.println("FLAPPY");
- M5.Lcd.setTextSize(3);
- M5.Lcd.setCursor( TFTW2-(6*9), TFTH2 + 8);
- M5.Lcd.println("-BIRD-");
- M5.Lcd.setTextSize(2);
- M5.Lcd.setCursor( 10, TFTH2 - 36);
- M5.Lcd.println("M5Stack");
- M5.Lcd.setCursor( TFTW2 - (17*9), TFTH2 + 36);
- M5.Lcd.println("Premi il bottone centrale");
- while (1) {
- // wait for push button
- if(M5.BtnB.wasPressed()) {
- break;
- }
- M5.update();
-
- }
- // init game settings
- game_init();
- }
- void game_init() {
- // clear screen
- M5.Lcd.fillScreen(BCKGRDCOL);
- // reset score
- score = 0;
- // init bird
- bird.x = 144;
- 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);
- EEPROM_Read(&maxScore,0);
-
- if(score>maxScore)
- {
- EEPROM_Write(&score,0);
- maxScore = score;
- M5.Lcd.setTextColor(TFT_RED);
- M5.Lcd.setTextSize(2);
- M5.Lcd.setCursor( TFTW2 - (13*6), TFTH2 - 26);
- M5.Lcd.println("NEW HIGHSCORE");
- }
-
- M5.Lcd.setTextColor(TFT_WHITE);
- M5.Lcd.setTextSize(3);
- // half width - num char * char width in pixels
- M5.Lcd.setCursor( TFTW2 - (9*9), TFTH2 - 6);
- M5.Lcd.println("GAME OVER");
- M5.Lcd.setTextSize(2);
- M5.Lcd.setCursor( 10, 10);
- M5.Lcd.print("score: ");
- M5.Lcd.print(score);
- M5.Lcd.setCursor( TFTW2 - (12*6), TFTH2 + 18);
- M5.Lcd.println("press button");
- M5.Lcd.setCursor( 10, 28);
- M5.Lcd.print("Max Score:");
- M5.Lcd.print(maxScore);
- while (1) {
- // wait for push button
- if(M5.BtnB.wasPressed()) {
- break;
- }
- M5.update();
- }
- }
- void resetMaxScore()
- {
- EEPROM_Write(&maxScore,0);
- }
- void EEPROM_Write(int *num, int MemPos)
- {
- byte ByteArray[2];
- memcpy(ByteArray, num, 2);
- for(int x = 0; x < 2; x++)
- {
- EEPROM.write((MemPos * 2) + x, ByteArray[x]);
- }
- }
- void EEPROM_Read(int *num, int MemPos)
- {
- byte ByteArray[2];
- for(int x = 0; x < 2; x++)
- {
- ByteArray[x] = EEPROM.read((MemPos * 2) + x);
- }
- memcpy(num, ByteArray, 2);
- }
|