0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

概要

この記事では、HTML、CSS、JavaScriptを使用した簡単なブラウザゲーム「ポップバルーン」の作り方を紹介します。移動する風船(ターゲット)をクリックしてスコアを稼ぐシンプルなゲームですが、複数の移動パターンや responsive デザインを実装しています。

特徴

  • 30秒間の制限時間
  • 3種類の移動パターン(ランダム、円運動、ジグザグ)
  • レスポンシブデザイン対応
  • ハイスコア機能
  • スマートフォン・タブレット対応(タッチイベント)

このゲームは Markdown AI で作成、公開しています。

デモ

1000005317.gif

実際にプレイしてみる

サンプルコードはこちら
<!DOCTYPE html>
<html>
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <style>
    body {
      margin: 0;
      padding: 15px;
      box-sizing: border-box;
    }
    .game-container {
      position: relative;
      width: 100%;
      max-width: 600px;
      height: 400px;
      margin: 0 auto;
      background: #f0f0f0;
      border: 2px solid #333;
      overflow: hidden;
    }
    @media (max-width: 600px) {
      .game-container {
        height: 300px;
      }
      body {
        padding: 10px;
      }
    }
    .target {
      position: absolute;
      border-radius: 50%;
      cursor: pointer;
      transition: width 0.3s, height 0.3s, background-color 0.2s;
    }
    .target:hover {
      filter: brightness(1.2);
    }
    .score-panel {
      position: absolute;
      top: 10px;
      left: 10px;
      font-size: 16px;
      font-family: Arial, sans-serif;
      background: rgba(255, 255, 255, 0.8);
      padding: 5px;
      border-radius: 5px;
    }
    .timer {
      position: absolute;
      top: 10px;
      right: 10px;
      font-size: 16px;
      font-family: Arial, sans-serif;
      color: #e53e3e;
      background: rgba(255, 255, 255, 0.8);
      padding: 5px;
      border-radius: 5px;
    }
    .game-over {
      position: absolute;
      top: 40%;
      left: 50%;
      transform: translate(-50%, -50%);
      font-size: 32px;
      font-family: Arial, sans-serif;
      color: #e53e3e;
      display: none;
      text-align: center;
      background: rgba(255, 255, 255, 0.9);
      padding: 20px;
      border-radius: 10px;
      width: 80%;
      max-width: 300px;
      box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
    }
    .start-button {
      position: absolute;
      top: 60%;
      left: 50%;
      transform: translate(-50%, -50%);
      padding: 10px 20px;
      font-size: 18px;
      background: #48BB78;
      color: white;
      border: none;
      border-radius: 5px;
      cursor: pointer;
    }
    .start-button:hover {
      background: #38A169;
    }
    @media (max-width: 400px) {
      .score-panel, .timer {
        font-size: 14px;
      }
      .game-over {
        font-size: 24px;
        padding: 15px;
      }
      .start-button {
        font-size: 16px;
      }
    }
  </style>
</head>
<body>
  <div class="game-container">
    <div class="score-panel">
      スコア: <span id="scoreValue">0</span><br>
      ハイスコア: <span id="highScore">0</span>
    </div>
    <div class="timer">残り時間: <span id="timeLeft">30</span></div>
    <div id="target" class="target"></div>
    <div class="game-over" id="gameOver">
      ゲームオーバー!
    </div>
    <button id="startButton" class="start-button">ゲームスタート</button>
  </div>

  <script>
    const target = document.getElementById('target');
    const scoreElement = document.getElementById('scoreValue');
    const highScoreElement = document.getElementById('highScore');
    const timeLeftElement = document.getElementById('timeLeft');
    const gameOverElement = document.getElementById('gameOver');
    const startButton = document.getElementById('startButton');
    
    let score = 0;
    let highScore = 0;
    let timeLeft = 30;
    let gameInterval;
    let movePattern = 0;
    let isGameRunning = false;
    let currentX = 0;
    let currentY = 0;
    let patternTime = 0;

    const colors = ['#4299E1', '#F56565', '#48BB78', '#ECC94B', '#9F7AEA'];
    const sizes = [30, 40, 50, 60];

    function getRandomPosition() {
      const container = document.querySelector('.game-container');
      const targetSize = parseInt(target.style.width);
      const maxX = container.clientWidth - targetSize;
      const maxY = container.clientHeight - targetSize;
      return {
        x: Math.random() * maxX,
        y: Math.random() * maxY
      };
    }

    function moveTarget() {
      const container = document.querySelector('.game-container');
      const targetSize = parseInt(target.style.width);
      const maxX = container.clientWidth - targetSize;
      const maxY = container.clientHeight - targetSize;
      
      switch(movePattern) {
        case 0: // ランダム移動
          const pos = getRandomPosition();
          currentX = pos.x;
          currentY = pos.y;
          break;
        case 1: // 円運動
          patternTime += 0.1;
          currentX = maxX/2 + Math.cos(patternTime) * (maxX/3);
          currentY = maxY/2 + Math.sin(patternTime) * (maxY/3);
          break;
        case 2: // ジグザグ
          patternTime += 0.1;
          currentX = (maxX/2) * (1 + Math.sin(patternTime));
          currentY = (maxY/2) * (1 + Math.cos(patternTime * 3));
          break;
      }

      // 画面内に収まるように位置を制限
      currentX = Math.max(0, Math.min(currentX, maxX));
      currentY = Math.max(0, Math.min(currentY, maxY));

      target.style.left = currentX + 'px';
      target.style.top = currentY + 'px';
      
      const newSize = sizes[Math.floor(Math.random() * sizes.length)];
      const colorIndex = Math.floor(Math.random() * colors.length);
      
      target.style.width = newSize + 'px';
      target.style.height = newSize + 'px';
      target.style.backgroundColor = colors[colorIndex];
      
      if (Math.random() < 0.1) {
        movePattern = Math.floor(Math.random() * 3);
      }
    }

    function updateTimer() {
      timeLeft--;
      timeLeftElement.textContent = timeLeft;
      
      if (timeLeft <= 0) {
        endGame();
      }
    }

    function startGame() {
      score = 0;
      timeLeft = 30;
      isGameRunning = true;
      scoreElement.textContent = score;
      timeLeftElement.textContent = timeLeft;
      gameOverElement.style.display = 'none';
      startButton.style.display = 'none';
      target.style.display = 'block';
      
      target.style.width = '50px';
      target.style.height = '50px';
      moveTarget();
      
      gameInterval = setInterval(updateTimer, 1000);
    }

    function endGame() {
      isGameRunning = false;
      clearInterval(gameInterval);
      target.style.display = 'none';
      gameOverElement.style.display = 'block';
      startButton.style.display = 'block';
      startButton.textContent = 'もう一度プレイ';
      
      if (score > highScore) {
        highScore = score;
        highScoreElement.textContent = highScore;
      }
    }

    target.addEventListener('click', () => {
      if (!isGameRunning) return;
      
      score++;
      scoreElement.textContent = score;
      moveTarget();
    });

    startButton.addEventListener('click', startGame);

    // タッチデバイス対応
    target.addEventListener('touchstart', (e) => {
      e.preventDefault(); // デフォルトの動作を防止
      if (!isGameRunning) return;
      
      score++;
      scoreElement.textContent = score;
      moveTarget();
    });

    // 初期表示設定
    target.style.display = 'none';
  </script>
