【ゲーム】JavaScript:19 ブロック崩し(Breakout)

 「ブロック崩し(Breakout)」は、プレイヤーが画面下部のパドルを左右に操作し、跳ね返ったボールで上部のブロックをすべて破壊してステージクリアを目指すクラシックアーケードゲームです。全3ステージで、ボールを3回まで落とせる制限付きのスリルあるプレイが楽しめます。

遊び方・操作方法

  • ←→キーでパドルを左右に移動
  • ボールを跳ね返して、画面上部のブロックをすべて壊す
  • ボールが画面下へ落ちるとボール残数が1減少
  • ボール残数が0になるとゲームオーバー

ルール

  • ステージごとに異なるブロック配置(定番/ピラミッド/交互配置)
  • ボールを3つまで落とせる(lives = 3
  • 全3ステージをクリアするとエンディング
  • ブロックを1つ壊すごとに得点+1
  • ボールが落ちると一時停止後に自動再開

🎮ゲームプレイ

以下のリンク先から実際にプレイできます。

19 ブロック崩し(Breakout)

素材のダウンロード

以下のリンクから使用する素材をダウンロードできます。

breakout_title.pngbreakout_bg.png

ゲーム画面イメージ

プログラム全文(breakout.html)

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>🎉 ブロック崩し 🎉</title>
  <style>
    /* ====== 全体設定 ====== */
    body {
      margin: 0;                    /* ページ余白をなくす */
      overflow: hidden;             /* スクロールバーを隠す */
      background: url('breakout_bg.png') no-repeat center center fixed;
      background-size: cover;       /* 背景画像を画面いっぱいに表示 */
      font-family: Arial, sans-serif;
    }
    /* ====== キャンバス ====== */
    #canvas {
      display: none;                /* 初期時は非表示 */
      position: absolute;
      top: 0; left: 0;
    }
    /* ====== HUD(ボール残数&得点表示) ====== */
    #hud {
      position: absolute;
      top: 20px; right: 20px;
      color: #fff;
      background: rgba(0, 0, 0, 0.6); /* 暗背景で視認性アップ */
      padding: 5px 10px;
      border-radius: 5px;
      display: none;                /* 初期時は非表示 */
      z-index: 1;
      line-height: 1.4;
      font-size: 18px;
    }
    /* ====== タイトル画面 ====== */
    #titleScreen {
      position: absolute;
      top: 0; left: 0;
      width: 100%; height: 100%;
      background: rgba(0, 0, 0, 0.7); /* 半透明黒背景 */
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      color: #fff;
      text-align: center;
      z-index: 2;
    }
    #titleScreen img {
      max-width: 50%;
      margin-bottom: 20px;
    }
    #titleScreen .message {
      background: rgba(0, 0, 0, 0.6); /* 暗背景で見やすく */
      padding: 10px;
      border-radius: 5px;
      margin: 5px 0;
      line-height: 1.4;
    }
    #titleScreen button {
      font-size: 1.2em;
      padding: 10px 20px;
      margin-top: 20px;
      cursor: pointer;
      background: #0a74da;          /* ブルーボタン */
      border: none;
      color: #fff;
      border-radius: 5px;
    }
    /* ====== オーバーレイ ====== */
    #overlay {
      position: absolute;
      top: 0; left: 0;
      width: 100%; height: 100%;
      background: rgba(0, 0, 0, 0.7);
      display: none;                /* 非表示 */
      flex-direction: column;
      align-items: center;
      justify-content: center;
      color: #fff;
      text-align: center;
      z-index: 3;
    }
    #overlay h1 {
      background: rgba(0, 0, 0, 0.6);
      padding: 15px;
      border-radius: 5px;
      margin: 0;
      font-size: 2em;
    }
    #overlay button {
      font-size: 1.2em;
      padding: 10px 20px;
      margin-top: 20px;
      cursor: pointer;
      background: #0a74da;
      border: none;
      color: #fff;
      border-radius: 5px;
      display: none;                /* 初期は隠す */
    }
  </style>
