これはゲーム開発Canvasチュートリアルの10ステップのうち7番目のステップです。このレッスンを終えたあとの完成予想のソースコードはGamedev-Canvas-workshop/lesson7.htmlで入手できます。
ブロックは既に画面上に現れていますが、ボールが素通りしてしまうのでは面白くありません。ボールがブロックで弾み、ブロックが壊れるように衝突検出を追加することを考えなくてはなりません。
これをどのように実装するかは私達が決められることですが、ボールが長方形に接触しているかどうか計算するというのは、Canvasには助けになる関数もないため難しいかもしれません。このチュートリアルでは最も簡単な方法をとります。ボールの中心が与えられたブロックのどれかに衝突していないか確認するのです。これは毎回完璧な結果を返すとは限りませんし、衝突検出をするにはもっと洗練された方法がありますが、基本的な概念を学ぶには十分です。
衝突検出関数
最初の第一歩として、毎フレーム描画されるたびに全てのブロックを通してループし、ひとつひとつのブロックの位置をボールの座標と比較する衝突検出関数を作成しましょう。コードがより読みやすくなるように、衝突検出のループの中でブロックオブジェクトを保存する変数b
を定義します。
function collisionDetection() {
for(var c=0; c<brickColumnCount; c++) {
for(var r=0; r<brickRowCount; r++) {
var b = bricks[c][r];
// いろいろな計算
}
}
}
もしボールの中央がブロックの1つの座標の内部だったらボールの向きを変えます。ボールの中央がブロックの内部にあるためには次の4つの命題が全て真でなければなりません。
- ボールのx座標がブロックのx座標より大きい
- ボールのx座標がブロックのx座標とその幅の和より小さい
- ボールのy座標がブロックのy座標より大きい
- ボールのy座標がブロックのy座標とその高さの和より小さい
コードに書き下ろしてみましょう。
function collisionDetection() {
for(var c=0; c<brickColumnCount; c++) {
for(var r=0; r<brickRowCount; r++) {
var b = bricks[c][r];
if(x > b.x && x < b.x+brickWidth && y > b.y && y < b.y+brickHeight) {
dy = -dy;
}
}
}
}
上記のブロックを自分のコードのkeyUpHandler()
関数の下に追加してください。
ブロックが当たった後に消えるようにする
上記のコードは期待したとおり動作し、ボールの向きを変えるはずです。問題はブロックがそのままとどまっているということです。ボールに既に当たったブロックを取り除く方法を考え出さなければなりません。これはそれぞれのブロックを画面上に描画したいかどうかを示す新たなパラメーターを追加することで達成できます。ブロックを初期化している部分のコードで、それぞれのブロックオブジェクトにstatus
プロパティを追加しましょう。次の部分のコードをハイライトした行で示したように更新してください。
var bricks = [];
for(var c=0; c<brickColumnCount; c++) {
bricks[c] = [];
for(var r=0; r<brickRowCount; r++) {
bricks[c][r] = { x: 0, y: 0, status: 1 };
}
}
次に、それぞれのブロックを描画する前にstatus
プロパティの値をdrawBricks()
関数で確認します。もしstatus
が1
なら描画します。でももし0
ならそのブロックは既にボールに当たっていますから、これ以上画面上に描画されてほしくありません。自分のdrawBricks()
関数を次のように更新してください。
function drawBricks() {
for(var c=0; c<brickColumnCount; c++) {
for(var r=0; r<brickRowCount; r++) {
if(bricks[c][r].status == 1) {
var brickX = (c*(brickWidth+brickPadding))+brickOffsetLeft;
var brickY = (r*(brickHeight+brickPadding))+brickOffsetTop;
bricks[c][r].x = brickX;
bricks[c][r].y = brickY;
ctx.beginPath();
ctx.rect(brickX, brickY, brickWidth, brickHeight);
ctx.fillStyle = "#0095DD";
ctx.fill();
ctx.closePath();
}
}
}
}
衝突検出関数で状態を追跡、更新する
ここではcollisionDetection()
関数内でstatus
プロパティをブロックに紐づけていきます。もしブロックがアクティブ (状態が1
) なら衝突が起きるかどうか確認します。もし衝突が起きるのなら、画面上に描画されないようにそのブロックの状態を0
に設定します。自分のcollisionDetection()
関数を以下に示すように更新してください。
function collisionDetection() {
for(var c=0; c<brickColumnCount; c++) {
for(var r=0; r<brickRowCount; r++) {
var b = bricks[c][r];
if(b.status == 1) {
if(x > b.x && x < b.x+brickWidth && y > b.y && y < b.y+brickHeight) {
dy = -dy;
b.status = 0;
}
}
}
}
}
衝突検出を有効にする
collisionDetection()
関数への呼び出しをメインのdraw()
関数に追加して仕上げとします。次の行をdraw()
関数の、drawPaddle()
の呼び出しのすぐ下に追加してください。
collisionDetection();
コードを比べる
これでボールの衝突検出がそれぞれのブロックに対してフレームごとに確認されるようになりました。ブロックを壊せるようになったのです。
練習: ボールの色をブロックに当たったときに変えましょう。
次のステップ
着実にゴールに近づいています。では、先に進みましょう。第8章ではどのようにスコアと勝ち負けを記録するか見てみます。