function resize() { // 1. INTERNE SPIEL-AUFLÖSUNG ERZWINGEN // Das behebt den "Zoom/Nur Ecke sichtbar" Fehler canvas.width = GAME_WIDTH; // 800 canvas.height = GAME_HEIGHT; // 400 // 2. Verfügbaren Platz im Browser berechnen (Minus etwas Rand) const windowWidth = window.innerWidth - 20; const windowHeight = window.innerHeight - 20; const targetRatio = GAME_WIDTH / GAME_HEIGHT; // 2.0 const windowRatio = windowWidth / windowHeight; let finalWidth, finalHeight; // 3. Letterboxing berechnen if (windowRatio < targetRatio) { // Screen ist schmaler (z.B. Handy Portrait) -> Breite limitiert finalWidth = windowWidth; finalHeight = windowWidth / targetRatio; } else { // Screen ist breiter (z.B. Desktop) -> Höhe limitiert finalHeight = windowHeight; finalWidth = finalHeight * targetRatio; } // 4. Größe auf den CONTAINER anwenden if (container) { container.style.width = `${Math.floor(finalWidth)}px`; container.style.height = `${Math.floor(finalHeight)}px`; } // Hinweis: Wir setzen KEINE style.width/height auf das Canvas Element selbst. // Das Canvas erbt "width: 100%; height: 100%" vom CSS und füllt den Container. } // Event Listener window.addEventListener('resize', resize); // Einmal sofort ausführen resize(); // --- DRAWING --- function drawGame() { // 1. Alles löschen ctx.clearRect(0, 0, GAME_WIDTH, GAME_HEIGHT); // --- HINTERGRUND (Level-Wechsel) --- let currentBg = null; // Haben wir Hintergründe geladen? if (bgSprites.length > 0) { // Wechsel alle 2000 Punkte (Server-Score) = 200 Punkte (Anzeige) const changeInterval = 2000; // Berechne Index: 0-1999 -> 0, 2000-3999 -> 1, etc. // Das % (Modulo) sorgt dafür, dass es wieder von vorne anfängt, wenn die Bilder ausgehen const bgIndex = Math.floor(score / changeInterval) % bgSprites.length; currentBg = bgSprites[bgIndex]; } // Zeichnen (wenn Bild geladen und nicht kaputt) if (currentBg && currentBg.complete && currentBg.naturalHeight !== 0) { // Streckt das Bild exakt auf die Spielgröße (800x400) ctx.drawImage(currentBg, 0, 0, GAME_WIDTH, GAME_HEIGHT); } else { // Fallback: Hellgrau, falls Bild fehlt ctx.fillStyle = "#f0f0f0"; ctx.fillRect(0, 0, GAME_WIDTH, GAME_HEIGHT); } // --- BODEN --- // Halb-transparent, damit er über dem Hintergrund liegt ctx.fillStyle = "rgba(60, 60, 60, 0.8)"; ctx.fillRect(0, GROUND_Y, GAME_WIDTH, 50); // --- HINDERNISSE --- obstacles.forEach(obs => { const img = sprites[obs.def.id]; // Prüfen ob Bild geladen ist if (img && img.complete && img.naturalHeight !== 0) { ctx.drawImage(img, obs.x, obs.y, obs.def.width, obs.def.height); } else { // Fallback Farbe (Münzen Gold, Rest aus Config) if (obs.def.type === "coin") ctx.fillStyle = "gold"; else ctx.fillStyle = obs.def.color || "red"; ctx.fillRect(obs.x, obs.y, obs.def.width, obs.def.height); } if(obs.speech) drawSpeechBubble(obs.x, obs.y, obs.speech); }); /* // --- DEBUG RAHMEN (Server Hitboxen) --- // Grün im Spiel, Rot bei Tod ctx.strokeStyle = isGameOver ? "red" : "lime"; ctx.lineWidth = 2; serverObstacles.forEach(srvObs => { ctx.strokeRect(srvObs.x, srvObs.y, srvObs.w, srvObs.h); }); */ // --- SPIELER --- // Y-Position und Höhe anpassen für Ducken const drawY = isCrouching ? player.y + 25 : player.y; const drawH = isCrouching ? 25 : 50; if (playerSprite.complete && playerSprite.naturalHeight !== 0) { ctx.drawImage(playerSprite, player.x, drawY, player.w, drawH); } else { ctx.fillStyle = player.color; ctx.fillRect(player.x, drawY, player.w, drawH); } // --- HUD (Powerup Status oben links) --- if (isGameRunning && !isGameOver) { ctx.fillStyle = "black"; ctx.font = "bold 10px monospace"; ctx.textAlign = "left"; let statusText = ""; if(godModeLives > 0) statusText += `🛡️ x${godModeLives} `; if(hasBat) statusText += `⚾ BAT `; if(bootTicks > 0) statusText += `👟 ${(bootTicks/60).toFixed(1)}s`; // Drift Info (nur wenn Objekte da sind) if (obstacles.length > 0 && serverObstacles.length > 0) { const drift = Math.abs(obstacles[0].x - serverObstacles[0].x).toFixed(1); // statusText += ` | Drift: ${drift}px`; // Einkommentieren für Debugging } if(statusText !== "") { ctx.fillText(statusText, 10, 40); } } // --- GAME OVER OVERLAY --- if (isGameOver) { // Dunkler Schleier über alles ctx.fillStyle = "rgba(0,0,0,0.7)"; ctx.fillRect(0,0,GAME_WIDTH, GAME_HEIGHT); } } // Sprechblasen Helper function drawSpeechBubble(x, y, text) { const bX = x-20; const bY = y-40; const bW = 120; const bH = 30; ctx.fillStyle="white"; ctx.fillRect(bX,bY,bW,bH); ctx.strokeRect(bX,bY,bW,bH); ctx.fillStyle="black"; ctx.font="10px Arial"; ctx.textAlign="center"; ctx.fillText(text, bX+bW/2, bY+20); }