Arduinoでフラッピーバードクローンを作ろう:TFTタッチスクリーンを使ったステップバイステップゲームプロジェクト
このArduinoプロジェクトでは、スマートフォンで人気のフラッピーバードゲームのレプリカを、ArduinoとTFTタッチスクリーンを使って作ります。以下の動画を見るか、下の文章を読んで仕組みを学んでください。 ゲームはシンプルですが面白く、中毒性があります。タッチスクリーンで鳥を操作し、速度が上がっていく動く柱を避けます。最高スコアは電源を切ってもEEPROMに保存されます。[/column] 前回のチュートリアル(Arduino TFTチュートリアル)では、ArduinoでTFTタッチスクリーンを使う方法を詳しく学び、ゲームの例をこのチュートリアルで説明することにしていました。前回と同様に、このArduinoゲームの背後にあるコードをステップバイステップで説明します。 コードが少し長いため、理解しやすいようプログラムのソースコードをセクションごとに分け、各セクションの説明を付けて掲載します。記事の最後に完全なソースコードを掲載します。 Henning Karlsen氏作のUTFTとURTouchライブラリを使用します。これらは彼のウェブサイト www.RinkyDinkElectronics.com からダウンロードできます。また、最高スコアをEEPROMに保存するためにEEPROMライブラリも使用します。EEPROMはボードの電源を切ってもデータを保持できるメモリです。 ライブラリをインクルードした後、UTFTとURTouchオブジェクトを作成し、ゲームに必要な変数を定義します。setupセクションではディスプレイとタッチを初期化し、EEPROMから最高スコアを読み取り、自作関数initiateGame()でゲームを初期化します。 自作関数initiateGame()でゲームの初期状態を描画します。まず画面をクリアし、青い背景を描画、底面を描画、テキストを追加し、自作関数drawBird()で鳥を描画します。その後、画面をタップするまでゲームが開始されないようwhileループで待機します。この状態で右上隅を押すと最高スコアをゼロにリセットし、画面のどこか他の場所を押すとwhileループを抜けてメインループに入り、ゲームが開始されます。 メインループセクションでは、xP変数で柱を描画し、yP変数で高さを管理します。最初、xPは画面サイズの319、yPは最初の柱の高さ100です。各イテレーションでxPの値をmovingRate変数の値(最初は3、ゲーム進行につれて増加)だけ減らします。 ゲームの仕組み:幅50ピクセルの柱が右から左へ移動し、次の柱は毎回異なるランダムな高さになります。論理的には各イテレーションで画面をクリアし、新しい位置でグラフィックを再描画すれば動きますが、画面のリフレッシュレートが低いため、グラフィックがチラついてしまいます。画面がすべてのピクセルをアクティブにするにはもう少し時間が必要なため、動いているものだけを再描画する工夫をします。 drawPilars()自作関数がどうしているか見てみましょう。xPとyP変数を受け取り、fillRect()関数で柱を描画します。各イテレーションで柱を新しい位置に描画し、左右に追加の青い長方形を描いて前回描画した柱を消去することで、動く柱だけを再描画する工夫をしています。ここでのif文は、fillRect()関数の'x2'パラメータが画面サイズ外の値になると動作しなかったための追加の工夫です。また、この関数の最後で到達スコアの値を表示します。 loopセクションに戻ると、yB変数が鳥のY位置で、落下率に依存し、各イテレーションで増加することで加速/重力の効果を出しています。また、衝突判定を行い、鳥が上端、地面、柱に当たったらゲームオーバーになるようif文で制御しています。 次はdrawBird()自作関数です。鳥は実際には写真を、Henning Karlsen氏作のImageConverter565ツールでビットマップに変換したものです。ツールで作成された「.c」ファイルをディレクトリに含めることで、スケッチ起動時に読み込まれます。またビットマップをこのように定義し、drawBitmap()関数で画面に描画します。鳥のX座標は固定で、Y座標にはyB変数を使用。柱と同様に、鳥の上下に青い長方形を描いて前の状態を消去します。 loopに戻ると、柱が画面を通過した後、xPを319にリセット、yPに20〜100のランダム値を設定して柱の高さを決め、スコアを1増やします。次のif文で鳥を操作:画面をタップすると落下率を負にして鳥をジャンプさせ、else if文で画面を押しっぱなしにできないようにしています。最後のif文はゲームの難易度用で、5ポイントごとに柱の移動速度を上げます。 最後にgameOver()自作関数の仕組みを見てみましょう。1秒遅延後、画面をクリアしてスコアとテキストを表示、スコアが最高スコアより高ければEEPROMに書き込み、すべての変数を初期値にリセットし、最後にinitiateGame()を呼び出してゲームを再開します。 以上です。コードの説明が十分明確だったことを願います。ご質問があれば、下のコメント欄でお気軽にお尋ねください。 Arduinoスケッチ、鳥の画像、鳥のビットマップファイルを含むダウンロードファイルはこちらです。概要

