Klima 3D: Wolken, kleineres Airport-Plateau, Pause-Zeichen zentral
- Wolken am Himmel: 4 Dunst-Gruppen (je 3-5 Kugeln, transluzent), ziehen vom +X Rand über die Insel Richtung Vulkan (−X, −Z-Drift). Eigene Phase, Höhe, Zyklusdauer pro Wolke. updateClouds in updateAmbient. - Airport-Plateau: von 3.4×1.55 auf 2.55×1.25 geschrumpft, nach +Z versetzt (0.28). Endet knapp innerhalb der Runway, deckt aber Terminal + Heli-Pad weiter ab. - Pause-Zentrum: neue Card mit ▶-Symbol in der Mitte der Szene, sichtbar bei state.speed === 0. Klick startet 1×-Speed. Glas-Optik, animation bei Einblendung. Sync via updatePauseCenter in renderUI und bei jedem Speed-Klick. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+129
-6
@@ -161,6 +161,52 @@
|
||||
.kw-place-hint.visible { opacity: 1; }
|
||||
.kw-place-hint.invalid { background: rgba(176,74,58,0.92); }
|
||||
|
||||
/* Zentrales Pause-Zeichen über der Szene — erscheint bei Speed 0 */
|
||||
.kw-pause-center {
|
||||
position: absolute;
|
||||
top: 50%; left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 5;
|
||||
pointer-events: auto;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
text-align: center;
|
||||
animation: kw-pause-fade-in 0.2s ease-out;
|
||||
}
|
||||
.kw-pause-btn {
|
||||
width: 110px; height: 110px;
|
||||
border-radius: 50%;
|
||||
background: rgba(30, 40, 50, 0.58);
|
||||
backdrop-filter: blur(10px) saturate(140%);
|
||||
-webkit-backdrop-filter: blur(10px) saturate(140%);
|
||||
color: #fff;
|
||||
font-size: 56px;
|
||||
line-height: 110px;
|
||||
border: 3px solid rgba(255,255,255,0.78);
|
||||
box-shadow: 0 6px 20px rgba(0,0,0,0.28);
|
||||
transition: transform 0.15s var(--ggs-ease), background 0.18s;
|
||||
text-indent: 8px; /* optische Mitte für die ▶-Spitze */
|
||||
}
|
||||
.kw-pause-center:hover .kw-pause-btn {
|
||||
transform: scale(1.08);
|
||||
background: rgba(74, 124, 138, 0.75);
|
||||
}
|
||||
.kw-pause-label {
|
||||
margin-top: 10px;
|
||||
display: inline-block;
|
||||
background: rgba(30, 40, 50, 0.72);
|
||||
color: #fff;
|
||||
padding: 5px 12px;
|
||||
border-radius: 999px;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
@keyframes kw-pause-fade-in {
|
||||
from { opacity: 0; transform: translate(-50%, -50%) scale(0.85); }
|
||||
to { opacity: 1; transform: translate(-50%, -50%) scale(1); }
|
||||
}
|
||||
|
||||
/* „Bau beenden"-Karte — kompakt, kontrastfarben, nur während Placement */
|
||||
.kw-exit-card {
|
||||
display: flex;
|
||||
@@ -579,6 +625,10 @@
|
||||
von Seitenrand zu Seitenrand — Panels schweben als Glasflächen drüber) -->
|
||||
<div id="scene-container">
|
||||
<div class="kw-place-hint" id="place-hint">Klicke zum Platzieren</div>
|
||||
<div class="kw-pause-center" id="pause-center" style="display:none" title="Fortsetzen">
|
||||
<div class="kw-pause-btn">▶</div>
|
||||
<div class="kw-pause-label">Pause — klicken zum Fortsetzen</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- LINKS: Status ------------------------------------------------ -->
|
||||
@@ -2058,6 +2108,7 @@ function renderUI() {
|
||||
renderGraphs();
|
||||
renderAchievements();
|
||||
renderTimeline();
|
||||
if (typeof updatePauseCenter === 'function') updatePauseCenter();
|
||||
// Szene wird aus der Frame-Loop gezeichnet (sceneRender), hier nicht
|
||||
// nötig — anders als im 2D-Canvas-Renderer, der jedes State-Update
|
||||
// synchron neu zeichnen musste.
|
||||
@@ -2781,18 +2832,19 @@ function makeCoalPlant() {
|
||||
function makeAirport() {
|
||||
const g = new THREE.Group();
|
||||
// Plateau-Sockel: hebt den Flughafen sichtbar aus dem Terrain und
|
||||
// verhindert, dass Teile am Hang im Berg verschwinden. Zwei-Schichten-
|
||||
// Look (Unterkante etwas breiter, Oberkante heller).
|
||||
// verhindert, dass Teile am Hang im Berg verschwinden. Länge bleibt
|
||||
// knapp innerhalb der Runway (2.4), Z-Richtung ist nach +Z versetzt,
|
||||
// um Terminal + Heli-Pad abzudecken (sitzen beide bei z≈0.55).
|
||||
const plateauBase = new THREE.Mesh(
|
||||
new THREE.BoxGeometry(3.4, 0.20, 1.55),
|
||||
new THREE.BoxGeometry(2.55, 0.20, 1.25),
|
||||
new THREE.MeshStandardMaterial({ color: 0x50555a, roughness: 1 })
|
||||
);
|
||||
plateauBase.position.y = 0.10; g.add(plateauBase);
|
||||
plateauBase.position.set(0, 0.10, 0.28); g.add(plateauBase);
|
||||
const plateauTop = new THREE.Mesh(
|
||||
new THREE.BoxGeometry(3.3, 0.04, 1.48),
|
||||
new THREE.BoxGeometry(2.48, 0.04, 1.18),
|
||||
new THREE.MeshStandardMaterial({ color: 0x787d82, roughness: 1 })
|
||||
);
|
||||
plateauTop.position.y = 0.22; g.add(plateauTop);
|
||||
plateauTop.position.set(0, 0.22, 0.28); g.add(plateauTop);
|
||||
// Alle Airport-Strukturen um LIFT anheben (sitzen jetzt auf der Plateau-Kante)
|
||||
const LIFT = 0.24;
|
||||
|
||||
@@ -4053,11 +4105,65 @@ function syncCitizenBeach() {
|
||||
}
|
||||
}
|
||||
|
||||
// --- Wolken am Himmel ---
|
||||
// Ziehen als Dunst-Gruppen quer über die Insel, eher Richtung Vulkan
|
||||
// (z-Drift nach hinten). Low-Poly-Sphären, transluzent, ohne Schatten —
|
||||
// pro Wolke eigener Zyklus und eigenes Höhenlevel.
|
||||
function makeCloud() {
|
||||
const g = new THREE.Group();
|
||||
const mat = new THREE.MeshBasicMaterial({
|
||||
color: 0xffffff, transparent: true, opacity: 0.72, depthWrite: false
|
||||
});
|
||||
// Drei bis fünf gestapelte Kugeln für Dunst-Silhouette
|
||||
const bumps = 3 + Math.floor(Math.random() * 3);
|
||||
for (let i = 0; i < bumps; i++) {
|
||||
const r = 0.9 + Math.random() * 0.9;
|
||||
const sphere = new THREE.Mesh(new THREE.SphereGeometry(r, 8, 6), mat);
|
||||
sphere.position.set(
|
||||
(i - bumps / 2) * 1.1 + (Math.random() - 0.5) * 0.4,
|
||||
(Math.random() - 0.5) * 0.4,
|
||||
(Math.random() - 0.5) * 0.4
|
||||
);
|
||||
g.add(sphere);
|
||||
}
|
||||
return g;
|
||||
}
|
||||
|
||||
// Vier Wolken, jede mit eigener Phase, Höhe und Flugdauer.
|
||||
const CLOUD_CYCLE = 60; // Sekunden pro Überquerung (Basis)
|
||||
const clouds = [];
|
||||
for (let i = 0; i < 4; i++) {
|
||||
const c = makeCloud();
|
||||
c.userData.phase = Math.random(); // 0..1 Startverschiebung
|
||||
c.userData.height = 13 + Math.random() * 4; // y
|
||||
c.userData.zStart = 4 + Math.random() * 6; // Insel-Vorder-Bereich
|
||||
c.userData.zEnd = -16 - Math.random() * 4; // hinterm Vulkan
|
||||
c.userData.scale = 0.8 + Math.random() * 0.5;
|
||||
c.userData.speed = 0.8 + Math.random() * 0.6;
|
||||
c.scale.setScalar(c.userData.scale);
|
||||
ambientGroup.add(c);
|
||||
clouds.push(c);
|
||||
}
|
||||
|
||||
function updateClouds(tNow) {
|
||||
for (const c of clouds) {
|
||||
const cycle = CLOUD_CYCLE / c.userData.speed;
|
||||
const u = ((tNow / cycle) + c.userData.phase) % 1; // 0..1 entlang der Bahn
|
||||
// Von +X nach -X, Z interpoliert von zStart zu zEnd (Drift Richtung Vulkan)
|
||||
c.position.set(
|
||||
50 - u * 100,
|
||||
c.userData.height,
|
||||
c.userData.zStart + (c.userData.zEnd - c.userData.zStart) * u
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Zentrale Ambient-Update-Funktion (aus sceneRender pro Frame)
|
||||
function updateAmbient(tNow, dt) {
|
||||
updateBoats(tNow);
|
||||
updateHeli(tNow);
|
||||
updateVillage(tNow, dt);
|
||||
updateClouds(tNow);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
@@ -4108,9 +4214,26 @@ $$('.ggs-speed-btn').forEach(btn => {
|
||||
const header = $('#header');
|
||||
if (sp === 0) { state.phase = state.phase === 'playing' ? 'paused' : state.phase; header.classList.add('paused'); }
|
||||
else { if (state.phase === 'paused') state.phase = 'playing'; header.classList.remove('paused'); state.lastTickMs = 0; }
|
||||
updatePauseCenter();
|
||||
});
|
||||
});
|
||||
|
||||
// Pause-Zeichen zentral in der Szene: sichtbar bei speed=0, sonst aus.
|
||||
// Klick entspricht dem Druck auf den 1×-Speed-Button.
|
||||
const pauseCenter = $('#pause-center');
|
||||
function updatePauseCenter() {
|
||||
if (!pauseCenter) return;
|
||||
const visible = state && state.speed === 0
|
||||
&& (state.phase === 'playing' || state.phase === 'paused' || state.phase === 'tutorial');
|
||||
pauseCenter.style.display = visible ? '' : 'none';
|
||||
}
|
||||
if (pauseCenter) {
|
||||
pauseCenter.addEventListener('click', () => {
|
||||
const btn = document.querySelector('[data-speed="1"]');
|
||||
if (btn) btn.click();
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
|
||||
if (e.key === 'p' || e.key === 'P') document.querySelector('[data-speed="0"]').click();
|
||||
|
||||
Reference in New Issue
Block a user