</head>
<body>
  <!-- ====== タイトル画面 ====== -->
  <div id="titleScreen">
    <img src="breakout_title.png" alt="🎉 ブロック崩し 🎉">
    <h1 class="message">▶️ 遊び方・ルール ◀️</h1>
    <p class="message">
      • ←→キーでパドルを左右に操作<br>
      • ボールを跳ね返してブロックをすべて壊そう!<br>
      • 全3ステージ、ボールは3個まで落とせる<br>
      • ボールがなくなるとゲームオーバー!
    </p>
    <button id="startBtn">▶️ スタート</button>
  </div>

  <!-- ====== ゲームキャンバス ====== -->
  <canvas id="canvas"></canvas>

  <!-- ====== HUD(ボール残数&得点) ====== -->
  <div id="hud"></div>

  <!-- ====== オーバーレイ ====== -->
  <div id="overlay">
    <h1 id="overlayMessage">メッセージ</h1>
    <button id="overlayBtn">🔄 タイトル画面に戻る</button>
  </div>

  <script>
    // ====== 要素取得 ======
    const canvas         = document.getElementById('canvas');
    const ctx            = canvas.getContext('2d');
    const hud            = document.getElementById('hud');
    const titleScreen    = document.getElementById('titleScreen');
    const startBtn       = document.getElementById('startBtn');
    const overlay        = document.getElementById('overlay');
    const overlayMessage = document.getElementById('overlayMessage');
    const overlayBtn     = document.getElementById('overlayBtn');

    // ====== 画面サイズ調整 ======
    function resizeCanvas() {
      canvas.width  = window.innerWidth;
      canvas.height = window.innerHeight;
    }
    window.addEventListener('resize', resizeCanvas);
    resizeCanvas();

    // ====== グローバル変数 ======
    let currentStage = 1;    // 現在のステージ
    let blocks       = [];   // ブロック配列
    let paddle, ball;        // パドル・ボールオブジェクト
    let gameRunning = false; // ゲーム進行フラグ
    let lives       = 3;     // ボール残数
    let score       = 0;     // 得点
    let animationId;         // アニメーションID

    // ====== 行ごとの色設定 ======
    const rowColors = [
      '#ff4d4d', // 赤
      '#ff944d', // オレンジ
      '#ffff4d', // 黄
      '#4dff4d', // 緑
      '#4d4dff'  // 青
    ];

    // ====== パドルクラス ======
    class Paddle {
      constructor(y) {
        this.width  = 100;                            // パドル幅
        this.height = 20;                             // パドル高さ
        this.x      = canvas.width/2 - this.width/2;  // 初期X位置
        this.y      = y;                              // Y位置
        this.speed  = 8;                              // 移動速度
        this.vx     = 0;                              // X方向速度
      }
      draw() {
        ctx.fillStyle = '#fff';
        ctx.fillRect(this.x, this.y, this.width, this.height);
      }
      update() {
        this.x += this.vx;
        // 画面外へ出ないよう制限
        if (this.x < 0) this.x = 0;
        if (this.x + this.width > canvas.width) {
          this.x = canvas.width - this.width;
        }
      }
    }

    // ====== ボールクラス ======
    class Ball {
      constructor() {
        this.radius = 10;    // ボール半径
        this.reset();
      }
      reset() {
        // 中央に戻し、ランダム角度で発射
        this.x = canvas.width/2;
        this.y = canvas.height/2;
        const angle = (Math.random() * Math.PI/2) + Math.PI/4; // 45°〜135°
        this.speed = 5;
        this.vx    = this.speed * Math.cos(angle);
        this.vy    = this.speed * Math.sin(angle);
      }
      draw() {
        ctx.beginPath();
        ctx.arc(this.x, this.y, this.radius, 0, Math.PI*2);
        ctx.fillStyle = '#ff0'; // 黄色
        ctx.fill();
        ctx.closePath();
      }
      update(paddle, blocks) {
        // 壁反射(左右)
        if (this.x - this.radius < 0 || this.x + this.radius > canvas.width) {
          this.vx = -this.vx;
        }
        // 上壁反射
        if (this.y - this.radius < 0) {
          this.vy = -this.vy;
        }
        // パドル反射
        if (this.y + this.radius > paddle.y &&
            this.x > paddle.x && this.x < paddle.x + paddle.width) {
          this.vy = -Math.abs(this.vy);
          // 当たった位置で反射角を調整
          const hitPos = (this.x - (paddle.x + paddle.width/2)) / (paddle.width/2);
          this.vx = this.speed * hitPos;
        }
        // ブロック衝突判定
        for (let b of blocks) {
          if (!b.destroyed &&
              this.x > b.x && this.x < b.x + b.width &&
              this.y - this.radius < b.y + b.height &&
              this.y + this.radius > b.y) {
            this.vy = -this.vy;     // Y方向反転
            b.destroyed = true;     // ブロック消去
            score++;                // 得点加算
            updateHUD();            // HUD更新
            break;
          }
        }
        // 座標更新
        this.x += this.vx;
        this.y += this.vy;
      }
    }

    // ====== ブロッククラス ======
    class Block {
      constructor(x, y, w, h, color) {
        this.x         = x;
        this.y         = y;
        this.width     = w;
        this.height    = h;
        this.color     = color;    // 行ごとの色
        this.destroyed = false;    // 消去フラグ
      }
      draw() {
        if (this.destroyed) return;
        ctx.fillStyle   = this.color;
        ctx.fillRect(this.x, this.y, this.width, this.height);
        ctx.strokeStyle = '#000';
        ctx.strokeRect(this.x, this.y, this.width, this.height);
      }
    }

    // ====== ステージ初期化 ======
    function initStage(stage) {
      paddle = new Paddle(canvas.height - 40);
      ball   = new Ball();
      blocks = [];

      const cols    = 10;
      const rows    = 5;
      const blockW  = canvas.width / cols - 10;
      const blockH  = 20;
      const offsetY = 60;

      if (stage === 1) {
        // 定番5行×10列
        for (let r = 0; r < rows; r++) {
          for (let c = 0; c < cols; c++) {
            const x = c * (blockW + 10) + 5;
            const y = offsetY + r * (blockH + 5);
            blocks.push(new Block(x, y, blockW, blockH, rowColors[r]));
          }
        }
      } else if (stage === 2) {
        // 中央ピラミッド型
        for (let r = 0; r < rows; r++) {
          for (let c = r; c < cols - r; c++) {
            const x = c * (blockW + 10) + 5;
            const y = offsetY + r * (blockH + 5);
            blocks.push(new Block(x, y, blockW, blockH, rowColors[r]));
          }
        }
      } else if (stage === 3) {
        // 交互配置
        for (let r = 0; r < rows; r++) {
          for (let c = 0; c < cols; c++) {
            if ((r + c) % 2 === 0) {
              const x = c * (blockW + 10) + 5;
              const y = offsetY + r * (blockH + 5);
              blocks.push(new Block(x, y, blockW, blockH, rowColors[r]));
            }
          }
        }
      }
    }

    // ====== HUD更新 ======
    function updateHUD() {
      // ボール残数と得点を1行で表示
      let bullets = '';
      for (let i = 0; i < lives; i++) {
        bullets += '<span style="color:yellow">●</span>';
      }
      hud.innerHTML = `ボール残数: ${bullets}  得点: ${score}`;
    }

    // ====== キー操作 ======
    window.addEventListener('keydown', e => {
      if (e.key === 'ArrowLeft')  paddle.vx = -paddle.speed;
      if (e.key === 'ArrowRight') paddle.vx =  paddle.speed;
    });
    window.addEventListener('keyup', e => {
      if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') paddle.vx = 0;
    });

    // ====== メインゲームループ ======
    function gameLoop() {
      if (!gameRunning) return;
      ctx.clearRect(0, 0, canvas.width, canvas.height);

      // 描画・更新
      paddle.update(); paddle.draw();
      for (let b of blocks) b.draw();
      ball.update(paddle, blocks);
      ball.draw();

      // ボール落下判定
      if (ball.y - ball.radius > canvas.height) {
        lives--;              // ボール数-1
        updateHUD();          // HUD更新

        if (lives <= 0) {
          gameOver();         // ゲームオーバー
          return;
        }

        // ボールロスト演出
        gameRunning = false;
        overlayMessage.textContent = '⚠️ ボールを失いました ⚠️';
        overlayBtn.style.display   = 'none';
        overlay.style.display      = 'flex';
        cancelAnimationFrame(animationId);

        // 2秒後に自動再開
        setTimeout(() => {
          overlay.style.display = 'none';
          ball.reset();
          paddle.x = canvas.width/2 - paddle.width/2;
          gameRunning = true;
          gameLoop();
        }, 2000);

        return;
      }

      // ブロック全消しチェック
      const remaining = blocks.filter(b => !b.destroyed).length;
      if (remaining === 0) {
        handleStageClear();
        return;
      }

      animationId = requestAnimationFrame(gameLoop);
    }

    // ====== ステージクリア処理 ======
    function handleStageClear() {
      gameRunning = false;
      overlayMessage.textContent = `🎊 ステージ${currentStage} クリア! 🎊`;
      overlayBtn.style.display    = 'none';
      overlay.style.display       = 'flex';

      // 5秒後に次ステージ or 全クリア
      setTimeout(() => {
        overlay.style.display = 'none';
        currentStage++;
        if (currentStage > 3) {
          showGameComplete();
        } else {
          initStage(currentStage);
          updateHUD();
          gameRunning = true;
          gameLoop();
        }
      }, 5000);
    }

    // ====== ゲームオーバー表示 ======
    function gameOver() {
      gameRunning = false;
      overlayMessage.textContent = `💥 ゲームオーバー 💥`;
      overlayBtn.style.display    = 'block';
      overlay.style.display       = 'flex';
    }

    // ====== 全クリア表示 ======
    function showGameComplete() {
      overlayMessage.textContent = `🎉 全ステージクリア! 🎉`;
      overlayBtn.style.display   = 'block';
      overlay.style.display      = 'flex';
    }

    // ====== スタートボタン処理 ======
    startBtn.addEventListener('click', () => {
      currentStage = 1;
      lives        = 3;
      score        = 0;
      updateHUD();

      titleScreen.style.display = 'none';
      canvas.style.display      = 'block';
      hud.style.display         = 'block';
      overlay.style.display     = 'none';

      initStage(currentStage);
      gameRunning = true;
      gameLoop();
    });

    // ====== オーバーレイ「タイトルへ戻る」 ======
    overlayBtn.addEventListener('click', () => {
      hud.style.display         = 'none';
      canvas.style.display      = 'none';
      overlay.style.display     = 'none';
      titleScreen.style.display = 'flex';
    });
  </script>