ソースコード
#include <UTFT.h>
#include <URTouch.h>
#include <EEPROM.h>
//==== オブジェクトの作成
UTFT myGLCD(SSD1289,38,39,40,41); // パラメータはお使いのディスプレイ/シールドモデルに合わせて調整
URTouch myTouch( 6, 5, 4, 3, 2);
//==== フォントの定義
extern uint8_t SmallFont[];
extern uint8_t BigFont[];
extern uint8_t SevenSegNumFont[];
extern unsigned int bird01[0x41A]; // 鳥のビットマップ
int x, y; // ディスプレイが押された座標の変数
// 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() {
// ディスプレイ初期化
myGLCD.InitLCD();
myGLCD.clrScr();
myTouch.InitTouch();
myTouch.setPrecision(PREC_MEDIUM);
highestScore = EEPROM.read(0); // EEPROMから最高スコアを読み取り
initiateGame(); // ゲーム初期化
}
// ===== initiateGame - カスタム関数
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("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); // 鳥を描画
// 画面がタップされるまで待機
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);
}
}
}
// ゲーム開始前に"TAP TO START"テキストをクリア
myGLCD.setColor(114, 198, 206);
myGLCD.fillRect(85, 100, 235, 116);
}// メインループセクション
void loop() {
xP=xP-movingRate; // xP - 柱のx座標; 範囲: 319 - (-51)
drawPilars(xP, yP); // 柱を描画
// yB - 鳥のy座標、fallRate変数の値に依存
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; // xPを319にリセット
yP = rand() % 100+20; // 柱の高さ用乱数
score++; // スコアを1増やす
}
//==== 鳥の操作
if (myTouch.dataAvailable()&& !screenPressed) {
fallRate=-6; // fallRateを負にすると鳥がジャンプ
screenPressed = true;
}
// 画面を押しっぱなしにできない/タップ必須
else if ( !myTouch.dataAvailable() && screenPressed){
screenPressed = false;
}
// 5ポイントごとに柱の移動速度を上げる
if ((score - lastSpeedUpScore) == 5) {
lastSpeedUpScore = score;
movingRate++;
}
}
// ===== drawPlillars - カスタム関数
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() - カスタム関数
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() - カスタム関数
void gameOver() {
delay(1000); // 1秒
// 画面をクリアしてテキスト表示
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);
// 最高スコアをEEPROMに書き込み
if (score > highestScore) {
highestScore = score;
EEPROM.write(0,highestScore);
}
// 変数を初期位置の値にリセット
xP=319;
yB=50;
fallRate=0;
score = 0;
lastSpeedUpScore = 0;
movingRate = 3;
gameStarted = false;
// ゲーム再開
initiateGame();
}ゲームの完全なコードはこちら:
/* Arduino Game Proejct
* Program made by Dejan Nedelkovski,
* www.HowToMechatronics.com
*/
/* This program uses the UTFT and URTouch libraries
* made by Henning Karlsen.
* You can find and download them at:
* www.RinkyDinkElectronics.com
*/
#include <UTFT.h>
#include <URTouch.h>
#include <EEPROM.h>
//==== Creating Objects
UTFT myGLCD(SSD1289,38,39,40,41); //Parameters should be adjusted to your Display/Schield model
URTouch myTouch( 6, 5, 4, 3, 2);
//==== Defining Fonts
extern uint8_t SmallFont[];
extern uint8_t BigFont[];
extern uint8_t SevenSegNumFont[];
extern unsigned int bird01[0x41A]; // Bird Bitmap
int x, y; // Variables for the coordinates where the display has been pressed
// 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() {
// Initiate display
myGLCD.InitLCD();
myGLCD.clrScr();
myTouch.InitTouch();
myTouch.setPrecision(PREC_MEDIUM);
highestScore = EEPROM.read(0); // Read the highest score from the EEPROM
initiateGame(); // Initiate the game
}
void loop() {
xP=xP-movingRate; // xP - x coordinate of the pilars; range: 319 - (-51)
drawPilars(xP, yP); // Draws the pillars
// yB - y coordinate of the bird which depends on value of the fallingRate variable
yB+=fallRateInt;
fallRate=fallRate+0.4; // Each inetration the fall rate increase so that we can the effect of acceleration/ gravity
fallRateInt= int(fallRate);
// Checks for collision
if(yB>=180 || yB<=0){ // top and bottom
gameOver();
}
if((xP<=85) && (xP>=5) && (yB<=yP-2)){ // upper pillar
gameOver();
}
if((xP<=85) && (xP>=5) && (yB>=yP+60)){ // lower pillar
gameOver();
}
// Draws the bird
drawBird(yB);
// After the pillar has passed through the screen
if (xP<=-51){
xP=319; // Resets xP to 319
yP = rand() % 100+20; // Random number for the pillars height
score++; // Increase score by one
}
//==== Controlling the bird
if (myTouch.dataAvailable()&& !screenPressed) {
fallRate=-6; // Setting the fallRate negative will make the bird jump
screenPressed = true;
}
// Doesn't allow holding the screen / you must tap it
else if ( !myTouch.dataAvailable() && screenPressed){
screenPressed = false;
}
// After each five points, increases the moving rate of the pillars
if ((score - lastSpeedUpScore) == 5) {
lastSpeedUpScore = score;
movingRate++;
}
}
// ===== initiateGame - Custom Function
void initiateGame() {
myGLCD.clrScr();
// Blue background
myGLCD.setColor(114, 198, 206);
myGLCD.fillRect(0,0,319,239);
// Ground
myGLCD.setColor(221,216,148);
myGLCD.fillRect(0, 215, 319, 239);
myGLCD.setColor(47,175,68);
myGLCD.fillRect(0, 205, 319, 214);
// Text
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); // Draws the bird
// Wait until we tap the sreen
while (!gameStarted) {
if (myTouch.dataAvailable()) {
myTouch.read();
x=myTouch.getX();
y=myTouch.getY();
// Reset higest 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);
}
}
}
// Clears the text "TAP TO START" before the game start
myGLCD.setColor(114, 198, 206);
myGLCD.fillRect(85, 100, 235, 116);
}
// ===== drawPlillars - Custom Function
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) {
// Draws blue rectangle right of the pillar
myGLCD.setColor(114, 198, 206);
myGLCD.fillRect(x+51, 0, x+60, y);
// Draws the pillar
myGLCD.setColor(0, 200, 20);
myGLCD.fillRect(x+49, 1, x+1, y-1);
// Draws the black frame of the pillar
myGLCD.setColor(0, 0, 0);
myGLCD.drawRect(x+50, 0, x, y);
// Draws the blue rectangle left of the pillar
myGLCD.setColor(114, 198, 206);
myGLCD.fillRect(x-1, 0, x-3, y);
// The bottom pillar
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);
}
// Draws the score
myGLCD.setColor(0, 0, 0);
myGLCD.setBackColor(221, 216, 148);
myGLCD.setFont(BigFont);
myGLCD.printNumI(score, 100, 220);
}
//====== drawBird() - Custom Function
void drawBird(int y) {
// Draws the bird - bitmap
myGLCD.drawBitmap (50, y, 35, 30, bird01);
// Draws blue rectangles above and below the bird in order to clear its previus state
myGLCD.setColor(114, 198, 206);
myGLCD.fillRoundRect(50,y,85,y-6);
myGLCD.fillRoundRect(50,y+30,85,y+36);
}
//======== gameOver() - Custom Function
void gameOver() {
delay(3000); // 1 second
// Clears the screen and prints the text
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);
// Writes the highest score in the EEPROM
if (score > highestScore) {
highestScore = score;
EEPROM.write(0,highestScore);
}
// Resets the variables to start position values
xP=319;
yB=50;
fallRate=0;
score = 0;
lastSpeedUpScore = 0;
movingRate = 3;
gameStarted = false;
// Restart game
initiateGame();
}
製造プロセス
- Arduinoで作るモニター用Ambilightシステム:没入感アップの自作ガイド
- Arduino UNOとOLEDディスプレイでクラシックPongゲームを自作:ステップバイステップチュートリアル
- DIY Arduino USBゲームコントローラー - 高性能ゲームパッドを自作
- Arduinoタッチブレークアウトゲーム:2.4インチTFT LCDでクラシックアーケードを再現
- 巨大レゴミニフィグ・アニマトロニクス「オペレーション」ゲームをArduinoで自作
- Arduinoオーディオ周波数検出器 – 高感度サウンドモジュールで最大音量ピークの周波数を測定
- Arduino三目並べ:MAX7219 LEDマトリクスと段ボールケースで自作
- SparkFun 7セグメントディスプレイで作る信頼性の高いArduinoカウントダウンタイマー
- Pixel Chaser: Arduino Nanoで作るワンタップLEDゲーム
- Arduinoで自作RCポルシェカー:ステップバイステップガイド
- DIY LEDルーレットゲーム – Arduino Nanoで作る1人用アーケードゲーム