2015年07月26日

Arduino(4) ブロック崩しを作ってみる(1)

Arduinoでカラー液晶に表示ができるようになったので、ゲームを作ってみたいと思います。

といっても、ArduinoはCPUパワーもありませんし、描画はLCDのコントローラ任せなので、こちらも期待できません。
というわけで、比較的描画量の少ないゲームとして、「ブロック崩し」を選んでみました。


Arduinoのプログラミング言語は「Arduino言語」と呼ばれていますが、実態はほぼC言語で、それにArduino特有のお約束事がついている感じです。

もっとも基本的なお約束は、プログラム(Arduinoがスケッチと呼んでいるもの)は2つの関数setupとloopを定義するものである、ということです。
setup()が最初に1回呼ばれ、そのあとloop()が繰り返し呼び出されます。
(従って、通常loop()自体はループ1回分の処理しか行いません。)

基本的な事柄は

Arduino 日本語リファレンス

を見てください。
setupとloopと一般的なC言語におけるmain関数との関係については

Studio Gyokimae

が参考になります。


今回は、まずはボールを画面の中で動かすところを作ってみます。

IDEのメニューから「新規ファイル」を選ぶと、以下のような雛形が現れます。
void setup() {
// put your setup code here, to run once:

}

void loop() {
// put your main code here, to run repeatedly:

}

この二つの関数を埋めればよいわけですが、今回は、setup()で必要な処理はLCDと変数の初期化だけです。
LCDの初期化は前回のgraphicstestなどのサンプルスケッチのsetup()をそのまま流用します。

loop()では、ボールが画面の端へ行ったら跳ね返るようにします。
ゲームではおなじみの処理ですね。
ついでですので、ラケットも描いて、ラケットはボールの位置に自動追従するようにしてみましょう。

プログラムはこんな感じです。
青い部分はサンプルスケッチからコピーしてきている部分です。
赤い部分がボールの処理、ピンクの部分はラケットの処理の部分です。

#include // Core graphics library
#include // Hardware-specific library

#define LCD_CS A3
#define LCD_CD A2
#define LCD_WR A1
#define LCD_RD A0
// optional
#define LCD_RESET A4

// Assign human-readable names to some common 16-bit color values:
#define BLACK 0x0000
#define BLUE 0x001F
#define RED 0xF800
#define GREEN 0x07E0
#define CYAN 0x07FF
#define MAGENTA 0xF81F
#define YELLOW 0xFFE0
#define WHITE 0xFFFF

Adafruit_TFTLCD tft(LCD_CS, LCD_CD, LCD_WR, LCD_RD, LCD_RESET);

#define RACKETSIZE 8
#define RACKETLINE 37
#define SCRNWIDTH 30
#define SCRNHEIGHT 40

typedef struct
{
uint8_t x,y;
int8_t vx,vy;
} Ball;

#define BallInit(ball) ball = {.x = SCRNWIDTH-1, .y = SCRNHEIGHT-SCRNWIDTH, .vx = -1, .vy = 1}
#define BallDraw(ball,tft,color) tft.fillRect(ball.x<<3, ball.y<<3, 8, 8, color)

#define RacketDraw(racket,tft,color) tft.fillRect(racket<<3, (RACKETLINE+1)<<3, RACKETSIZE<<3, 8, color)

#define MAX(a,b) (((a) > (b)) ? (a) : (b))
#define MIN(a,b) (((a) < (b)) ? (a) : (b))

int16_t racket=(SCRNWIDTH-RACKETSIZE)>>1;
Ball ball;

void setup(void) {
tft.reset();
uint16_t identifier = tft.readID();
tft.begin(identifier);
tft.fillScreen(BLACK);

BallInit(ball);
}


void loop()
{
BallDraw(ball, tft, BLACK);
ball.x += ball.vx;
if (ball.x >= SCRNWIDTH - 1 || ball.x < 1) {
ball.vx = -ball.vx;
}
ball.y += ball.vy;
if (ball.y == RACKETLINE) {
if (ball.x >= racket && ball.x < racket+RACKETSIZE) {
ball.vy = -ball.vy;
}
}
if (ball.y == 0) {
ball.vy = -ball.vy;
}
if (ball.y > SCRNHEIGHT) {
BallInit(ball);
}
BallDraw(ball, tft, YELLOW);

RacketDraw(racket,tft,BLACK);
racket=(ball.x)-(RACKETSIZE>>1);
racket=MAX(0, racket);
racket=MIN(racket, SCRNWIDTH-RACKETSIZE);
RacketDraw(racket,tft,WHITE);

delay(16);
}


今回は全ての処理を8ドット単位で行うことにしました。
LCDは240×320ピクセルですが、これを30×40の画面として使うことになります。
このほうが、将来いろいろな解像度の液晶に移植しやすいのではないかと考えたためです。

そのため、座標系は画面の左上(USBジャックのある側が上)を原点として、

X軸:0..29
Y軸:0..39

となります。

ボールの変数は、位置を表す(x,y)、それに水平・垂直方向の移動量を現す(vx,vy)が必要です。
Arduino言語はC言語ですので、構造体が使えます。
ということでBall型を定義しました。

しかし残念なことに、「構造体typedefした型を引数とする関数」はスケッチの中では定義できません。(typedefせずにstructのままなら引数で渡せます。)
これを行うには、構造体をtypedefを別ファイルで定義するか、typedef後に関数のプロトタイプを明示的に宣言する(→解説必要があるようです。
そこで、とりあえず初期化と描画の2つの処理を、関数ではなくマクロとして定義しました。


なお、Arduino言語ではクラス定義も(一応)行えますが、今回は使っていません。
クラス定義も構造体同様に「ライブラリ」として、ファイルを分けて定義しなければならないためです。

構造体とそれを扱う関数、あるいはクラス定義、については別ファイル(.c、.cpp、.hの拡張子を持つファイル)でライブラリとして定義し、スケッチからはインクルードして使用せよというのがArduinoの思想のようです。


最後の delay関数は、msec単位でのウエイトを行います。約1/60秒ということになります。
loop()を抜けると、またloop()の先頭から実行されます。

posted by boochow at 20:10| Comment(2) | Arduino | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
いつも記事楽しく拝見しています。

スケッチの関数引数として使えないのは、構造体ではなくtypedefした構造体だそうです。
Posted by くらはし at 2016年06月18日 08:46
亀レスすみません。typedefしなければ使えるんですね。ありがとうございます。
Posted by boochow at 2016年08月11日 10:40
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。
※ブログオーナーが承認したコメントのみ表示されます。
人気記事