</body>
</html>

アルゴリズムの流れ

ステップ処理内容主な関数/命令
1. 初期化キャンバスサイズ調整、ステージ・スコア・ライフ設定resizeCanvas(), startBtn イベント
2. ステージ生成パドル・ボール・ブロック配置initStage(stage)
3. HUD更新ボール残数と得点を表示updateHUD()
4. 入力受付←→キーでパドル速度を設定keydownkeyup イベント
5. メインループ画面クリア→オブジェクト更新・描画→得点・衝突判定gameLoop()
6. ボール落下判定ボールが下端を越えたらライフ減少orゲームオーバーgameLoop()gameOver()
7. ブロック消去判定全ブロック破壊でステージクリアhandleStageClear()
8. ステージ遷移次ステージ or 全クリア表示handleStageClear(), showGameComplete()
9. 終了画面ゲームオーバー or 全クリア時にオーバーレイ表示gameOver(), showGameComplete()

関数・クラスの詳しい解説

名称説明
class Paddleパドルの位置・サイズ・速度・描画(draw())・移動(update())管理
class Ballボールの位置・速度・描画(draw())・跳ね返り&衝突判定(update())・初期化(reset()
class Blockブロックの位置・サイズ・色・消去フラグ管理、描画(draw()
resizeCanvas()ウィンドウリサイズ時に <canvas> を画面全体に設定
initStage(stage)指定ステージのブロック配置ルールに従って blocks[] を生成
updateHUD()<div id="hud"> にボール残数●と得点を反映
gameLoop()毎フレームの描画・更新、落下判定、クリア判定、再帰ループ呼出し管理
handleStageClear()ステージクリア時のオーバーレイ表示、次ステージ or 全クリア遷移
gameOver()ライフ切れ時のオーバーレイ表示と「タイトルへ戻る」ボタン表示
showGameComplete()全ステージクリア時のオーバーレイ表示

改造のポイント

  • ブロック配置カスタマイズinitStage() 内に新しい配置パターンを追加して、ステージ数を増やす
  • ボール挙動の強化:ボール速度の増加、ランダムバウンド、回転(スピン)効果を導入
  • パワーアップ要素:壊したブロックからパワーアップアイテムをドロップし、パドル拡大やマルチボールなどを実装
  • サウンド/エフェクト追加Audio API で効果音や BGM を再生し、演出を強化
  • スコアランキングlocalStorage へスコアを保存し、ハイスコア表示画面を追加
  • レスポンシブ調整:モバイル/タブレットでも遊べるよう、タッチ操作を追加

アドバイス:ゲーム性を深めるには、パワーアップ/パワーダウンアイテムを導入してリスクとリワードを設計し、ユーザーが自分の戦略を考えられるようにすると良いでしょう。また、ステージクリア時に動的な演出(パーティクルやアニメーション)を入れると達成感が高まります!