ブロック崩しを作ろう!

注意点

テキストをちゃんと読んでから質問してください。
テキストをしっかり読まずに、図やテキストを流し読みをして分かった気になって、テキストを読めば分かることを質問してくる方が非常に多いです。

エディタ(導入必須)

繰り返しになりますが、xyzzy、Emacsなどのエディタの導入をお願いします。
特にWindows の方は xyzzy を必ず使って下さい。

エディタの導入方法はこちらです。

プログラミングが苦手で、上手くいかず質問してくる人ほど xyzzy を使っていないケースが多く、非常に困ります
自力でできず、TA・SAに質問しようという人は、必ず xyzzy を使うようにして下さい。

配列を使ってボールを 3 つ出す

まず、今回の講義資料をダウンロードしてください。
第5回資料(Windows用)
第5回資料(Mac用)

配列

配列とは、同じ型の複数のデータをひとまとめにしたものです。

// 配列の型[] 配列名 = new 配列の型[配列の大きさ];
int[] block_x = new int[3];
	

配列の場合、変数定義の部分でこのように記述します。
このプログラムは、block_x という int 型の配列で、大きさは 3 である、という意味になります。

block_x[0] = 0;
block_x[1] = 100;
block_x[2] = 200;
	

配列を使う場合には、このように記述します。

それぞれ、以下のような意味です。

block_x[0] = 0; で、配列の 0番目(先頭) に 0 をセット。
block_x[1] = 100; で、配列の 1 番目に 100 をセット。
block_x[2] = 200; で、配列の 2 番目に、200 をセット。

配列は 0 から数えますので、大きさ 3 の配列は、0, 1, 2 の 3 つのデータを保持できます。 配列を使うことで、定義の部分でわざわざ

int block_x0;
int block_x1;
int block_x2;
	

のように、いちいち記述せずにすむのです。

それでは、配列を使ってブロックを 3 個同時に描画してみましょう。
ダウンロードした資料を解凍して、Sample01 フォルダ直下にあるGame.javaを開いて下さい。
このプログラムを実行すると、赤色のブロックが3つ画面に表示されます。

まずは変数定義です。
ブロックの幅と高さに加えて、ブロックの座標を大きさ 3 の配列として定義しています。

/********* 変数定義はこちらに *********/
// 型 変数名; の順に書いて定義する
// ブロックの幅
int block_width;
// ブロック高さ
int block_height;
// ブロックの X, Y 座標
int block_x[] = new int[3];
int block_y[] = new int[3];
	

定義した変数は、以下のように初期化します。
ここでは、0 番目のブロックは (0, 0)、1 番目のブロックは (100, 0)、2 番目のブロックは (200, 0) の座標値でそれぞれ初期化しています。

/********* 初期化の手順はこちらに *********/
public void initGame()
{
    // ブロックの幅を50で初期化
    block_width = 50;
    // ブロックの高さ30で初期化
    block_height = 30;

    // ブロックの座標を初期化する
    block_x[0] = 0;
    block_y[0] = 0;
    block_x[1] = 100;
    block_y[1] = 0;
    block_x[2] = 200;
    block_y[2] = 0;
}
	

それでは、配列を使ってみましょう。
単純に書くと、更新処理と描画処理は以下のような内容となります。
ブロックは動かさないので、updateGameの中身には何も書いていません。

/********* 物体の移動等の更新処理はこちらに *********/
public void updateGame()
{
}

/********* 画像の描画はこちらに *********/
public void drawGame()
{
    // 画面を白で塗りつぶします
    gc.clearScreen();

    // ブロックの色を赤に
    gc.setColor(255, 0, 0);
    // ブロックを描画します
    gc.fillRect(block_x[0], block_y[0], block_width, block_height);
    gc.fillRect(block_x[1], block_y[1], block_width, block_height);
    gc.fillRect(block_x[2], block_y[2], block_width, block_height);
}
	

drawGameの中身には、ブロックを描画する処理を3回書いています。
ここで描画しているブロックは3個だけなのでまだマシですが、これが10個や100個だったら場合、描画処理を書くのがすごく面倒です。

このような場合に使われる構文として、for 文があります。

for 文を使って、ボールを 沢山出す

