javascript

/

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

純粋な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; // ボールを反転
      }
    }
  }
}

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

blockmovie9

追加した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(衝突後)に
        }
      }
    }
  }
}

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

blockmovie10

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

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

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

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

blockmovie11

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

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

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

下記のような感じです。

blockmovie12

これは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; というコードを付け加えました。 これにより、無事にブロック全てを壊すことが出来ます!

blockmovie13

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

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

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

ということで次回は、スコアとLifeを追加予定です。

参考

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

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

webアプリ 初心者向け 備忘録