FlappyBird.ino 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  1. // By Ponticelli Domenico.
  2. // https://github.com/pcelli85/M5Stack_FlappyBird_game
  3. #include <M5Stack.h>
  4. #include <EEPROM.h>
  5. #define TFTW 320 // screen width
  6. #define TFTH 240 // screen height
  7. #define TFTW2 160 // half screen width
  8. #define TFTH2 120 // half screen height
  9. // game constant
  10. #define SPEED 1
  11. #define GRAVITY 9.8
  12. #define JUMP_FORCE 2.15
  13. #define SKIP_TICKS 20.0 // 1000 / 50fps
  14. #define MAX_FRAMESKIP 5
  15. // bird size
  16. #define BIRDW 16 // bird width
  17. #define BIRDH 16 // bird height
  18. #define BIRDW2 8 // half width
  19. #define BIRDH2 8 // half height
  20. // pipe size
  21. #define PIPEW 24 // pipe width
  22. #define GAPHEIGHT 42 // pipe gap height
  23. // floor size
  24. #define FLOORH 30 // floor height (from bottom of the screen)
  25. // grass size
  26. #define GRASSH 4 // grass height (inside floor, starts at floor y)
  27. int maxScore = 0;
  28. const int buttonPin = 2;
  29. // background
  30. const unsigned int BCKGRDCOL = M5.Lcd.color565(138,235,244);
  31. // bird
  32. const unsigned int BIRDCOL = M5.Lcd.color565(255,254,174);
  33. // pipe
  34. const unsigned int PIPECOL = M5.Lcd.color565(99,255,78);
  35. // pipe highlight
  36. const unsigned int PIPEHIGHCOL = M5.Lcd.color565(250,255,250);
  37. // pipe seam
  38. const unsigned int PIPESEAMCOL = M5.Lcd.color565(0,0,0);
  39. // floor
  40. const unsigned int FLOORCOL = M5.Lcd.color565(246,240,163);
  41. // grass (col2 is the stripe color)
  42. const unsigned int GRASSCOL = M5.Lcd.color565(141,225,87);
  43. const unsigned int GRASSCOL2 = M5.Lcd.color565(156,239,88);
  44. // bird sprite
  45. // bird sprite colors (Cx name for values to keep the array readable)
  46. #define C0 BCKGRDCOL
  47. #define C1 M5.Lcd.color565(195,165,75)
  48. #define C2 BIRDCOL
  49. #define C3 TFT_WHITE
  50. #define C4 TFT_RED
  51. #define C5 M5.Lcd.color565(251,216,114)
  52. static unsigned int birdcol[] =
  53. { C0, C0, C1, C1, C1, C1, C1, C0, C0, C0, C1, C1, C1, C1, C1, C0,
  54. C0, C1, C2, C2, C2, C1, C3, C1, C0, C1, C2, C2, C2, C1, C3, C1,
  55. C0, C2, C2, C2, C2, C1, C3, C1, C0, C2, C2, C2, C2, C1, C3, C1,
  56. C1, C1, C1, C2, C2, C3, C1, C1, C1, C1, C1, C2, C2, C3, C1, C1,
  57. C1, C2, C2, C2, C2, C2, C4, C4, C1, C2, C2, C2, C2, C2, C4, C4,
  58. C1, C2, C2, C2, C1, C5, C4, C0, C1, C2, C2, C2, C1, C5, C4, C0,
  59. C0, C1, C2, C1, C5, C5, C5, C0, C0, C1, C2, C1, C5, C5, C5, C0,
  60. C0, C0, C1, C5, C5, C5, C0, C0, C0, C0, C1, C5, C5, C5, C0, C0};
  61. // bird structure
  62. static struct BIRD {
  63. long x, y, old_y;
  64. long col;
  65. float vel_y;
  66. } bird;
  67. // pipe structure
  68. static struct PIPES {
  69. long x, gap_y;
  70. long col;
  71. } pipes;
  72. // score
  73. int score;
  74. // temporary x and y var
  75. static short tmpx, tmpy;
  76. // ---------------
  77. // draw pixel
  78. // ---------------
  79. // faster drawPixel method by inlining calls and using setAddrWindow and pushColor
  80. // using macro to force inlining
  81. #define drawPixel(a, b, c) M5.Lcd.setAddrWindow(a, b, a, b); M5.Lcd.pushColor(c)
  82. void setup() {
  83. // put your setup code here, to run once:
  84. M5.begin();
  85. M5.Power.begin();
  86. resetMaxScore();
  87. }
  88. void loop() {
  89. // put your main code here, to run repeatedly:
  90. game_start();
  91. game_loop();
  92. game_over();
  93. }
  94. // ---------------
  95. // game loop
  96. // ---------------
  97. void game_loop() {
  98. // ===============
  99. // prepare game variables
  100. // draw floor
  101. // ===============
  102. // instead of calculating the distance of the floor from the screen height each time store it in a variable
  103. unsigned char GAMEH = TFTH - FLOORH;
  104. // draw the floor once, we will not overwrite on this area in-game
  105. // black line
  106. M5.Lcd.drawFastHLine(0, GAMEH, TFTW, TFT_BLACK);
  107. // grass and stripe
  108. M5.Lcd.fillRect(0, GAMEH+1, TFTW2, GRASSH, GRASSCOL);
  109. M5.Lcd.fillRect(TFTW2, GAMEH+1, TFTW2, GRASSH, GRASSCOL2);
  110. // black line
  111. M5.Lcd.drawFastHLine(0, GAMEH+GRASSH, TFTW, TFT_BLACK);
  112. // mud
  113. M5.Lcd.fillRect(0, GAMEH+GRASSH+1, TFTW, FLOORH-GRASSH, FLOORCOL);
  114. // grass x position (for stripe animation)
  115. long grassx = TFTW;
  116. // game loop time variables
  117. double delta, old_time, next_game_tick, current_time;
  118. next_game_tick = current_time = millis();
  119. int loops;
  120. // passed pipe flag to count score
  121. bool passed_pipe = false;
  122. // temp var for setAddrWindow
  123. unsigned char px;
  124. while (1) {
  125. loops = 0;
  126. while( millis() > next_game_tick && loops < MAX_FRAMESKIP) {
  127. // ===============
  128. // input
  129. // ===============
  130. if (M5.BtnB.wasPressed()) {
  131. // if the bird is not too close to the top of the screen apply jump force
  132. if (bird.y > BIRDH2*0.5) bird.vel_y = -JUMP_FORCE;
  133. // else zero velocity
  134. else bird.vel_y = 0;
  135. }
  136. M5.update();
  137. // ===============
  138. // update
  139. // ===============
  140. // calculate delta time
  141. // ---------------
  142. old_time = current_time;
  143. current_time = millis();
  144. delta = (current_time-old_time)/1000;
  145. // bird
  146. // ---------------
  147. bird.vel_y += GRAVITY * delta;
  148. bird.y += bird.vel_y;
  149. // pipe
  150. // ---------------
  151. pipes.x -= SPEED;
  152. // if pipe reached edge of the screen reset its position and gap
  153. if (pipes.x < -PIPEW) {
  154. pipes.x = TFTW;
  155. pipes.gap_y = random(10, GAMEH-(10+GAPHEIGHT));
  156. }
  157. // ---------------
  158. next_game_tick += SKIP_TICKS;
  159. loops++;
  160. }
  161. // ===============
  162. // draw
  163. // ===============
  164. // pipe
  165. // ---------------
  166. // we save cycles if we avoid drawing the pipe when outside the screen
  167. if (pipes.x >= 0 && pipes.x < TFTW) {
  168. // pipe color
  169. M5.Lcd.drawFastVLine(pipes.x+3, 0, pipes.gap_y, PIPECOL);
  170. M5.Lcd.drawFastVLine(pipes.x+3, pipes.gap_y+GAPHEIGHT+1, GAMEH-(pipes.gap_y+GAPHEIGHT+1), PIPECOL);
  171. // highlight
  172. M5.Lcd.drawFastVLine(pipes.x, 0, pipes.gap_y, PIPEHIGHCOL);
  173. M5.Lcd.drawFastVLine(pipes.x, pipes.gap_y+GAPHEIGHT+1, GAMEH-(pipes.gap_y+GAPHEIGHT+1), PIPEHIGHCOL);
  174. // bottom and top border of pipe
  175. drawPixel(pipes.x, pipes.gap_y, PIPESEAMCOL);
  176. drawPixel(pipes.x, pipes.gap_y+GAPHEIGHT, PIPESEAMCOL);
  177. // pipe seam
  178. drawPixel(pipes.x, pipes.gap_y-6, PIPESEAMCOL);
  179. drawPixel(pipes.x, pipes.gap_y+GAPHEIGHT+6, PIPESEAMCOL);
  180. drawPixel(pipes.x+3, pipes.gap_y-6, PIPESEAMCOL);
  181. drawPixel(pipes.x+3, pipes.gap_y+GAPHEIGHT+6, PIPESEAMCOL);
  182. }
  183. // erase behind pipe
  184. if (pipes.x <= TFTW) M5.Lcd.drawFastVLine(pipes.x+PIPEW, 0, GAMEH, BCKGRDCOL);
  185. // bird
  186. // ---------------
  187. tmpx = BIRDW-1;
  188. do {
  189. px = bird.x+tmpx+BIRDW;
  190. // clear bird at previous position stored in old_y
  191. // we can't just erase the pixels before and after current position
  192. // because of the non-linear bird movement (it would leave 'dirty' pixels)
  193. tmpy = BIRDH - 1;
  194. do {
  195. drawPixel(px, bird.old_y + tmpy, BCKGRDCOL);
  196. } while (tmpy--);
  197. // draw bird sprite at new position
  198. tmpy = BIRDH - 1;
  199. do {
  200. drawPixel(px, bird.y + tmpy, birdcol[tmpx + (tmpy * BIRDW)]);
  201. } while (tmpy--);
  202. } while (tmpx--);
  203. // save position to erase bird on next draw
  204. bird.old_y = bird.y;
  205. // grass stripes
  206. // ---------------
  207. grassx -= SPEED;
  208. if (grassx < 0) grassx = TFTW;
  209. M5.Lcd.drawFastVLine( grassx %TFTW, GAMEH+1, GRASSH-1, GRASSCOL);
  210. M5.Lcd.drawFastVLine((grassx+64)%TFTW, GAMEH+1, GRASSH-1, GRASSCOL2);
  211. // ===============
  212. // collision
  213. // ===============
  214. // if the bird hit the ground game over
  215. if (bird.y > GAMEH-BIRDH) break;
  216. // checking for bird collision with pipe
  217. if (bird.x+BIRDW >= pipes.x-BIRDW2 && bird.x <= pipes.x+PIPEW-BIRDW) {
  218. // bird entered a pipe, check for collision
  219. if (bird.y < pipes.gap_y || bird.y+BIRDH > pipes.gap_y+GAPHEIGHT) break;
  220. else passed_pipe = true;
  221. }
  222. // if bird has passed the pipe increase score
  223. else if (bird.x > pipes.x+PIPEW-BIRDW && passed_pipe) {
  224. passed_pipe = false;
  225. // erase score with background color
  226. M5.Lcd.setTextColor(BCKGRDCOL);
  227. M5.Lcd.setCursor( TFTW2, 4);
  228. M5.Lcd.print(score);
  229. // set text color back to white for new score
  230. M5.Lcd.setTextColor(TFT_WHITE);
  231. // increase score since we successfully passed a pipe
  232. score++;
  233. }
  234. // update score
  235. // ---------------
  236. M5.Lcd.setCursor( TFTW2, 4);
  237. M5.Lcd.print(score);
  238. }
  239. // add a small delay to show how the player lost
  240. delay(1200);
  241. }
  242. // ---------------
  243. // game start
  244. // ---------------
  245. void game_start() {
  246. M5.Lcd.fillScreen(TFT_BLACK);
  247. M5.Lcd.fillRect(10, TFTH2 - 20, TFTW-20, 1, TFT_WHITE);
  248. M5.Lcd.fillRect(10, TFTH2 + 32, TFTW-20, 1, TFT_WHITE);
  249. M5.Lcd.setTextColor(TFT_WHITE);
  250. M5.Lcd.setTextSize(3);
  251. // half width - num char * char width in pixels
  252. M5.Lcd.setCursor( TFTW2-(6*9), TFTH2 - 16);
  253. M5.Lcd.println("FLAPPY");
  254. M5.Lcd.setTextSize(3);
  255. M5.Lcd.setCursor( TFTW2-(6*9), TFTH2 + 8);
  256. M5.Lcd.println("-BIRD-");
  257. M5.Lcd.setTextSize(2);
  258. M5.Lcd.setCursor( 10, TFTH2 - 36);
  259. M5.Lcd.println("M5Stack");
  260. M5.Lcd.setCursor( TFTW2 - (17*9), TFTH2 + 36);
  261. M5.Lcd.println("Premi il bottone centrale");
  262. while (1) {
  263. // wait for push button
  264. if(M5.BtnB.wasPressed()) {
  265. break;
  266. }
  267. M5.update();
  268. }
  269. // init game settings
  270. game_init();
  271. }
  272. void game_init() {
  273. // clear screen
  274. M5.Lcd.fillScreen(BCKGRDCOL);
  275. // reset score
  276. score = 0;
  277. // init bird
  278. bird.x = 144;
  279. bird.y = bird.old_y = TFTH2 - BIRDH;
  280. bird.vel_y = -JUMP_FORCE;
  281. tmpx = tmpy = 0;
  282. // generate new random seed for the pipe gape
  283. randomSeed(analogRead(0));
  284. // init pipe
  285. pipes.x = 0;
  286. pipes.gap_y = random(20, TFTH-60);
  287. }
  288. // ---------------
  289. // game over
  290. // ---------------
  291. void game_over() {
  292. M5.Lcd.fillScreen(TFT_BLACK);
  293. EEPROM_Read(&maxScore,0);
  294. if(score>maxScore)
  295. {
  296. EEPROM_Write(&score,0);
  297. maxScore = score;
  298. M5.Lcd.setTextColor(TFT_RED);
  299. M5.Lcd.setTextSize(2);
  300. M5.Lcd.setCursor( TFTW2 - (13*6), TFTH2 - 26);
  301. M5.Lcd.println("NEW HIGHSCORE");
  302. }
  303. M5.Lcd.setTextColor(TFT_WHITE);
  304. M5.Lcd.setTextSize(3);
  305. // half width - num char * char width in pixels
  306. M5.Lcd.setCursor( TFTW2 - (9*9), TFTH2 - 6);
  307. M5.Lcd.println("GAME OVER");
  308. M5.Lcd.setTextSize(2);
  309. M5.Lcd.setCursor( 10, 10);
  310. M5.Lcd.print("score: ");
  311. M5.Lcd.print(score);
  312. M5.Lcd.setCursor( TFTW2 - (12*6), TFTH2 + 18);
  313. M5.Lcd.println("press button");
  314. M5.Lcd.setCursor( 10, 28);
  315. M5.Lcd.print("Max Score:");
  316. M5.Lcd.print(maxScore);
  317. while (1) {
  318. // wait for push button
  319. if(M5.BtnB.wasPressed()) {
  320. break;
  321. }
  322. M5.update();
  323. }
  324. }
  325. void resetMaxScore()
  326. {
  327. EEPROM_Write(&maxScore,0);
  328. }
  329. void EEPROM_Write(int *num, int MemPos)
  330. {
  331. byte ByteArray[2];
  332. memcpy(ByteArray, num, 2);
  333. for(int x = 0; x < 2; x++)
  334. {
  335. EEPROM.write((MemPos * 2) + x, ByteArray[x]);
  336. }
  337. }
  338. void EEPROM_Read(int *num, int MemPos)
  339. {
  340. byte ByteArray[2];
  341. for(int x = 0; x < 2; x++)
  342. {
  343. ByteArray[x] = EEPROM.read((MemPos * 2) + x);
  344. }
  345. memcpy(num, ByteArray, 2);
  346. }