bug fixes
All checks were successful
Dynamic Branch Deploy / build-and-deploy (push) Successful in 2m25s
All checks were successful
Dynamic Branch Deploy / build-and-deploy (push) Successful in 2m25s
This commit is contained in:
@@ -42,13 +42,14 @@ func initGameConfig() {
|
|||||||
{ID: "desk", Type: "obstacle", Width: 40, Height: 30, Color: "#8B4513", Image: "desk1.png"},
|
{ID: "desk", Type: "obstacle", Width: 40, Height: 30, Color: "#8B4513", Image: "desk1.png"},
|
||||||
{ID: "teacher", Type: "teacher", Width: 30, Height: 60, Color: "#000080", Image: "teacher1.png", CanTalk: true, SpeechLines: []string{"Halt!", "Handy weg!"}},
|
{ID: "teacher", Type: "teacher", Width: 30, Height: 60, Color: "#000080", Image: "teacher1.png", CanTalk: true, SpeechLines: []string{"Halt!", "Handy weg!"}},
|
||||||
{ID: "trashcan", Type: "obstacle", Width: 25, Height: 35, Color: "#555", Image: "trash1.png"},
|
{ID: "trashcan", Type: "obstacle", Width: 25, Height: 35, Color: "#555", Image: "trash1.png"},
|
||||||
{ID: "eraser", Type: "obstacle", Width: 30, Height: 20, Color: "#fff", Image: "eraser1.png", YOffset: 45.0},
|
{ID: "eraser", Type: "obstacle", Width: 30, Height: 20, Color: "#fff", Image: "eraser1.png", YOffset: 35.0},
|
||||||
|
|
||||||
// --- BOSS OBJEKTE (Kommen häufiger im Bosskampf) ---
|
|
||||||
{ID: "principal", Type: "teacher", Width: 40, Height: 70, Color: "#000", Image: "principal1.png", CanTalk: true, SpeechLines: []string{"EXMATRIKULATION!"}},
|
{ID: "principal", Type: "teacher", Width: 40, Height: 70, Color: "#000", Image: "principal1.png", CanTalk: true, SpeechLines: []string{"EXMATRIKULATION!"}},
|
||||||
|
|
||||||
// --- COINS ---
|
// --- COINS ---
|
||||||
{ID: "coin", Type: "coin", Width: 20, Height: 20, Color: "gold", Image: "coin1.png", YOffset: 20.0},
|
{ID: "coin0", Type: "coin", Width: 20, Height: 20, Color: "gold", Image: "coin1.png", YOffset: 40.0},
|
||||||
|
{ID: "coin1", Type: "coin", Width: 20, Height: 20, Color: "gold", Image: "coin1.png", YOffset: 50.0},
|
||||||
|
{ID: "coin2", Type: "coin", Width: 20, Height: 20, Color: "gold", Image: "coin1.png", YOffset: 60.0},
|
||||||
|
|
||||||
// --- POWERUPS ---
|
// --- POWERUPS ---
|
||||||
{ID: "p_god", Type: "powerup", Width: 30, Height: 30, Color: "cyan", Image: "powerup_god1.png", YOffset: 20.0}, // Godmode
|
{ID: "p_god", Type: "powerup", Width: 30, Height: 30, Color: "cyan", Image: "powerup_god1.png", YOffset: 20.0}, // Godmode
|
||||||
@@ -56,7 +57,7 @@ func initGameConfig() {
|
|||||||
{ID: "p_boot", Type: "powerup", Width: 30, Height: 30, Color: "lime", Image: "powerup_boot1.png", YOffset: 20.0}, // Boots
|
{ID: "p_boot", Type: "powerup", Width: 30, Height: 30, Color: "lime", Image: "powerup_boot1.png", YOffset: 20.0}, // Boots
|
||||||
},
|
},
|
||||||
// Mehrere Hintergründe für Level-Wechsel
|
// Mehrere Hintergründe für Level-Wechsel
|
||||||
Backgrounds: []string{"gym-background.jpg", "school-background.jpg", "school2-background.jpg"},
|
Backgrounds: []string{"school-background.jpg", "gym-background.jpg", "school2-background.jpg"},
|
||||||
}
|
}
|
||||||
log.Println("✅ Config mit Powerups geladen")
|
log.Println("✅ Config mit Powerups geladen")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,8 +47,8 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st
|
|||||||
|
|
||||||
for i := 0; i < totalTicks; i++ {
|
for i := 0; i < totalTicks; i++ {
|
||||||
|
|
||||||
currentSpeed := BaseSpeed + (float64(score)/500.0)*0.5
|
currentSpeed := BaseSpeed + (float64(score)/750.0)*0.5
|
||||||
if currentSpeed > 12.0 {
|
if currentSpeed > 14.0 {
|
||||||
currentSpeed = 12.0
|
currentSpeed = 12.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -145,12 +145,12 @@
|
|||||||
function openModal(id) {
|
function openModal(id) {
|
||||||
document.getElementById('modal-' + id).style.display = 'flex';
|
document.getElementById('modal-' + id).style.display = 'flex';
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeModal() {
|
function closeModal() {
|
||||||
const modals = document.querySelectorAll('.modal-overlay');
|
const modals = document.querySelectorAll('.modal-overlay');
|
||||||
modals.forEach(el => el.style.display = 'none');
|
modals.forEach(el => el.style.display = 'none');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Schließen wenn man daneben klickt
|
|
||||||
window.onclick = function(event) {
|
window.onclick = function(event) {
|
||||||
if (event.target.classList.contains('modal-overlay')) {
|
if (event.target.classList.contains('modal-overlay')) {
|
||||||
closeModal();
|
closeModal();
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
function updateGameLogic() {
|
function updateGameLogic() {
|
||||||
// 1. Speed Berechnung (Sync mit Server!)
|
// 1. Speed Berechnung (Sync mit Server!)
|
||||||
let currentSpeed = BASE_SPEED + (score / 500.0) * 0.5;
|
let currentSpeed = BASE_SPEED + (score / 750.0) * 0.5;
|
||||||
if (currentSpeed > 12.0) currentSpeed = 12.0;
|
if (currentSpeed > 14.0) currentSpeed = 14.0;
|
||||||
|
|
||||||
// 2. Input & Sprung
|
// 2. Input & Sprung
|
||||||
if (isCrouching) inputLog.push({ t: currentTick - lastSentTick, act: "DUCK" });
|
if (isCrouching) inputLog.push({ t: currentTick - lastSentTick, act: "DUCK" });
|
||||||
|
|||||||
@@ -1,10 +1,25 @@
|
|||||||
|
// ==========================================
|
||||||
|
// INIT & ASSETS
|
||||||
|
// ==========================================
|
||||||
async function loadAssets() {
|
async function loadAssets() {
|
||||||
playerSprite.src = "assets/player.png";
|
playerSprite.src = "assets/player.png";
|
||||||
if (gameConfig.backgrounds && gameConfig.backgrounds.length > 0) {
|
|
||||||
const bgName = gameConfig.backgrounds[0];
|
// Hintergründe laden
|
||||||
if (!bgName.startsWith("#")) bgSprite.src = "assets/" + bgName;
|
const bgPromises = gameConfig.backgrounds.map((bgFile, index) => {
|
||||||
}
|
return new Promise((resolve) => {
|
||||||
const promises = gameConfig.obstacles.map(def => {
|
const img = new Image();
|
||||||
|
img.src = "assets/" + bgFile;
|
||||||
|
img.onload = () => { bgSprites[index] = img; resolve(); };
|
||||||
|
img.onerror = () => {
|
||||||
|
console.warn("BG fehlt:", bgFile);
|
||||||
|
bgSprites[index] = null;
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Hindernisse laden
|
||||||
|
const obsPromises = gameConfig.obstacles.map(def => {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
if (!def.image) { resolve(); return; }
|
if (!def.image) { resolve(); return; }
|
||||||
const img = new Image(); img.src = "assets/" + def.image;
|
const img = new Image(); img.src = "assets/" + def.image;
|
||||||
@@ -12,26 +27,155 @@ async function loadAssets() {
|
|||||||
img.onerror = () => { resolve(); };
|
img.onerror = () => { resolve(); };
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
if (bgSprite.src) {
|
|
||||||
promises.push(new Promise(r => { bgSprite.onload = r; bgSprite.onerror = r; }));
|
// Player laden (kleiner Promise Wrapper)
|
||||||
}
|
const pPromise = new Promise(r => {
|
||||||
await Promise.all(promises);
|
playerSprite.onload = r;
|
||||||
|
playerSprite.onerror = r;
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all([pPromise, ...bgPromises, ...obsPromises]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// START LOGIK
|
||||||
|
// ==========================================
|
||||||
window.startGameClick = async function() {
|
window.startGameClick = async function() {
|
||||||
if (!isLoaded) return;
|
if (!isLoaded) return;
|
||||||
startScreen.style.display = 'none';
|
startScreen.style.display = 'none';
|
||||||
document.body.classList.add('game-active'); // Handy Rotate Check
|
document.body.classList.add('game-active');
|
||||||
try {
|
try {
|
||||||
const sRes = await fetch('/api/start', {method:'POST'});
|
const sRes = await fetch('/api/start', {method:'POST'});
|
||||||
const sData = await sRes.json();
|
const sData = await sRes.json();
|
||||||
sessionID = sData.sessionId;
|
sessionID = sData.sessionId;
|
||||||
rng = new PseudoRNG(sData.seed);
|
rng = new PseudoRNG(sData.seed);
|
||||||
isGameRunning = true;
|
isGameRunning = true;
|
||||||
|
// Wir resetten die Zeit, damit es keinen Sprung gibt
|
||||||
|
lastTime = performance.now();
|
||||||
resize();
|
resize();
|
||||||
} catch(e) { location.reload(); }
|
} catch(e) {
|
||||||
|
alert("Start Fehler: " + e.message);
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// SCORE EINTRAGEN
|
||||||
|
// ==========================================
|
||||||
|
window.submitScore = async function() {
|
||||||
|
const nameInput = document.getElementById('playerNameInput');
|
||||||
|
const name = nameInput.value;
|
||||||
|
const btn = document.getElementById('submitBtn');
|
||||||
|
|
||||||
|
if (!name) return alert("Namen eingeben!");
|
||||||
|
btn.disabled = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/submit-name', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify({ sessionId: sessionID, name: name })
|
||||||
|
});
|
||||||
|
if (!res.ok) throw new Error("Server Error");
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
let myClaims = JSON.parse(localStorage.getItem('escape_claims') || '[]');
|
||||||
|
myClaims.push({
|
||||||
|
name: name, score: Math.floor(score / 10), code: data.claimCode,
|
||||||
|
date: new Date().toLocaleString('de-DE'), sessionId: sessionID
|
||||||
|
});
|
||||||
|
localStorage.setItem('escape_claims', JSON.stringify(myClaims));
|
||||||
|
|
||||||
|
document.getElementById('inputSection').style.display = 'none';
|
||||||
|
loadLeaderboard();
|
||||||
|
alert(`Gespeichert! Code: ${data.claimCode}`);
|
||||||
|
} catch (e) {
|
||||||
|
alert("Fehler: " + e.message);
|
||||||
|
btn.disabled = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// MEINE CODES & LÖSCHEN
|
||||||
|
// ==========================================
|
||||||
|
window.showMyCodes = function() {
|
||||||
|
if(window.openModal) window.openModal('codes');
|
||||||
|
const listEl = document.getElementById('codesList');
|
||||||
|
if(!listEl) return;
|
||||||
|
const claims = JSON.parse(localStorage.getItem('escape_claims') || '[]');
|
||||||
|
|
||||||
|
if (claims.length === 0) {
|
||||||
|
listEl.innerHTML = "<div style='padding:10px; text-align:center; color:#666;'>Keine Codes gespeichert.</div>";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let html = "";
|
||||||
|
for (let i = claims.length - 1; i >= 0; i--) {
|
||||||
|
const c = claims[i];
|
||||||
|
const canDelete = c.sessionId ? true : false;
|
||||||
|
const btnStyle = canDelete ? "cursor:pointer; color:#ff4444; border-color:#ff4444;" : "cursor:not-allowed; color:gray; border-color:gray;";
|
||||||
|
const btnAttr = canDelete ? `onclick="deleteClaim(${i}, '${c.sessionId}', '${c.code}')"` : "disabled";
|
||||||
|
|
||||||
|
html += `
|
||||||
|
<div style="border-bottom:1px solid #444; padding:8px 0; display:flex; justify-content:space-between; align-items:center;">
|
||||||
|
<div style="text-align:left;">
|
||||||
|
<span style="color:#00e5ff; font-weight:bold; font-size:12px;">${c.code}</span>
|
||||||
|
<span style="color:#ffcc00;">(${c.score} Pkt)</span><br>
|
||||||
|
<span style="color:#aaa; font-size:9px;">${c.name} • ${c.date}</span>
|
||||||
|
</div>
|
||||||
|
<button ${btnAttr} style="background:transparent; border:1px solid; padding:5px; font-size:9px; margin:0; ${btnStyle}">LÖSCHEN</button>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
listEl.innerHTML = html;
|
||||||
|
};
|
||||||
|
|
||||||
|
window.deleteClaim = async function(index, sid, code) {
|
||||||
|
if(!confirm("Wirklich löschen?")) return;
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/claim/delete', {
|
||||||
|
method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ sessionId: sid, claimCode: code })
|
||||||
|
});
|
||||||
|
if (!res.ok) {
|
||||||
|
if(!confirm("Server Fehler (evtl. schon weg). Lokal löschen?")) return;
|
||||||
|
}
|
||||||
|
let claims = JSON.parse(localStorage.getItem('escape_claims') || '[]');
|
||||||
|
claims.splice(index, 1);
|
||||||
|
localStorage.setItem('escape_claims', JSON.stringify(claims));
|
||||||
|
window.showMyCodes();
|
||||||
|
loadLeaderboard();
|
||||||
|
} catch(e) { alert("Verbindungsfehler!"); }
|
||||||
|
};
|
||||||
|
|
||||||
|
async function loadLeaderboard() {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/leaderboard?sessionId=${sessionID}`);
|
||||||
|
const entries = await res.json();
|
||||||
|
let html = "<h3>BESTENLISTE</h3>";
|
||||||
|
entries.forEach(e => {
|
||||||
|
const color = e.isMe ? "yellow" : "white";
|
||||||
|
html += `<div style="display:flex; justify-content:space-between; color:${color}; margin-bottom:5px;"><span>#${e.rank} ${e.name}</span><span>${Math.floor(e.score/10)}</span></div>`;
|
||||||
|
});
|
||||||
|
document.getElementById('leaderboard').innerHTML = html;
|
||||||
|
} catch(e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadStartScreenLeaderboard() {
|
||||||
|
try {
|
||||||
|
const listEl = document.getElementById('startLeaderboardList');
|
||||||
|
if (!listEl) return;
|
||||||
|
const res = await fetch('/api/leaderboard');
|
||||||
|
const entries = await res.json();
|
||||||
|
if (entries.length === 0) { listEl.innerHTML = "<div style='padding:20px'>Noch keine Scores.</div>"; return; }
|
||||||
|
let html = "";
|
||||||
|
entries.forEach(e => {
|
||||||
|
let icon = "#" + e.rank;
|
||||||
|
if (e.rank === 1) icon = "🥇"; if (e.rank === 2) icon = "🥈"; if (e.rank === 3) icon = "🥉";
|
||||||
|
html += `<div class="hof-entry"><span><span class="hof-rank">${icon}</span> ${e.name}</span><span class="hof-score">${Math.floor(e.score / 10)}</span></div>`;
|
||||||
|
});
|
||||||
|
listEl.innerHTML = html;
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
|
||||||
function gameOver(reason) {
|
function gameOver(reason) {
|
||||||
if (isGameOver) return;
|
if (isGameOver) return;
|
||||||
isGameOver = true;
|
isGameOver = true;
|
||||||
@@ -44,52 +188,71 @@ function gameOver(reason) {
|
|||||||
drawGame();
|
drawGame();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// DER FIXIERTE GAME LOOP
|
||||||
|
// ==========================================
|
||||||
function gameLoop(timestamp) {
|
function gameLoop(timestamp) {
|
||||||
requestAnimationFrame(gameLoop);
|
requestAnimationFrame(gameLoop);
|
||||||
|
|
||||||
if (!isLoaded || !isGameRunning || isGameOver) {
|
// 1. Wenn Assets noch nicht da sind, machen wir gar nichts
|
||||||
|
if (!isLoaded) return;
|
||||||
|
|
||||||
|
// 2. PHYSIK-LOGIK (Nur wenn Spiel läuft und nicht Game Over)
|
||||||
|
// Das hier sorgt dafür, dass der Dino stehen bleibt, wenn wir im Menü sind
|
||||||
|
if (isGameRunning && !isGameOver) {
|
||||||
|
|
||||||
|
if (!lastTime) lastTime = timestamp;
|
||||||
|
const deltaTime = timestamp - lastTime;
|
||||||
lastTime = timestamp;
|
lastTime = timestamp;
|
||||||
return;
|
|
||||||
|
if (deltaTime > 1000) { accumulator = 0; return; }
|
||||||
|
|
||||||
|
accumulator += deltaTime;
|
||||||
|
|
||||||
|
while (accumulator >= MS_PER_TICK) {
|
||||||
|
updateGameLogic();
|
||||||
|
currentTick++;
|
||||||
|
score++;
|
||||||
|
if (currentTick - lastSentTick >= CHUNK_SIZE) sendChunk();
|
||||||
|
accumulator -= MS_PER_TICK;
|
||||||
|
}
|
||||||
|
|
||||||
|
const scoreEl = document.getElementById('score');
|
||||||
|
if (scoreEl) scoreEl.innerText = Math.floor(score / 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!lastTime) lastTime = timestamp;
|
// 3. RENDERING (IMMER!)
|
||||||
const deltaTime = timestamp - lastTime;
|
// Das hier war das Problem. Früher stand hier "return" wenn !isGameRunning.
|
||||||
lastTime = timestamp;
|
// Jetzt malen wir immer. Wenn isGameRunning false ist, malt er einfach den Start-Zustand.
|
||||||
|
|
||||||
if (deltaTime > 1000) {
|
|
||||||
accumulator = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
accumulator += deltaTime;
|
|
||||||
|
|
||||||
while (accumulator >= MS_PER_TICK) {
|
|
||||||
updateGameLogic();
|
|
||||||
currentTick++;
|
|
||||||
score++;
|
|
||||||
|
|
||||||
if (currentTick - lastSentTick >= CHUNK_SIZE) sendChunk();
|
|
||||||
|
|
||||||
accumulator -= MS_PER_TICK;
|
|
||||||
}
|
|
||||||
|
|
||||||
const scoreEl = document.getElementById('score');
|
|
||||||
if (scoreEl) scoreEl.innerText = Math.floor(score / 10);
|
|
||||||
|
|
||||||
drawGame();
|
drawGame();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function initGame() {
|
async function initGame() {
|
||||||
try {
|
try {
|
||||||
const cRes = await fetch('/api/config'); gameConfig = await cRes.json();
|
const cRes = await fetch('/api/config'); gameConfig = await cRes.json();
|
||||||
|
|
||||||
|
// Erst alles laden
|
||||||
await loadAssets();
|
await loadAssets();
|
||||||
await loadStartScreenLeaderboard();
|
await loadStartScreenLeaderboard();
|
||||||
|
|
||||||
isLoaded = true;
|
isLoaded = true;
|
||||||
if(loadingText) loadingText.style.display = 'none'; if(startBtn) startBtn.style.display = 'inline-block';
|
if(loadingText) loadingText.style.display = 'none';
|
||||||
|
if(startBtn) startBtn.style.display = 'inline-block';
|
||||||
|
|
||||||
const savedHighscore = localStorage.getItem('escape_highscore') || 0;
|
const savedHighscore = localStorage.getItem('escape_highscore') || 0;
|
||||||
const hsEl = document.getElementById('localHighscore'); if(hsEl) hsEl.innerText = savedHighscore;
|
const hsEl = document.getElementById('localHighscore');
|
||||||
|
if(hsEl) hsEl.innerText = savedHighscore;
|
||||||
|
|
||||||
|
// Loop starten (mit dummy timestamp start)
|
||||||
requestAnimationFrame(gameLoop);
|
requestAnimationFrame(gameLoop);
|
||||||
} catch(e) { if(loadingText) loadingText.innerText = "Fehler!"; }
|
|
||||||
|
// Initiales Zeichnen erzwingen (damit Hintergrund sofort da ist)
|
||||||
|
drawGame();
|
||||||
|
|
||||||
|
} catch(e) {
|
||||||
|
console.error(e);
|
||||||
|
if(loadingText) loadingText.innerText = "Ladefehler (siehe Konsole)";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
initGame();
|
initGame();
|
||||||
@@ -44,53 +44,72 @@ resize();
|
|||||||
// --- DRAWING ---
|
// --- DRAWING ---
|
||||||
|
|
||||||
function drawGame() {
|
function drawGame() {
|
||||||
|
// 1. Alles löschen
|
||||||
ctx.clearRect(0, 0, GAME_WIDTH, GAME_HEIGHT);
|
ctx.clearRect(0, 0, GAME_WIDTH, GAME_HEIGHT);
|
||||||
|
|
||||||
// --- BACKGROUND ---
|
// --- HINTERGRUND (Level-Wechsel) ---
|
||||||
// Hier war der Check schon drin, das ist gut
|
let currentBg = null;
|
||||||
if (bgSprite.complete && bgSprite.naturalHeight !== 0) {
|
|
||||||
ctx.drawImage(bgSprite, 0, 0, GAME_WIDTH, GAME_HEIGHT);
|
// 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 {
|
} else {
|
||||||
|
// Fallback: Hellgrau, falls Bild fehlt
|
||||||
ctx.fillStyle = "#f0f0f0";
|
ctx.fillStyle = "#f0f0f0";
|
||||||
ctx.fillRect(0, 0, GAME_WIDTH, GAME_HEIGHT);
|
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.fillStyle = "rgba(60, 60, 60, 0.8)";
|
||||||
ctx.fillRect(0, GROUND_Y, GAME_WIDTH, 50);
|
ctx.fillRect(0, GROUND_Y, GAME_WIDTH, 50);
|
||||||
|
|
||||||
// --- HINDERNISSE (HIER WAR DER FEHLER) ---
|
// --- HINDERNISSE ---
|
||||||
obstacles.forEach(obs => {
|
obstacles.forEach(obs => {
|
||||||
const img = sprites[obs.def.id];
|
const img = sprites[obs.def.id];
|
||||||
|
|
||||||
// FIX: Wir prüfen jetzt strikt, ob das Bild wirklich bereit ist
|
// Prüfen ob Bild geladen ist
|
||||||
if (img && img.complete && img.naturalHeight !== 0) {
|
if (img && img.complete && img.naturalHeight !== 0) {
|
||||||
ctx.drawImage(img, obs.x, obs.y, obs.def.width, obs.def.height);
|
ctx.drawImage(img, obs.x, obs.y, obs.def.width, obs.def.height);
|
||||||
} else {
|
} else {
|
||||||
// Fallback: Wenn Bild fehlt/kaputt -> Farbiges Rechteck
|
// Fallback Farbe (Münzen Gold, Rest aus Config)
|
||||||
// Wir prüfen auf Typ Coin, damit Coins gold sind, auch wenn Bild fehlt
|
|
||||||
if (obs.def.type === "coin") ctx.fillStyle = "gold";
|
if (obs.def.type === "coin") ctx.fillStyle = "gold";
|
||||||
else ctx.fillStyle = obs.def.color;
|
else ctx.fillStyle = obs.def.color || "red";
|
||||||
|
|
||||||
ctx.fillRect(obs.x, obs.y, obs.def.width, obs.def.height);
|
ctx.fillRect(obs.x, obs.y, obs.def.width, obs.def.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(obs.speech) drawSpeechBubble(obs.x, obs.y, obs.speech);
|
if(obs.speech) drawSpeechBubble(obs.x, obs.y, obs.speech);
|
||||||
});
|
});
|
||||||
|
|
||||||
/*
|
/*
|
||||||
// --- DEBUG ---
|
// --- DEBUG RAHMEN (Server Hitboxen) ---
|
||||||
|
// Grün im Spiel, Rot bei Tod
|
||||||
ctx.strokeStyle = isGameOver ? "red" : "lime";
|
ctx.strokeStyle = isGameOver ? "red" : "lime";
|
||||||
ctx.lineWidth = 2;
|
ctx.lineWidth = 2;
|
||||||
serverObstacles.forEach(srvObs => {
|
serverObstacles.forEach(srvObs => {
|
||||||
ctx.strokeRect(srvObs.x, srvObs.y, srvObs.w, srvObs.h);
|
ctx.strokeRect(srvObs.x, srvObs.y, srvObs.w, srvObs.h);
|
||||||
});
|
});
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// --- PLAYER ---
|
|
||||||
|
// --- SPIELER ---
|
||||||
|
// Y-Position und Höhe anpassen für Ducken
|
||||||
const drawY = isCrouching ? player.y + 25 : player.y;
|
const drawY = isCrouching ? player.y + 25 : player.y;
|
||||||
const drawH = isCrouching ? 25 : 50;
|
const drawH = isCrouching ? 25 : 50;
|
||||||
|
|
||||||
// Hier war der Check auch schon korrekt
|
|
||||||
if (playerSprite.complete && playerSprite.naturalHeight !== 0) {
|
if (playerSprite.complete && playerSprite.naturalHeight !== 0) {
|
||||||
ctx.drawImage(playerSprite, player.x, drawY, player.w, drawH);
|
ctx.drawImage(playerSprite, player.x, drawY, player.w, drawH);
|
||||||
} else {
|
} else {
|
||||||
@@ -98,27 +117,31 @@ function drawGame() {
|
|||||||
ctx.fillRect(player.x, drawY, player.w, drawH);
|
ctx.fillRect(player.x, drawY, player.w, drawH);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- POWERUP UI (Oben Links) ---
|
// --- HUD (Powerup Status oben links) ---
|
||||||
if (isGameRunning && !isGameOver) {
|
if (isGameRunning && !isGameOver) {
|
||||||
ctx.fillStyle = "black";
|
ctx.fillStyle = "black";
|
||||||
ctx.font = "bold 10px monospace";
|
ctx.font = "bold 10px monospace";
|
||||||
ctx.textAlign = "left";
|
ctx.textAlign = "left";
|
||||||
let statusText = "";
|
let statusText = "";
|
||||||
|
|
||||||
if(godModeLives > 0) statusText += `🛡️ x${godModeLives} `;
|
if(godModeLives > 0) statusText += `🛡️ x${godModeLives} `;
|
||||||
if(hasBat) statusText += `⚾ BAT `;
|
if(hasBat) statusText += `⚾ BAT `;
|
||||||
if(bootTicks > 0) statusText += `👟 ${(bootTicks/60).toFixed(1)}s`;
|
if(bootTicks > 0) statusText += `👟 ${(bootTicks/60).toFixed(1)}s`;
|
||||||
|
|
||||||
// Drift Anzeige
|
// Drift Info (nur wenn Objekte da sind)
|
||||||
/*
|
|
||||||
if (obstacles.length > 0 && serverObstacles.length > 0) {
|
if (obstacles.length > 0 && serverObstacles.length > 0) {
|
||||||
const drift = Math.abs(obstacles[0].x - serverObstacles[0].x).toFixed(1);
|
const drift = Math.abs(obstacles[0].x - serverObstacles[0].x).toFixed(1);
|
||||||
statusText += ` | Drift: ${drift}px`;
|
// statusText += ` | Drift: ${drift}px`; // Einkommentieren für Debugging
|
||||||
|
}
|
||||||
|
|
||||||
|
if(statusText !== "") {
|
||||||
|
ctx.fillText(statusText, 10, 40);
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
ctx.fillText(statusText, 10, 40);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- GAME OVER OVERLAY ---
|
||||||
if (isGameOver) {
|
if (isGameOver) {
|
||||||
|
// Dunkler Schleier über alles
|
||||||
ctx.fillStyle = "rgba(0,0,0,0.7)";
|
ctx.fillStyle = "rgba(0,0,0,0.7)";
|
||||||
ctx.fillRect(0,0,GAME_WIDTH, GAME_HEIGHT);
|
ctx.fillRect(0,0,GAME_WIDTH, GAME_HEIGHT);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ let accumulator = 0;
|
|||||||
let sprites = {};
|
let sprites = {};
|
||||||
let playerSprite = new Image();
|
let playerSprite = new Image();
|
||||||
let bgSprite = new Image();
|
let bgSprite = new Image();
|
||||||
|
let bgSprites = [];
|
||||||
// Spiel-Objekte
|
// Spiel-Objekte
|
||||||
let player = {
|
let player = {
|
||||||
x: 50, y: 300, w: 30, h: 50, color: "red",
|
x: 50, y: 300, w: 30, h: 50, color: "red",
|
||||||
|
|||||||
@@ -367,12 +367,21 @@ input {
|
|||||||
|
|
||||||
/* 1. Haupt-Container: Alles zentrieren! */
|
/* 1. Haupt-Container: Alles zentrieren! */
|
||||||
#startScreen {
|
#startScreen {
|
||||||
flex-direction: row; /* Nebeneinander lassen, da Bildschirm breit ist */
|
position: absolute;
|
||||||
align-items: center; /* Vertikal mittig */
|
top: 0; left: 0;
|
||||||
justify-content: center; /* Horizontal mittig */
|
width: 100%; height: 100%;
|
||||||
gap: 20px; /* Abstand zwischen Menü und Leaderboard */
|
background:
|
||||||
padding: 10px;
|
linear-gradient(rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.6)),
|
||||||
overflow-y: auto; /* Scrollen erlauben zur Sicherheit */
|
url('assets/school-background.jpg');
|
||||||
|
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 10;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 2. Linke Seite (Menü) zentrieren */
|
/* 2. Linke Seite (Menü) zentrieren */
|
||||||
@@ -419,9 +428,16 @@ input {
|
|||||||
|
|
||||||
/* 8. Game Over Screen auch zentrieren */
|
/* 8. Game Over Screen auch zentrieren */
|
||||||
#gameOverScreen {
|
#gameOverScreen {
|
||||||
|
position: absolute;
|
||||||
|
top: 0; left: 0;
|
||||||
|
width: 100%; height: 100%;
|
||||||
|
background: rgba(0, 0, 0, 0.9);
|
||||||
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding-top: 0;
|
z-index: 10;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 20px;
|
||||||
}
|
}
|
||||||
#inputSection { margin: 5px 0; }
|
#inputSection { margin: 5px 0; }
|
||||||
input { padding: 5px; font-size: 12px; width: 150px; margin-bottom: 5px; }
|
input { padding: 5px; font-size: 12px; width: 150px; margin-bottom: 5px; }
|
||||||
|
|||||||
Reference in New Issue
Block a user