esp32 spaceShooter with M5STACK
esp32-spaceShooter ゲームを M5STACK に移植しました。 2018.01.12
SHOOTボタンで スタート と シュート。LEFTボタンで左。RIGHTで右移動です。
敵を全て破壊すると点数が加わり 次のステージに進む事ができます。
GitHub の 「HailTheBDFL」 に 「esp32-spaceShooter」 があります。
ESP32 用で Arduino IDE で使用できます。
基の仕様は ESP32 + ILI9314 TFT Display で M5STACK ( ILI9314 TFT ) に
移植致しました。 初心者にも解りやすいスケッチですので 勉強にもなる
かと思います。「HailTheBDFL」 に感謝致します。
追記:2018.03.10
Parts and Circuits の M5Stack-SpaceShooter から ダウンロード できます。
準備:
「 esp32-spaceShooter-v1_0 」を ダウンロードします。
移植:合計 24箇所で 削除 13箇所、変更 3つ、追加 8箇所 です。
1. 削除:もしくはコメントアウト( // ) します。:13箇所です。
_ 01. #include <Adafruit_GFX.h>
_ 02. #include <Adafruit_ILI9341.h>
_ 03. #define TFT_CS 19
_ 04. #define TFT_DC 22
_ 05. #define TFT_MOSI 23
_ 06. #define TFT_CLK 26
_ 07. #define TFT_RST 21
_ 08. #define TFT_MISO 25
_ 09. Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_MOSI,
_ TFT_CLK, TFT_RST, TFT_MISO);
_ 10. touchAttachInterrupt(27, left, threshold); //Touch input
_ 11. touchAttachInterrupt(12, right, threshold);
_ 12. touchAttachInterrupt(14, up, threshold);
_ 13. ttouchAttachInterrupt(15, select, threshold);
2. 変更:DISPLAYを Adafruit_ILI9341 から M5STACK用に 3つ変更します。
_ 14. void setup() の「tft.begin(); 」 は 「 M5.begin(); 」に変更します。
_ 15. 全ての「 tft 」を 「 M5.Lcd 」に変更します。
_ Arduino IDE の「編集」の「検索」で 「検索テキスト = tft. 」,
_ 「 置換テキスト = M5.Lcd. 」, 「 全て置換 」で 一気に変更できます。
_ 16. 「 tft.setRotation(3); 」は「 M5.Lcd.setRotation(0); 」:3 から 0
_ にします。これは 画面の向きです。
3. 追加:M5STACK用に 7箇所追加します。
_ 場所:宣言部の最初の行:#include <Adafruit_ILI9341.h> のあった所。
_ 17. #include <M5Stack.h>
_ 場所:setup() の1番下に追加します。ボタンをプルアップします。
_ 18. pinMode(BUTTON_A_PIN, INPUT_PULLUP);
_ 19. pinMode(BUTTON_B_PIN, INPUT_PULLUP);
_ 20. pinMode(BUTTON_C_PIN, INPUT_PULLUP);
_ 場所:void loop() { の最初の行に追加します。ボタン操作です。
_ 21. if(M5.BtnA.isPressed()) { left () ;}
_ 22. if(M5.BtnB.isPressed()) { right () ;}
_ 23. if(M5.BtnC.isPressed()) { select() ;}
_ 場所:void loop() { の最後の行に追加します。
_ ボタンやスピーカーを使用した時は M5.update を入れます。
_ 24. M5.update();
コントロール:
_ 原作のコントロールは ESP32 の Touch input 端子を使用しています。
_ Touch input 端子は TOUCH 0 – TOUCH 9 まで 10個あります。
_ GPIO 0,2,4,12,13,14,15,27,32,33 です。
_ コントロールの内容は up, down, left, right, select の 4つです。
_ M5STACK の 全面フロントにある 左から A , B , C ボタンに置き換えます。
_ ボタンは 3つですので left = A, right = B, select = C にしました。
_ 結果:スタート と シュートは C ボタンです。左移動は A, 右は B です。
_ SHOOTのCボタンは ボタンを痛めない様に 押している時は連射する
_ 様に「isPressed」にしました。
_ ボタン操作:
_ if(M5.BtnC.isPressed()) { select() ;}
_ C ボタンを「押している間は」 次の命令(select)を実行。
_ if(M5.BtnC.wasPressed(); { select() ;}
_ C ボタンを「押した時に」 次の命令(select)を実行。
_ 他に、read, isReleased, wasReleased, pressedFor, releasedFor,
_ lastChange の便利な命令があります。
感想:
スケッチは 解り安い内容で 各自のアイデアを盛り込める事が出来そうです。
音を追加したり 画像をもっとリアルで綺麗に 変更できるかと思います。
高速画像操作では M5STACKのサンプルにある「TFT_Flash_Bitmap」の
drawIcon命令は サンプルを動かすと高速である事が体験できます。
M5STACK には多様なライブラリーがありますので これをマスターすると
素晴らしい表示が出来るかと思います。
音:SHOOT の音を追加して雰囲気をだそうとしましたが、うるさくなり
_ 逆に雰囲気を下げるのでやめました。
.
スケッチ:追記:2018.02.14:
_ Space Shooter with M5STACK : 2018.01.12 Transplant by macsbug
//======================== intro ======================================= // Space Shooter, basically knock-off Space Invaders // and also maybe a bit of Galaga // Written by Tyler Edwards for the Hackerbox #0020 badge // But should work on any ESP32 and Adafruit ILI9341 display // I am sorry for the messy code, you'll just // have to live with it // Tyler on GitHub: https://github.com/HailTheBDFL/ // Hackerboxes: http://hackerboxes.com/ //=========================== setup =================================== // Space Shooter with M5STACK : 2018.01.12 Transplant by macsbug // Controller : Buttons A = LEFT, B = RIGHT, C = START or SHOOTING // Github:https://macsbug.wordpress.com/2018/01/12/esp32-spaceshooter-with-m5stack/ //=================================================================== #include <M5Stack.h> //============================= game variables ========================= unsigned long offsetM = 0; unsigned long offsetT = 0; unsigned long offsetF = 0; unsigned long offsetB = 0; unsigned long offsetA = 0; unsigned long offsetAF = 0; unsigned long offsetAB = 0; unsigned long offsetS = 0; int threshold = 40; boolean startPrinted = false; boolean beginGame = false; boolean beginGame2 = true; boolean play = false; int score = 0; int scoreInc = 10; int level = 1; //---------------------Player--------------------------------------- int shipX = 147; int shipY = 190; int oldShipX = 0; int oldShipY = 0; int changeShipX = 0; int changeShipY = 0; int shipSpeed = 50; boolean doSplode = false; boolean fire = false; int fFireX[5] = {0, 0, 0, 0, 0}; int fFireY[5] = {0, 0, 0, 0, 0}; int fFireAge[5] = {0, 0, 0, 0, 0}; //--------------------------Aliens---------------------------------- boolean alienLive[18]; int alienLiveCount = 18; int alienX = 7; int alienY = 25; int oldAlienX = 7; int oldAlienY = 25; int changeAlienX = 6; int changeAlienY = 0; int alienSpeed = 200; int oldAlienSpeed; int aFireX[5]; int aFireY[5]; boolean aFireAge[5]; int chanceOfFire = 2; //================================ bitmaps ======================== //your starship const int shipImgW = 14; const int shipImgH = 16; char shipImg[] = "ZZZZZZWWZZZZZZZZZZZYWWYZZZZZZZZZZWWWWZZZZZZZZZZ" "WWWWZZZZZZZZZWWWWWWZZZZZZZZWWWWWWZZZZZYZZWWWWWWZZYZZYZZWWWWWWZZ" "YZWWZZWWBBWWZZWWWWZZWBBBBWZZWWWWZWWBBBBWWZWWWWZWWWWWWWWZWWWWWWW" "WWWWWWWWWWRWWWWWWWWWWRWZZWWWWWWWWWWZZZZZWRRWWRRWZZZ"; //flames const int flamesImgW = 12; const int flamesImgH = 6; char flamesImg[] = "RZZZZZZZZZZRRZRYYRRYYRZRRZRYYRRYYRZRZZRYRZZRY" "RZZZZZRZZZZRZZZZZZRZZZZRZZZ"; //alien const int alienImgW = 14; const int alienImgH = 11; char alienImg[] = "GGGZZZZZZZZGGGZZZGZZZZZZGZZZZZGGGGGGGGGGZZZGGG" "GGGGGGGGGZGGGZGGGGGGZGGGGGGZZGGGGZZGGGGGGGGGGGGGGGGGGGGGGGGGGGG" "GGGGZZZGGZZGGZZZGZGGZGGZZGGZGGZZZZZZGZZGZZZZZ"; //ship 'sploded const int splodedImgW = 14; const int splodedImgH = 16; char splodedImg[] = "ZZZZZZWWZZZZZZZZZZRYWWYRZZZYZZZRRWWRRRRZRWYZ" "RRRRRYYRRRZWYZRYRYYRYYRRRZWWRYYYRYYYYYRZWWRYYRYRYYYYRRWWRYYYRWR" "YBRRZRRRYRRWWWRYRWZZRYYRRBBWRYRWWZZRYYBBBRRYBWWRZZRYYYRRYYZZWZR" "RWRYYRBYRZZWZZRYBRYYYYYRRZZRWWYYYWWRRRZZZZWRRWWRRRWZZZ"; //=============================== setup and loop ================== void setup() { memset(alienLive, true, 18); memset(aFireX, 0, 5); memset(aFireY, 0, 5); memset(aFireAge, 0, 5); M5.begin(); M5.Lcd.setRotation(0);//M5.Lcd.setRotation(3); M5.Lcd.fillScreen(ILI9341_BLACK); M5.Lcd.setTextColor(0x5E85); M5.Lcd.setTextSize(4); randomSeed(analogRead(6)); pinMode(BUTTON_A_PIN, INPUT_PULLUP); pinMode(BUTTON_B_PIN, INPUT_PULLUP); pinMode(BUTTON_C_PIN, INPUT_PULLUP); } //================================================================== void loop() { if(M5.BtnA.isPressed()) { left () ;} if(M5.BtnB.isPressed()) { right () ;} if(M5.BtnC.isPressed()) { select() ;} //-------------Start Screen-------------- if (millis() - offsetS >= 900 and !beginGame) { if (!startPrinted) { M5.Lcd.setCursor(77, 105); M5.Lcd.print(">START<"); startPrinted = true; offsetS = millis(); } else { M5.Lcd.fillRect(77, 105, 244, 32, ILI9341_BLACK); startPrinted = false; offsetS = millis(); } } if (beginGame and beginGame2) { M5.Lcd.fillRect(77, 105, 244, 32, ILI9341_BLACK); beginGame2 = false; play = true; } //-------------Player----------------------------------------------- if (millis() - offsetM >= shipSpeed and play) { moveShip(); offsetM = millis(); } if (oldShipX != shipX or oldShipY != shipY) { M5.Lcd.fillRect(oldShipX, oldShipY, 28, 44, ILI9341_BLACK); oldShipX = shipX; oldShipY = shipY; drawBitmap(shipImg, shipImgW, shipImgH, shipX, shipY, 2); } if (fire and play) { fireDaLazer();} if (millis() - offsetB >= 50) { for (int i = 0; i < 5; i++) { if (fFireAge[i] < 20 and fFireAge[i] > 0){keepFirinDaLazer(i);} if (fFireAge[i] == 20) { stopFirinDaLazer(i);} } offsetB = millis(); } if (millis() - offsetT > 50) { changeShipX = 0; changeShipY = 0; } //---------------Aliens-------------------------------------------- if (millis() - offsetA >= alienSpeed and play) { moveAliens(); offsetA = millis(); } if (findAlienX(5) >= 294){changeAlienX = -3;changeAlienY = 7;} if (alienX <= 6){changeAlienX = 3; changeAlienY = 7;} alienLiveCount = 0; for (int i = 0; i < 18; i++) { if (alienLive[i]) { alienLiveCount += 1; if (alienShot(i)) { M5.Lcd.fillRect(findOldAlienX(i),findOldAlienY(i),28,22,BLACK); alienLiveCount -= 1; alienLive[i] = false; score += scoreInc; } if (onPlayer(i) or exceedBoundary(i)) { gameOver(); } } } if (alienLiveCount == 1) { oldAlienSpeed = alienSpeed; if (alienSpeed > 50) { alienSpeed -= 10; } else { alienSpeed = 20; } } if (alienLiveCount == 0) { levelUp(); } M5.update(); } // functions ======================================================= void gameOver() { play = false; if (doSplode) { drawBitmap(splodedImg,splodedImgW,splodedImgH,shipX,shipY,2); } M5.Lcd.fillScreen(ILI9341_BLACK); drawScore(false); delay(1000); M5.Lcd.setCursor(17, 168); M5.Lcd.setTextSize(2); M5.Lcd.print("(Reset device to replay)"); while (1) { } } //================================================================== void drawScore(boolean win) { M5.Lcd.setCursor(53, 40); M5.Lcd.setTextColor(ILI9341_WHITE); M5.Lcd.setTextSize(4); if (win) { M5.Lcd.print("LEVEL UP!"); } else { M5.Lcd.print("GAME OVER"); } for (;millis() - offsetM <= 1000;) M5.Lcd.setCursor(59, 89); M5.Lcd.setTextSize(3); M5.Lcd.print("Score: "); M5.Lcd.print(score); offsetM = millis(); for (;millis() - offsetM <= 1000;) { } M5.Lcd.setCursor(71, 128); M5.Lcd.print("Level: "); M5.Lcd.print(level); } //================================================================== void levelUp() { play = false; memset(alienLive, true, 18); memset(aFireX, 0, 5); memset(aFireY, 0, 5); memset(aFireAge, 0, 5); alienX = 7; alienY = 25; oldAlienX = 7; oldAlienY = 25; alienSpeed = oldAlienSpeed; if (alienSpeed > 100) { alienSpeed -= 10; chanceOfFire -= 10; } else if (alienSpeed > 50) { alienSpeed -= 10; chanceOfFire -=5; } else if (alienSpeed > 25) { alienSpeed -= 5; chanceOfFire -=1; } score += 50; scoreInc += 5; changeShipX = 0; changeShipY = 0; for (unsigned long i = millis(); millis() - i <= 1600;) { if (millis() - offsetM >= 20) { M5.Lcd.fillRect(oldShipX, oldShipY, 28, 44, ILI9341_BLACK); drawBitmap(shipImg,shipImgW,shipImgH,shipX,shipY,2); drawBitmap(flamesImg,flamesImgW,flamesImgH,shipX + 1, shipY + 32,2); oldShipX = shipX; oldShipY = shipY; shipY -= 6; offsetM = millis(); } } drawScore(true); level += 1; shipX = 147; shipY = 190; for (; millis() - offsetM <= 4000;) { } M5.Lcd.fillScreen(ILI9341_BLACK); offsetM = millis(); play = true; } //================================================================== boolean alienShot(int num) { for (int i=0; i < 5; i++) { if (fFireAge[i] < 20 and fFireAge[i] > 0) { if (fFireX[i] > findAlienX(num) - 4 and fFireX[i] < findAlienX(num) + 28 and fFireY[i] < findAlienY(num) + 22 and fFireY[i] > findAlienY(num) + 4) { fFireAge[i] = 20; return true; } } } return false; } //================================================================== boolean onPlayer(int num) { if (findAlienX(num) - shipX < 24 and findAlienX(num) - shipX > -28 and findAlienY(num) - shipY < 32 and findAlienY(num) - shipY > -22) { doSplode = true; return true; } else { return false;} } //================================================================== boolean exceedBoundary(int num) { if (findAlienY(num) > 218) { return true; } else { return false; } } //================================================================== void moveAliens() { for (int i = 0; i < 18; i++) { if (alienLive[i]) { M5.Lcd.fillRect(findOldAlienX(i),findOldAlienY(i),28,22,BLACK); drawBitmap(alienImg,alienImgW,alienImgH,findAlienX(i), findAlienY(i),2); } } oldAlienX = alienX; oldAlienY = alienY; alienX += changeAlienX; alienY += changeAlienY; if (changeAlienY != 0) { changeAlienY = 0; } } //================================================================== int findAlienX (int num) { return alienX + 42*(num % 6); } //================================================================== int findAlienY (int num) { return alienY + 33*(num / 6); } //================================================================== int findOldAlienX(int num) { return oldAlienX + 42*(num % 6); } //================================================================== int findOldAlienY(int num) { return oldAlienY + 33*(num / 6); } //---------------------------Player--------------------------------- void fireDaLazer() { int bulletNo = -1; for (int i = 0; i < 4; i++) { if (fFireAge[i] == 0) { bulletNo = i;} } if (bulletNo != -1) { fFireAge[bulletNo] = 1; fFireX[bulletNo] = shipX + 13; fFireY[bulletNo] = shipY - 4; M5.Lcd.fillRect(fFireX[bulletNo],fFireY[bulletNo],4,3,MAGENTA); } fire = false; } //================================================================== void keepFirinDaLazer(int bulletNo) { M5.Lcd.fillRect(fFireX[bulletNo],fFireY[bulletNo],4,4,BLACK); fFireY[bulletNo] -= 8; M5.Lcd.fillRect(fFireX[bulletNo],fFireY[bulletNo],4,4,MAGENTA); fFireAge[bulletNo] += 1; } //================================================================== void stopFirinDaLazer(int bulletNo) { M5.Lcd.fillRect(fFireX[bulletNo],fFireY[bulletNo],4,4,BLACK); fFireAge[bulletNo] = 0; } //================================================================== void moveShip() { if (shipX + changeShipX < 288 and shipX + changeShipX > 6 and changeShipX != 0){ shipX += changeShipX; } if (shipY + changeShipY > 24 and shipY + changeShipY < 192 and changeShipY != 0){ shipY += changeShipY; } if (oldShipX != shipX or oldShipY != shipY) { M5.Lcd.fillRect(oldShipX, oldShipY, 28, 44, ILI9341_BLACK); oldShipX = shipX; oldShipY = shipY; drawBitmap(shipImg, shipImgW, shipImgH, shipX, shipY, 2); } } //================================================================== void drawBitmap(char img[],int imgW,int imgH,int x,int y,int scale){ uint16_t cellColor; char curPix; for (int i = 0; i < imgW*imgH; i++) { curPix = img[i]; if (curPix == 'W') { cellColor = ILI9341_WHITE; } else if (curPix == 'Y') { cellColor = ILI9341_YELLOW; } else if (curPix == 'B') { cellColor = ILI9341_BLUE; } else if (curPix == 'R') { cellColor = ILI9341_RED; } else if (curPix == 'G') { cellColor = 0x5E85; } if (curPix != 'Z' and scale == 1) { M5.Lcd.drawPixel(x + i % imgW, y + i / imgW, cellColor); } else if (curPix != 'Z' and scale > 1) { M5.Lcd.fillRect(x + scale*(i%imgW),y + scale*(i/imgW),scale,scale,cellColor); } } } //=========================== button functions ===================== void up() { if (millis() - offsetT >= 50 and play) { changeShipX = 0; changeShipY = -6; offsetT = millis(); } } //================================================================== void down() { if (millis() - offsetT >= 50 and play) { changeShipX = 0; changeShipY = 6; offsetT = millis(); } } //================================================================== void left() { if (millis() - offsetT >= 50 and play) { changeShipX = -6; changeShipY = 0; offsetT = millis(); } } //================================================================== void right() { if (millis() - offsetT >= 50 and play) { changeShipX = 6; changeShipY = 0; offsetT = millis(); } } //================================================================== void select() { if (millis() - offsetF >= 500 and play) { fire = true; offsetF = millis(); } if (!beginGame) { beginGame = true;} } //==================================================================
macsbugさん、昨日はありがとうございました。
M5で、色々はじました。
spaceShooterできました!
なんでこのスケッチで、こんなゲームになるのか不思議です。
2. 変更:16.の 「 M5.Lcd.setRotation(0); 」
私の環境だと、『0』でなく『1』でした。
色々試してみます。
大瀧雅寛
5月 12, 2018 at 2:55 pm
大瀧さん、サイトの訪問をありがとうございます。
M5 は 直ぐアプリに取りかかれるので便利ですね。
そしてサンプルが沢山あり、どの程度できるか参考になります。
M5.Lcd.setRotation(0); 『0』でなく『1』でした。の情報をありがとうございます。
移植をしていると 『7』で動くものもあり課題になっています。
>なんでこのスケッチで、こんなゲームになるのか不思議です。
そうです。これに気がつかれるとは流石ですね。
このスケッチの手法は 学ぶものが多く 素晴らしいです。
エイリアン等の画像の定義は “GGYBRZ,,” と言う文字で定義されています。
文字を幾つか並べて 1つの「エイリアン」の画像を形成しています。
文字には Y,B,R,G があり、文字1つは 画面上の点( pixel 1個)で 色を示しています。
私は この 文字で 点 ( 命令は drawPixel ) を 表現する所に 驚きました。
他、B=青、R=赤、G=緑 です。
この文字の構成をエイリアンでなく 方眼紙で「うさぎさん」を下書きして
スケッチすると、お子様向けの可愛いゲームができそうです。
画像の抽画は drawBitmap サブルーチンで行われています。
Y は 黄色で M5.Lcd.drawPixel 命令で黄色の点を描いています。
Z は M5.Lcd.fillRect で 黒で塗り潰しています。
こうして幾つかの文字を点で描き 1つのキャラクターが描かれます。
そして 全体を右左に動かし、スムーズな動きを実行しています。
速度の速い ESP32 とプログラムの手法のおかげです。
他、サンプルにある様に M5STACK は アイコン位の大きさを 1つの画像として 高速に動かす命令があります。
さらに 他のライブラリーとして eSPI Library は スプライト命令が可能で さらに高速に抽画する事が可能です。
文字で丁寧に画像を定義したり、プログラム次第で アイコンの様な大きさで綺麗な画像を画面上に自由に動かす事ができると思います。
文字や数値を表示するだけでなく M5 の画面に綺麗に描くことにより素晴らしい物が作れるかと思っています。
macsbug
5月 12, 2018 at 5:32 pm
[…] Данная же игра немного переделана под M5stack. Плата M5Stack также имеет на борту дисплей ILI9314 TFT. Подогнал ее под m5stack один японский товарищ. Ссылка на его проект: https://macsbug.wordpress.com/2018/01/12/esp32-spaceshooter-with-m5stack/ […]
Игра galaxian на ESP32 M5stack. - У Павла!
11月 18, 2020 at 3:24 am
Thank you for visiting the site.
Thank you for the commentary and links.
The value of M5.Lcd.setRotation (1 / 0) has changed on the M5Stack side and may have been 1 in the past and 0 now.
macsbug
11月 18, 2020 at 6:47 am