第8回ゲーム動作の基本2 ブロック崩しゲームの制作(ブロックを並べる)

第8回ゲーム動作の基本2
ブロック崩しゲームの制作(ブロックを並べる)
前回のラケットボールを改造して,ブロック崩しゲームを作成します。今回のポイント
は,ブロックは複数あることです。数10個のブロック1つ1つに対して,ボールが当た
ったときの判断や処理(ボールが跳ね返るとともにブロックが消える)を考えなければな
りません。
ブロックは,1つ1つ表示する場所は違えども,働きは皆同じはずです。現実世界では,
大量に製品を生産するときには,1つ1つ手作りにせず,部品の金型を作ってスピーディ
ーに作りますよね。Processing や Java 等では,
「クラス」や「インスタンス」という概念
を取り入れて,同じモノを大量生産します。これをオブジェクト指向プログラミングと言
います。
また,同じようなデータをひとまとめに扱うために,
「配列」を使います。今回は,これ
らの使い方を説明し,ブロック崩しゲームへと組み込みます。
1. クラスとインスタンスの使い方
1.1. クラスやメソッドの宣言
先ほども述べたように,あるモノの金型がクラスです。言い換えれば設計図のようなも
のです。この「モノ」をプログラムの世界では「オブジェクト」と呼びます。あるオブジ
ェクトを使うには,このクラス(設計図)を元にして制作(生成)します。これを「イン
スタンス」
(実体化)といいます。つまり,オブジェクトを使うには,クラスから具体化す
る必要があります。
サンプルスケッチ「classSample1.pde」を開きます。ここでは,クラスとして「Circle」
というものを作ります。クラスの名前は先頭の文字は大文字にします。
1
2
コードの後半がクラスの宣言です。これは,キーワード「class」に続いて,
class クラス名 {
フィールドの宣言
コンストラクタ
メソッドの定義
}
のように記述します。クラス名は最初の文字は大文字で表記します。
最初に記述するフィールドとは,そのクラスの中で使用する変数のことを指します。実
際には,インスタンスごとに独立した変数になります。今までの変数の宣言と同じように
型を指定します。
オブジェクトを新たに宣言するときには,
「クラス名 オブジェクト名」のように記述し
ます。サンプルスケッチでは,冒頭の部分になります。これは,あるクラスの型を持つオ
ブジェクトを作りますよという宣言です。ですが,まだこの段階ではインスタンスは生成
しません。
次のコンストラクタとは,インスタンスを生成する際に呼ばれる関数(メソッド)で,
フィールドに値を設定したりするので,フィールドの初期化をする部分と考えていいです。
クラス名と同じ名前の関数を定義するように書きます。これは,インスタンスを生成す
る
maru = new Circle(200, 300, 50, color(255, 0, 0));
と対応しています。一般には,
オブジェクト名 = new クラス名(・・・)
と記述します。このインスタンスを生成するときの引数の並びに対応しています。コンス
トラクタの{}の中は,「new」で指定した値をフィールドに受け渡しています。これで,フ
ィールドの初期値を設定しています。
最後のメソッドとは,そのクラスのインスタンスが持つ,独自の関数と思っていいです。
クラスでなくても独自の関数(ユーザー関数)を作ることができます。これを「メソッド」
とも言います。
自分で関数を作るときには,一般には以下のように記述します。
3
戻り値の型 メソッド名(引数){
具体的な処理
return 戻り値
}
戻り値とは,そのメソッドを実行した結果,引き渡される値のことを示します。「return
戻り値」で,戻り値が決められます。たとえば,整数値を返す時には「戻り値の型」は「int」
となり,変数の宣言のときの変数の型と同じです。
今回は戻り値がないので「void」を記述します。サンプルのメソッド「drawCircle」は
塗り色を指定して,円を描く関数です。
「draw()」ではインスタンスに対して,そのクラスで定義されたメソッドを呼び出して
います。メソッドを実行するには,
インスタンス名(オブジェクト名).メソッド名
のように,「.」(ドット)を付けて記述します。これは,
「インスタンスのメソッドを実行
する」という意味で,
「.」は「~の~」にあたると思っていいでしょう。
1.2. フィールドの値を参照する
次にサンプルスケッチ「classSample2.pde」を開きます。これは,クラスの宣言の中に
メソッドは記述していません。その代り,「draw()」で,インスタンスの各フィールドの値
を参照して,塗り色の設定や円の描画を行っています。
4
5
前述のメソッドの実行と同じように,「インスタンス名.フィールド名」で指定すると,
そのインスタンスの各フィールドを外部から参照することができます。逆に,インスタン
ス名を明示しないと,これらのフィールドの値は取り出すことができません。
2. 配列の使い方
今までのサンプルスケッチは 1 つの円を描いていましたが,たとえば3つ描きたいとき
はどうしたらいいでしょうか?単純に考えると,サンプルスケッチ「classSample3.pde」
のように,オブジェクトを3つ作り,それぞれのインスタンスを生成すればいいようです。
※クラスの宣言部は省略
しかし,生成するオブジェクトの数がもっと多くなると,非常に面倒くさくなりそうで
す。そこで,
「配列」を使います。これは,変数ですが,値を入れるところが複数あります。
ちょうど,引き出しが複数あるキャビネットのようなものです。それぞれの引き出しには
番号が付いていて,配列変数名と引き出しの番号を組み合わせて使います。
サンプルスケッチ「classSample4.pde」を読み込みます。通常の変数やクラスを使うと
きの表記に「[]」を加えます。これは,
「これから使う変数やオブジェクトは配列です」と
いうことを宣言するところです。その後に,
「new」を使い,
maru = new Circle[3];
のように配列の引き出しの数(要素)を指定します。この例では3個なります。これから
各要素を番号(これをインデックスという)で指定しますが,番号は 0~2 になります。最
初の要素を指定するには「maru[0]」のようにします。
6
配列 maru の構成
インデックス
要素
0
maru[0]
1
maru[1]
2
maru[2]
このインデックスは,整数型変数でも指定できるので,for 文を使って,すべての要素に
対して操作することができます。
この例では,それぞれの要素に,クラス「circle」のインスタンスを生成する処理や,
円を描くメソッド「drawCircle」の実行に繰り返し処理を使っています。
3. ブロック崩しゲームへ改造する
3.1. ラケットゲームのボールとパドルをクラスで表現する
まずは,前回のラケットゲームをオブジェクト指向のプログラムに改造します。サンプ
ルスケッチ「ball5.pde」を読み込みます。
クラスとして「Ball」と「Paddle」を定義しましたが,今回から各クラスの記述を別の
ファイルにしました。と言っても,Processing のスケッチでは,複数のファイルをひとま
とめに扱うことができます。それにはタブを使います。赤丸で示したボタンをクリックす
ると,メニューが現れます。
7
「New Tab」をクリックすると「name for new file」と表示されるので,入力欄にクラス
名を入れて新しいタブを作ります。サンプルスケッチでは,既に作成してあり,それぞれ
のクラスの定義が記述してあります。
各クラスには同じ名前のメソッド「update()」がありますが,それぞれボールやパドル
の位置を決める処理で,実際に描画するのは別のメソッド「drawBall()」や「drawPaddle()」
になります。
メインのコード,今回は「ball5.pde」ですが,ここの「draw()」で,それぞれのメソッ
ドを実行しているわけです。
8
9
クラス Ball の定義
// クラス定義
class Ball {
int bx;
int by;
int bSize;
int bSpeed;
int bDx;
int bDy;
// コンストラクタ
Ball(int x, int y, int size, int speed, int dx, int dy) {
this.bx = x;
this.by = y;
this.bSize = size;
this.bSpeed = speed;
this.bDx = dx;
this.bDy = dy;
}
// インスタンスメソッド
// ボールを描画
void drawBall() {
ellipse(bx, by, bSize, bSize);
}
// ボールの位置を決める
void update() {
// 左右の壁に当たったら,X 方向の運動の向きを変える
if(bx <= bSize / 2 || bx >= width - bSize / 2) {
bDx = - bDx;
}
// 上の壁に当たったら,X 方向の運動の向きを変える
if(by <= bSize / 2) {
bDy = - bDy;
10
}
bx += bDx;
by += bDy;
// 下の壁に到達したら,ボールを再び初期の位置に戻す
if (by > height - bSize / 2) {
bx = 250;
by = 50;
}
// ボールがパドルに当たったときの処理
// ボールの Y 座標とパドルの Y 座標の差が,ボールの半径とパドルの高さの
// 半分を足した距離以内になり,ボールの X 座標がパドルの両端以内に
// あるときに,パドルに当たったと判断する
// パドルの端に当たったときには,跳ね返る角度が浅くなる
if(bDy > 0 && myPaddle.py - by > 0
&& myPaddle.py - by <= bSize / 2 + 10) {
if(abs(bx - myPaddle.px) <= 30) {
bDy = - bDy;
if(bDx < 0){
bDx = -10;
} else {
bDx = 10;
}
} else if(abs(bx - myPaddle.px) <= 50) {
bDy = - bDy;
if(bDx < 0){
bDx = -20;
} else {
bDx = 20;
}
}
}
}
}
11
クラス Paddle の定義
ここで,「this.bx」といった表記がありますが。この「this」は自分自身つまり,生成
12
されたインスタンスそのものを表します。「this」がなくても動作しますが,敢えて明示し
てあります。
3.2. ブロックを表示する
次にブロック崩しゲームに発展させます。まずはブロックの表示を考えてみます。ブロ
ックは複数あります。ゲームの設計によっては,ブロックの数を変えることもあるかも知
れません。
スケッチ「ball6.pde」を読み込んでください。このようなときには,ブロックをクラス
で定義した方が,処理が簡単になります。一見複雑そうですが,まずブロックの集合体の
クラス Blocks を定義します。このクラスのインスタンスは,個々のブロックを定義したク
ラス Block のインスタンスの配列になります。今回は,50 個のブロックを1行当たり10
個並べることにします。
13
14
つまり,1つ1つのブロックは Block のインスタンスで,これらを要素にした配列とし
て,取り扱うことができます。1 つのブロックを特定するために,配列のインデックスで指
し示せばいいわけです。
新たなタブ「Blocks」にこの2つのクラスの定義を行っています。クラス Blocks では,
フィールドの定義とコンストラクタ,そしてブロックを描画するインスタンスメソッドが
定義されています。コンストラクタでは,クラス Block のインスタンスを配列として生成
しています。
クラス Block では,1つのブロックについてのフィールドとコンストラクタだけを定義
15
しています。
メインのコードでは,このクラス Blocks のインスタンス myBlocks を生成しています。
もちろん,このインスタンスは配列になっており,インデックスで各ブロックを特定する
ことができます。
実行すると,ブロックが描画されますが,ボールが当たっても何も起きません。次回は,
ボールが当たったときの処理を記述します。
16