From b2721d759c71d94edfc2cd90e8728717ae639238 Mon Sep 17 00:00:00 2001 From: Thomas Date: Tue, 14 Apr 2026 10:29:37 +0200 Subject: [PATCH] Klimawaechter 2D: Canvas-Fix + alle Massnahmen sichtbar + Vulkan flacher MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Canvas-Fix: min-height:450px + overflow:visible (Canvas war unsichtbar) - Neue Renderer: Radwege (Fahrraeder), Mangroven, Sand-Aufschuettung, Kohlekraftwerk, Flughafen (mit fliegendem Flugzeug), Wolkenimpfung - Vulkan: flacher (120→95), breiter, weiter im Hintergrund - Eiskegel: breiter, deckt braune Raender komplett ab Co-Authored-By: Claude Opus 4.6 (1M context) --- App/game.html | 2 +- .../sims/sim-05-treibhaus/game-renderer.ts | 108 +++++++++++++++++- 2 files changed, 103 insertions(+), 7 deletions(-) diff --git a/App/game.html b/App/game.html index 9bacac4..61f6412 100644 --- a/App/game.html +++ b/App/game.html @@ -56,7 +56,7 @@ /* === CENTER: Game Canvas + Controls === */ .center { display:flex; flex-direction:column; gap:.6rem; } - .canvas-wrap { background:var(--white); border-radius:14px; border:1px solid rgba(0,0,0,.05); overflow:hidden; box-shadow:0 4px 16px rgba(0,0,0,.05); position:relative; } + .canvas-wrap { background:var(--white); border-radius:14px; border:1px solid rgba(0,0,0,.05); overflow:visible; box-shadow:0 4px 16px rgba(0,0,0,.05); position:relative; min-height:450px; } /* Tutorial Overlay */ .tutorial-overlay { position:absolute; inset:0; background:rgba(15,30,40,.85); backdrop-filter:blur(8px); display:flex; align-items:center; justify-content:center; z-index:10; padding:1.5rem; } diff --git a/App/src/sims/sim-05-treibhaus/game-renderer.ts b/App/src/sims/sim-05-treibhaus/game-renderer.ts index 11c7f3a..5b40dbe 100644 --- a/App/src/sims/sim-05-treibhaus/game-renderer.ts +++ b/App/src/sims/sim-05-treibhaus/game-renderer.ts @@ -192,6 +192,26 @@ export class KlimawaechterRenderer { const x = 0.95 return { type: 'sea-wall', x, scale: 1 + index * 0.08, ownerId: stableId, builtTick: tick } } + if (id === 'bikes') { + return { type: 'bikes', x: 0.35 + index * 0.04, scale: 1, ownerId: stableId, builtTick: tick } + } + if (id === 'mangrove') { + const x = 0.75 + index * 0.03 + return { type: 'mangrove', x, scale: baseScale, ownerId: stableId, builtTick: tick } + } + if (id === 'sand-fill') { + const x = 0.82 + index * 0.025 + return { type: 'sand-fill', x, scale: 1, ownerId: stableId, builtTick: tick } + } + if (id === 'coal') { + return { type: 'coal', x: 0.12, scale: 1 + index * 0.05, ownerId: stableId, builtTick: tick } + } + if (id === 'airport') { + return { type: 'airport', x: 0.55, scale: 1, ownerId: stableId, builtTick: tick } + } + if (id === 'cloud-seed') { + return { type: 'cloud-seed', x: 0.5, scale: 1, ownerId: stableId, builtTick: tick } + } return { type: 'tree', x: 0.5, scale: 1, ownerId: stableId, builtTick: tick } } @@ -312,9 +332,9 @@ export class KlimawaechterRenderer { // === VULKAN mit Gletscher-Deckel (links, ragt aus der Landschaft) === const volcanoBaseX = W * 0.16 const volcanoBaseY = landSurface(volcanoBaseX) - 2 - const volcanoHeight = 120 - const volcanoHalfBase = 55 - const volcanoTopHalf = 14 + const volcanoHeight = 95 // flacher + const volcanoHalfBase = 65 // breiter + const volcanoTopHalf = 16 // breitere Spitze // Fels ctx.fillStyle = `rgb(${106 + tempStress * 20},${90 + tempStress * 10},${74})` ctx.beginPath() @@ -337,8 +357,9 @@ export class KlimawaechterRenderer { const glacierBaseY = volcanoBaseY - (volcanoHeight - glacierFullH + glacierFullH - glacierH) const grime = (1 - glacierFraction) * 40 // Die Deckel-Form ist ein Trapez, breiter als der Vulkan-Top - const capBottomHalf = (volcanoTopHalf + 6) * (0.5 + glacierFraction * 0.5) - const capTopHalf = volcanoTopHalf * (0.6 + glacierFraction * 0.4) + // Breiter: Eis deckt die gesamte Spitze ab (keine braunen Raender) + const capBottomHalf = (volcanoTopHalf + 12) * (0.6 + glacierFraction * 0.4) + const capTopHalf = (volcanoTopHalf + 4) * (0.7 + glacierFraction * 0.3) ctx.fillStyle = `rgb(${240 - grime},${246 - grime},${250 - grime})` ctx.beginPath() ctx.moveTo(volcanoBaseX - capBottomHalf, glacierBaseY) @@ -445,13 +466,88 @@ export class KlimawaechterRenderer { } ctx.globalAlpha = 1 - // ===== DEICHE & MAUERN ===== + // ===== KÜSTENBAUWERKE ===== + for (const obj of this.placed.filter(p => p.type === 'sand-fill')) { + // Sand-Aufschüttung: kleine Hügel an der Wasserlinie + const sx = obj.x * W, sy = seaY + ctx.fillStyle = '#d4c090' + ctx.beginPath() + ctx.ellipse(sx, sy, 12, 5, 0, Math.PI, 0) + ctx.fill() + ctx.fillStyle = '#c8b480' + ctx.beginPath() + ctx.ellipse(sx + 8, sy, 8, 3, 0, Math.PI, 0) + ctx.fill() + } + for (const obj of this.placed.filter(p => p.type === 'mangrove')) { + // Mangroven: grüne Büsche an der Uferlinie + const mx = obj.x * W, my = seaY - 4 + ctx.fillStyle = '#2a6a3a' + ctx.beginPath(); ctx.arc(mx, my, 6 * obj.scale, 0, Math.PI * 2); ctx.fill() + ctx.fillStyle = '#3a8a4a' + ctx.beginPath(); ctx.arc(mx + 4, my - 2, 4 * obj.scale, 0, Math.PI * 2); ctx.fill() + // Wurzeln im Wasser + ctx.strokeStyle = '#3a5a2a'; ctx.lineWidth = 1 + ctx.beginPath(); ctx.moveTo(mx - 3, my + 3); ctx.lineTo(mx - 5, my + 10); ctx.stroke() + ctx.beginPath(); ctx.moveTo(mx + 2, my + 3); ctx.lineTo(mx + 4, my + 8); ctx.stroke() + } for (const obj of this.placed.filter(p => p.type === 'dike')) { this.drawDike(ctx, obj.x * W, groundY) } for (const obj of this.placed.filter(p => p.type === 'sea-wall')) { this.drawSeaWall(ctx, obj.x * W, groundY - 18, baseSeaY) } + // Kohlekraftwerk: vor dem Vulkan + for (const obj of this.placed.filter(p => p.type === 'coal')) { + const cx = obj.x * W, cy = this.landSurfaceFn(obj.x * W) - 5 + ctx.fillStyle = '#5a5a5a' + ctx.fillRect(cx - 10, cy - 12, 20, 12) + // Schornstein + ctx.fillStyle = '#4a4a4a' + ctx.fillRect(cx + 4, cy - 22, 6, 12) + // Rauch + ctx.fillStyle = 'rgba(80,80,80,0.3)' + ctx.beginPath(); ctx.arc(cx + 7, cy - 26 - Math.sin(this.t) * 3, 4, 0, Math.PI * 2); ctx.fill() + ctx.beginPath(); ctx.arc(cx + 9, cy - 32 - Math.sin(this.t * 0.7) * 4, 3, 0, Math.PI * 2); ctx.fill() + } + // Radwege: Fahrräder auf der Strasse (vor den Häusern) + for (const obj of this.placed.filter(p => p.type === 'bikes')) { + const bx = obj.x * W, by = groundY - 2 + // Kleines Fahrrad-Symbol + ctx.strokeStyle = '#4a8a4a'; ctx.lineWidth = 1.2 + ctx.beginPath(); ctx.arc(bx - 3, by, 2.5, 0, Math.PI * 2); ctx.stroke() + ctx.beginPath(); ctx.arc(bx + 3, by, 2.5, 0, Math.PI * 2); ctx.stroke() + ctx.beginPath(); ctx.moveTo(bx - 3, by); ctx.lineTo(bx, by - 3); ctx.lineTo(bx + 3, by); ctx.stroke() + } + // Flughafen + for (const obj of this.placed.filter(p => p.type === 'airport')) { + const ax = obj.x * W, ay = groundY - 8 + // Landebahn + ctx.fillStyle = '#5a5a5a'; ctx.fillRect(ax - 18, ay, 36, 4) + ctx.strokeStyle = '#fff'; ctx.lineWidth = 0.5; ctx.setLineDash([3, 2]) + ctx.beginPath(); ctx.moveTo(ax - 16, ay + 2); ctx.lineTo(ax + 16, ay + 2); ctx.stroke() + ctx.setLineDash([]) + // Flugzeug + const flyX = ax + Math.sin(this.t * 0.3) * 40 + const flyY = ay - 15 - Math.abs(Math.sin(this.t * 0.3)) * 20 + ctx.fillStyle = '#eee'; ctx.globalAlpha = 0.7 + ctx.beginPath(); ctx.ellipse(flyX, flyY, 6, 1.5, Math.sin(this.t * 0.3) * 0.3, 0, Math.PI * 2); ctx.fill() + ctx.globalAlpha = 1 + } + // Wolkenimpfung + for (const obj of this.placed.filter(p => p.type === 'cloud-seed')) { + // Spezialwolke mit Partikeln + const cx = W * 0.5 + Math.sin(this.t * 0.2) * 30, cy = H * 0.12 + ctx.fillStyle = 'rgba(200,220,240,0.4)' + ctx.beginPath(); ctx.ellipse(cx, cy, 20, 8, 0, 0, Math.PI * 2); ctx.fill() + ctx.beginPath(); ctx.ellipse(cx - 10, cy + 2, 14, 6, 0, 0, Math.PI * 2); ctx.fill() + // Partikel + ctx.fillStyle = 'rgba(180,200,220,0.3)' + for (let i = 0; i < 4; i++) { + const px = cx - 8 + i * 5 + Math.sin(this.t + i) * 2 + ctx.beginPath(); ctx.arc(px, cy + 10 + Math.sin(this.t * 2 + i) * 3, 1.5, 0, Math.PI * 2); ctx.fill() + } + } // ===== BEWOHNER ===== const popLossPct = Math.max(0, 10000 - snap.resources.population) / 10000