【ゲーム】JavaScript:56 ドロップキャッチゲーム

 「🍎 ドロップキャッチゲーム 🍎」は、上空から降ってくる果物の絵文字を左右に動かせるカゴ(🧺)でキャッチし、得点を競うシンプルかつ中毒性の高いアクションゲームです。背景には落ち着いた木目調の画像を敷き、キャッチの爽快感とタイミングを楽しめます。

遊び方・操作方法

  1. タイトル画面で「スタート」ボタンをクリック。
  2. ゲーム画面では、左右の矢印キー(← →)またはマウスドラッグ、タッチ操作でカゴを左右に移動します。
  3. 上からランダムに落ちてくる果物をカゴでキャッチするとスコアが+1。
  4. キャッチに失敗して地面に落ちた果物が画面下端を越えるとライフが-1。
  5. ライフが0になるとゲーム終了。どこまで高得点を稼げるか挑戦しましょう!

ルール

  • 初期ライフ:3。キャッチ失敗ごとにライフが1減少。
  • ライフが0になると、最終スコアが表示され、タイトル画面に戻れます。
  • 果物は複数種類(🍎、🍋、🍇、…)がランダムで落下し、落下速度・間隔も少しずつ変化。
  • キャッチ成功でスコア+1、失敗でライフ-1。

🎮ゲームプレイ

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

56 ドロップキャッチゲーム

素材のダウンロード

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

drop_catch_game_title.pngdrop_catch_game_bg.png

