STGGAME.html

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Bullet Hell STG Game</title>
  <style>
    * { box-sizing: border-box; }

    body {
      margin: 0;
      min-height: 100vh;
      background: radial-gradient(circle at top, #1f2b5c, #070915 70%);
      display: flex;
      align-items: center;
      justify-content: center;
      font-family: system-ui, sans-serif;
      color: white;
      overflow: hidden;
    }

    .wrap { text-align: center; }

    h1 {
      margin: 0 0 10px;
      font-size: 28px;
      letter-spacing: 0.08em;
    }

    canvas {
      background: #050816;
      border: 3px solid #ffffff33;
      border-radius: 16px;
      box-shadow: 0 20px 80px #000a;
      display: block;
    }

    .info {
      margin-top: 10px;
      color: #dce6ff;
      font-size: 14px;
    }

    .panel {
      position: fixed;
      top: 16px;
      left: 50%;
      transform: translateX(-50%);
      display: flex;
      gap: 20px;
      background: #0008;
      border: 1px solid #fff2;
      padding: 8px 16px;
      border-radius: 999px;
      backdrop-filter: blur(8px);
      font-weight: 700;
    }
  </style>
</head>
<body>
  <div class="panel">
    <div>Score: <span id="score">0</span></div>
    <div>HP: <span id="hp">5</span></div>
    <div>Power: <span id="power">1</span></div>
  </div>

  <div class="wrap">
    <h1>BULLET STORM</h1>
    <canvas id="game" width="480" height="640"></canvas>
    <div class="info">移動: WASD / 方向キー ショット: Space パワーアップを取ると弾が強化 リスタート: Enter</div>
  </div>

  <script>
    const canvas = document.getElementById("game");
    const ctx = canvas.getContext("2d");
    const scoreEl = document.getElementById("score");
    const hpEl = document.getElementById("hp");
    const powerEl = document.getElementById("power");

    const keys = {};

    const player = {
      x: canvas.width / 2,
      y: canvas.height - 70,
      w: 34,
      h: 42,
      speed: 5,
      hp: 5,
      power: 1,
      shotCooldown: 0,
      invincible: 0
    };

    let bullets = [];
    let enemyBullets = [];
    let enemies = [];
    let items = [];
    let particles = [];
    let stars = [];
    let score = 0;
    let enemyTimer = 0;
    let itemTimer = 0;
    let gameOver = false;
    let boss = null;
    let bossTimer = 0;
    let frame = 0;

    for (let i = 0; i < 100; i++) {
      stars.push({
        x: Math.random() * canvas.width,
        y: Math.random() * canvas.height,
        r: Math.random() * 2 + 0.5,
        speed: Math.random() * 2 + 1
      });
    }

    window.addEventListener("keydown", (e) => {
      keys[e.key.toLowerCase()] = true;
      if (gameOver && e.key === "Enter") restart();
    });

    window.addEventListener("keyup", (e) => {
      keys[e.key.toLowerCase()] = false;
    });

    function restart() {
      player.x = canvas.width / 2;
      player.y = canvas.height - 70;
      player.hp = 5;
      player.power = 1;
      player.shotCooldown = 0;
      player.invincible = 0;
      bullets = [];
      enemyBullets = [];
      enemies = [];
      items = [];
      particles = [];
      score = 0;
      enemyTimer = 0;
      itemTimer = 0;
      frame = 0;
      gameOver = false;
      updateUI();
    }

    function updateUI() {
      scoreEl.textContent = score;
      hpEl.textContent = player.hp;
      powerEl.textContent = player.power;
    }

    function addPlayerBullet(x, y, vx, vy, power = 1) {
      bullets.push({ x, y, w: 6, h: 16, vx, vy, power });
    }

    function shoot() {
      if (player.power === 1) {
        addPlayerBullet(player.x, player.y - 25, 0, -10);
      } else if (player.power === 2) {
        addPlayerBullet(player.x - 9, player.y - 25, 0, -10);
        addPlayerBullet(player.x + 9, player.y - 25, 0, -10);
      } else if (player.power === 3) {
        addPlayerBullet(player.x, player.y - 28, 0, -11);
        addPlayerBullet(player.x - 16, player.y - 20, -1.2, -10);
        addPlayerBullet(player.x + 16, player.y - 20, 1.2, -10);
      } else {
        addPlayerBullet(player.x, player.y - 30, 0, -12, 2);
        addPlayerBullet(player.x - 14, player.y - 24, -0.8, -11);
        addPlayerBullet(player.x + 14, player.y - 24, 0.8, -11);
        addPlayerBullet(player.x - 24, player.y - 10, -1.7, -10);
        addPlayerBullet(player.x + 24, player.y - 10, 1.7, -10);
      }

      player.shotCooldown = Math.max(4, 10 - player.power);
    }

    function spawnEnemy() {
      if (boss) return;
      const size = Math.random() * 16 + 28;
      enemies.push({
        x: Math.random() * (canvas.width - size) + size / 2,
        y: -size,
        w: size,
        h: size,
        speed: Math.random() * 1.4 + 1.4,
        hp: size > 38 ? 4 : 2,
        shotTimer: Math.floor(Math.random() * 50),
        type: Math.random() < 0.35 ? "spread" : "normal"
      });
    }

    function spawnPowerItem(x = Math.random() * (canvas.width - 40) + 20, y = -20) {
      items.push({
        x,
        y,
        w: 24,
        h: 24,
        speed: 2.2,
        type: "power"
      });
    }

    function enemyShoot(enemy) {
      if (enemy.type === "spread") {
        for (let i = -2; i <= 2; i++) {
          enemyBullets.push({
            x: enemy.x,
            y: enemy.y + enemy.h / 2,
            w: 8,
            h: 8,
            vx: i * 1.1,
            vy: 3.2
          });
        }
      } else {
        const dx = player.x - enemy.x;
        const dy = player.y - enemy.y;
        const len = Math.hypot(dx, dy) || 1;
        enemyBullets.push({
          x: enemy.x,
          y: enemy.y + enemy.h / 2,
          w: 8,
          h: 8,
          vx: dx / len * 3.2,
          vy: dy / len * 3.2
        });
      }
    }

    function createExplosion(x, y) {
      for (let i = 0; i < 16; i++) {
        particles.push({
          x,
          y,
          vx: (Math.random() - 0.5) * 7,
          vy: (Math.random() - 0.5) * 7,
          life: 24,
          r: Math.random() * 4 + 2
        });
      }
    }

    function isHit(a, b) {
      return (
        a.x - a.w / 2 < b.x + b.w / 2 &&
        a.x + a.w / 2 > b.x - b.w / 2 &&
        a.y - a.h / 2 < b.y + b.h / 2 &&
        a.y + a.h / 2 > b.y - b.h / 2
      );
    }

    function damagePlayer() {
      if (player.invincible > 0) return;
      player.hp--;
      player.power = Math.max(1, player.power - 1);
      player.invincible = 80;
      createExplosion(player.x, player.y);
      updateUI();
      if (player.hp <= 0) gameOver = true;
    }

    function update() {
      if (gameOver) return;
      frame++;

      // boss spawn
      bossTimer++;
      if (!boss && bossTimer > 2000) {
        boss = {
          x: canvas.width / 2,
          y: 120,
          w: 120,
          h: 120,
          hp: 200,
          maxHp: 200,
          shotTimer: 0
        };
      }

      if (keys["arrowleft"] || keys["a"]) player.x -= player.speed;
      if (keys["arrowright"] || keys["d"]) player.x += player.speed;
      if (keys["arrowup"] || keys["w"]) player.y -= player.speed;
      if (keys["arrowdown"] || keys["s"]) player.y += player.speed;

      player.x = Math.max(player.w / 2, Math.min(canvas.width - player.w / 2, player.x));
      player.y = Math.max(player.h / 2, Math.min(canvas.height - player.h / 2, player.y));

      if (player.invincible > 0) player.invincible--;
      if (player.shotCooldown > 0) player.shotCooldown--;
      if ((keys[" "] || keys["space"]) && player.shotCooldown <= 0) shoot();

      bullets.forEach((b) => {
        b.x += b.vx;
        b.y += b.vy;
      });
      bullets = bullets.filter((b) => b.y > -30 && b.x > -30 && b.x < canvas.width + 30);

      enemyBullets.forEach((b) => {
        b.x += b.vx;
        b.y += b.vy;
      });
      enemyBullets = enemyBullets.filter((b) => b.y < canvas.height + 30 && b.x > -30 && b.x < canvas.width + 30);

      enemyTimer++;
      if (enemyTimer > Math.max(20, 40 - Math.floor(score / 1000))) {
        spawnEnemy();
        enemyTimer = 0;
      }

      itemTimer++;
      if (itemTimer > 520) {
        spawnPowerItem();
        itemTimer = 0;
      }

      enemies.forEach((e) => {
        e.y += e.speed;
        e.shotTimer++;
        if (e.shotTimer > 70) {
          enemyShoot(e);
          e.shotTimer = 0;
        }
      });

      // boss behavior
      if (boss) {
        boss.shotTimer++;
        if (boss.shotTimer % 40 === 0) {
          for (let i = 0; i < 20; i++) {
            const angle = (Math.PI * 2 / 20) * i + frame * 0.02;
            enemyBullets.push({
              x: boss.x,
              y: boss.y,
              w: 10,
              h: 10,
              vx: Math.cos(angle) * 3,
              vy: Math.sin(angle) * 3
            });
          }
        }
      }

      items.forEach((item) => {
        item.y += item.speed;
        item.x += Math.sin((frame + item.y) * 0.04) * 0.8;
      });
      items = items.filter((item) => item.y < canvas.height + 40);

      for (let i = items.length - 1; i >= 0; i--) {
        if (isHit(player, items[i])) {
          player.power = Math.min(4, player.power + 1);
          score += 300;
          createExplosion(items[i].x, items[i].y);
          items.splice(i, 1);
          updateUI();
        }
      }

      for (let i = enemies.length - 1; i >= 0; i--) {
        const e = enemies[i];
        if (isHit(player, e)) {
          createExplosion(e.x, e.y);
          enemies.splice(i, 1);
          damagePlayer();
          continue;
        }

        if (e.y > canvas.height + 50) {
          enemies.splice(i, 1);
          damagePlayer();
        }
      }

      for (let i = enemyBullets.length - 1; i >= 0; i--) {
        if (isHit(player, enemyBullets[i])) {
          enemyBullets.splice(i, 1);
          damagePlayer();
        }
      }

      for (let i = enemies.length - 1; i >= 0; i--) {
        for (let j = bullets.length - 1; j >= 0; j--) {
          if (isHit(enemies[i], bullets[j])) {
            enemies[i].hp -= bullets[j].power;
            bullets.splice(j, 1);

            if (enemies[i].hp <= 0) {
              const drop = Math.random() < 0.18;
              if (drop) spawnPowerItem(enemies[i].x, enemies[i].y);
              createExplosion(enemies[i].x, enemies[i].y);
              enemies.splice(i, 1);
              score += 100;
              updateUI();
            }
            break;
          }
        }
      }

      particles.forEach((p) => {
        p.x += p.vx;
        p.y += p.vy;
        p.life--;
      });
      particles = particles.filter((p) => p.life > 0);

      stars.forEach((s) => {
        s.y += s.speed;
        if (s.y > canvas.height) {
          s.y = 0;
          s.x = Math.random() * canvas.width;
        }
      });
    }

    function drawPlayer() {
      if (player.invincible > 0 && Math.floor(frame / 5) % 2 === 0) return;

      ctx.save();
      ctx.translate(player.x, player.y);

      // body
      const grad = ctx.createLinearGradient(0, -20, 0, 30);
      grad.addColorStop(0, "#7df9ff");
      grad.addColorStop(1, "#0077ff");
      ctx.fillStyle = grad;
      ctx.beginPath();
      ctx.moveTo(0, -26);
      ctx.lineTo(20, 22);
      ctx.lineTo(0, 12);
      ctx.lineTo(-20, 22);
      ctx.closePath();
      ctx.fill();

      // cockpit
      ctx.fillStyle = "#ffffff";
      ctx.beginPath();
      ctx.arc(0, -6, 6, 0, Math.PI * 2);
      ctx.fill();

      // wings glow
      ctx.fillStyle = "#00eaff";
      ctx.globalAlpha = 0.5;
      ctx.fillRect(-24, 0, 8, 10);
      ctx.fillRect(16, 0, 8, 10);
      ctx.globalAlpha = 1;

      // engine flame
      ctx.fillStyle = "#ffcf5a";
      ctx.beginPath();
      ctx.moveTo(-8, 24);
      ctx.lineTo(0, 40 + Math.random() * 6);
      ctx.lineTo(8, 24);
      ctx.closePath();
      ctx.fill();

      ctx.restore();
    }

    function drawEnemy(e) {
      ctx.save();
      ctx.translate(e.x, e.y);

      // core
      const grad = ctx.createRadialGradient(0, 0, 4, 0, 0, e.w / 2);
      grad.addColorStop(0, "#fff");
      grad.addColorStop(1, e.type === "spread" ? "#ff00cc" : "#ff0000");
      ctx.fillStyle = grad;
      ctx.beginPath();
      ctx.arc(0, 0, e.w / 2, 0, Math.PI * 2);
      ctx.fill();

      // spikes
      ctx.strokeStyle = "#fff";
      ctx.lineWidth = 2;
      for (let i = 0; i < 6; i++) {
        const angle = (Math.PI * 2 / 6) * i + frame * 0.01;
        ctx.beginPath();
        ctx.moveTo(Math.cos(angle) * 6, Math.sin(angle) * 6);
        ctx.lineTo(Math.cos(angle) * (e.w / 2 + 8), Math.sin(angle) * (e.w / 2 + 8));
        ctx.stroke();
      }

      ctx.restore();
    }

    function drawPowerItem(item) {
      ctx.save();
      ctx.translate(item.x, item.y);
      ctx.rotate(frame * 0.05);
      ctx.fillStyle = "#68ff7a";
      ctx.fillRect(-12, -12, 24, 24);
      ctx.fillStyle = "#052";
      ctx.font = "bold 18px system-ui";
      ctx.textAlign = "center";
      ctx.textBaseline = "middle";
      ctx.fillText("P", 0, 1);
      ctx.restore();
    }

    function draw() {
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      ctx.fillStyle = "#050816";
      ctx.fillRect(0, 0, canvas.width, canvas.height);

      ctx.fillStyle = "#ffffff";
      stars.forEach((s) => {
        ctx.globalAlpha = 0.4 + Math.random() * 0.5;
        ctx.beginPath();
        ctx.arc(s.x, s.y, s.r, 0, Math.PI * 2);
        ctx.fill();
      });
      ctx.globalAlpha = 1;

      bullets.forEach((b) => {
        ctx.fillStyle = b.power >= 2 ? "#fff36a" : "#8ffcff";
        ctx.fillRect(b.x - b.w / 2, b.y - b.h / 2, b.w, b.h);
      });

      enemyBullets.forEach((b) => {
        ctx.fillStyle = "#ff9a3b";
        ctx.beginPath();
        ctx.arc(b.x, b.y, b.w / 2, 0, Math.PI * 2);
        ctx.fill();
      });

      items.forEach(drawPowerItem);
      enemies.forEach(drawEnemy);
      // draw boss
      if (boss) {
        ctx.save();
        ctx.translate(boss.x, boss.y);

        const grad = ctx.createRadialGradient(0, 0, 10, 0, 0, boss.w / 2);
        grad.addColorStop(0, "#fff");
        grad.addColorStop(1, "#ff00aa");
        ctx.fillStyle = grad;
        ctx.beginPath();
        ctx.arc(0, 0, boss.w / 2, 0, Math.PI * 2);
        ctx.fill();
        ctx.restore();

        // HP bar
        ctx.fillStyle = "#000";
        ctx.fillRect(80, 20, 320, 16);
        ctx.fillStyle = "#ff0066";
        ctx.fillRect(80, 20, 320 * (boss.hp / boss.maxHp), 16);
      }

      drawPlayer();

      particles.forEach((p) => {
        ctx.globalAlpha = p.life / 24;
        ctx.fillStyle = "#ffd35a";
        ctx.beginPath();
        ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2);
        ctx.fill();
      });
      ctx.globalAlpha = 1;

      if (gameOver) {
        ctx.fillStyle = "#000b";
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        ctx.fillStyle = "white";
        ctx.textAlign = "center";
        ctx.font = "bold 42px system-ui";
        ctx.fillText("GAME OVER", canvas.width / 2, canvas.height / 2 - 30);
        ctx.font = "20px system-ui";
        ctx.fillText("Score: " + score, canvas.width / 2, canvas.height / 2 + 10);
        ctx.fillText("Enterでリスタート", canvas.width / 2, canvas.height / 2 + 48);
      }
    }

    function loop() {
      update();
      draw();
      requestAnimationFrame(loop);
    }

    updateUI();
    loop();
  </script>
</body>
</html>

投稿者: chosuke

趣味はゲームやアニメや漫画などです

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です