</body>
</html>

実装の解説

1. HTML構造

ゲームの基本構造は非常にシンプルです。

<div class="game-container">
  <div class="score-panel">
    スコア: <span id="scoreValue">0</span><br>
    ハイスコア: <span id="highScore">0</span>
  </div>
  <div class="timer">残り時間: <span id="timeLeft">30</span></div>
  <div id="target" class="target"></div>
  <div class="game-over" id="gameOver">
    ゲームオーバー!
  </div>
  <button id="startButton" class="start-button">ゲームスタート</button>
</div>

2. CSSスタイリング

重要なポイントとなるCSSの一部を紹介します。

.game-container {
  position: relative;
  width: 100%;
  max-width: 600px;
  height: 400px;
  margin: 0 auto;
  background: #f0f0f0;
  border: 2px solid #333;
  overflow: hidden;
}

.target {
  position: absolute;
  border-radius: 50%;
  cursor: pointer;
  transition: width 0.3s, height 0.3s, background-color 0.2s;
}

3. JavaScriptの実装

ゲームの主要な機能を実現するJavaScriptコードの解説です。

3.1 移動パターンの実装

3種類の移動パターンを実装しています。

function moveTarget() {
  switch(movePattern) {
    case 0: // ランダム移動
      const pos = getRandomPosition();
      currentX = pos.x;
      currentY = pos.y;
      break;
    case 1: // 円運動
      patternTime += 0.1;
      currentX = maxX/2 + Math.cos(patternTime) * (maxX/3);
      currentY = maxY/2 + Math.sin(patternTime) * (maxY/3);
      break;
    case 2: // ジグザグ
      patternTime += 0.1;
      currentX = (maxX/2) * (1 + Math.sin(patternTime));
      currentY = (maxY/2) * (1 + Math.cos(patternTime * 3));
      break;
  }
}

3.2 タッチデバイス対応

スマートフォンやタブレットでも遊べるように、タッチイベントを実装しています。

target.addEventListener('touchstart', (e) => {
  e.preventDefault();
  if (!isGameRunning) return;
  
  score++;
  scoreElement.textContent = score;
  moveTarget();
});

改善点とカスタマイズのアイデア

  1. 効果音の追加
  2. 難易度選択機能の実装
  3. パワーアップアイテムの追加
  4. アニメーション効果の強化
  5. ソーシャルシェア機能の追加

実装上の工夫

見た目以上にシンプルな実装になっています。requestAnimationFrameを使用せずともsetIntervalとCSS transitionの組み合わせで滑らかな動きを実現し、三角関数を使って複雑な動きを表現しています。また、10%の確率で移動パターンが変更されることで、プレイヤーは次の動きを予測しづらく、シンプルながらも高いゲーム性を実現しています。

まとめ

このゲームは、JavaScriptでインタラクティブなWebアプリケーションを作る良い練習になります。モバイル対応やアニメーション、タイマー処理など、実践的なWeb開発の要素を含んでいます。

参考リンク

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?