290 lines
11 KiB
GDScript
290 lines
11 KiB
GDScript
extends Node3D
|
|
class_name EarthWorld
|
|
## Die Erde: eine Kleinstadt mit vier Menschen, die ihren Tag leben.
|
|
|
|
var spawn := Vector3(0, 1.4, 12.0)
|
|
var humans: Array = []
|
|
var spieler: Node3D = null # setzt main, für den Etagenschnitt
|
|
var _bauten: Array = [] # alles, was der Etagenschnitt aufschneidet
|
|
|
|
var _sun: DirectionalLight3D
|
|
var _sky: ProceduralSkyMaterial
|
|
var _env: Environment
|
|
var _nachtlichter: Array = []
|
|
|
|
|
|
func _ready() -> void:
|
|
_environment()
|
|
_boden()
|
|
_bebauung()
|
|
_orte()
|
|
_park()
|
|
_saeule()
|
|
_menschen()
|
|
_himmel_aktualisieren()
|
|
|
|
|
|
func _environment() -> void:
|
|
var we := WorldEnvironment.new()
|
|
_env = Environment.new()
|
|
_env.background_mode = Environment.BG_SKY
|
|
var sky := Sky.new()
|
|
_sky = ProceduralSkyMaterial.new()
|
|
sky.sky_material = _sky
|
|
_env.sky = sky
|
|
_env.ambient_light_source = Environment.AMBIENT_SOURCE_SKY
|
|
_env.glow_enabled = true
|
|
_env.glow_intensity = 0.35
|
|
_env.fog_enabled = true
|
|
_env.fog_density = 0.0012
|
|
we.environment = _env
|
|
add_child(we)
|
|
|
|
_sun = DirectionalLight3D.new()
|
|
_sun.shadow_enabled = true
|
|
_sun.shadow_blur = 1.5
|
|
add_child(_sun)
|
|
|
|
|
|
func _boden() -> void:
|
|
Prims.box(self, Vector3(0, -0.53, 0), Vector3(300, 1, 300), Color(0.42, 0.48, 0.40))
|
|
_strassen()
|
|
|
|
|
|
## Arterien breit, roji schmal -- beide als flaches Band mit Belag.
|
|
func _strassen() -> void:
|
|
for st in HumanDB.STRASSEN:
|
|
var a: Vector3 = st["a"]
|
|
var b: Vector3 = st["b"]
|
|
var d := b - a
|
|
if d.length() < 0.2:
|
|
continue
|
|
var breit := float(st["breite"])
|
|
var farbe := Color(0.62, 0.60, 0.58) if breit > 5.0 else Color(0.55, 0.53, 0.52)
|
|
var band := Prims.box(self, (a + b) * 0.5 + Vector3(0, 0.02, 0),
|
|
Vector3(breit, 0.06, d.length() + breit), farbe, false)
|
|
band.rotation.y = atan2(d.x, d.z)
|
|
|
|
|
|
## Alles, was nicht Wohnung oder Sonderbau ist, steht als geschlossener Klotz
|
|
## mit ein bis vier Stockwerken da. Erst dadurch wird der Bezirk dicht.
|
|
func _bebauung() -> void:
|
|
var rng := RandomNumberGenerator.new()
|
|
rng.seed = 8123
|
|
var belegt: Dictionary = {}
|
|
for key in HumanDB.PLACES:
|
|
var s: Array = HumanDB.PLACES[key]["size"]
|
|
if float(s[0]) > 0.0:
|
|
belegt[Orte.center(HumanDB.PLACES, key).snapped(Vector3.ONE * 0.1)] = true
|
|
for p in HumanDB.PARZELLEN:
|
|
var c: Vector3 = p["pos"]
|
|
if belegt.has(c.snapped(Vector3.ONE * 0.1)):
|
|
continue
|
|
var g: Vector2 = p["size"]
|
|
if minf(g.x, g.y) < 3.0:
|
|
continue
|
|
var n := 1 + rng.randi_range(0, 3)
|
|
var hoehe := Orte.hoehe(n)
|
|
var ton := Color.from_hsv(fmod(0.07 + rng.randf() * 0.12, 1.0),
|
|
0.10 + rng.randf() * 0.14, 0.62 + rng.randf() * 0.26)
|
|
var bau := Node3D.new()
|
|
add_child(bau)
|
|
Prims.box(bau, c + Vector3(0, hoehe * 0.5, 0), Vector3(g.x, hoehe, g.y), ton)
|
|
for f in range(n):
|
|
_fenster(bau, c + Vector3(0, Orte.hoehe(f), 0), g, ton)
|
|
Prims.box(bau, c + Vector3(0, hoehe + 0.2, 0),
|
|
Vector3(g.x + 0.5, 0.4, g.y + 0.5), ton.darkened(0.25))
|
|
if rng.randf() < 0.35:
|
|
Prims.box(bau, c + Vector3(0, hoehe + 0.9, 0),
|
|
Vector3(g.x * 0.4, 1.0, g.y * 0.4), ton.darkened(0.1))
|
|
_bauten.append({"c": c, "size": g, "hoch": hoehe, "etagen": [bau], "dach": [],
|
|
"waende": [], "voll": true})
|
|
|
|
|
|
## Wohnungen und Sonderbauten: geschlossene Häuser mit Stockwerken, Treppenhaus
|
|
## und Möbeln. Der Geist gleitet hinein, der Etagenschnitt öffnet sie ihm.
|
|
func _orte() -> void:
|
|
for key in HumanDB.PLACES:
|
|
var pl: Dictionary = HumanDB.PLACES[key]
|
|
var s: Array = pl["size"]
|
|
if float(s[0]) <= 0.0:
|
|
continue
|
|
var c := Orte.center(HumanDB.PLACES, key)
|
|
var groesse := Vector3(float(s[0]), Orte.ETAGE, float(s[1]))
|
|
var farbe: Color = pl["color"]
|
|
var tuer: float = maxf(1.6, minf(3.0, minf(groesse.x, groesse.z) - 3.0))
|
|
var n := Orte.etagen(HumanDB.PLACES, key)
|
|
var etagen: Array = []
|
|
var waende: Array = []
|
|
for f in range(n):
|
|
var eb := Node3D.new()
|
|
add_child(eb)
|
|
var cf := c + Vector3(0, Orte.hoehe(f), 0)
|
|
var ton := farbe.lightened(0.06 * float(f))
|
|
waende.append(Prims.room(eb, cf, groesse, ton, farbe.darkened(0.35),
|
|
Orte.DIR_VEC[pl["dir"]] if f == 0 else Vector3.ZERO, tuer, true))
|
|
_fenster(eb, cf, Vector2(groesse.x, groesse.z), ton)
|
|
_einrichtung(eb, cf, groesse, farbe, key if f == 0 or HumanDB.ETAGEN_ARBEIT.has(key)
|
|
else "heim_")
|
|
if f + 1 < n:
|
|
_treppe(eb, key, f)
|
|
var licht := OmniLight3D.new()
|
|
licht.position = cf + Vector3(0, 2.2, 0)
|
|
licht.omni_range = 12.0
|
|
licht.light_color = Color(1.0, 0.85, 0.6)
|
|
licht.light_energy = 0.0
|
|
eb.add_child(licht)
|
|
_nachtlichter.append(licht)
|
|
etagen.append(eb)
|
|
var dach := Node3D.new()
|
|
add_child(dach)
|
|
Prims.box(dach, c + Vector3(0, Orte.hoehe(n) + 0.1, 0),
|
|
Vector3(groesse.x + 0.4, 0.2, groesse.z + 0.4), farbe.darkened(0.3))
|
|
Prims.label(self, c + Vector3(0, Orte.hoehe(n) + 1.6, 0), str(pl["name"]), 0.55,
|
|
Color(1, 1, 0.9))
|
|
_bauten.append({"c": c, "size": Vector2(groesse.x, groesse.z), "hoch": Orte.hoehe(n),
|
|
"etagen": etagen, "dach": [dach], "waende": waende})
|
|
|
|
|
|
## Ein Fensterband je Wand, damit man die Stockwerke von außen zählt.
|
|
func _fenster(parent: Node, cf: Vector3, g: Vector2, ton: Color) -> void:
|
|
var glas := ton.darkened(0.55).lerp(Color(0.35, 0.45, 0.6), 0.5)
|
|
for d: Vector3 in [Vector3.FORWARD, Vector3.BACK, Vector3.LEFT, Vector3.RIGHT]:
|
|
var quer := absf(d.z) > 0.0
|
|
var laenge: float = (g.x if quer else g.y) - 2.4
|
|
if laenge < 1.0:
|
|
continue
|
|
var off := Vector3(d.x * (g.x * 0.5 + 0.05), 1.75, d.z * (g.y * 0.5 + 0.05))
|
|
Prims.box(parent, cf + off, Vector3(laenge, 0.9, 0.08) if quer else Vector3(0.08, 0.9, laenge),
|
|
glas, false)
|
|
|
|
|
|
## Eine Rampe von Podest zu Podest -- die Treppe, die die Bewohner hochgehen.
|
|
func _treppe(parent: Node, key: String, f: int) -> void:
|
|
var a := Orte.treppe(HumanDB.PLACES, key, f)
|
|
var b := Orte.treppe(HumanDB.PLACES, key, f + 1)
|
|
var d := b - a
|
|
var stufe := Prims.box(parent, (a + b) * 0.5, Vector3(1.2, 0.18, d.length()),
|
|
Color(0.55, 0.50, 0.46), false)
|
|
stufe.basis = Basis.looking_at(d.normalized(), Vector3.UP)
|
|
Prims.box(parent, b + Vector3(0, -0.09, 0), Vector3(1.4, 0.18, 1.4), Color(0.55, 0.50, 0.46), false)
|
|
|
|
|
|
## Ein paar Möbel, damit man sieht, wofür ein Raum da ist.
|
|
func _einrichtung(parent: Node, c: Vector3, groesse: Vector3, farbe: Color, key: String) -> void:
|
|
var w := maxf(0.5, groesse.x * 0.5 - 2.0)
|
|
var h := maxf(0.5, groesse.z * 0.5 - 2.0)
|
|
if key.begins_with("heim_"):
|
|
Prims.box(parent, c + Vector3(-w * 0.6, 0.35, -h * 0.5), Vector3(1.9, 0.7, 3.0),
|
|
Color(0.85, 0.85, 0.90))
|
|
Prims.box(parent, c + Vector3(w * 0.6, 0.75, 0), Vector3(1.5, 0.15, 2.2),
|
|
farbe.darkened(0.5))
|
|
Prims.box(parent, c + Vector3(w * 0.6, 1.05, 0), Vector3(0.8, 0.55, 0.1),
|
|
Color(0.2, 0.25, 0.3))
|
|
elif key == "cafe":
|
|
for i in range(3):
|
|
Prims.cyl(parent, c + Vector3(-w * 0.5 + float(i) * w * 0.5, 0.4, 0.4), 0.7, 0.8,
|
|
Color(0.55, 0.40, 0.30))
|
|
Prims.box(parent, c + Vector3(0, 0.55, -h * 0.7), Vector3(groesse.x - 3.0, 1.1, 0.8),
|
|
Color(0.45, 0.32, 0.24))
|
|
elif key == "buero":
|
|
for i in range(3):
|
|
Prims.box(parent, c + Vector3(-w * 0.5 + float(i) * w * 0.5, 0.75, 0),
|
|
Vector3(1.6, 0.15, 1.2), Color(0.75, 0.75, 0.78))
|
|
elif key == "baeckerei":
|
|
Prims.box(parent, c + Vector3(0, 0.55, h * 0.5), Vector3(groesse.x - 3.0, 1.1, 1.0),
|
|
Color(0.70, 0.55, 0.35))
|
|
Prims.box(parent, c + Vector3(0, 0.85, -h * 0.6), Vector3(2.6, 1.7, 1.2),
|
|
Color(0.4, 0.4, 0.42))
|
|
elif key == "uni":
|
|
for i in range(3):
|
|
Prims.box(parent, c + Vector3(0, 0.5, -h * 0.6 + float(i) * h * 0.6),
|
|
Vector3(groesse.x - 3.0, 0.2, 0.7), Color(0.66, 0.62, 0.58))
|
|
Prims.box(parent, c + Vector3(0, 1.1, -h), Vector3(groesse.x * 0.55, 1.6, 0.15),
|
|
Color(0.15, 0.25, 0.20))
|
|
elif key == "werkstatt":
|
|
Prims.box(parent, c + Vector3(0, 0.6, 0), Vector3(groesse.x - 4.0, 1.2, 1.4),
|
|
Color(0.52, 0.48, 0.46))
|
|
|
|
|
|
## Der mittlere Block bleibt frei: Rasen, Teich, Bäume, Bänke. Der Park ist
|
|
## der einzige Ort im Bezirk, an dem nicht gebaut wird.
|
|
func _park() -> void:
|
|
var rng := RandomNumberGenerator.new()
|
|
rng.seed = 3
|
|
var halb: float = (float(HumanDB.ACHSEN[2]) - float(HumanDB.ACHSEN[1])) * 0.5 \
|
|
- HumanDB.ARTERIE * 0.5
|
|
Prims.box(self, Vector3(0, 0.01, 0), Vector3(halb * 2.0, 0.05, halb * 2.0),
|
|
Color(0.40, 0.58, 0.34), false)
|
|
Prims.cyl(self, Vector3(-halb * 0.4, 0.06, halb * 0.25), halb * 0.28, 0.1,
|
|
Color(0.30, 0.50, 0.72), false)
|
|
for i in range(26):
|
|
var p := Vector3(rng.randf_range(-halb, halb), 0, rng.randf_range(-halb, halb))
|
|
if p.distance_to(Vector3(-halb * 0.4, 0, halb * 0.25)) < halb * 0.38:
|
|
continue
|
|
if p.distance_to(Vector3(0, 0, 12.0)) < 6.0:
|
|
continue
|
|
var hoehe := rng.randf_range(2.6, 5.0)
|
|
Prims.cyl(self, p + Vector3(0, hoehe * 0.5, 0), 0.3, hoehe, Color(0.35, 0.26, 0.18))
|
|
Prims.sphere(self, p + Vector3(0, hoehe + 0.9, 0), rng.randf_range(1.3, 2.2),
|
|
Color(0.24, 0.48 + rng.randf() * 0.22, 0.22))
|
|
for i in range(5):
|
|
var a := TAU * float(i) / 5.0 + 0.4
|
|
var p := Vector3(cos(a) * halb * 0.62, 0.45, sin(a) * halb * 0.62)
|
|
var b := Prims.box(self, p, Vector3(2.4, 0.15, 0.7), Color(0.55, 0.40, 0.28))
|
|
b.rotation.y = -a
|
|
Prims.label(self, Vector3(0, 5.0, halb - 3.0), "Stadtpark", 0.6, Color(1, 1, 0.9))
|
|
|
|
|
|
func _saeule() -> void:
|
|
var pos := Vector3(0, 0, 12.0)
|
|
Prims.cyl(self, pos + Vector3(0, 0.2, 0), 2.6, 0.4, Color(0.50, 0.48, 0.55), false)
|
|
Prims.cyl(self, pos + Vector3(0, 2.2, 0), 0.7, 4.0, Color(0.55, 0.80, 1.0), false, 1.6)
|
|
Prims.sphere(self, pos + Vector3(0, 4.6, 0), 0.9, Color(0.75, 0.92, 1.0), 2.2)
|
|
Prims.label(self, pos + Vector3(0, 6.0, 0), "Rückkehr in die Traumwelt", 0.4,
|
|
Color(0.75, 0.95, 1))
|
|
var n := Node3D.new()
|
|
n.position = pos + Vector3(0, 1.6, 0)
|
|
add_child(n)
|
|
n.add_to_group("interactable")
|
|
n.set_meta("kind", "traum")
|
|
n.set_meta("arg", "")
|
|
n.set_meta("titel", "Rückkehr in die Traumwelt")
|
|
|
|
|
|
func _menschen() -> void:
|
|
for i in range(HumanDB.LIST.size()):
|
|
var m := Bewohner.new()
|
|
add_child(m)
|
|
m.setup(HumanDB.LIST[i], i, HumanDB.PLACES, HumanDB.NETZ, HumanDB.ANKER)
|
|
humans.append(m)
|
|
|
|
|
|
func _process(_delta: float) -> void:
|
|
_himmel_aktualisieren()
|
|
if spieler != null:
|
|
Prims.schnitt(_bauten, spieler.global_position, spieler.cam.global_position)
|
|
|
|
|
|
func _himmel_aktualisieren() -> void:
|
|
var t: float = Game.time_h
|
|
var sonne := clampf(sin((t - 6.0) / 12.0 * PI), 0.0, 1.0)
|
|
_sun.rotation_degrees = Vector3(-((t - 6.0) / 12.0) * 180.0, -35.0, 0.0)
|
|
_sun.light_energy = 0.25 + sonne * 1.0
|
|
_sun.light_color = Color(1.0, 0.75 + sonne * 0.25, 0.55 + sonne * 0.4)
|
|
|
|
var tag_top := Color(0.30, 0.55, 0.90)
|
|
var nacht_top := Color(0.03, 0.04, 0.12)
|
|
var tag_hor := Color(0.75, 0.85, 0.95)
|
|
var nacht_hor := Color(0.10, 0.10, 0.25)
|
|
_sky.sky_top_color = nacht_top.lerp(tag_top, sonne)
|
|
_sky.sky_horizon_color = nacht_hor.lerp(tag_hor, sonne)
|
|
_sky.ground_horizon_color = nacht_hor.lerp(Color(0.55, 0.60, 0.55), sonne)
|
|
_sky.ground_bottom_color = nacht_top.lerp(Color(0.30, 0.38, 0.28), sonne)
|
|
_env.ambient_light_energy = 0.45 + sonne * 0.8
|
|
_env.fog_light_color = nacht_hor.lerp(tag_hor, sonne)
|
|
|
|
var nacht := 1.0 - sonne
|
|
for l in _nachtlichter:
|
|
l.light_energy = clampf(nacht * 2.4 - 0.3, 0.0, 2.2)
|