185 lines
5.4 KiB
JavaScript
185 lines
5.4 KiB
JavaScript
// ==========================================
|
|
// RESIZE LOGIK (LETTERBOXING)
|
|
// ==========================================
|
|
function resize() {
|
|
canvas.width = GAME_WIDTH; // 800
|
|
canvas.height = GAME_HEIGHT; // 400
|
|
|
|
// 2. Verfügbaren Platz berechnen
|
|
const windowWidth = window.innerWidth - 20;
|
|
const windowHeight = window.innerHeight - 20;
|
|
|
|
const targetRatio = GAME_WIDTH / GAME_HEIGHT;
|
|
const windowRatio = windowWidth / windowHeight;
|
|
|
|
let finalWidth, finalHeight;
|
|
|
|
// 3. Skalierung berechnen (Aspect Ratio erhalten)
|
|
if (windowRatio < targetRatio) {
|
|
finalWidth = windowWidth;
|
|
finalHeight = windowWidth / targetRatio;
|
|
} else {
|
|
finalHeight = windowHeight;
|
|
finalWidth = finalHeight * targetRatio;
|
|
}
|
|
|
|
// 4. Container Größe setzen (Canvas füllt Container via CSS)
|
|
if (container) {
|
|
container.style.width = `${Math.floor(finalWidth)}px`;
|
|
container.style.height = `${Math.floor(finalHeight)}px`;
|
|
}
|
|
}
|
|
|
|
window.addEventListener('resize', resize);
|
|
resize();
|
|
|
|
|
|
|
|
function drawGame(alpha = 1.0) {
|
|
|
|
ctx.clearRect(0, 0, GAME_WIDTH, GAME_HEIGHT);
|
|
|
|
let currentBg = null;
|
|
if (bgSprites.length > 0) {
|
|
const changeInterval = 10000;
|
|
const currentRawIndex = Math.floor(score / changeInterval);
|
|
if (currentRawIndex > maxRawBgIndex) maxRawBgIndex = currentRawIndex;
|
|
const bgIndex = maxRawBgIndex % bgSprites.length;
|
|
currentBg = bgSprites[bgIndex];
|
|
}
|
|
|
|
if (currentBg && currentBg.complete && currentBg.naturalHeight !== 0) {
|
|
ctx.drawImage(currentBg, 0, 0, GAME_WIDTH, GAME_HEIGHT);
|
|
} else {
|
|
ctx.fillStyle = "#f0f0f0";
|
|
ctx.fillRect(0, 0, GAME_WIDTH, GAME_HEIGHT);
|
|
}
|
|
|
|
|
|
ctx.fillStyle = "rgba(60, 60, 60, 0.8)";
|
|
ctx.fillRect(0, GROUND_Y, GAME_WIDTH, 50);
|
|
|
|
|
|
platforms.forEach(p => {
|
|
|
|
const rX = (p.prevX !== undefined) ? lerp(p.prevX, p.x, alpha) : p.x;
|
|
const rY = p.y;
|
|
|
|
|
|
ctx.fillStyle = "#5D4037";
|
|
ctx.fillRect(rX, rY, p.w, p.h);
|
|
ctx.fillStyle = "#8D6E63";
|
|
ctx.fillRect(rX, rY, p.w, 5);
|
|
});
|
|
|
|
|
|
obstacles.forEach(obs => {
|
|
const def = obs.def || {};
|
|
const img = sprites[def.id];
|
|
|
|
const rX = (obs.prevX !== undefined) ? lerp(obs.prevX, obs.x, alpha) : obs.x;
|
|
const rY = obs.y;
|
|
|
|
const hbw = def.width || obs.w || 30;
|
|
const hbh = def.height || obs.h || 30;
|
|
|
|
if (img && img.complete && img.naturalHeight !== 0) {
|
|
|
|
const scale = def.imgScale || 1.0;
|
|
const offX = def.imgOffsetX || 0.0;
|
|
const offY = def.imgOffsetY || 0.0;
|
|
|
|
const drawW = hbw * scale;
|
|
const drawH = hbh * scale;
|
|
|
|
const baseX = rX + (hbw - drawW) / 2;
|
|
const baseY = rY + (hbh - drawH);
|
|
|
|
ctx.drawImage(img, baseX + offX, baseY + offY, drawW, drawH);
|
|
|
|
} else {
|
|
let color = "#FF00FF";
|
|
if (def.type === "coin") color = "gold";
|
|
else if (def.color) color = def.color;
|
|
|
|
ctx.fillStyle = color;
|
|
ctx.fillRect(rX, rY, hbw, hbh);
|
|
|
|
ctx.strokeStyle = "rgba(255,255,255,0.5)"; ctx.lineWidth = 2;
|
|
ctx.strokeRect(rX, rY, hbw, hbh);
|
|
ctx.fillStyle = "white"; ctx.font = "bold 10px monospace";
|
|
ctx.fillText(def.id || "?", rX, rY - 5);
|
|
}
|
|
|
|
if (typeof DEBUG_SYNC !== 'undefined' && DEBUG_SYNC) {
|
|
ctx.strokeStyle = "rgba(0,255,0,0.5)"; // Grün transparent
|
|
ctx.lineWidth = 1;
|
|
ctx.strokeRect(rX, rY, hbw, hbh);
|
|
}
|
|
|
|
if(obs.speech) drawSpeechBubble(rX, rY, obs.speech);
|
|
});
|
|
|
|
|
|
if (typeof DEBUG_SYNC !== 'undefined' && DEBUG_SYNC) {
|
|
if (serverObstacles) {
|
|
ctx.strokeStyle = "cyan";
|
|
ctx.lineWidth = 1;
|
|
serverObstacles.forEach(sObj => {
|
|
ctx.strokeRect(sObj.x, sObj.y, sObj.w, sObj.h);
|
|
});
|
|
}
|
|
}
|
|
|
|
|
|
let rPlayerY = lerp(player.prevY !== undefined ? player.prevY : player.y, player.y, alpha);
|
|
|
|
|
|
const drawY = isCrouching ? rPlayerY + 25 : rPlayerY;
|
|
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);
|
|
}
|
|
|
|
if (typeof drawParticles === 'function') {
|
|
drawParticles();
|
|
}
|
|
|
|
|
|
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`;
|
|
|
|
if(statusText !== "") {
|
|
ctx.fillText(statusText, 10, 40);
|
|
}
|
|
}
|
|
|
|
if (isGameOver) {
|
|
ctx.fillStyle = "rgba(0,0,0,0.7)";
|
|
ctx.fillRect(0,0,GAME_WIDTH, GAME_HEIGHT);
|
|
}
|
|
}
|
|
|
|
// Helper: Sprechblase zeichnen
|
|
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.strokeStyle = "black"; ctx.lineWidth = 1; ctx.strokeRect(bX, bY, bW, bH);
|
|
ctx.fillStyle = "black"; ctx.font = "10px Arial"; ctx.textAlign = "center";
|
|
ctx.fillText(text, bX + bW/2, bY + 20);
|
|
} |