ボールを沢山出してみよう!

エディタ(導入必須)

繰り返しになりますが、エディタの導入をお願いします。

エディタは、プログラムを書くためのソフトです。
テキストの、ツール・素材の紹介ページにエディタがありますので、ダウンロードして使いましょう。
こちらです。

特に、Windows の方は xyzzy を必ず使って下さい。
本格的にプログラムを書く上で、ちゃんとしたエディタを使うことは欠かせません。
絶対にメモ帳を使ってはいけません。

※勘違いしている方が多いようですが、メモ帳などのエディタはあくまで 「テキストファイルを編集するためのソフト」です。
メモ帳で保存したからといって、保存するデータが変わるわけではありません。
メモ帳で保存しても、xyzzy で保存しても、保存される .java ファイルの形式は変わりません。

しかし、メモ帳を使って作業をされると、今後困りますので、極力 xyzzy を使うようにしてください。

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

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

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

配列

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

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

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

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

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

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

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

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

int ball_x0;
int ball_x1;
int ball_x2;
	

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

それでは、配列を使ってボールを 3 個同時に出してみましょう。
ダウンロードした資料を解凍して、Sample01 フォルダ直下にあるgame.javaを開いて下さい。
このプログラムでは、3つのボールを斜めに動かして、壁に当たったら跳ね返るようにしています。

/********* 変数定義はこちらに *********/
// 型 変数名; の順に書いて定義する
// ボールのX座標
int[] ball_x = new int[3];
// ボールのY座標
int[] ball_y = new int[3];
// ボールのX方向の速度
int[] ball_speed_x = new int[3];
// ボールのY方向の速度
int[] ball_speed_y = new int[3];
	

ここでは、ボールの座標と速度を、それぞれ大きさ 3 の配列として定義しています。

/********* 初期化の手順はこちらに *********/
public void initGame()
{
    // ボールの座標を初期化する
    ball_x[0] = 0;
    ball_y[0] = 100;
    ball_x[1] = 100;
    ball_y[1] = 100;
    ball_x[2] = 200;
    ball_y[2] = 100;

    // ボールの速度を初期化する
    ball_speed_x[0] = 5;
    ball_speed_y[0] = 5;
    ball_speed_x[1] = 5;
    ball_speed_y[1] = 5;
    ball_speed_x[2] = 5;
    ball_speed_y[2] = 5;
}
	

0 番目のボールは (0, 100)、1 番目のボールは (100, 100)、2 番目のボールは (200, 100) でそれぞれ初期化しています。
速度は全部 5 で初期化しています。

/********* 物体の移動等の更新処理はこちらに *********/
public void updateGame()
{
    // Y方向に ball_speed_y ずつ進める
    ball_y[0] = ball_y[0] + ball_speed_y[0];
    ball_y[1] = ball_y[1] + ball_speed_y[1];
    ball_y[2] = ball_y[2] + ball_speed_y[2];

    // X方向に ball_speed_x ずつ進める
    ball_x[0] = ball_x[0] + ball_speed_x[0];
    ball_x[1] = ball_x[1] + ball_speed_x[1];
    ball_x[2] = ball_x[2] + ball_speed_x[2];

    // ボールが画面の上を越えるか、画面の下を越えた場合
    if(ball_y[0] < 0 || ball_y[0] > 456)
    {
        // ボールのY方向の速度を反転させる
        ball_speed_y[0] = -ball_speed_y[0];
    }
    if(ball_y[1] < 0 || ball_y[1] > 456)
    {
        // ボールのY方向の速度を反転させる
        ball_speed_y[1] = -ball_speed_y[1];
    }
    if(ball_y[2] < 0 || ball_y[2] > 456)
    {
        // ボールのY方向の速度を反転させる
        ball_speed_y[2] = -ball_speed_y[2];
    }
    
    // ボールが画面の右を越えるか、画面の左を越えた場合
    if(ball_x[0] > 616 || ball_x[0] < 0)
    {
        // ボールのX方向の速度を反転させる
        ball_speed_x[0] = -ball_speed_x[0];
    }
    if(ball_x[1] > 616 || ball_x[1] < 0)
    {
        // ボールのX方向の速度を反転させる
        ball_speed_x[1] = -ball_speed_x[1];
    }
    if(ball_x[2] > 616 || ball_x[2] < 0)
    {
        // ボールのX方向の速度を反転させる
        ball_speed_x[2] = -ball_speed_x[2];
    }
}

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

    // img0.png を描画します
    gc.drawImage(0, ball_x[0], ball_y[0]);
    gc.drawImage(0, ball_x[1], ball_y[1]);
    gc.drawImage(0, ball_x[2], ball_y[2]);
}
	