Sample02 フォルダ直下にある Game.java を開いて下さい。
Sample02は、Sample01の内容をfor文を使って書いています。

まずは初期化処理を見てください。

/********* 初期化の手順はこちらに *********/
public void initGame()
{
    // ブロックの幅を50で初期化
    block_width = 50;
    // ブロックの高さ30で初期化
    block_height = 30;

    for(int i=0; i<3; i=i+1)
    {
        // ブロックの座標を初期化する
        block_x[i] = 100 * i;
        block_y[i] = 0;
    }
}
	

initGame の中身が、Sample01 と比べてかなりスマートになっています。
( * は、掛け算を行うときの記号です。ちなみに、 / が割り算です。)
ここで使われているものが、for文です。

for 文

for 文の書き方は、for ( 初期値; 継続条件; カウンタの加減算 ) となります。

for 文は、同じような処理を何度も繰り返す場合に使われる構文です。
初期値で定義した変数の値が、継続条件を満たしている(継続条件が真である)限り、何度も { } の中が実行されます。
(初期値で定義した変数のことを、カウンタと呼びます)

initGame の中では、for文に差し掛かった瞬間、まず i というカウンタ変数が定義され、0 で初期化されています。

その後、 { } の中が実行されます。
i の中身は 0 なので、実行される内容は次のような感じになります。
(block_x[0] = 100 * 0; では、100 × 0 なので、0 が代入されます。)

// ブロックの座標を初期化する
block_x[0] = 100 * 0;
block_y[0] = 0;
	

 { } の中身を実行し終わると、再び for 文の先頭に戻ってきて、カウンタの加減算処理が実行されます。
ここでは、i=i+1 なので、i の中身が 1 となります。

カウンタの加減算の後、継続条件を判定します。
i の中身は 1 なので、i < 3 は真となります。
真なので、再び { } の中が実行されます。実行される内容は以下の通りです。

// ブロックの座標を初期化する
block_x[1] = 100 * 1;
block_y[1] = 0;
	

これまでと同様、 { } の中身を実行し終わると、カウンタの加減算処理です。
i=i+1 なので、i の中身が 2 となります。
その後、継続条件が判定され、i < 3 は真となり、再び { } の中が実行されます。実行される内容は以下の通りです。

// ブロックの座標を初期化する
block_x[2] = 100 * 2;
block_y[2] = 0;
	

 { } の中身を実行し終わると、カウンタの加減算処理です。
i=i+1 なので、i の中身が 3 となります。

その後、継続条件が判定されます。
i < 3 は偽なので、for 文がここで終了します。

なお、この for 文 で定義した i は、for 文の中( { } 内)以外で使うことはできません。

もちろん、for 文の中に、if 文や for 文などの他の構文を入れることも可能です。
腕に自身のある方はどんどん使っていきましょう。

 

ついでに、描画処理についても見ていきましょう。
drawGame 内のブロック描画処理もfor文を使ったものへと変更されています。

/********* 画像の描画はこちらに *********/
public void drawGame()
{
    // 画面を白で塗りつぶします
    gc.clearScreen();

    // ブロックの色を赤に
    gc.setColor(255, 0, 0);
    // ブロックを描画します
    for(int i=0; i<3; i=i+1)
    {
        gc.fillRect(block_x[i], block_y[i], block_width, block_height);
    }
}
	

このfor文の { } 内は、i が 0, 1, 2 の場合の3回分実行されます。
つまり、次のように書くのと同じ内容となるわけです。

gc.fillRect(block_x[0], block_y[0], block_width, block_height);
gc.fillRect(block_x[1], block_y[1], block_width, block_height);
gc.fillRect(block_x[2], block_y[2], block_width, block_height);
	

フラグ(boolean)

フラグとは、立っている状態( ON )と、折れている状態( OFF )の2つの状態をとる変数です。
GameCanvas (というよりもJava) では、boolean 型 の変数で、フラグを管理することが出来ます。

実際の使い方は次のような感じです。
変数定義で boolean 型の変数を定義し、
initGame で変数の中身を true に初期化しています。

/********* 変数定義はこちらに *********/
// 型 変数名; の順に書いて定義する
// フラグ
boolean flag;

