Neon Dodge
by BlazeKoala99849 lines22.0 KB
<!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.