ボールを動かす部分と、ボールを端で跳ね返らせる部分、ボールを描画する部分を、それぞれボールの数だけ書いています。

同じような処理を何度も書いていて、非常に面倒くさいです。
これがもしボールの数が 10 個だったらと思うと・・・考えたくもないですね。

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

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

Sample02 フォルダ直下にある game.java を開いて下さい。

/********* 初期化の手順はこちらに *********/
public void initGame()
{
    for(int i=0; i<3; i=i+1)
    {
        // ボールの座標を初期化する
        ball_x[i] = 100 * i;
        ball_y[i] = 100;
        ball_speed_x[i] = 5;
        ball_speed_y[i] = 5;
    }
}
	

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

for 文

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

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

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

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

// ボールの座標を初期化する
ball_x[0] = 100 * 0;
ball_y[0] = 100;
ball_speed_x[0] = 5;
ball_speed_y[0] = 5;
	

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

カウンタの加減算の後、継続条件を判定します。
i の中身は 1 なので、i < 3 は真となります。
真なので、再びブロックの中が実行されます。実行される内容は次のような感じです。

// ボールの座標を初期化する
ball_x[1] = 100 * 1;
ball_y[1] = 100;
ball_speed_x[1] = 5;
ball_speed_y[1] = 5;
	

これまでと同様、ブロックの中身を実行し終わると、カウンタの加減算処理です。
i=i+1 なので、i の中身が 2 となります。
その後、継続条件が判定され、i < 3 は真となり、再びブロックの中が実行されます。実行される内容は次のような感じです。

// ボールの座標を初期化する
ball_x[2] = 100 * 2;
ball_y[2] = 100;
ball_speed_x[2] = 5;
ball_speed_y[2] = 5;
	

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

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

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

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

フラグ(boolean)

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

実際の使い方は、次のような感じです。

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

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

変数定義で、boolean 型の変数を定義し、
initGame で、変数の中身を true に初期化しています。

if(flag)
{
}
	

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

時間差でボールを出す。

フラグを使って時間差でボールを出してみましょう。
Sample03 フォルダ直下にある game.java を開いて下さい。

このプログラムでは、1 秒ごとに新しいボールを出しています。
updateGame → drawGame は、1/30 秒毎に繰り返し実行されているので、30 回に 1 度ボールを出せば良いことになります。

/********* 変数定義はこちらに *********/
// 型 変数名; の順に書いて定義する
// ボールのX座標
int[] ball_x = new int[10];
// ボールのY座標
int[] ball_y = new int[10];
// ボールのX方向の速度
int[] ball_speed_x = new int[10];
// ボールのY方向の速度
int[] ball_speed_y = new int[10];

// ボールが生きているか、死んでいるかのフラグ
boolean[] ball_alive_flag = new boolean[10];

// 次に有効にするボール番号
int next_alive_ball_num;

// ゲームの経過時間
int time;
	

ここでは、新たに3つの変数が追加されました。
また、配列の大きさが 10 となっています。

/********* 初期化の手順はこちらに *********/
public void initGame()
{
    for(int i=0; i<10; i=i+1)
    {
        // ボールの座標を初期化する
        ball_x[i] = 0;
        ball_y[i] = 0;
        // ボールの速度を初期化する
        ball_speed_x[i] = 5;
        ball_speed_y[i] = 5;
        // ボールのフラグを初期化
        ball_alive_flag[i] = false;
    }

    // 次に有効にするボール番号を初期化
    next_alive_ball_num = 0;
    // ゲームの経過時間を初期化
    time = 0;
}
	

