B L O G

ブログ記事

thumbnail

純粋なJavascriptでのブロック崩しの作り方⑦

やりたいこと

フレームワークやライブラリを使わず、純粋な javascript のみでブロック崩しを作る。
今回は第 7 回目となり、ブロックの衝突判定を追加するところまでやっていきます。

前回はブロックを描画するところまでやりました ↓

ブロックには当たり判定がないと楽しくありませんね!
なので、前回描画したブロックがボールに衝突した時に、ブロックを消す処理を作っていきます。
ではさっそくやっていきましょう!

環境

  • Google Chrome : Version: 130.0.6723.92
  • VSCode : Version : 1.90.2 (Universal)

衝突を検出する関数

まず、ボールとブロックの衝突を検出する関数を下記のように定義します。


// 衝突を検出する関数
function CollisionDetection() {
for (let c = 0; c < blockColumnCount; c++) {
for (let r = 0; r < blockRowCount; r++) {
const block = blocks[c][r];
// 衝突判定
}
}
}

新しく CollisionDetection という関数を定義してあげます。 中身の for 文に関しては前回の通りで、const block = bricks[c][r];で各ブロックの値を定数 block として保存してあげます。

次に、コメントで衝突判定と書いてる部分に実際に衝突判定を追加していきます。

ブロックが当たった後にボールを反転させる

衝突した時に行いたい処理は、ボールを反転させ、衝突したブロックを消すことです。
まずはボールを反転させる処理を追加していきましょう。

下記のようなコードを keyUpHandler() 関数の下に追加してください。


// 衝突を検出する関数
function CollisionDetection() {
for (let c = 0; c < blockColumnCount; c++) {
for (let r = 0; r < blockRowCount; r++) {
const block = blocks[c][r];
// 衝突判定
if (x > block.x && x < block.x + blockWidth && y > block.y && y < block.y + blockHeight) {
dy = -dy; // ボールを反転
}
}
}
}

以下のようにブロックにボールが衝突すると、ボールが反転したかと思います。

ブロック崩しのボール反転実装

追加した if 文は長い条件ですが、書いてることは単純です。
一つ一つ見ていきましょう。

x > block.x は、ブロックの左端より、ボールの中心が右にある時です。 x < block.x + blockWidth はその逆、ブロックの右端より、ボールの中心が左にある時です。
この二つの条件でボールの中心の X 座標が、ブロック内にあるか分かります。

これと同じことを Y 座標でもやってあげるだけです。
y > block.y は、ブロックの上端より、ボールの中心が下にある時です。
y < block.y + blockHeight は、ブロックの下端より、ボールの中心が上にある時です。

最後に&&で全ての条件を結ぶことで、この 4 つの条件が揃った時のみかっこ内の処理が実行されます。

ブロックが当たった後に消えるようにする

ブロックが衝突した後に消す為には、それぞれのブロックを衝突後か衝突前かを確かめる必要があります。

既存のブロックを格納するための配列の、それぞれのブロックオブジェクトに status プロパティを追加しきます。

ブロックを格納するための配列を下記のように修正してみてください。


// ブロックを格納するための配列
const blocks = []; // 空の配列を定義
for (let c = 0; c < blockColumnCount; c++) {
blocks[c] = []; // 空の配列を代入
for (let r = 0; r < blockRowCount; r++) {
blocks[c][r] = { x: 0, y: 0, status: 1 };
}
}

上記のコードでは status: 1 というブロパティを新たに持たせました。

次に drawBlocks 関数中身を次のように if 文を追加してみてください。


// ブロックを描く関数
function drawBlocks() {
for (let c = 0; c < blockColumnCount; c++) {
for (let r = 0; r < blockRowCount; r++) {
if (blocks[c][r].status === 1) {
// ブロックの座標を計算
const blockX = c _ (blockWidth + blockPadding) + blockOffsetLeft;
const blockY = r _ (blockHeight + blockPadding) + blockOffsetTop;

        // ブロックの座標を代入
        blocks[c][r].x = blockX;
        blocks[c][r].y = blockY;

        ctx.beginPath();
        ctx.rect(blockX, blockY, blockWidth, blockHeight);
        ctx.fillStyle = "#0095DD";
        ctx.fill();
        ctx.closePath();
      }
    }

}
}

