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() {
|
||||
const g = new THREE.Group();
|
||||
// Asphalt bleibt als Grundplatte
|
||||
const asphalt = new THREE.Mesh(
|
||||
new THREE.BoxGeometry(1.4, 0.04, 0.45),
|
||||
new THREE.MeshStandardMaterial({ color: 0x3a3a3a, roughness: 1 })
|
||||
);
|
||||
asphalt.position.y = 0.02; g.add(asphalt);
|
||||
for (let i = -2; i <= 2; i++) {
|
||||
const stripe = new THREE.Mesh(
|
||||
new THREE.BoxGeometry(0.18, 0.005, 0.04),
|
||||
new THREE.MeshStandardMaterial({ color: 0xf2f2f2, roughness: 1 })
|
||||
);
|
||||
stripe.position.set(i * 0.28, 0.045, 0); g.add(stripe);
|
||||
}
|
||||
// Durchgehende weiße Mittellinie: eine einzige Box über die volle
|
||||
// Asphalt-Länge. Bei snap-spacing 1.288 (Überlappung 8 %) greifen
|
||||
// benachbarte Segmente ineinander → visuell eine durchlaufende Linie.
|
||||
const centerline = new THREE.Mesh(
|
||||
new THREE.BoxGeometry(1.4, 0.006, 0.05),
|
||||
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]) {
|
||||
const post = new THREE.Mesh(
|
||||
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 };
|
||||
mesh.position.set(p.x, p.y, p.z);
|
||||
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);
|
||||
placedMeshes.set(key, mesh);
|
||||
});
|
||||
@@ -4171,6 +4182,76 @@ const villageHouses = []; // stabile Liste, nur Visibility toggelt mit Populat
|
||||
const smokePuffs = []; // [{mesh, age, lifetime, vy}]
|
||||
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() {
|
||||
// Alte Häuser + Puffs raus
|
||||
for (const h of villageHouses) villageGroup.remove(h);
|
||||
@@ -4760,6 +4841,7 @@ function startGame(levelId) {
|
||||
// Start-Dorf aufstellen + Vulkan-Schutzwall zurücksetzen (Engine hatte
|
||||
// hasMountainShield in createGame nicht gesetzt → false/undefined).
|
||||
buildVillage();
|
||||
buildBeachLife();
|
||||
syncMountainShield();
|
||||
syncCitizenBeach();
|
||||
syncMeasureMeshes(); // die drei Start-Bäume sichtbar machen
|
||||
@@ -5064,6 +5146,7 @@ function deserializeState(d) {
|
||||
}
|
||||
syncMeasureMeshes();
|
||||
buildVillage(); // Dorf passend zur geladenen Bevölkerung
|
||||
buildBeachLife(); // Strandleben beim Laden ebenfalls neu setzen
|
||||
syncMountainShield(); // Schutzwall-Flag aus Save übernehmen
|
||||
const badge = $('#level-badge');
|
||||
if (badge) badge.textContent = 'Level ' + state.difficulty + ' · ' + state.diff.label;
|
||||
|
||||
Reference in New Issue
Block a user