初期化処理です。
ボールは全て左上から現れるものとし、全てのボールを(0, 0) で初期化しています。
最初は1つもボールが無い状態なので、ボールのフラグは全て false です。

次に有効にするボール番号は、最初のボール番号である 0
ゲームの経過時間も、開始時間なので 0 です。

/********* 物体の移動等の更新処理はこちらに *********/
public void updateGame()
{
    // 30回に1回、ボールを有効に
    if(time % 30 == 0)
    {
        // ボールは10個までなので、それを超えない場合のみ実行する
        if(next_alive_ball_num < 10)
        {
            // ボールを有効に
            ball_alive_flag[next_alive_ball_num] = true;

            // 次のボールの番号へ。
            next_alive_ball_num = next_alive_ball_num + 1;
        }
    }
    
    for(int i=0; i<10; i=i+1)
    {
        // 有効(true)なボールのみ実行
        if(ball_alive_flag[i])
        {
            // Y方向に ball_speed_y ずつ進める
            ball_y[i] = ball_y[i] + ball_speed_y[i];

            // X方向に ball_speed_x ずつ進める
            ball_x[i] = ball_x[i] + ball_speed_x[i];

            // ボールが画面の上を越えるか、画面の下を越えた場合
            if(ball_y[i] < 0 || ball_y[i] > 456)
            {
                // ボールのY方向の速度を反転させる
                ball_speed_y[i] = -ball_speed_y[i];
            }

            // ボールが画面の右を越えるか、画面の左を越えた場合
            if(ball_x[i] > 616 || ball_x[i] < 0)
            {
                // ボールのX方向の速度を反転させる
                ball_speed_x[i] = -ball_speed_x[i];
            }
        }
    }

    // 時間を 1 進める
    time = time + 1;
}
	

updateGame です。順番に説明していきます。
難しいですが、あせらず順番に理解していきましょう。

if(time % 30 == 0) は、30回に1回だけ成立します。
% は、割り算した余りです。例えば、time の中身が 100 なら、time % 30 の答えは 10 となります。
つまり、この if 文が成立するのは、time の中身が 0, 30, 60, 90 ... となる、30の倍数の時です。

next_alive_ball_num は、次に有効にするボール番号です。
最初に if( next_alive_ball_num < 10 ) が真となった場合、next_alive_ball_num の中身は 0 なので、
ball_alive_flag[next_alive_ball_num] = true; は、ball_alive_flag[0] = true; となります。

フラグを true にした後で、next_alive_ball_num に 1 加算しています。
これにより、次に ball_alive_flag[next_alive_ball_num] = true; に来た際には、前回の次の配列要素が true となります。

if( ball_alive_flag[i] ) は、ball_alive_flag[i] が true の場合のみ真となります。
このブロックの中では、ボールを動かし、壁に跳ね返らせる処理を行っています。
つまり、有効な( true である)ボールだけを動かしています。

そして最後に、time = time + 1; で、時間に 1 加算しています。

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

        // img0.png を描画します
        for(int i=0; i<10; i=i+1)
        {
            // 有効(true)なボールのみ実行
            if(ball_alive_flag[i])
            {
                gc.drawImage(0, ball_x[i], ball_y[i]);
            }
        }
    }
	

drawGame です。
ボールを動かす処理同様、有効な( true である)ボールだけを描画しています。

課題1

複数のボールを時間差で出し、ラケットで打ち返せるようにして下さい。

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

応用課題1

応用課題です。
ボタンを押すと、ラケットから弾が出るようにしてみましょう。
連続で発射できるように、弾は配列で管理するものとします。
弾の画像は、img3.png です。

難しい課題なので、無理だと思ったら挑戦しなくても結構です。
最終課題で、シューティングゲームのようなものを作ってみたいと考えている方は、是非挑戦してみてください。

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

応用課題2

応用課題1のように、ボタンを押すとラケットから弾が出るようにします。
そして、弾がボールに当たったら、ボールと弾が消えるようにしてください。
ボールは左右に動き、端に来たら跳ね返るものとします。

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

戻る