🎮ArcadeLab

Neon Dodge

by BlazeKoala99
849 lines22.0 KB
▶ Play
<!DOCTYPE html>
<html lang="de">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Neon Dodge</title>

  <style>
    * { box-sizing: border-box; }

    body {
      min-height: 100vh;
      margin: 0;
      padding: 20px;
      display: grid;
      place-items: center;
      color: #f4f7ff;
      background: radial-gradient(ellipse at 50% 0%, #26346b, #10152b 55%, #080b17);
      font-family: system-ui, sans-serif;
    }

    .game {
      width: min(100%, 480px);
      padding: 22px;
      border: 1px solid #ffffff22;
      border-radius: 24px;
      background: #ffffff0c;
      box-shadow: 0 24px 80px #0008, inset 0 1px #ffffff16;
      backdrop-filter: blur(12px);
    }

    header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 14px;
    }

    h1 {
      margin: 0;
      font-size: 1.7rem;
      letter-spacing: 2px;
      background: linear-gradient(90deg, #67e8f9, #c4b5fd, #f0abfc);
      color: transparent;
      background-clip: text;
    }

    .wallet {
      padding: 8px 12px;
      border: 1px solid #facc1555;
      border-radius: 12px;
      color: #fde68a;
      background: #facc1514;
      font-weight: 700;
    }

    .hud {
      display: flex;
      justify-content: space-between;
      gap: 8px;
      margin: 0 2px 10px;
      color: #cbd5e1;
      font-size: 0.92rem;
    }

    canvas {
      display: block;
      width: 100%;
      height: auto;
      border: 1px solid #91a4ff55;
      border-radius: 16px;
      background: #0b1025;
      touch-action: none;
    }

    .controls {
      margin: 10px 0 0;
      color: #aab4d3;
      text-align: center;
      font-size: 0.85rem;
    }

    .screen { display: none; }
    .screen.active { display: block; }

    .menu {
      padding: 18px 4px 8px;
      text-align: center;
    }

    .menu h2 {
      margin: 0 0 8px;
      font-size: 1.5rem;
    }

    .menu p {
      margin: 0 0 18px;
      color: #bac7e8;
      line-height: 1.5;
    }

    .buttons {
      display: grid;
      gap: 10px;
      margin-top: 16px;
    }

    button {
      padding: 12px 18px;
      border: 0;
      border-radius: 12px;
      color: #08111e;
      background: linear-gradient(90deg, #67e8f9, #a5b4fc);
      font: inherit;
      font-weight: 800;
      cursor: pointer;
      transition: transform 150ms, filter 150ms;
    }

    button:hover {
      transform: translateY(-2px);
      filter: brightness(1.08);
    }

    button:active { transform: translateY(0); }

    button.secondary {
      color: #e6eaff;
      border: 1px solid #ffffff20;
      background: #ffffff12;
    }

    button:disabled {
      cursor: default;
      opacity: 0.55;
      transform: none;
    }

    .shop-title {
      display: flex;
      justify-content: space-between;
      align-items: center;
      gap: 10px;
      margin: 18px 0 10px;
    }

    .shop-title h2 { margin: 0; }

    .skin-list {
      display: grid;
      gap: 9px;
      max-height: 330px;
      overflow-y: auto;
      padding: 2px 4px 2px 0;
    }

    .skin {
      display: grid;
      grid-template-columns: 42px 1fr auto;
      gap: 10px;
      align-items: center;
      padding: 10px;
      border: 1px solid #ffffff1c;
      border-radius: 14px;
      background: #ffffff08;
    }

    .swatch {
      width: 34px;
      height: 34px;
      border: 2px solid #ffffffaa;
      border-radius: 11px;
      box-shadow: 0 0 14px currentColor;
    }

    .skin-name { font-weight: 700; }
    .skin-price { color: #aab4d3; font-size: 0.82rem; }

    .skin button {
      min-width: 100px;
      padding: 8px 10px;
      font-size: 0.85rem;
    }

    .small-note {
      margin: 12px 0 0;
      color: #8e9aba;
      text-align: center;
      font-size: 0.8rem;
    }

    .touch-controls {
      display: flex;
      justify-content: center;
      gap: 22px;
      margin: 12px 0;
      touch-action: none;
    }

    .touch-button {
      width: 84px;
      height: 58px;
      padding: 0;
      color: #e6eaff;
      border: 1px solid #91a4ff66;
      background: #ffffff16;
      font-size: 1.5rem;
      user-select: none;
      -webkit-user-select: none;
      touch-action: none;
    }

    .touch-button:active {
      background: #67e8f955;
      transform: scale(0.94);
    }
  </style>
</head>

<body>
  <main class="game">
    <header>
      <h1>NEON DODGE</h1>
      <div class="wallet">🪙 <span id="coins">0</span></div>
    </header>

    <section id="menuScreen" class="screen active">
      <div class="menu">
        <h2>Bereit?</h2>
        <p>Weiche den Blöcken aus, sammle Münzen und schalte neue Skins frei.</p>

        <div class="hud">
          <span>🏆 Bestwert: <b id="bestMenu">0</b></span>
          <span>🎨 Skin: <b id="selectedMenu">Blau</b></span>
        </div>

        <div class="buttons">
          <button id="playButton">▶ Spielen</button>
          <button id="shopButton" class="secondary">🛒 Skin-Shop</button>
        </div>
      </div>
    </section>

    <section id="shopScreen" class="screen">
      <div class="shop-title">
        <h2>Skin-Shop</h2>
        <button id="backButton" class="secondary">← Menü</button>
      </div>

      <div id="skinList" class="skin-list"></div>
      <p class="small-note">Münzen bekommst du, indem du sie während des Spiels einsammelst.</p>
    </section>

    <section id="gameScreen" class="screen">
      <div class="hud">
        <span>⭐ Punkte: <b id="score">0</b></span>
        <span>🪙 Runde: <b id="runCoins">0</b></span>
        <span>🏆 Best: <b id="bestGame">0</b></span>
      </div>

      <canvas id="canvas" width="420" height="520"></canvas>

      <div class="touch-controls">
        <button class="touch-button" id="leftButton" aria-label="Nach links">◀</button>
        <button class="touch-button" id="rightButton" aria-label="Nach rechts">▶</button>
      </div>

      <p class="controls">Steuerung: Pfeiltasten, A/D oder Touch-Buttons</p>

      <div class="buttons">
        <button id="pauseButton" class="secondary">Ⅱ Pause</button>
      </div>
    </section>

    <section id="endScreen" class="screen">
      <div class="menu">
        <h2 id="endTitle">Spiel vorbei!</h2>
        <p id="endMessage"></p>

        <div class="buttons">
          <button id="againButton">↻ Runde neu starten</button>
          <button id="endShopButton" class="secondary">🛒 Zum Shop</button>
          <button id="endMenuButton" class="secondary">⌂ Hauptmenü</button>
        </div>
      </div>
    </section>
  </main>

  <script>
    const canvas = document.querySelector("#canvas");
    const ctx = canvas.getContext("2d");

    const skins = [
      { id: "blue", name: "Blau", color: "#45d9f5", price: 0 },
      { id: "pink", name: "Pink", color: "#fb5ca8", price: 15 },
      { id: "green", name: "Limette", color: "#a3e635", price: 25 },
      { id: "gold", name: "Gold", color: "#facc15", price: 40 },
      { id: "purple", name: "Violett", color: "#a78bfa", price: 60 },
      { id: "orange", name: "Feuer", color: "#fb923c", price: 85 }
    ];

    const defaultSave = {
      coins: 0,
      best: 0,
      owned: ["blue"],
      selected: "blue"
    };

    function loadSave() {
      try {
        const stored = JSON.parse(localStorage.getItem("neonDodgeSave"));
        return {
          ...defaultSave,
          ...stored,
          owned: Array.isArray(stored?.owned) ? stored.owned : ["blue"]
        };
      } catch {
        return { ...defaultSave, owned: ["blue"] };
      }
    }

    let save = loadSave();

    const keys = {};
    const player = { x: 190, y: 455, w: 40, h: 40, speed: 6 };

    const stars = Array.from({ length: 65 }, () => ({
      x: Math.random() * canvas.width,
      y: Math.random() * canvas.height,
      r: Math.random() * 1.6 + 0.3,
      speed: Math.random() * 0.8 + 0.2
    }));

    let blocks = [];
    let pickups = [];
    let powerups = [];
    let score = 0;
    let runCoins = 0;
    let running = false;
    let paused = false;
    let lastBlock = 0;
    let lastCoin = 0;
    let lastPowerup = 0;
    let lastTime = 0;
    let animationId = 0;
    let shieldUntil = 0;
    let magnetUntil = 0;
    let touchDirection = 0;

    const screens = {
      menu: document.querySelector("#menuScreen"),
      shop: document.querySelector("#shopScreen"),
      game: document.querySelector("#gameScreen"),
      end: document.querySelector("#endScreen")
    };

    function saveGame() {
      try {
        localStorage.setItem("neonDodgeSave", JSON.stringify(save));
      } catch {
        // Das Spiel kann auch ohne verfügbaren lokalen Speicher gespielt werden.
      }
      updateMenu();
      renderShop();
    }

    function showScreen(name) {
      Object.values(screens).forEach(screen => screen.classList.remove("active"));
      screens[name].classList.add("active");
    }

    function selectedSkin() {
      return skins.find(skin => skin.id === save.selected) || skins[0];
    }

    function updateMenu() {
      document.querySelector("#coins").textContent = save.coins;
      document.querySelector("#bestMenu").textContent = save.best;
      document.querySelector("#selectedMenu").textContent = selectedSkin().name;
      document.querySelector("#bestGame").textContent = save.best;
    }

    function renderShop() {
      const list = document.querySelector("#skinList");
      list.innerHTML = "";

      for (const skin of skins) {
        const owned = save.owned.includes(skin.id);
        const selected = save.selected === skin.id;

        const row = document.createElement("div");
        row.className = "skin";

        const swatch = document.createElement("div");
        swatch.className = "swatch";
        swatch.style.background = skin.color;
        swatch.style.color = skin.color;

        const details = document.createElement("div");

        const name = document.createElement("div");
        name.className = "skin-name";
        name.textContent = skin.name;

        const price = document.createElement("div");
        price.className = "skin-price";
        price.textContent = owned ? "Freigeschaltet" : `🪙 ${skin.price} Münzen`;

        details.append(name, price);

        const button = document.createElement("button");
        button.textContent = selected
          ? "Ausgewählt"
          : owned
            ? "Auswählen"
            : "Kaufen";

        button.disabled = selected || (!owned && save.coins < skin.price);

        if (selected || owned) {
          button.className = "secondary";
        }

        button.addEventListener("click", () => {
          if (save.owned.includes(skin.id)) {
            save.selected = skin.id;
          } else if (save.coins >= skin.price) {
            save.coins -= skin.price;
            save.owned.push(skin.id);
            save.selected = skin.id;
          }
          saveGame();
        });

        row.append(swatch, details, button);
        list.appendChild(row);
      }
    }

    function startGame() {
      cancelAnimationFrame(animationId);

      blocks = [];
      pickups = [];
      powerups = [];
      score = 0;
      runCoins = 0;
      player.x = (canvas.width - player.w) / 2;
      lastBlock = 0;
      lastCoin = 0;
      lastPowerup = 0;
      lastTime = 0;
      shieldUntil = 0;
      magnetUntil = 0;
      running = true;
      paused = false;

      document.querySelector("#score").textContent = "0";
      document.querySelector("#runCoins").textContent = "0";
      document.querySelector("#pauseButton").textContent = "Ⅱ Pause";

      showScreen("game");
      animationId = requestAnimationFrame(gameLoop);
    }

    function roundedRect(x, y, w, h, r) {
      ctx.beginPath();
      ctx.roundRect(x, y, w, h, r);
      ctx.fill();
    }

    function draw() {
      const background = ctx.createLinearGradient(0, 0, 0, canvas.height);
      background.addColorStop(0, "#121a3b");
      background.addColorStop(1, "#080d20");
      ctx.fillStyle = background;
      ctx.fillRect(0, 0, canvas.width, canvas.height);

      // Sterne
      ctx.fillStyle = "#c7d2fe";
      for (const star of stars) {
        ctx.globalAlpha = 0.45;
        ctx.beginPath();
        ctx.arc(star.x, star.y, star.r, 0, Math.PI * 2);
        ctx.fill();

        if (running && !paused) {
          star.y += star.speed;
          if (star.y > canvas.height) star.y = 0;
        }
      }
      ctx.globalAlpha = 1;

      // Münzen
      for (const coin of pickups) {
        ctx.save();
        ctx.shadowColor = "#facc15";
        ctx.shadowBlur = 18;
        ctx.fillStyle = "#facc15";
        ctx.beginPath();
        ctx.arc(coin.x, coin.y, coin.r, 0, Math.PI * 2);
        ctx.fill();

        ctx.fillStyle = "#fff3a3";
        ctx.beginPath();
        ctx.arc(coin.x - 3, coin.y - 3, 3, 0, Math.PI * 2);
        ctx.fill();
        ctx.restore();
      }

      // Hindernisse
      for (const block of blocks) {
        ctx.save();
        ctx.shadowColor = "#fb4f8b";
        ctx.shadowBlur = 18;
        ctx.fillStyle = "#fb4f8b";
        roundedRect(block.x, block.y, block.w, block.h, 9);
        ctx.restore();

        ctx.fillStyle = "#ffd1e1";
        roundedRect(block.x + 7, block.y + 5, 5, 5, 2);
      }

      // Power-ups: 🛡️ Schutzschild und 🧲 Magnet
      for (const powerup of powerups) {
        ctx.save();
        ctx.shadowColor = powerup.type === "shield" ? "#67e8f9" : "#facc15";
        ctx.shadowBlur = 18;
        ctx.fillStyle = powerup.type === "shield" ? "#67e8f9" : "#facc15";
        ctx.beginPath();
        ctx.arc(powerup.x, powerup.y, 17, 0, Math.PI * 2);
        ctx.fill();

        ctx.shadowBlur = 0;
        ctx.textAlign = "center";
        ctx.textBaseline = "middle";
        ctx.font = "20px system-ui";
        ctx.fillText(powerup.type === "shield" ? "🛡️" : "🧲", powerup.x, powerup.y);
        ctx.restore();
      }

      // Spieler
      const skin = selectedSkin();
      ctx.save();
      ctx.shadowColor = skin.color;
      ctx.shadowBlur = 24;
      ctx.fillStyle = skin.color;
      roundedRect(player.x, player.y, player.w, player.h, 12);
      ctx.restore();

      ctx.fillStyle = "#ffffffcc";
      roundedRect(player.x + 9, player.y + 8, 22, 5, 3);

      // Schild-Aura, solange der Schutzschild aktiv ist
      if (shieldUntil > performance.now()) {
        ctx.save();
        ctx.strokeStyle = "#67e8f9";
        ctx.lineWidth = 3;
        ctx.shadowColor = "#67e8f9";
        ctx.shadowBlur = 18;
        ctx.beginPath();
        ctx.arc(
          player.x + player.w / 2,
          player.y + player.h / 2,
          31,
          0,
          Math.PI * 2
        );
        ctx.stroke();
        ctx.restore();
      }

      // Aktive Power-ups anzeigen
      const activePowerups = [];
      if (shieldUntil > performance.now()) activePowerups.push("🛡️");
      if (magnetUntil > performance.now()) activePowerups.push("🧲");

      if (activePowerups.length) {
        ctx.save();
        ctx.font = "22px system-ui";
        ctx.textAlign = "right";
        ctx.textBaseline = "top";
        ctx.fillText(activePowerups.join(" "), canvas.width - 12, 12);
        ctx.restore();
      }

      if (paused) {
        ctx.fillStyle = "#070b1dcc";
        ctx.fillRect(0, 0, canvas.width, canvas.height);

        ctx.textAlign = "center";
        ctx.fillStyle = "#fff";
        ctx.font = "bold 32px system-ui";
        ctx.fillText("PAUSE", canvas.width / 2, 250);

        ctx.font = "16px system-ui";
        ctx.fillStyle = "#bac7e8";
        ctx.fillText("Drücke Fortsetzen", canvas.width / 2, 280);
      }
    }

    function gameLoop(time) {
      if (!running) return;

      animationId = requestAnimationFrame(gameLoop);

      if (paused) {
        draw();
        return;
      }

      const delta = lastTime ? Math.min(time - lastTime, 40) : 16;
      lastTime = time;

      // Spielerbewegung
      if (keys.arrowleft || keys.a || touchDirection === -1) {
        player.x -= player.speed * delta / 16;
      }
      if (keys.arrowright || keys.d || touchDirection === 1) {
        player.x += player.speed * delta / 16;
      }
      player.x = Math.max(0, Math.min(canvas.width - player.w, player.x));

      // Punkte für die überlebte Zeit
      score += delta / 1000 * 10;
      document.querySelector("#score").textContent = Math.floor(score);

      // Hindernisse erzeugen und bewegen
      if (time - lastBlock > 700) {
        const w = 35 + Math.random() * 45;
        blocks.push({
          x: Math.random() * (canvas.width - w),
          y: -35,
          w,
          h: 24,
          speed: 3 + Math.min(score / 250, 4)
        });
        lastBlock = time;
      }

      for (const block of blocks) {
        block.y += block.speed * delta / 16;
      }
      blocks = blocks.filter(block => block.y < canvas.height + block.h);

      // Münzen erzeugen und bewegen
      if (time - lastCoin > 1100) {
        pickups.push({
          x: 15 + Math.random() * (canvas.width - 30),
          y: -15,
          r: 9,
          speed: 3
        });
        lastCoin = time;
      }

      for (const coin of pickups) {
        coin.y += coin.speed * delta / 16;

        // Der Magnet zieht Münzen zum Spieler
        if (magnetUntil > time) {
          const targetX = player.x + player.w / 2;
          const targetY = player.y + player.h / 2;
          coin.x += (targetX - coin.x) * 0.08;
          coin.y += (targetY - coin.y) * 0.08;
        }
      }

      // Power-ups erzeugen
      if (time - lastPowerup > 9000) {
        powerups.push({
          x: 25 + Math.random() * (canvas.width - 50),
          y: -20,
          type: Math.random() < 0.5 ? "shield" : "magnet",
          speed: 2.5
        });
        lastPowerup = time;
      }

      for (const powerup of powerups) {
        powerup.y += powerup.speed * delta / 16;
      }
      powerups = powerups.filter(powerup => powerup.y < canvas.height + 20);

      // Münzen einsammeln
      pickups = pickups.filter(coin => {
        const hit =
          coin.x + coin.r > player.x &&
          coin.x - coin.r < player.x + player.w &&
          coin.y + coin.r > player.y &&
          coin.y - coin.r < player.y + player.h;

        if (hit) {
          runCoins++;
          save.coins++;
          document.querySelector("#runCoins").textContent = runCoins;
          updateMenu();
          return false;
        }

        return true;
      });

      // Power-ups einsammeln: wirken jeweils 6 Sekunden
      powerups = powerups.filter(powerup => {
        const hit =
          powerup.x > player.x - 18 &&
          powerup.x < player.x + player.w + 18 &&
          powerup.y > player.y - 18 &&
          powerup.y < player.y + player.h + 18;

        if (hit) {
          if (powerup.type === "shield") {
            shieldUntil = time + 6000;
          } else {
            magnetUntil = time + 6000;
          }
          return false;
        }

        return true;
      });

      // Hinderniskollision
      const hitBlock = blocks.some(block =>
        player.x < block.x + block.w &&
        player.x + player.w > block.x &&
        player.y < block.y + block.h &&
        player.y + player.h > block.y
      );

      if (hitBlock) {
        if (shieldUntil > time) {
          // Der Schild schützt vor einem Zusammenstoß
          shieldUntil = 0;
          blocks = blocks.filter(block =>
            !(
              player.x < block.x + block.w &&
              player.x + player.w > block.x &&
              player.y < block.y + block.h &&
              player.y + player.h > block.y
            )
          );
        } else {
          endGame();
          return;
        }
      }

      draw();
    }

    function endGame() {
      running = false;
      save.best = Math.max(save.best, Math.floor(score));
      saveGame();

      document.querySelector("#endTitle").textContent =
        score > 0 ? "Gute Runde!" : "Spiel vorbei!";

      document.querySelector("#endMessage").textContent =
        `Du hast ${Math.floor(score)} Punkte und ${runCoins} Münzen gesammelt. Bestwert: ${save.best}.`;

      showScreen("end");
    }

    function togglePause() {
      if (!running) return;

      paused = !paused;
      document.querySelector("#pauseButton").textContent =
        paused ? "▶ Fortsetzen" : "Ⅱ Pause";
    }

    document.querySelector("#playButton").addEventListener("click", startGame);
    document.querySelector("#againButton").addEventListener("click", startGame);
    document.querySelector("#pauseButton").addEventListener("click", togglePause);

    document.querySelector("#shopButton").addEventListener("click", () => {
      renderShop();
      showScreen("shop");
    });

    document.querySelector("#backButton").addEventListener("click", () => {
      showScreen("menu");
    });

    document.querySelector("#endShopButton").addEventListener("click", () => {
      renderShop();
      showScreen("shop");
    });

    document.querySelector("#endMenuButton").addEventListener("click", () => {
      showScreen("menu");
    });

    // Tastatursteuerung
    window.addEventListener("keydown", event => {
      const key = event.key.toLowerCase();

      if (["arrowleft", "arrowright", " "].includes(key)) {
        event.preventDefault();
      }

      keys[key] = true;

      if (key === " " && running) {
        togglePause();
      }
    });

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

    // Touch-Steuerung
    function setTouchDirection(direction) {
      touchDirection = direction;
    }

    function clearTouchDirection() {
      touchDirection = 0;
    }

    const leftButton = document.querySelector("#leftButton");
    const rightButton = document.querySelector("#rightButton");

    leftButton.addEventListener("pointerdown", event => {
      event.preventDefault();
      setTouchDirection(-1);
    });

    rightButton.addEventListener("pointerdown", event => {
      event.preventDefault();
      setTouchDirection(1);
    });

    for (const button of [leftButton, rightButton]) {
      button.addEventListener("pointerup", clearTouchDirection);
      button.addEventListener("pointerleave", clearTouchDirection);
      button.addEventListener("pointercancel", clearTouchDirection);
    }

    updateMenu();
    renderShop();
  </script>
</body>
</html>

Game Source: Neon Dodge

Creator: BlazeKoala99

Libraries: none

Complexity: complex (849 lines, 22.0 KB)

The full source code is displayed above on this page.

Remix Instructions

To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: neon-racer-blazekoala99" to link back to the original. Then publish at arcadelab.ai/publish.