if (blocks[c][r].status === 1)で、status が 1 の場合のみ、ブロックを描画するようにしています。

ここまで、先程のブロック衝突判定の CollisionDetection 関数に戻ります。 下記のようなコードに修正してみてください。


// 衝突を検出する関数
function CollisionDetection() {
for (let c = 0; c < blockColumnCount; c++) {
for (let r = 0; r < blockRowCount; r++) {
const block = blocks[c][r];
// もしステータスが 1(衝突前)なら
if (block.status === 1) {
if (x > block.x && x < block.x + blockWidth && y > block.y && y < block.y + blockHeight) {
dy = -dy;
block.status = 0; // ステータスを 0(衝突後)に
}
}
}
}
}

以下のようになったかと思います。

ブロック崩しのボール衝突実装

まず、ブロックに衝突後にボールを反転させた処理の後に、block.status = 0; でブロックのステータスを 0 にすることで、drawblocks 関数の条件で弾かれるようにします。

ここで忘れてはいけないのはそれぞれの関数の役割です。
drawBlocks は描画処理、CollisionDetection 関数は当たり判定の処理です。

つまり、drawBlocks 関数で if (block.status === 1)という条件を追加したように、CollisionDetection 関数内でも if (block.status === 1)という条件を追加してあげる必要があります。

もしこの条件をつけ忘れると下記のようになってしまいます。

ブロック崩しのボール反転実装の失敗

お分かりでしょうか?
ブロックの描画自体が消えてるのに、衝突判定は残ってしまってますね。
ですので、CollisionDetection 関数内でも if (block.status === 1)という条件を忘れずに追加しましょう!

ブロックが壊れずに残ってしまうのを修正

現在のコードのままで、ブロックを壊し続けていくと、特定のブロックが残ってしまいます。

下記のような感じです。

ブロック崩しのボール反転実装の失敗

これは dy = -dy;で、垂直方向を反転させてるだけなので、ボールの反射角度が常に一定であるために起こります。

これを修正するには、draw 関数の下端の反転処理内に以下のコードを付け加えることで解決できます。


// 下端の反転
else if (y + dy > canvas.height - ballRadius) {
if (x + ballRadius > paddleX && x - ballRadius < paddleX + paddleWidth) {
dy = -dy;
dx += (Math.random() - 0.5) \* 0.5; // ランダムで dx を微調整
} else {
alert("GAME OVER");
document.location.reload();
return;
}
}

新たに dx += (Math.random() - 0.5) * 0.5; というコードを付け加えました。 これにより、無事にブロック全てを壊すことが出来ます!

ブロック崩しのボール反転実装の成功

Math.random()というのは、JavaScript の標準関数で、0 以上 1 未満のランダムな小数値を返します。
これに- 0.5 をすることで、-0.5 以上 0.5 未満の範囲のランダム値を生成します。
さらにそれに、0.5 をすることで、範囲は -0.25 以上 0.25 未満 になります。

これによりあまり大きな乱数を追加せず、微小な変化だけを加えることができます。
このランダムな微小値を dx に加えることで、ボールの進行角度を少しずらすことができます。

今回は以上です。お疲れ様でした!
今回でほぼブロック崩しゲームと呼べるくらいにはなったのでは無いでしょうか? しかし、ゲームとしてはスコアや Life などが無いとあまり面白くありませんね。

ということで次回は、スコアと勝利メッセージを追加する予定です。

参考

出典: Mozilla Contributors. “純粋な JavaScript を使ったブロック崩しゲーム”. MDN Web Docs.

この内容は、Creative Commons Attribution-ShareAlike (CC-BY-SA) 2.5(またはそれ以上)のライセンスのもとで公開されています。

関連する記事