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() { // Alles löschen ctx.clearRect(0, 0, GAME_WIDTH, GAME_HEIGHT); // Background if (bgSprite.complete && bgSprite.naturalHeight !== 0) { // Hintergrundbild exakt auf 800x400 skalieren ctx.drawImage(bgSprite, 0, 0, GAME_WIDTH, GAME_HEIGHT); } else { // Fallback Farbe ctx.fillStyle = "#f0f0f0"; ctx.fillRect(0, 0, GAME_WIDTH, GAME_HEIGHT); } // Boden 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]; if (img) { ctx.drawImage(img, obs.x, obs.y, obs.def.width, obs.def.height); } else { ctx.fillStyle = obs.def.color; 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) ctx.strokeStyle = isGameOver ? "red" : "lime"; ctx.lineWidth = 2; serverObstacles.forEach(srvObs => { ctx.strokeRect(srvObs.x, srvObs.y, srvObs.w, srvObs.h); }); // Spieler 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); } // Game Over Overlay (Dunkelheit) if (isGameOver) { ctx.fillStyle = "rgba(0,0,0,0.7)"; ctx.fillRect(0,0,GAME_WIDTH, GAME_HEIGHT); } } 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); }