Klima 3D: Strandleben winzig + Radweg-Mittellinie durchgehend + Airport-Sync
- Strandleben (beachLifeGroup): 12 winzige Figuren / Sonnenschirme / Strandhandtücher am Küstenband (Terrain 0.20..0.55), Scale 0.55..0.90. makeBeachFigure / makeBeachUmbrella / makeBeachTowel; buildBeachLife wird von startGame und deserializeState gerufen. - Radwege: 5 einzelne Stripes durch EINE durchgehende weiße Mittellinie ersetzt (Box 1.4×0.006×0.05). Bei snap-Spacing 0.92 × BIKE_LENGTH überlappen sich die Mittellinien benachbarter Segmente zu einer visuell kontinuierlichen Linie entlang der Küstenbiegung. - syncMeasureMeshes erzwingt für Airport-Meshes jetzt IMMER die away-from-volcano-Rotation (auch für alte Saves, die noch mit zufälliger Rotation gespeichert wurden). inst.rotation wird dabei korrigiert, damit spätere Saves konsistent sind. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2956,18 +2956,21 @@ function makeCloudSeeder() {
|
|||||||
|
|
||||||
function makeBikePath() {
|
function makeBikePath() {
|
||||||
const g = new THREE.Group();
|
const g = new THREE.Group();
|
||||||
|
// Asphalt bleibt als Grundplatte
|
||||||
const asphalt = new THREE.Mesh(
|
const asphalt = new THREE.Mesh(
|
||||||
new THREE.BoxGeometry(1.4, 0.04, 0.45),
|
new THREE.BoxGeometry(1.4, 0.04, 0.45),
|
||||||
new THREE.MeshStandardMaterial({ color: 0x3a3a3a, roughness: 1 })
|
new THREE.MeshStandardMaterial({ color: 0x3a3a3a, roughness: 1 })
|
||||||
);
|
);
|
||||||
asphalt.position.y = 0.02; g.add(asphalt);
|
asphalt.position.y = 0.02; g.add(asphalt);
|
||||||
for (let i = -2; i <= 2; i++) {
|
// Durchgehende weiße Mittellinie: eine einzige Box über die volle
|
||||||
const stripe = new THREE.Mesh(
|
// Asphalt-Länge. Bei snap-spacing 1.288 (Überlappung 8 %) greifen
|
||||||
new THREE.BoxGeometry(0.18, 0.005, 0.04),
|
// benachbarte Segmente ineinander → visuell eine durchlaufende Linie.
|
||||||
new THREE.MeshStandardMaterial({ color: 0xf2f2f2, roughness: 1 })
|
const centerline = new THREE.Mesh(
|
||||||
);
|
new THREE.BoxGeometry(1.4, 0.006, 0.05),
|
||||||
stripe.position.set(i * 0.28, 0.045, 0); g.add(stripe);
|
new THREE.MeshStandardMaterial({ color: 0xf8f8f8, roughness: 1 })
|
||||||
}
|
);
|
||||||
|
centerline.position.y = 0.045; g.add(centerline);
|
||||||
|
// Zwei Rad-Pictogramm-Stelen am Rand für Deko
|
||||||
for (const sx of [-0.55, 0.55]) {
|
for (const sx of [-0.55, 0.55]) {
|
||||||
const post = new THREE.Mesh(
|
const post = new THREE.Mesh(
|
||||||
new THREE.CylinderGeometry(0.02, 0.025, 0.22, 5),
|
new THREE.CylinderGeometry(0.02, 0.025, 0.22, 5),
|
||||||
@@ -3114,6 +3117,14 @@ function syncMeasureMeshes() {
|
|||||||
const p = inst.pos || { x: 0, y: 0, z: 0 };
|
const p = inst.pos || { x: 0, y: 0, z: 0 };
|
||||||
mesh.position.set(p.x, p.y, p.z);
|
mesh.position.set(p.x, p.y, p.z);
|
||||||
if (typeof inst.rotation === 'number') mesh.rotation.y = inst.rotation;
|
if (typeof inst.rotation === 'number') mesh.rotation.y = inst.rotation;
|
||||||
|
// Airport: Runway-Ausrichtung IMMER vom Vulkan weg erzwingen
|
||||||
|
// (greift auch bei alten Saves mit zufälliger Rotation).
|
||||||
|
if (id === 'airport') {
|
||||||
|
const dx = p.x - VOLCANO_X;
|
||||||
|
const dz = p.z - VOLCANO_Z;
|
||||||
|
mesh.rotation.y = Math.atan2(-dz, dx);
|
||||||
|
inst.rotation = mesh.rotation.y;
|
||||||
|
}
|
||||||
measuresGroup.add(mesh);
|
measuresGroup.add(mesh);
|
||||||
placedMeshes.set(key, mesh);
|
placedMeshes.set(key, mesh);
|
||||||
});
|
});
|
||||||
@@ -4171,6 +4182,76 @@ const villageHouses = []; // stabile Liste, nur Visibility toggelt mit Populat
|
|||||||
const smokePuffs = []; // [{mesh, age, lifetime, vy}]
|
const smokePuffs = []; // [{mesh, age, lifetime, vy}]
|
||||||
const smokeGeom = new THREE.SphereGeometry(0.08, 6, 5);
|
const smokeGeom = new THREE.SphereGeometry(0.08, 6, 5);
|
||||||
|
|
||||||
|
// --- Strandleben: winzige Figuren + Sonnenschirme am Strand ---
|
||||||
|
// Rein dekorativ, wird einmal beim Level-Start aufgebaut und bleibt
|
||||||
|
// stehen. Spawnt auf dem Küstenband (Terrain-Höhe 0.25..0.45).
|
||||||
|
const beachLifeGroup = new THREE.Group();
|
||||||
|
scene.add(beachLifeGroup);
|
||||||
|
function makeBeachFigure() {
|
||||||
|
const g = new THREE.Group();
|
||||||
|
const colors = [0xd04040, 0x2a60a0, 0xe8c080, 0x60a050, 0xc0a040];
|
||||||
|
const body = new THREE.Mesh(
|
||||||
|
new THREE.BoxGeometry(0.06, 0.14, 0.04),
|
||||||
|
new THREE.MeshStandardMaterial({ color: colors[Math.floor(Math.random() * colors.length)], roughness: 1 })
|
||||||
|
);
|
||||||
|
body.position.y = 0.07; g.add(body);
|
||||||
|
const head = new THREE.Mesh(
|
||||||
|
new THREE.SphereGeometry(0.03, 6, 4),
|
||||||
|
new THREE.MeshStandardMaterial({ color: 0xe8c8a0, roughness: 1 })
|
||||||
|
);
|
||||||
|
head.position.y = 0.17; g.add(head);
|
||||||
|
return g;
|
||||||
|
}
|
||||||
|
function makeBeachUmbrella() {
|
||||||
|
const g = new THREE.Group();
|
||||||
|
const pole = new THREE.Mesh(
|
||||||
|
new THREE.CylinderGeometry(0.006, 0.006, 0.18, 6),
|
||||||
|
new THREE.MeshStandardMaterial({ color: 0x8a6040, roughness: 1 })
|
||||||
|
);
|
||||||
|
pole.position.y = 0.09; g.add(pole);
|
||||||
|
const colors = [0xd04040, 0xe8a040, 0x4a8aa0, 0xc060a0];
|
||||||
|
const shade = new THREE.Mesh(
|
||||||
|
new THREE.ConeGeometry(0.10, 0.05, 8),
|
||||||
|
new THREE.MeshStandardMaterial({ color: colors[Math.floor(Math.random() * colors.length)], roughness: 1 })
|
||||||
|
);
|
||||||
|
shade.position.y = 0.21; g.add(shade);
|
||||||
|
return g;
|
||||||
|
}
|
||||||
|
function makeBeachTowel() {
|
||||||
|
const g = new THREE.Group();
|
||||||
|
const colors = [0xd04040, 0xf0d040, 0x4a8aa0, 0xc060a0, 0x40c080];
|
||||||
|
const towel = new THREE.Mesh(
|
||||||
|
new THREE.BoxGeometry(0.14, 0.003, 0.08),
|
||||||
|
new THREE.MeshStandardMaterial({ color: colors[Math.floor(Math.random() * colors.length)], roughness: 1 })
|
||||||
|
);
|
||||||
|
towel.position.y = 0.002; g.add(towel);
|
||||||
|
return g;
|
||||||
|
}
|
||||||
|
function buildBeachLife() {
|
||||||
|
while (beachLifeGroup.children.length) beachLifeGroup.remove(beachLifeGroup.children[0]);
|
||||||
|
// 12 Stellen am Küstenband, zufällig auf Figuren/Schirme/Handtücher verteilt
|
||||||
|
for (let i = 0; i < 12; i++) {
|
||||||
|
let x = 0, z = 0, y = 0;
|
||||||
|
for (let tries = 0; tries < 20; tries++) {
|
||||||
|
const a = Math.random() * Math.PI * 2;
|
||||||
|
const rScale = 0.82 + Math.random() * 0.08;
|
||||||
|
x = Math.cos(a) * ISLAND_HALF_WIDTH * rScale;
|
||||||
|
z = Math.sin(a) * ISLAND_HALF_LENGTH * rScale;
|
||||||
|
y = terrainHeight(x, z);
|
||||||
|
if (y >= 0.2 && y <= 0.55) break;
|
||||||
|
}
|
||||||
|
if (y < 0.2) continue;
|
||||||
|
const roll = Math.random();
|
||||||
|
const piece = roll < 0.45 ? makeBeachFigure()
|
||||||
|
: roll < 0.80 ? makeBeachUmbrella()
|
||||||
|
: makeBeachTowel();
|
||||||
|
piece.position.set(x, y, z);
|
||||||
|
piece.rotation.y = Math.random() * Math.PI * 2;
|
||||||
|
piece.scale.setScalar(0.55 + Math.random() * 0.35); // winzig
|
||||||
|
beachLifeGroup.add(piece);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function buildVillage() {
|
function buildVillage() {
|
||||||
// Alte Häuser + Puffs raus
|
// Alte Häuser + Puffs raus
|
||||||
for (const h of villageHouses) villageGroup.remove(h);
|
for (const h of villageHouses) villageGroup.remove(h);
|
||||||
@@ -4760,6 +4841,7 @@ function startGame(levelId) {
|
|||||||
// Start-Dorf aufstellen + Vulkan-Schutzwall zurücksetzen (Engine hatte
|
// Start-Dorf aufstellen + Vulkan-Schutzwall zurücksetzen (Engine hatte
|
||||||
// hasMountainShield in createGame nicht gesetzt → false/undefined).
|
// hasMountainShield in createGame nicht gesetzt → false/undefined).
|
||||||
buildVillage();
|
buildVillage();
|
||||||
|
buildBeachLife();
|
||||||
syncMountainShield();
|
syncMountainShield();
|
||||||
syncCitizenBeach();
|
syncCitizenBeach();
|
||||||
syncMeasureMeshes(); // die drei Start-Bäume sichtbar machen
|
syncMeasureMeshes(); // die drei Start-Bäume sichtbar machen
|
||||||
@@ -5064,6 +5146,7 @@ function deserializeState(d) {
|
|||||||
}
|
}
|
||||||
syncMeasureMeshes();
|
syncMeasureMeshes();
|
||||||
buildVillage(); // Dorf passend zur geladenen Bevölkerung
|
buildVillage(); // Dorf passend zur geladenen Bevölkerung
|
||||||
|
buildBeachLife(); // Strandleben beim Laden ebenfalls neu setzen
|
||||||
syncMountainShield(); // Schutzwall-Flag aus Save übernehmen
|
syncMountainShield(); // Schutzwall-Flag aus Save übernehmen
|
||||||
const badge = $('#level-badge');
|
const badge = $('#level-badge');
|
||||||
if (badge) badge.textContent = 'Level ' + state.difficulty + ' · ' + state.diff.label;
|
if (badge) badge.textContent = 'Level ' + state.difficulty + ' · ' + state.diff.label;
|
||||||
|
|||||||
Reference in New Issue
Block a user