Battle Stars - Fixed Loading Edition
by AtomicFox16689 lines34.0 KB
Das Problem lag daran, dass der Ladebalken das Spiel blockiert hat oder die Variablen für die Animation in manchen Browsern asynchron liefen.
Hier ist der vollständig korrigierte Code. Ich habe das Timing der Lade-Engine umgeschrieben. Die Fortschrittsanzeige läuft nun absolut flüssig für 2 Sekunden hoch auf 99%, verweilt dort für exakt 2 Sekunden und startet das Spiel dann fehlerfrei.
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Battle Stars - Fixed Loading Edition</title>
<style>
body {
margin: 0;
background: radial-gradient(circle at center, #0b0b1e 0%, #030308 100%);
color: white;
font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100vh;
overflow: hidden;
user-select: none;
-webkit-user-select: none;
}
/* Ladebildschirm Fix */
#loading-screen {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: #030308;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 9999;
opacity: 1;
transition: opacity 0.5s ease-out;
}
.loading-title {
font-size: 42px;
font-weight: 900;
letter-spacing: 6px;
text-transform: uppercase;
background: linear-gradient(45deg, #00ffff, #ff0055, #ffd700);
background-size: 200% auto;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
animation: shine 3s linear infinite;
margin-bottom: 30px;
}
@keyframes shine {
to { background-position: 200% center; }
}
.progress-container {
width: 400px;
height: 12px;
background: rgba(255, 255, 255, 0.05);
border-radius: 20px;
padding: 3px;
border: 1px solid rgba(255, 255, 255, 0.1);
box-shadow: 0 0 30px rgba(0, 0, 0, 0.7);
}
.progress-bar {
height: 100%;
width: 0%;
background: linear-gradient(90deg, #ff0055, #00ffff);
box-shadow: 0 0 20px rgba(0, 255, 255, 0.6), 0 0 40px rgba(255, 0, 85, 0.4);
border-radius: 20px;
}
.progress-text {
margin-top: 15px;
font-size: 13px;
font-weight: 700;
color: #6c6c8a;
text-transform: uppercase;
letter-spacing: 2px;
}
#ui-container {
display: flex;
flex-direction: row;
align-items: center;
gap: 25px;
margin-bottom: 15px;
background: rgba(10, 10, 25, 0.4);
padding: 15px 30px;
border-radius: 24px;
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.05);
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.4);
max-width: 95vw;
}
.selector-row {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
}
.menu-title {
font-size: 11px;
font-weight: 900;
letter-spacing: 2px;
text-transform: uppercase;
color: #8a8aa8;
margin: 0;
}
.grid-group {
display: grid;
grid-template-columns: repeat(12, 1fr);
gap: 5px;
max-height: 85px;
overflow-y: auto;
padding: 4px;
}
.btn-group {
display: flex;
gap: 8px;
}
button {
background: rgba(255, 255, 255, 0.03);
color: #a0a0c0;
border: 1px solid rgba(255, 255, 255, 0.08);
padding: 8px 16px;
font-size: 11px;
font-weight: 700;
cursor: pointer;
border-radius: 10px;
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
text-transform: uppercase;
}
button:hover {
background: rgba(255, 255, 255, 0.08);
color: white;
border-color: rgba(255, 255, 255, 0.2);
transform: translateY(-1px);
}
button.active {
background: linear-gradient(135deg, #00ffff 0%, #0055ff 100%);
color: white;
border-color: #ffffff;
box-shadow: 0 0 15px rgba(0, 255, 255, 0.4);
}
#game-wrapper {
position: relative;
}
canvas {
border-radius: 24px;
box-shadow: 0 40px 100px rgba(0,0,0,0.8);
border: 1px solid rgba(255, 255, 255, 0.05);
display: block;
}
#shoot-btn {
position: absolute;
bottom: 45px;
right: 45px;
width: 84px;
height: 84px;
background: radial-gradient(circle at 35% 35%, #ff3344 0%, #990011 100%);
color: white;
border: 2px solid rgba(255, 255, 255, 0.4);
border-radius: 50%;
font-size: 13px;
font-weight: 800;
cursor: pointer;
box-shadow: 0 10px 25px rgba(255, 0, 50, 0.4), inset 0 2px 5px rgba(255,255,255,0.3);
display: flex;
justify-content: center;
align-items: center;
text-transform: uppercase;
letter-spacing: 1px;
z-index: 10;
}
#shoot-btn:active {
transform: scale(0.92);
box-shadow: 0 4px 12px rgba(255, 0, 50, 0.5);
}
#joystick-zone {
position: absolute;
bottom: 30px;
left: 30px;
width: 160px;
height: 160px;
z-index: 10;
display: flex;
justify-content: center;
align-items: center;
}
#joystick-base {
width: 120px;
height: 120px;
background: rgba(255, 255, 255, 0.06);
border: 2px solid rgba(255, 255, 255, 0.2);
border-radius: 50%;
backdrop-filter: blur(5px);
-webkit-backdrop-filter: blur(5px);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
display: flex;
justify-content: center;
align-items: center;
position: relative;
}
#joystick-stick {
width: 54px;
height: 54px;
background: radial-gradient(circle at 35% 35%, #00ffff 0%, #0055ff 100%);
border: 2px solid rgba(255, 255, 255, 0.6);
border-radius: 50%;
box-shadow: 0 6px 15px rgba(0, 85, 255, 0.5);
position: absolute;
cursor: pointer;
}
</style>
</head>
<body>
<div id="loading-screen">
<div class="loading-title">Battle Stars</div>
<div class="progress-container">
<div class="progress-bar" id="progress-bar"></div>
</div>
<div class="progress-text" id="progress-text">Lade Assets... 0%</div>
</div>
<div id="ui-container">
<div class="selector-row">
<div class="menu-title">Brawler Pool (24 Helden verfügbar)</div>
<div class="grid-group" id="brawler-select"></div>
</div>
<div class="selector-row">
<div class="menu-title">Einsatzort</div>
<div class="btn-group" id="world-select">
<button class="active" onclick="selectWorld('cyber')">Cyber Arena</button>
<button onclick="selectWorld('desert')">Wüsten Oase</button>
<button onclick="selectWorld('lava')">Lava Hölle</button>
</div>
</div>
</div>
<div id="game-wrapper">
<canvas id="gameCanvas" width="1024" height="768"></canvas>
<div id="joystick-zone">
<div id="joystick-base">
<div id="joystick-stick"></div>
</div>
</div>
<div id="shoot-btn" onmousedown="triggerShoot(event)" ontouchstart="triggerShoot(event)">Feuer</div>
</div>
<script>
const canvas = document.getElementById("gameCanvas");
const ctx = canvas.getContext("2d");
let gameOver = false;
let won = false;
let player_shoot_cooldown = 0;
let currentBrawlerType = 'shelly';
let currentWorldType = 'cyber';
const keys = {};
let particles = [];
let mouseX = 512;
let mouseY = 384;
// Joystick
const joystickZone = document.getElementById('joystick-zone');
const joystickBase = document.getElementById('joystick-base');
const joystickStick = document.getElementById('joystick-stick');
let joystickActive = false;
let joystickStartX = 0;
let joystickStartY = 0;
let joystickMoveX = 0;
let joystickMoveY = 0;
window.addEventListener('mousemove', e => {
const rect = canvas.getBoundingClientRect();
mouseX = e.clientX - rect.left;
mouseY = e.clientY - rect.top;
});
function triggerShoot(e) {
if (e) e.preventDefault();
if (gameOver || player_shoot_cooldown > 0) return;
fireWeapon(player.x, player.y, mouseX, mouseY, false, player.weapon);
player_shoot_cooldown = player.weapon.cooldown;
}
// Joystick Logik
function handleJoystickStart(clientX, clientY) {
joystickActive = true;
const baseRect = joystickBase.getBoundingClientRect();
joystickStartX = baseRect.left + baseRect.width / 2;
joystickStartY = baseRect.top + baseRect.height / 2;
}
function handleJoystickMove(clientX, clientY) {
if (!joystickActive) return;
let deltaX = clientX - joystickStartX;
let deltaY = clientY - joystickStartY;
let distance = Math.hypot(deltaX, deltaY);
const maxRadius = 45;
if (distance > maxRadius) {
deltaX = (deltaX / distance) * maxRadius;
deltaY = (deltaY / distance) * maxRadius;
}
joystickStick.style.transform = `translate(${deltaX}px, ${deltaY}px)`;
joystickMoveX = deltaX / maxRadius;
joystickMoveY = deltaY / maxRadius;
}
function handleJoystickEnd() {
joystickActive = false;
joystickStick.style.transform = 'translate(0px, 0px)';
joystickMoveX = 0;
joystickMoveY = 0;
}
joystickZone.addEventListener('mousedown', e => { handleJoystickStart(e.clientX, e.clientY); e.stopPropagation(); });
window.addEventListener('mousemove', e => { if (joystickActive) handleJoystickMove(e.clientX, e.clientY); });
window.addEventListener('mouseup', () => { if (joystickActive) handleJoystickEnd(); });
joystickZone.addEventListener('touchstart', e => { const t = e.touches[0]; handleJoystickStart(t.clientX, t.clientY); e.stopPropagation(); e.preventDefault(); }, { passive: false });
window.addEventListener('touchmove', e => { if (joystickActive) { const t = e.touches[0]; handleJoystickMove(t.clientX, t.clientY); e.preventDefault(); } }, { passive: false });
window.addEventListener('touchend', () => { if (joystickActive) handleJoystickEnd(); });
// ⏳ FIXED LOADING MECHANIC
function startLoading() {
const progressBar = document.getElementById('progress-bar');
const progressText = document.getElementById('progress-text');
const loadingScreen = document.getElementById('loading-screen');
let startTime = Date.now();
const durationPhase1 = 2000; // 2 Sekunden bis 99%
function animateLoading() {
let elapsed = Date.now() - startTime;
if (elapsed < durationPhase1) {
// Phase 1: Von 0% auf 99% hochzählen innerhalb von 2 Sek.
let percent = (elapsed / durationPhase1) * 99;
let displayPercent = Math.floor(percent);
progressBar.style.width = displayPercent + '%';
progressText.textContent = Lade Spieldaten... ${displayPercent}%;
requestAnimationFrame(animateLoading);
} else {
// Phase 2: Fest bei 99% verweilen
progressBar.style.width = '99%';
progressText.textContent = Verifiziere Texturen... 99%;
// Exakt nach 2 weiteren Sekunden (4000ms Gesamtladezeit) das Spiel starten
setTimeout(() => {
progressBar.style.width = '100%';
progressText.textContent = 'Fertig! 100%';
setTimeout(() => {
loadingScreen.style.opacity = '0';
setTimeout(() => {
loadingScreen.style.display = 'none';
// Initialisiere die Engine sicher nach dem Laden
generateBrawlerGrid();
resetGame();
gameLoop();
}, 500);
}, 200);
}, 2000);
}
}
requestAnimationFrame(animateLoading);
}
function spawnParticles(x, y, color, count = 8, speedFactor = 1, sizeMin = 2, sizeMax = 5) {
for (let i = 0; i < count; i++) {
let angle = Math.random() * Math.PI * 2;
let speed = (Math.random() * 3 + 1) * speedFactor;
particles.push({
x: x, y: y, vx: Math.cos(angle) * speed, vy: Math.sin(angle) * speed,
radius: Math.random() * (sizeMax - sizeMin) + sizeMin, alpha: 1,
decay: Math.random() * 0.02 + 0.015, color: color
});
}
}
const WORLD_CONFIGS = {
cyber: { floorBg: '#09210e', stripeColor: 'rgba(0, 0, 0, 0.15)', gridColor: 'rgba(0, 255, 150, 0.03)', obsShadow: 'rgba(2, 2, 8, 0.6)', obsGradStart: '#2e2e42', obsGradEnd: '#12121c', obsBorder: '#3f3f61', lakeGradStart: '#0088ff', lakeGradEnd: '#001133', lakeBorder: 'rgba(0, 180, 255, 0.3)', lakeGlow: 'rgba(0, 120, 255, 0.4)', wallImpactColor: '#00ffff' },
desert: { floorBg: '#cf9e56', stripeColor: 'rgba(150, 90, 40, 0.18)', gridColor: 'rgba(255, 255, 255, 0.05)', obsShadow: 'rgba(40, 20, 5, 0.5)', obsGradStart: '#b57031', obsGradEnd: '#6e3e15', obsBorder: '#8a4f1d', lakeGradStart: '#1be0cd', lakeGradEnd: '#005e54', lakeBorder: 'rgba(50, 255, 220, 0.3)', lakeGlow: 'rgba(27, 224, 205, 0.3)', wallImpactColor: '#ffe4b5' },
lava: { floorBg: '#140808', stripeColor: 'rgba(0, 0, 0, 0.4)', gridColor: 'rgba(255, 50, 0, 0.04)', obsShadow: 'rgba(0, 0, 0, 0.8)', obsGradStart: '#363636', obsGradEnd: '#141414', obsBorder: '#e63900', lakeGradStart: '#ff3700', lakeGradEnd: '#520000', lakeBorder: 'rgba(255, 100, 0, 0.5)', lakeGlow: 'rgba(255, 50, 0, 0.5)', wallImpactColor: '#ff5500' }
};
const BRAWLER_CONFIGS = {
shelly: { name: 'Shelly', color: '#b050cf', glowColor: '#da70d6', maxHealth: 4500, speed: 4.0, weapon: { type: 'shotgun', damage: 350, cooldown: 35, bulletSpeed: 11, range: 260, count: 5, spread: 0.35, color: '#ffaa00' } },
colt: { name: 'Colt', color: '#e6b800', glowColor: '#FFD700', maxHealth: 3500, speed: 4.5, weapon: { type: 'bullet', damage: 400, cooldown: 12, bulletSpeed: 14, range: 400, count: 2, color: '#00ffff' } },
piper: { name: 'Piper', color: '#0099ff', glowColor: '#00bfff', maxHealth: 2800, speed: 3.8, weapon: { type: 'sniper', damage: 1600, cooldown: 55, bulletSpeed: 22, range: 620, count: 1, color: '#ffffff' } },
brock: { name: 'Brock', color: '#cc4400', glowColor: '#ff5500', maxHealth: 3200, speed: 4.1, weapon: { type: 'sniper', damage: 1200, cooldown: 45, bulletSpeed: 15, range: 500, count: 1, color: '#ff3300' } },
el_primo: { name: 'Primo', color: '#3355cc', glowColor: '#4d79ff', maxHealth: 6000, speed: 4.3, weapon: { type: 'shotgun', damage: 450, cooldown: 25, bulletSpeed: 12, range: 140, count: 3, spread: 0.2, color: '#ffcc00' } },
poco: { name: 'Poco', color: '#22aa22', glowColor: '#33cc33', maxHealth: 4000, speed: 4.0, weapon: { type: 'shotgun', damage: 700, cooldown: 30, bulletSpeed: 10, range: 300, count: 4, spread: 0.5, color: '#00ff88' } },
spike: { name: 'Spike', color: '#00cc00', glowColor: '#00ff00', maxHealth: 3000, speed: 3.9, weapon: { type: 'shotgun', damage: 400, cooldown: 38, bulletSpeed: 9, range: 320, count: 6, spread: 0.4, color: '#bbff33' } },
crow: { name: 'Crow', color: '#2b2b3d', glowColor: '#5c5c8a', maxHealth: 3000, speed: 4.7, weapon: { type: 'shotgun', damage: 320, cooldown: 18, bulletSpeed: 16, range: 380, count: 3, spread: 0.25, color: '#cc00ff' } },
bo: { name: 'Bo', color: '#80552c', glowColor: '#996633', maxHealth: 4200, speed: 4.0, weapon: { type: 'bullet', damage: 550, cooldown: 32, bulletSpeed: 13, range: 420, count: 3, color: '#ff9900' } },
bull: { name: 'Bull', color: '#111111', glowColor: '#333333', maxHealth: 5500, speed: 4.1, weapon: { type: 'shotgun', damage: 420, cooldown: 33, bulletSpeed: 11, range: 180, count: 5, spread: 0.4, color: '#ff5500' } },
barley: { name: 'Barley', color: '#e649a3', glowColor: '#ff66cc', maxHealth: 3200, speed: 3.9, weapon: { type: 'shotgun', damage: 650, cooldown: 35, bulletSpeed: 8, range: 360, count: 3, spread: 0.3, color: '#ff00aa' } },
rico: { name: 'Rico', color: '#00ccaa', glowColor: '#00ffcc', maxHealth: 3400, speed: 4.2, weapon: { type: 'bullet', damage: 380, cooldown: 15, bulletSpeed: 15, range: 450, count: 2, color: '#00ffcc' } },
darryl: { name: 'Darryl', color: '#4d2600', glowColor: '#663300', maxHealth: 5000, speed: 4.2, weapon: { type: 'shotgun', damage: 300, cooldown: 28, bulletSpeed: 12, range: 200, count: 6, spread: 0.35, color: '#e6b800' } },
penny: { name: 'Penny', color: '#802b80', glowColor: '#993399', maxHealth: 3600, speed: 4.0, weapon: { type: 'sniper', damage: 1000, cooldown: 40, bulletSpeed: 14, range: 440, count: 1, color: '#ffccff' } },
tara: { name: 'Tara', color: '#4d004d', glowColor: '#660066', maxHealth: 3800, speed: 4.1, weapon: { type: 'shotgun', damage: 400, cooldown: 34, bulletSpeed: 12, range: 350, count: 3, spread: 0.2, color: '#ff0066' } },
mortis: { name: 'Mortis', color: '#381e38', glowColor: '#4a274a', maxHealth: 4400, speed: 4.6, weapon: { type: 'shotgun', damage: 900, cooldown: 42, bulletSpeed: 18, range: 120, count: 1, spread: 0.1, color: '#9933ff' } },
gene: { name: 'Gene', color: '#e62e5c', glowColor: '#ff3366', maxHealth: 4100, speed: 4.0, weapon: { type: 'shotgun', damage: 800, cooldown: 32, bulletSpeed: 11, range: 340, count: 4, spread: 0.6, color: '#ff3399' } },
leon: { name: 'Leon', color: '#008040', glowColor: '#00994d', maxHealth: 3600, speed: 4.6, weapon: { type: 'shotgun', damage: 340, cooldown: 18, bulletSpeed: 15, range: 330, count: 4, spread: 0.15, color: '#33cc33' } },
sandy: { name: 'Sandy', color: '#b359e6', glowColor: '#cc66ff', maxHealth: 4300, speed: 4.2, weapon: { type: 'shotgun', damage: 750, cooldown: 26, bulletSpeed: 11, range: 280, count: 3, spread: 0.45, color: '#cc99ff' } },
amber: { name: 'Amber', color: '#e62e00', glowColor: '#ff3300', maxHealth: 3800, speed: 4.3, weapon: { type: 'bullet', damage: 180, cooldown: 5, bulletSpeed: 13, range: 350, count: 1, color: '#ffcc00' } },
gale: { name: 'Gale', color: '#8cbfe6', glowColor: '#99ccff', maxHealth: 4000, speed: 4.0, weapon: { type: 'shotgun', damage: 360, cooldown: 24, bulletSpeed: 12, range: 360, count: 5, spread: 0.25, color: '#99e6ff' } },
colette: { name: 'Colette', color: '#e65c8a', glowColor: '#ff6699', maxHealth: 3900, speed: 4.1, weapon: { type: 'sniper', damage: 1100, cooldown: 36, bulletSpeed: 16, range: 480, count: 1, color: '#ff66cc' } },
surge: { name: 'Surge', color: '#b30000', glowColor: '#cc0000', maxHealth: 4200, speed: 3.9, weapon: { type: 'bullet', damage: 1150, cooldown: 40, bulletSpeed: 13, range: 410, count: 1, color: '#ff3333' } },
lou: { name: 'Lou', color: '#2a85e6', glowColor: '#3399ff', maxHealth: 3700, speed: 4.1, weapon: { type: 'bullet', damage: 420, cooldown: 14, bulletSpeed: 14, range: 430, count: 3, color: '#66ffff' } }
};
const player = {
x: 80, y: 384, radius: 18, speed: 4, health: 3500, maxHealth: 3500, color: '#FFD700', glowColor: '', weapon: null,
init(type) {
const config = BRAWLER_CONFIGS[type];
this.color = config.color; this.glowColor = config.glowColor;
this.maxHealth = config.maxHealth; this.health = config.maxHealth;
this.speed = config.speed; this.weapon = config.weapon;
},
draw() {
ctx.save();
let bloom = ctx.createRadialGradient(this.x, this.y, this.radius, this.x, this.y, this.radius * 2);
bloom.addColorStop(0, this.glowColor + "44"); bloom.addColorStop(1, "rgba(0,0,0,0)");
ctx.fillStyle = bloom; ctx.beginPath(); ctx.arc(this.x, this.y, this.radius * 2, 0, Math.PI * 2); ctx.fill();
let gradient = ctx.createRadialGradient(this.x - 5, this.y - 5, 0, this.x, this.y, this.radius);
gradient.addColorStop(0, '#ffffff'); gradient.addColorStop(0.2, this.color); gradient.addColorStop(0.9, '#03030d'); gradient.addColorStop(1, '#000000');
ctx.beginPath(); ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2); ctx.fillStyle = gradient; ctx.fill();
ctx.lineWidth = 1.5; ctx.strokeStyle = 'rgba(255, 255, 255, 0.4)'; ctx.stroke(); ctx.restore();
this.drawHealthBar();
},
drawHealthBar() {
const barW = 56; const barH = 7; const bx = this.x - barW / 2; const by = this.y - this.radius - 16;
ctx.fillStyle = 'rgba(2, 2, 8, 0.8)'; ctx.beginPath(); ctx.roundRect(bx - 2, by - 2, barW + 4, barH + 4, 5); ctx.fill();
ctx.fillStyle = '#11111a'; ctx.beginPath(); ctx.roundRect(bx, by, barW, barH, 4); ctx.fill();
const currentW = (this.health / this.maxHealth) * barW;
if (currentW > 0) {
let hpGradient = ctx.createLinearGradient(bx, by, bx + barW, by);
hpGradient.addColorStop(0, '#00ffc4'); hpGradient.addColorStop(1, '#00b33c');
ctx.fillStyle = hpGradient; ctx.beginPath(); ctx.roundRect(bx, by, currentW, barH, 4); ctx.fill();
}
},
getRect() { return { x: this.x - this.radius, y: this.y - this.radius, w: this.radius * 2, h: this.radius * 2 }; }
};
class Enemy {
constructor(x, y, type) {
this.x = x; this.y = y; this.radius = 18; this.type = type;
const config = BRAWLER_CONFIGS[type];
this.speed = config.speed * 0.55; this.health = config.maxHealth * 0.8; this.maxHealth = config.maxHealth * 0.8;
this.weapon = config.weapon; this.shootCooldown = Math.floor(Math.random() * 40);
}
draw() {
ctx.save();
let bloom = ctx.createRadialGradient(this.x, this.y, this.radius, this.x, this.y, this.radius * 1.8);
bloom.addColorStop(0, "rgba(255, 0, 85, 0.25)"); bloom.addColorStop(1, "rgba(0,0,0,0)");
ctx.fillStyle = bloom; ctx.beginPath(); ctx.arc(this.x, this.y, this.radius * 1.8, 0, Math.PI * 2); ctx.fill();
let gradient = ctx.createRadialGradient(this.x - 4, this.y - 4, 0, this.x, this.y, this.radius);
gradient.addColorStop(0, '#ffccd5'); gradient.addColorStop(0.3, '#d60042'); gradient.addColorStop(0.9, '#1a0006'); gradient.addColorStop(1, '#000000');
ctx.beginPath(); ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2); ctx.fillStyle = gradient; ctx.fill();
ctx.lineWidth = 1.5; ctx.strokeStyle = 'rgba(255, 77, 121, 0.4)'; ctx.stroke();
ctx.beginPath(); ctx.arc(this.x, this.y, 5, 0, Math.PI * 2); ctx.fillStyle = BRAWLER_CONFIGS[this.type].color; ctx.fill(); ctx.restore();
this.drawHealthBar();
}
drawHealthBar() {
const barW = 50; const barH = 6; const bx = this.x - barW / 2; const by = this.y - this.radius - 16;
ctx.fillStyle = 'rgba(2, 2, 8, 0.8)'; ctx.beginPath(); ctx.roundRect(bx - 2, by - 2, barW + 4, barH + 4, 5); ctx.fill();
const currentW = (this.health / this.maxHealth) * barW;
if (currentW > 0) {
let enemyHpGrad = ctx.createLinearGradient(bx, by, bx + barW, by);
enemyHpGrad.addColorStop(0, '#ff4d66'); enemyHpGrad.addColorStop(1, '#99001b');
ctx.fillStyle = enemyHpGrad; ctx.beginPath(); ctx.roundRect(bx, by, currentW, barH, 4); ctx.fill();
}
}
getRect() { return { x: this.x - this.radius, y: this.y - this.radius, w: this.radius * 2, h: this.radius * 2 }; }
update() {
let dx = player.x - this.x; let dy = player.y - this.y; let dist = Math.hypot(dx, dy);
if (dist > 0) {
dx /= dist; dy /= dist;
this.x += dx * this.speed; if (checkCollisionWithMap(this.getRect())) this.x -= dx * this.speed;
this.y += dy * this.speed; if (checkCollisionWithMap(this.getRect())) this.y -= dy * this.speed;
}
if (this.shootCooldown > 0) this.shootCooldown--;
if (this.shootCooldown === 0 && dist < this.weapon.range && Math.random() < 0.025) {
fireWeapon(this.x, this.y, player.x, player.y, true, this.weapon);
this.shootCooldown = this.weapon.cooldown * 1.5;
}
}
}
class Bullet {
constructor(x, y, angle, speed, damage, maxRange, isEnemy = false, size = 6, color = '#ffffff') {
this.startX = x; this.startY = y; this.x = x; this.y = y; this.radius = size;
this.isEnemy = isEnemy; this.damage = damage; this.maxRange = maxRange; this.color = color;
this.dx = Math.cos(angle) * speed; this.dy = Math.sin(angle) * speed;
}
draw() {
ctx.save(); ctx.shadowBlur = 18; ctx.shadowColor = this.isEnemy ? '#ff1100' : this.color;
let bulletGrad = ctx.createRadialGradient(this.x, this.y, 0, this.x, this.y, this.radius);
bulletGrad.addColorStop(0, '#ffffff'); bulletGrad.addColorStop(0.3, this.isEnemy ? '#ff3c00' : this.color); bulletGrad.addColorStop(1, 'rgba(0,0,0,0)');
ctx.beginPath(); ctx.arc(this.x, this.y, this.radius * 1.5, 0, Math.PI * 2); ctx.fillStyle = bulletGrad; ctx.fill(); ctx.restore();
}
update() {
this.x += this.dx; this.y += this.dy;
if (Math.random() < 0.5) {
particles.push({
x: this.x, y: this.y, vx: -this.dx * 0.1, vy: -this.dy * 0.1, radius: this.radius * 0.4, alpha: 0.5, decay: 0.08,
color: this.isEnemy ? '#ff2200' : this.color
});
}
}
isOutOfRange() { return Math.hypot(this.x - this.startX, this.y - this.startY) > this.maxRange; }
getRect() { return { x: this.x - this.radius, y: this.y - this.radius, w: this.radius * 2, h: this.radius * 2 }; }
}
const obstacles = [
{ x: 120, y: 100, w: 100, h: 60 }, { x: 804, y: 100, w: 100, h: 60 }, { x: 120, y: 608, w: 100, h: 60 }, { x: 804, y: 608, w: 100, h: 60 },
{ x: 260, y: 220, w: 50, h: 140 }, { x: 714, y: 220, w: 50, h: 140 }, { x: 260, y: 408, w: 50, h: 140 }, { x: 714, y: 408, w: 50, h: 140 },
{ x: 412, y: 220, w: 200, h: 45 }, { x: 412, y: 503, w: 200, h: 45 }, { x: 487, y: 40, w: 50, h: 50 }, { x: 487, y: 678, w: 50, h: 50 },
{ x: 80, y: 220, w: 60, h: 50 }, { x: 80, y: 498, w: 60, h: 50 }, { x: 884, y: 220, w: 60, h: 50 }, { x: 884, y: 498, w: 60, h: 50 }
];
const lakes = [
{ x: 412, y: 110, w: 200, h: 70 }, { x: 412, y: 588, w: 200, h: 70 }, { x: 472, y: 344, w: 80, h: 80 }
];
let enemies = []; let bullets = [];
function rectCollide(r1, r2) { return r1.x < r2.x + r2.w && r1.x + r1.w > r2.x && r1.y < r2.y + r2.h && r1.y + r1.h > r2.y; }
function checkCollisionWithMap(rect) {
for (let obs of obstacles) if (rectCollide(rect, obs)) return true;
for (let lake of lakes) if (rectCollide(rect, lake)) return true;
return false;
}
function fireWeapon(fromX, fromY, toX, toY, isEnemy, weapon) {
let baseAngle = Math.atan2(toY - fromY, toX - fromX);
if (weapon.type === 'shotgun') {
const startAngle = baseAngle - (weapon.spread / 2); const angleStep = weapon.spread / (weapon.count - 1);
for (let i = 0; i < weapon.count; i++) {
let angle = startAngle + (angleStep * i);
bullets.push(new Bullet(fromX, fromY, angle, weapon.bulletSpeed, weapon.damage, weapon.range, isEnemy, 5, weapon.color));
}
} else if (weapon.type === 'bullet') {
bullets.push(new Bullet(fromX, fromY, baseAngle, weapon.bulletSpeed, weapon.damage, weapon.range, isEnemy, 6, weapon.color));
if(weapon.count > 1) {
setTimeout(() => {
if (!gameOver && (!isEnemy || enemies.some(e => e.x === fromX && e.y === fromY))) {
bullets.push(new Bullet(fromX, fromY, baseAngle, weapon.bulletSpeed, weapon.damage, weapon.range, isEnemy, 6, weapon.color));
}
}, 80);
}
} else if (weapon.type === 'sniper') {
bullets.push(new Bullet(fromX, fromY, baseAngle, weapon.bulletSpeed, weapon.damage, weapon.range, isEnemy, 8, weapon.color));
}
}
function generateBrawlerGrid() {
const grid = document.getElementById('brawler-select');
grid.innerHTML = '';
Object.keys(BRAWLER_CONFIGS).forEach(key => {
const btn = document.createElement('button');
btn.textContent = BRAWLER_CONFIGS[key].name; btn.id = btn-${key};
if(key === currentBrawlerType) btn.classList.add('active');
btn.onclick = () => selectBrawler(key);
grid.appendChild(btn);
});
}
function selectBrawler(type) {
if (gameOver) return;
currentBrawlerType = type;
document.querySelectorAll('#brawler-select button').forEach(btn => btn.classList.remove('active'));
document.getElementById(btn-${type}).classList.add('active');
player.init(type);
}
function selectWorld(worldName) {
currentWorldType = worldName;
document.querySelectorAll('#world-select button').forEach(btn => btn.classList.remove('active'));
event.target.classList.add('active');
}
function resetGame() {
player.x = 80; player.y = 384; player.init(currentBrawlerType);
const brawlerKeys = Object.keys(BRAWLER_CONFIGS);
const spawnPoints = [{x: 930, y: 160}, {x: 930, y: 608}, {x: 840, y: 384}, {x: 650, y: 384}];
enemies = spawnPoints.map(pt => new Enemy(pt.x, pt.y, brawlerKeys[Math.floor(Math.random() * brawlerKeys.length)]));
bullets = []; particles = []; gameOver = false; won = false;
}
window.addEventListener('keydown', e => {
keys[e.key.toLowerCase()] = true;
if (gameOver && e.key.toLowerCase() === 'r') resetGame();
if (!gameOver && e.key === ' ' && player_shoot_cooldown === 0) triggerShoot();
});
window.addEventListener('keyup', e => keys[e.key.toLowerCase()] = false);
canvas.addEventListener('mousedown', e => {
if (gameOver || player_shoot_cooldown > 0) return;
const rect = canvas.getBoundingClientRect();
fireWeapon(player.x, player.y, e.clientX - rect.left, e.clientY - rect.top, false, player.weapon);
player_shoot_cooldown = player.weapon.cooldown;
});
function drawArenaBackground(cfg) {
ctx.fillStyle = cfg.floorBg; ctx.fillRect(0, 0, canvas.width, canvas.height);
let light = ctx.createRadialGradient(canvas.width/2, canvas.height/2, canvas.width/3, canvas.width/2, canvas.height/2, canvas.width*0.6);
light.addColorStop(0, 'rgba(0,0,0,0)'); light.addColorStop(1, 'rgba(0,0,0,0.65)');
ctx.strokeStyle = cfg.gridColor; ctx.lineWidth = 1;
for (let x = 0; x < canvas.width; x += 32) { ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, canvas.height); ctx.stroke(); }
for (let y = 0; y < canvas.height; y += 32) { ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(canvas.width, y); ctx.stroke(); }
ctx.fillStyle = cfg.stripeColor;
for (let i = 0; i < canvas.width; i += 128) { if((i / 128) % 2 === 0) ctx.fillRect(i, 0, 64, canvas.height); }
ctx.fillStyle = light; ctx.fillRect(0, 0, canvas.width, canvas.height);
}
function update() {
for (let i = particles.length - 1; i >= 0; i--) {
let p = particles[i]; p.x += p.vx; p.y += p.vy; p.alpha -= p.decay;
if (p.alpha <= 0) particles.splice(i, 1);
}
if (gameOver) return;
let dx = 0, dy = 0;
if (joystickActive) { dx = joystickMoveX; dy = joystickMoveY; }
else {
if (keys['a'] || keys['arrowleft']) dx = -1;
if (keys['d'] || keys['arrowright']) dx = 1;
if (keys['w'] || keys['arrowup']) dy = -1;
if (keys['s'] || keys['arrowdown']) dy = 1;
if (dx !== 0 && dy !== 0) { dx *= 0.7071; dy *= 0.7071; }
}
if (dx !== 0 || dy !== 0) {
player.x += dx * player.speed; if (checkCollisionWithMap(player.getRect())) player.x -= dx * player.speed;
player.y += dy * player.speed; if (checkCollisionWithMap(player.getRect())) player.y -= dy * player.speed;
player.x = Math.max(player.radius, Math.min(canvas.width - player.radius, player.x));
player.y = Math.max(player.radius, Math.min(canvas.height - player.radius, player.y));
}
if (player_shoot_cooldown > 0) player_shoot_cooldown--;
enemies.forEach(enemy => enemy.update());
const currentCfg = WORLD_CONFIGS[currentWorldType];
for (let i = bullets.length - 1; i >= 0; i--) {
let b = bullets[i]; b.update(); let bRect = b.getRect();
if (b.x < 0 || b.x > canvas.width || b.y < 0 || b.y > canvas.height || b.isOutOfRange()) { bullets.splice(i, 1); continue; }
let hit = false;
for (let obs of obstacles) { if (rectCollide(bRect, obs)) { spawnParticles(b.x, b.y, currentCfg.wallImpactColor, 8, 0.8, 2, 4); bullets.splice(i, 1); hit = true; break; } }
if (hit) continue;
if (b.isEnemy) {
if (rectCollide(bRect, player.getRect())) {
player.health -= b.damage; spawnParticles(player.x, player.y, '#ff003c', 16, 1.8, 2, 5); bullets.splice(i, 1);
if (player.health <= 0) { gameOver = true; won = false; }
}
} else {
for (let j = enemies.length - 1; j >= 0; j--) {
let enemy = enemies[j];
if (rectCollide(bRect, enemy.getRect())) {
enemy.health -= b.damage; spawnParticles(enemy.x, enemy.y, '#00ffff', 16, 1.8, 2, 5); bullets.splice(i, 1);
if (enemy.health <= 0) { spawnParticles(enemy.x, enemy.y, '#ff0055', 35, 3.5, 3, 7); enemies.splice(j, 1); }
break;
}
}
}
}
if (enemies.length === 0) { gameOver = true; won = true; }
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
const cfg = WORLD_CONFIGS[currentWorldType];
drawArenaBackground(cfg);
lakes.forEach(lake => {
ctx.save(); ctx.shadowBlur = 15; ctx.shadowColor = cfg.lakeGlow;
let lakeGrad = ctx.createLinearGradient(lake.x, lake.y, lake.x + lake.w, lake.y + lake.h);
lakeGrad.addColorStop(0, cfg.lakeGradStart); lakeGrad.addColorStop(1, cfg.lakeGradEnd);
ctx.fillStyle = lakeGrad; ctx.beginPath(); ctx.roundRect(lake.x, lake.y, lake.w, lake.h, 16); ctx.fill();
ctx.strokeStyle = cfg.lakeBorder; ctx.lineWidth = 1.5; ctx.stroke(); ctx.restore();
});
obstacles.forEach(obs => {
ctx.save(); ctx.fillStyle = cfg.obsShadow; ctx.beginPath(); ctx.roundRect(obs.x - 5, obs.y + 8, obs.w, obs.h, 10); ctx.fill();
let obsGrad = ctx.createLinearGradient(obs.x, obs.y, obs.x, obs.y + obs.h);
obsGrad.addColorStop(0, cfg.obsGradStart); obsGrad.addColorStop(1, cfg.obsGradEnd);
ctx.fillStyle = obsGrad; ctx.beginPath(); ctx.roundRect(obs.x, obs.y, obs.w, obs.h, 8); ctx.fill();
ctx.strokeStyle = cfg.obsBorder; ctx.lineWidth = 2; ctx.stroke(); ctx.restore();
});
bullets.forEach(b => b.draw());
if (player.health > 0) player.draw();
enemies.forEach(enemy => enemy.update() || enemy.draw());
ctx.save(); ctx.globalCompositeOperation = 'screen';
particles.forEach(p => { ctx.save(); ctx.globalAlpha = p.alpha; ctx.fillStyle = p.color; ctx.beginPath(); ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2); ctx.fill(); ctx.restore(); });
ctx.restore();
if (gameOver) {
ctx.fillStyle = 'rgba(3, 3, 10, 0.92)'; ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.save(); ctx.shadowBlur = 50; ctx.textAlign = 'center';
if (won) {
ctx.shadowColor = '#00ffff'; ctx.font = '900 80px system-ui';
let winGrad = ctx.createLinearGradient(0, canvas.height/2 - 60, 0, canvas.height/2 + 10); winGrad.addColorStop(0, '#ffffff'); winGrad.addColorStop(1, '#00ffff');
ctx.fillStyle = winGrad; ctx.fillText('SIEG!', canvas.width / 2, canvas.height / 2 - 10);
} else {
ctx.shadowColor = '#ff0055'; ctx.font = '900 80px system-ui';
let loseGrad = ctx.createLinearGradient(0, canvas.height/2 - 60, 0, canvas.height/2 + 10); loseGrad.addColorStop(0, '#ffffff'); loseGrad.addColorStop(1, '#ff0055');
ctx.fillStyle = loseGrad; ctx.fillText('BESIEGT!', canvas.width / 2, canvas.height / 2 - 10);
}
ctx.restore(); ctx.font = '700 20px system-ui'; ctx.fillStyle = '#5c5c7a';
ctx.fillText('DRÜCKE "R" ODER DIE LEERTASTE FÜR NEUSTART', canvas.width / 2, canvas.height / 2 + 55);
}
}
function gameLoop() { update(); draw(); requestAnimationFrame(gameLoop); }
// Ladevorgang starten
startLoading();
Möchtest du als Nächstes ein **Super-Fähigkeit (Ulti)** System hinzufügen oder ein **Münzen/Sterne-Sammel-System** starten?
Game Source: Battle Stars - Fixed Loading Edition
Creator: AtomicFox16
Libraries: none
Complexity: complex (689 lines, 34.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: battle-stars-fixed-loading-edition-atomicfox16-mu1be3jz" to link back to the original. Then publish at arcadelab.ai/publish.