/********* 初期化の手順はこちらに *********/
public void initGame()
{
    // フラグの初期化
    flag = true;
}
	

updateGame や drawGame の中で使う場合には、このようにして使います。
この if 文は、変数 flag の中身が true なら真、false なら偽となります。

if(flag)
{
}
	

ボールがぶつかったらブロックが消えるようにする

フラグを利用して、ボールがぶつかったらブロックが消えるようなプログラムを作成してみます。
Sample03 フォルダ直下にある Game.java を開いて下さい。

このプログラムでは、さきほど説明したフラグを利用して、ブロックの表示・非表示を管理しています。

まずは変数定義を見てください。

/********* 変数定義はこちらに *********/
// 型 変数名; の順に書いて定義する

(中略)

// ブロックの幅
int block_width;
// ブロック高さ
int block_height;
// ブロックの X, Y 座標
int block_x[] = new int[10];
int block_y[] = new int[10];

// ブロックが生きているかどうか
boolean block_alive_flag[] = new boolean[10];

	

ボール関係の変数はこれまでに作成したテニスゲームとほとんど変わりません。
ここでは、配列 block_alive_flag に注目してください。
block_alive_flag は、ブロックの表示・非表示を管理するためのフラグ配列です。
このフラグが true ならブロックが表示され、false ならブロックは非表示となります。
また、ブロックを横一列に10個並べたいので、配列の大きさが 10 となっています。

 

続いて、変数の初期化です。
定義した変数は、以下のように初期化します。

/********* 初期化の手順はこちらに *********/
public void initGame()
{
    (中略)

    // ブロックの幅を64で初期化
    block_width = 64;
    // ブロックの高さ30で初期化
    block_height = 30;

    for(int i=0; i<10; i=i+1)
    {
        block_x[i] = block_width * i;
        block_y[i] = 0;
        // ブロックの初期状態を表示状態にする
        block_alive_flag[i] = true;
    }
}
	

横一列にブロックを10個並べたいので、block_width には64をセットしています。
そしてfor文内では、block_x[0] = 64、block_x[1] = 128、block_x[2] = 192 ... と、64ごとにブロックが並べられるように座標値をセットしています。

座標値と合わせて、ブロックの表示・非表示を管理するフラグである block_alive_flag も初期化しています。
最初はすべてのブロックを表示するので、block_alive_flag 配列の中身にすべて true をセットしています。

 

続いては更新処理です。

/********* 物体の移動等の更新処理はこちらに *********/
public void updateGame()
{
    (中略)

    // ボールとブロックの当たり判定
    for(int i=0; i<10; i=i+1)
    {
        // ブロックが生きていれば(trueなら)判定を実行
        if(block_alive_flag[i])
        {
            // ブロックと、ボールとの当たり判定
            if( gc.checkHitRect(block_x[i], block_y[i], block_width, block_height, ball_x, ball_y, ball_size, ball_size) )
            {
                // ぶつかったブロックを非表示にするために、falseをセットする
                block_alive_flag[i] = false;
            }
        }
    }
}
	

ボールの動きに関する部分は、ここまでの回で解説しているので省略しています。

for 文内の処理に注目してください。
ここでは、すべてのブロックに対して順番に、フラグを活用した処理を行っています。

まず、if (block_alive_flag[i]) で、block_alive_flag 配列の中身が true かどうかを判定しています。
true ならそのブロックは表示されているので、そのブロックとボールがぶつかっているかどうかをチェックしています。
そして、ぶつかっていた場合にはブロックを非表示にするために、block_alive_flag 配列の中身を false に書き換えています。

block_alive_flag 配列の中身が false だった場合には、ブロックとボールがぶつかっているかどうかは調べません。
false なブロックはすでに表示されていないブロックなので、ボールが当たっても何も起こらないからです。

 

最後は描画処理です。

/********* 画像の描画はこちらに *********/
public void drawGame()
{
    // 画面を白で塗りつぶします
    gc.clearScreen();

    // img0.png を(ball_x, ball_y)に描画します
    gc.drawImage(0, ball_x, ball_y);

    // 色を赤にセット
    gc.setColor(255, 0, 0);
    // ブロックを描画します
    for(int i=0; i<10; i=i+1)
    {
        // ブロックが生きていれば(trueなら)描画する
        if(block_alive_flag[i])
        {
            gc.fillRect(block_x[i], block_y[i], block_width, block_height);
        }
    }
}
	