ゲーム画面イメージ

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

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>🍎 ドロップキャッチゲーム 🍎</title>
  <style>
    html, body {
      margin: 0; padding: 0;
      width: 100vw; height: 100vh;
      font-family: 'Yu Gothic', 'Meiryo', sans-serif;
      background: url('drop_catch_game_bg.png') no-repeat center center fixed;
      background-size: cover;
    }
    body {
      width: 100vw;
      height: 100vh;
      overflow: auto;
    }
    .container {
      width: 800px;
      margin: 40px auto;
      background: rgba(255,255,255,0.93);
      border-radius: 22px;
      box-shadow: 0 4px 24px rgba(0,0,0,0.13);
      padding-bottom: 32px;
      min-height: 600px;
      position: relative;
    }
    .title-img {
      display: block;
      margin: 20px auto 18px auto;
      width: 400px;
      max-width: 80%;
      height: auto;
    }
    h1 {
      text-align: center;
      font-size: 2.1em;
      margin: 0.6em 0 0.15em 0;
      color: #c73a1c;
      text-shadow: 1px 1px 7px #fff;
      letter-spacing: 0.04em;
    }
    .rule-section {
      background: rgba(255,245,210,0.97);
      border-radius: 14px;
      margin: 28px 32px 14px 32px;
      padding: 16px 24px;
      box-shadow: 0 2px 8px rgba(128,64,0,0.09);
    }
    .rule-title {
      text-align: center;
      font-weight: bold;
      font-size: 1.3em;
      margin-bottom: 10px;
      color: #c73a1c;
    }
    .rule-text {
      text-align: left;
      font-size: 1.08em;
      line-height: 1.65;
      color: #7a3222;
      letter-spacing: 0.01em;
    }
    .btn {
      display: block;
      margin: 28px auto 0 auto;
      padding: 14px 50px;
      font-size: 1.2em;
      border: none;
      border-radius: 28px;
      background: linear-gradient(90deg, #ff8870, #d14725);
      color: #fff;
      font-weight: bold;
      box-shadow: 0 2px 8px #c1624b90;
      cursor: pointer;
      transition: background 0.2s;
    }
    .btn:hover { background: linear-gradient(90deg, #ffad9c, #b22b0b); }
    /* ゲームエリア */
    .game-area {
      width: 640px;
      height: 400px;
      background: rgba(255,245,230,0.89);
      border-radius: 18px;
      margin: 32px auto 0 auto;
      box-shadow: 0 2px 14px #8884;
      position: relative;
      overflow: hidden;
      border: 2px solid #ffc4b7;
      display: flex;
      flex-direction: column;
      justify-content: flex-end;
      touch-action: none;
    }
    /* アイテム(果物) */
    .item {
      position: absolute;
      font-size: 2.2em;
      transition: filter 0.09s;
      pointer-events: none;
      text-shadow: 0 2px 8px #fff9;
      left: 0;
      top: 0;
      width: 38px;
      height: 38px;
      display: flex;
      align-items: center;
      justify-content: center;
    }
    /* カゴ */
    .basket {
      position: absolute;
      bottom: 12px;
      left: 50%;
      transform: translateX(-50%);
      font-size: 2.8em;
      user-select: none;
      pointer-events: none;
      text-shadow: 0 2px 10px #fff6;
      transition: left 0.14s;
      width: 90px;
      height: 50px;
      display: flex;
      align-items: flex-end;
      justify-content: center;
    }
    /* スコアなどのバー */
    .score-bar {
      width: 640px;
      margin: 0 auto;
      background: rgba(255,235,230,0.89);
      border-radius: 14px;
      box-shadow: 0 1px 7px #eacb4f33;
      color: #c73a1c;
      font-weight: bold;
      font-size: 1.18em;
      padding: 14px 30px 6px 30px;
      display: flex;
      justify-content: space-between;
      align-items: center;
      letter-spacing: 0.02em;
    }
    .message-box {
      text-align: center;
      background: rgba(255,245,210,0.98);
      font-size: 2em;
      color: #c73a1c;
      font-weight: bold;
      border-radius: 16px;
      box-shadow: 0 4px 24px #ffb70033;
      width: 80%;
      max-width: 480px;
      margin: 40px auto 0 auto;
      padding: 38px 18px;
      position: relative;
      z-index: 2;
    }
    .center-btn { margin: 24px auto 0 auto; }
    @media (max-width: 900px) {
      .container, .game-area, .score-bar { width: 98vw !important; min-width: 0; }
      .game-area { height: 44vw !important;}
    }
  </style>
</head>
<body>
  <div class="container" id="main-container"></div>
  <script>
    // --- ドロップキャッチゲームのJavaScript ---
    // ゲーム状態
    let gameLoop = null;
    let items = [];
    let basketX = 320; // 中央
    let score = 0;
    let life = 3;
    let gameActive = false;
    const GAME_WIDTH = 640;
    const GAME_HEIGHT = 400;
    const BASKET_WIDTH = 90; // CSSと一致
    const BASKET_HEIGHT = 50;
    const ITEM_WIDTH = 38;   // CSSと一致
    const ITEM_HEIGHT = 38;

    // アイテムの絵文字リスト
    const ITEM_EMOJIS = ['🍎', '🍋', '🍇', '🍌', '🥝', '🍊', '🍐'];
    // カゴの絵文字
    const BASKET_EMOJI = '🧺';

    // タイトル画面
    function showTitleScreen() {
      stopGame();
      document.getElementById('main-container').innerHTML = `
        <h1>🍎 ドロップキャッチゲーム 🍎</h1>
        <img src="drop_catch_game_title.png" class="title-img" alt="ドロップキャッチゲーム タイトル">
        <div class="rule-section">
          <div class="rule-title">🎲 遊び方・ルール 🎲</div>
          <div class="rule-text">
            ・上から落ちてくる果物をカゴ(🧺)でキャッチしましょう。<br>
            ・左右キー(または画面タップ/ドラッグ)でカゴを左右に動かせます。<br>
            ・キャッチできないとライフが減ります。<br>
            ・ライフがなくなるとゲーム終了です。<br>
            ・どこまでスコアを伸ばせるか挑戦しましょう!
          </div>
        </div>
        <button class="btn" id="start-btn">スタート</button>
      `;
      document.getElementById('start-btn').onclick = startGame;
    }

    // ゲーム開始
    function startGame() {
      // 状態初期化
      items = [];
      basketX = GAME_WIDTH/2;
      score = 0;
      life = 3;
      gameActive = true;
      showGameScreen();
      // キー・タッチリスナー
      document.onkeydown = handleKey;
      let area = document.getElementById('game-area');
      area.addEventListener('touchmove', handleTouch, {passive:false});
      area.addEventListener('mousedown', handleMouseDown);
      // ゲームループ
      gameLoop = setInterval(gameTick, 30);
      setTimeout(spawnItem, 1000);
    }

    // ゲーム画面
    function showGameScreen() {
      document.getElementById('main-container').innerHTML = `
        <h1>🍎 ドロップキャッチゲーム 🍎</h1>
        <div class="score-bar">
          <span>スコア:<span id="score-num">${score}</span></span>
          <span>ライフ:${"❤️".repeat(life)}</span>
        </div>
        <div class="game-area" id="game-area"></div>
      `;
      drawGameObjects();
    }

    // ゲーム内描画
    function drawGameObjects() {
      let area = document.getElementById('game-area');
      if (!area) return;
      // カゴ(バスケット)の描画
      area.innerHTML = `
        <div class="basket" id="basket" style="left:${basketX-BASKET_WIDTH/2}px; width:${BASKET_WIDTH}px;">
          ${BASKET_EMOJI}
        </div>
      `;
      // アイテムを描画
      for (const item of items) {
        area.innerHTML += `<div class="item" style="left:${item.x-ITEM_WIDTH/2}px; top:${item.y}px; width:${ITEM_WIDTH}px; height:${ITEM_HEIGHT}px;">${item.emoji}</div>`;
      }
    }

    // ゲーム進行
    function gameTick() {
      // アイテム移動
      for (let i = 0; i < items.length; i++) {
        items[i].y += items[i].speed;
      }
      // キャッチ判定・落下処理
      let caught = [];
      let missed = [];
      for (let i = 0; i < items.length; i++) {
        // キャッチ判定修正
        // アイテムの中心とカゴの範囲が重なればキャッチ
        let itemLeft = items[i].x - ITEM_WIDTH/2;
        let itemRight = items[i].x + ITEM_WIDTH/2;
        let basketLeft = basketX - BASKET_WIDTH/2;
        let basketRight = basketX + BASKET_WIDTH/2;
        let itemBottom = items[i].y + ITEM_HEIGHT;
        let basketTop = GAME_HEIGHT - BASKET_HEIGHT;

        if (
          // 水平範囲重なり
          itemRight > basketLeft &&
          itemLeft < basketRight &&
          // 下端がカゴの高さ内に到達
          itemBottom > basketTop &&
          items[i].y < GAME_HEIGHT
        ) {
          score++;
          caught.push(i);
        } else if (items[i].y > GAME_HEIGHT) {
          life--;
          missed.push(i);
        }
      }
      // 配列から削除(後ろから消す)
      for (let i of caught.reverse()) items.splice(i,1);
      for (let i of missed.reverse()) items.splice(i,1);

      // 描画
      showGameScreen();

      // ライフ0で終了
      if (life <= 0) {
        stopGame();
        setTimeout(showEndScreen, 800);
      }
    }

    // アイテム生成
    function spawnItem() {
      if (!gameActive) return;
      let x = 40 + Math.random() * (GAME_WIDTH-80);
      let emoji = ITEM_EMOJIS[Math.floor(Math.random()*ITEM_EMOJIS.length)];
      items.push({x: x, y: -30, speed: 3+Math.random()*2, emoji: emoji});
      // 次のアイテム
      setTimeout(spawnItem, 800 + Math.random()*700);
    }

    // カゴ操作(キー)
    function handleKey(e) {
      if (!gameActive) return;
      if (e.key === "ArrowLeft") {
        basketX = Math.max(BASKET_WIDTH/2, basketX - 32);
      } else if (e.key === "ArrowRight") {
        basketX = Math.min(GAME_WIDTH - BASKET_WIDTH/2, basketX + 32);
      }
    }
    // カゴ操作(タッチ・ドラッグ対応)
    function handleTouch(e) {
      if (!gameActive) return;
      e.preventDefault();
      let touch = e.touches[0];
      let rect = e.target.getBoundingClientRect();
      let x = touch.clientX - rect.left;
      basketX = Math.max(BASKET_WIDTH/2, Math.min(GAME_WIDTH-BASKET_WIDTH/2, x));
    }
    // マウスダウンからドラッグ
    function handleMouseDown(e) {
      if (!gameActive) return;
      let area = document.getElementById('game-area');
      let moveFn = (e2)=>{
        let rect = area.getBoundingClientRect();
        let x = (e2.touches?e2.touches[0].clientX:e2.clientX) - rect.left;
        basketX = Math.max(BASKET_WIDTH/2, Math.min(GAME_WIDTH-BASKET_WIDTH/2, x));
      };
      let upFn = ()=>{area.removeEventListener('mousemove',moveFn); area.removeEventListener('mouseup',upFn);};
      area.addEventListener('mousemove',moveFn);
      area.addEventListener('mouseup',upFn);
    }

    // ゲーム停止
    function stopGame() {
      gameActive = false;
      clearInterval(gameLoop);
      gameLoop = null;
      document.onkeydown = null;
      let area = document.getElementById('game-area');
      if (area) {
        area.ontouchmove = null;
        area.onmousedown = null;
      }
    }

    // 終了画面
    function showEndScreen() {
      document.getElementById('main-container').innerHTML = `
        <h1>🍎 ドロップキャッチゲーム 🍎</h1>
        <img src="drop_catch_game_title.png" class="title-img" alt="ドロップキャッチゲーム タイトル">
        <div class="message-box">
          ゲーム終了!<br>
          <span style="font-size:1.2em;color:#c73a1c;">あなたのスコア:${score} 点</span>
        </div>
        <button class="btn center-btn" id="back-title-btn">タイトル画面に戻る</button>
      `;
      document.getElementById('back-title-btn').onclick = showTitleScreen;
    }

    // 初期表示
    showTitleScreen();
  </script>
</body>
</html>

アルゴリズムの流れ

手順処理内容
1showTitleScreen() でタイトル画面を描画
2「スタート」押下 → startGame() → 状態初期化・ゲームループ開始
3gameTick() が30msごとに呼ばれ、アイテム位置を更新
4アイテムごとにカゴとの当たり判定を行い、成功・失敗を判定
5キャッチ成功で score++、失敗で life--
6ライフ0で stopGame()showEndScreen()
7spawnItem() がランダム間隔で新アイテムを生成
8入力イベント (keydown/touchmove/mousemove) でカゴを動かす

関数の詳細

関数名機能概要詳細説明
showTitleScreen()タイトル画面描画ルール説明と「スタート」ボタンを表示し、必要あれば既存ゲームを停止
startGame()ゲーム開始処理状態初期化、入力リスナー登録、ゲームループ&アイテム生成タイマー起動
showGameScreen()ゲーム画面描画スコアバーと空のゲームエリアをセットアップし、drawGameObjects() を呼び出す
drawGameObjects()オブジェクト描画カゴと全アイテムの DOM 要素を動的に配置
gameTick()毎フレーム更新処理アイテム移動・当たり判定・スコア/ライフ更新・画面再描画
spawnItem()アイテム生成ランダムな位置・速度で果物を items 配列に追加
handleKey(e)キー入力ハンドラ←→キーで basketX を左右移動
handleTouch(e)タッチムーブハンドラタッチ位置に合わせて basketX を更新
handleMouseDown(e)マウスドラッグハンドラドラッグ中のマウス移動に応じてカゴを動かす
stopGame()ゲーム停止処理ループ停止、入力リスナー解除
showEndScreen()ゲーム終了画面描画最終スコアと「タイトルに戻る」ボタンを表示

改造のポイント

  • アイテム多様化:絵文字ごとに得点倍率やライフ回復効果などを設けると戦略性がアップ。
  • 難易度設定:落下速度や生成間隔を変更する「Easy/Normal/Hard」モード追加で幅広い層に対応。
  • タイムアタック:制限時間内で何点取れるかを競うモードを追加すれば緊張感が高まります。
  • エフェクト演出:キャッチ時のパーティクルやバイブレーション、サウンド効果を加えると没入感が向上。
  • ハイスコア保存localStorage を使って最高スコアをブラウザに保存し、リロード後も記録を保持可能に。

アドバイス
まずは基本的なキャッチ&スコアシステムを安定稼働させ、次にビジュアル&サウンド演出、難易度・モード拡張を段階的に追加すると、ユーザーの継続プレイを促進できます。