drawGame 内でも、フラグを利用した処理を行っています。
if (block_alive_flag[i]) で、配列の中身が true かどうかを調べて、true の時だけそのブロックを画面に表示しています。
こうすることで、ボールが当たって false になったブロックを表示せず、ボールが当たっていないブロックだけを表示することができるというわけです。

課題

これまでに学んだ内容を活用して、ブロック崩しゲームを作成します。
以下の内容を全て満たした課題プログラムを作ってください。

Kadai01フォルダの中のGame.javaを元に挑戦してみましょう。
Kadai01フォルダの中のanswer01.jar ファイルを開くと、どのように動かせば良いか見ることができます。


図:課題1の実行画面。ブロックが5列分表示されている。ボールがラケットに当たると跳ね返り、ボールがブロックに当たるとブロックが消える。

ヒント

ブロックの座標を初期化する際には、% と / の2つを利用します。

% は、余りを求める時の記号です。
例えば、17 % 10 の結果は 7 となります。

/ は割り算の記号です。
整数同士で割り算した場合、余りは切り捨てられます。
例えば、17 / 10 の結果は 1 となります。

これらを利用すると、50個のブロックは次のように書いて初期化することができます。

// ブロックの座標を初期化する
for(int i=0; i<50; i=i+1)
{
    block_x[i] = 64 * (i % 10);
    block_y[i] = 50 * (i / 10);

    (以下略)
	

どんな手順で処理が行われるか、詳しく見ていきましょう。

i=0 の時は、
0 % 10 の結果は 0
0 / 10 の結果は 0
よって、
block_x[0] = 0; block_y[0] = 0; となります。

i=1 の時は、
1 % 10 の結果は 1
1 / 10 の結果は 0
よって、
block_x[1] = 64; block_y[1] = 0; となります。

i=2 の時は、
2 % 10 の結果は 2
2 / 10 の結果は 0
よって、
block_x[2] = 128; block_y[2] = 0; となります。

 

しばらく進めて、

i=9 の時は、
9 % 10 の結果は 9
9 / 10 の結果は 0
よって、
block_x[9] = 576; block_y[9] = 0; となります。

i=10 の時は、
10 % 10 の結果は 0
10 / 10 の結果は 1
よって、
block_x[10] = 0; block_y[10] = 64; となります。

i=11 の時は、
11 % 10 の結果は 1
11 / 10 の結果は 1
よって、
block_x[11] = 64; block_y[11] = 64; となります。

i=12 の時は、
12 % 10 の結果は 2
12 / 10 の結果は 1
よって、
block_x[12] = 128; block_y[12] = 64; となります。

0〜9までは、ブロックのY座標は 0 のままで、X座標が 0, 64, 128, 192 ... となります。
10を超えたところからブロックのY座標が64になり、2列目に突入しました。X座標はまた 0 からです。

 

さらにしばらく進めて、

i=19 の時は、
19 % 10 の結果は 9
19 / 10 の結果は 1
よって、
block_x[19] = 576; block_y[19] = 64; となります。

i=20 の時は、
20 % 10 の結果は 0
20 / 10 の結果は 2
よって、
block_x[20] = 0; block_y[20] = 128; となります。

i=21 の時は、
21 % 10 の結果は 1
21 / 10 の結果は 2
よって、
block_x[21] = 64; block_y[21] = 128; となります。

i=22 の時は、
22 % 10 の結果は 2
22 / 10 の結果は 2
よって、
block_x[22] = 128; block_y[22] = 128; となります。

10〜19までは、ブロックのY座標は全て 64 で、X座標が 0, 64, 128, 192 ... となります。
20を超えたところからブロックのY座標が 128 となり、3列目に突入します。X座標はまた 0 からです。

このような感じで、0〜9 が1列目、10〜19が2列目、20〜29が3列目、30〜39が4列目、40〜49が5列目、といった形で合計50個のブロックの座標を初期化しています。

戻る