extends Node3D class_name DreamWorld ## Die Traumwelt: eine schwebende Insel, die ein Hügel ist. Unten der Dorfplatz ## um den Traumbaum, der Hörsaal und das Tor; hangaufwärts wächst das Dorf, ## ein Haus je erfülltem Traum, jedes eine Terrasse höher. var spawn := Vector3(0, 2.0, 20.0) var bewohner: Array = [] var spieler: Node3D = null # setzt main, für den Etagenschnitt var nav_map: RID # Navigationsnetz über Insel, Wege und Häuser var _bauten: Array = [] var _lichter: Array = [] var _freihalten: Array[Vector3] = [] var _we: WorldEnvironment var _env: Environment ## Nur die aktive Welt stellt den Himmel; zwei WorldEnvironments vertragen sich nicht. func aktiv_setzen(an: bool) -> void: _we.environment = _env if an else null func _ready() -> void: _environment() _insel() _wege() _haeuser() _lehrpulte() _schwarzes_brett() _lichtung() _tor_zur_erde() _gruen() _navigation() _wesen() ## Navigationsnetz aus den Kollisionsformen: Insel, Häuser, Bäume, Laternen. func _navigation() -> void: var region := NavigationRegion3D.new() add_child(region) var nm := NavigationMesh.new() nm.geometry_parsed_geometry_type = NavigationMesh.PARSED_GEOMETRY_STATIC_COLLIDERS nm.agent_radius = 0.4 nm.agent_height = 1.6 nm.agent_max_climb = 0.35 nm.agent_max_slope = 40.0 nm.cell_height = 0.1 var quelle := NavigationMeshSourceGeometryData3D.new() NavigationServer3D.parse_source_geometry_data(nm, quelle, self) NavigationServer3D.bake_from_source_geometry_data(nm, quelle) nav_map = NavigationServer3D.map_create() NavigationServer3D.map_set_active(nav_map, true) NavigationServer3D.map_set_cell_height(nav_map, 0.1) region.set_navigation_map(nav_map) region.navigation_mesh = nm NavigationServer3D.map_set_use_async_iterations(nav_map, false) NavigationServer3D.map_force_update(nav_map) ## Dämmerung: tiefes Blau oben, warmes Lavendel am Horizont, Nebel im Tal. func _environment() -> void: _we = WorldEnvironment.new() var we := _we _env = Environment.new() var env := _env env.background_mode = Environment.BG_SKY var sky := Sky.new() var mat := ProceduralSkyMaterial.new() mat.sky_top_color = Color(0.05, 0.05, 0.19) mat.sky_horizon_color = Color(0.58, 0.34, 0.56) mat.ground_bottom_color = Color(0.04, 0.03, 0.11) mat.ground_horizon_color = Color(0.30, 0.19, 0.42) mat.sun_angle_max = 30.0 sky.sky_material = mat env.sky = sky env.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR env.ambient_light_color = Color(0.58, 0.50, 0.78) env.ambient_light_energy = 1.5 env.glow_enabled = true env.glow_intensity = 0.42 env.glow_bloom = 0.08 env.fog_enabled = true env.fog_light_color = Color(0.46, 0.30, 0.56) env.fog_density = 0.0011 we.environment = env add_child(we) var sun := DirectionalLight3D.new() sun.rotation_degrees = Vector3(-34, 40, 0) sun.light_energy = 1.3 sun.light_color = Color(1.0, 0.84, 0.74) sun.shadow_enabled = true sun.shadow_blur = 1.2 sun.directional_shadow_max_distance = 110.0 add_child(sun) # ------------------------------------------------------------------ Insel ## Der Hügel als Höhenfeld: ein Gitter, das die Höhe von DreamDB abfragt -- ## innen eben, außen ansteigend, in jedem Gebäude eine Terrasse. Darauf läuft ## der Spieler (Kollision) und die Bewohner (Bodenhöhe je Bild). func _insel() -> void: var radius := Game.insel_radius() var schritt := 1.5 var n := int(ceil(radius * 2.0 / schritt)) + 7 var halb := float(n - 1) * schritt * 0.5 var daten := PackedFloat32Array() daten.resize(n * n) var hoehen: Array = [] for iz in range(n): var zeile: PackedFloat32Array = PackedFloat32Array() zeile.resize(n) for ix in range(n): var x := -halb + float(ix) * schritt var z := -halb + float(iz) * schritt var r := Vector2(x, z).length() var h: float if r <= radius - 4.0: h = DreamDB.hoehe_bei(x, z) elif r <= radius: # der Rand rundet sich ab, statt abzubrechen var t := (r - (radius - 4.0)) / 4.0 h = lerpf(DreamDB.hoehe_bei(x, z), -1.5, t * t) else: # die Kante fällt ab -- die Insel schwebt. Begrenzt, sonst hängt # das quadratische Gitter in den Ecken als Vorhang herab. h = maxf(-1.5 - (r - radius) * 2.4, -12.0) zeile[ix] = h daten[iz * n + ix] = h hoehen.append(zeile) var st := SurfaceTool.new() st.begin(Mesh.PRIMITIVE_TRIANGLES) var tief := Color(0.40, 0.40, 0.62) var hoch := Color(0.70, 0.60, 0.88) var kante := Color(0.16, 0.12, 0.27) var eck := func(ix: int, iz: int) -> void: var x := -halb + float(ix) * schritt var z := -halb + float(iz) * schritt var h: float = hoehen[iz][ix] var hx0: float = hoehen[iz][maxi(ix - 1, 0)] var hx1: float = hoehen[iz][mini(ix + 1, n - 1)] var hz0: float = hoehen[maxi(iz - 1, 0)][ix] var hz1: float = hoehen[mini(iz + 1, n - 1)][ix] var nrm := Vector3(-(hx1 - hx0) / (2.0 * schritt), 1.0, -(hz1 - hz0) / (2.0 * schritt)).normalized() var c: Color = kante if h < 0.0 else tief.lerp(hoch, clampf(h / DreamDB.GIPFEL, 0.0, 1.0)) st.set_color(c) st.set_normal(nrm) st.add_vertex(Vector3(x, h, z)) for iz in range(n - 1): for ix in range(n - 1): var mx := -halb + (float(ix) + 0.5) * schritt var mz := -halb + (float(iz) + 0.5) * schritt if Vector2(mx, mz).length() > radius + 5.0: continue # außerhalb der Kante nichts zeichnen eck.call(ix, iz) eck.call(ix, iz + 1) eck.call(ix + 1, iz) eck.call(ix + 1, iz) eck.call(ix, iz + 1) eck.call(ix + 1, iz + 1) var mi := MeshInstance3D.new() mi.mesh = st.commit() var m := StandardMaterial3D.new() m.vertex_color_use_as_albedo = true m.roughness = 0.92 m.cull_mode = BaseMaterial3D.CULL_DISABLED mi.material_override = m add_child(mi) var body := StaticBody3D.new() var cs := CollisionShape3D.new() var shape := HeightMapShape3D.new() shape.map_width = n shape.map_depth = n shape.map_data = daten cs.shape = shape cs.scale = Vector3(schritt, 1.0, schritt) body.add_child(cs) add_child(body) # der Fels darunter -- klein, die Insel soll schweben, nicht stehen Prims.cyl(self, Vector3(0, -12.0, 0), radius * 0.52, 5.0, Color(0.17, 0.13, 0.28), false) Prims.cyl(self, Vector3(0, -17.0, 0), radius * 0.22, 6.0, Color(0.12, 0.09, 0.21), false) # treibende Lichter statt schwebender Würfel var rng := RandomNumberGenerator.new() rng.seed = 7 for i in range(44): var a := rng.randf() * TAU var r := rng.randf_range(10.0, maxf(24.0, radius - 4.0)) var p := Vector3(cos(a) * r, 0, sin(a) * r) p.y = DreamDB.hoehe_bei(p.x, p.z) + rng.randf_range(2.5, 9.0) var c := Color(0.62 + rng.randf() * 0.3, 0.7 + rng.randf() * 0.25, 1.0) var licht := Prims.sphere(self, p, rng.randf_range(0.14, 0.34), c, 2.4) licht.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF _lichter.append(licht) ## Die gewachsenen Wege folgen dem Hang: jedes Band von Knoten zu Knoten ist ## geneigt. An jedem zweiten Knoten eine Laterne. func _wege() -> void: var farbe := Color(0.70, 0.62, 0.86) for k in DreamDB.NETZ.kanten: var a: Vector3 = DreamDB.NETZ.knoten[k[0]] var b: Vector3 = DreamDB.NETZ.knoten[k[1]] a.y = DreamDB.hoehe_bei(a.x, a.z) + 0.04 b.y = DreamDB.hoehe_bei(b.x, b.z) + 0.04 var d := b - a if d.length() < 0.2: continue var band := Prims.box(self, (a + b) * 0.5, Vector3(2.6, 0.06, d.length()), farbe, false) band.basis = Basis.looking_at(d.normalized(), Vector3.UP) var nr := 0 for k in DreamDB.NETZ.knoten: var p: Vector3 = k p.y = DreamDB.hoehe_bei(p.x, p.z) Prims.cyl(self, p + Vector3(0, 0.05, 0), 1.3, 0.06, farbe, false) # knapp über den Bändern if nr % 2 == 0 and Vector2(p.x, p.z).length() > 12.0: _laterne(p + Vector3(1.6, 0, 0)) nr += 1 ## Eine Laterne: Pfahl und leuchtende Kugel. Kein eigenes Licht -- der Glow ## trägt sie, und so dürfen es viele sein. func _laterne(p: Vector3) -> void: Prims.cyl(self, p + Vector3(0, 1.1, 0), 0.09, 2.2, Color(0.36, 0.30, 0.48), false) var k := Prims.sphere(self, p + Vector3(0, 2.35, 0), 0.24, Color(1.0, 0.86, 0.62), 2.6) k.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF ## Jedes Gebäude steht auf seiner Terrasse: Wände, Dach, Laterne an der Tür, ## Licht innen. Das Schild darüber. func _haeuser() -> void: for key in DreamDB.PLACES: var pl: Dictionary = DreamDB.PLACES[key] var s: Array = pl["size"] if s[0] <= 0.0 or not Game.ort_offen(key): continue var c := Orte.center(DreamDB.PLACES, key) c.y = DreamDB.terrasse(key) var groesse := Vector3(s[0], 3.2, s[1]) var farbe: Color = pl["color"] var waende: Array = Prims.room(self, c, groesse, farbe, farbe.darkened(0.35), Orte.DIR_VEC[pl["dir"]], 3.4, true) var dach := Prims.dach(self, c, groesse, farbe) var schild := Prims.label(self, c + Vector3(0, groesse.y + 4.2, 0), pl["name"], 0.6, Color(1, 0.95, 0.85)) _bauten.append({"c": c, "size": Vector2(groesse.x, groesse.z), "hoch": groesse.y + 3.0, "teile": [dach, schild], "waende": waende}) _einrichtung(c, groesse, farbe, key) var licht := OmniLight3D.new() licht.position = c + Vector3(0, 2.4, 0) licht.omni_range = 16.0 licht.light_energy = 1.8 licht.light_color = farbe.lightened(0.5) add_child(licht) var tuer := Orte.door(DreamDB.PLACES, key) tuer.y = c.y var seite: Vector3 = Orte.DIR_VEC[pl["dir"]] var quer := Vector3(-seite.z, 0, seite.x) _laterne(tuer - seite * 1.2 + quer * 2.4) func _einrichtung(c: Vector3, groesse: Vector3, farbe: Color, key: String) -> void: if key == "hoersaal": _hoersaal_einrichten(c, groesse, farbe, key) return var w := groesse.x * 0.5 - 1.8 var h := groesse.z * 0.5 - 1.8 Prims.cyl(self, c + Vector3(-w, 0.3, -h), 1.4, 0.5, farbe.darkened(0.2), false) match int(key.substr(5)) % 4: 0: # Webstuhl Prims.box(self, c + Vector3(w, 1.1, 0), Vector3(0.3, 2.2, 3.0), farbe.darkened(0.45)) for i in range(5): Prims.box(self, c + Vector3(w, 1.1, -1.2 + i * 0.6), Vector3(0.1, 1.8, 0.06), Color(0.9, 0.85, 1.0), false, 1.4) 1: # Regale voller Fundstücke for i in range(3): Prims.box(self, c + Vector3(w, 0.6 + i * 0.7, 0), Vector3(1.2, 0.12, 4.0), farbe.darkened(0.45)) for j in range(4): Prims.box(self, c + Vector3(w, 0.85 + i * 0.7, -1.5 + j * 1.0), Vector3(0.4, 0.4, 0.4), Color(0.8, 0.9, 1.0), false, 0.8) 2: # Karten und ein Fernrohr Prims.box(self, c + Vector3(w, 0.75, 0), Vector3(1.6, 0.12, 2.6), farbe.darkened(0.45)) var rohr := Prims.cyl(self, c + Vector3(w, 1.6, 0), 0.18, 2.0, Color(0.85, 0.9, 1.0), false, 0.6) rohr.rotation.x = 0.9 _: # Schale mit Licht Prims.cyl(self, c + Vector3(w, 0.5, 0), 1.0, 1.0, farbe.darkened(0.4), false) Prims.sphere(self, c + Vector3(w, 1.4, 0), 0.55, Color(0.8, 1.0, 1.0), 2.0) ## Bankreihen mit Mittelgang, Tafel an der Rückwand. func _hoersaal_einrichten(c: Vector3, groesse: Vector3, farbe: Color, key: String) -> void: var richtung: Vector3 = Orte.DIR_VEC[DreamDB.PLACES[key]["dir"]] * -1.0 var quer := absf(richtung.z) > 0.0 var bank := ((groesse.x if quer else groesse.z) - 5.0) * 0.5 var laengs: Vector3 = Vector3(1, 0, 0) if quer else Vector3(0, 0, 1) for i in range(3): var tief := -3.6 + float(i) * 1.8 for sx: float in [-1.0, 1.0]: var mitte: Vector3 = c + richtung * tief + laengs * (sx * (bank * 0.5 + 1.0)) var sitz: Vector3 = Vector3(bank, 0.25, 0.9) if quer else Vector3(0.9, 0.25, bank) var lehne: Vector3 = Vector3(bank, 0.9, 0.15) if quer else Vector3(0.15, 0.9, bank) Prims.box(self, mitte + Vector3(0, 0.5, 0), sitz, farbe.darkened(0.35)) Prims.box(self, mitte + richtung * 0.7 + Vector3(0, 0.9, 0), lehne, farbe.darkened(0.45)) var tafel: Vector3 = Vector3(groesse.x - 5.0, 2.0, 0.15) if quer \ else Vector3(0.15, 2.0, groesse.z - 5.0) Prims.box(self, c + richtung * 4.4 + Vector3(0, 1.4, 0), tafel, Color(0.16, 0.18, 0.30)) ## Vorn in jedem offenen Lehrort steht ein Pult. func _lehrpulte() -> void: for key in DreamDB.lehrorte(): if not Game.ort_offen(key) or DreamDB.techniken_von(key).is_empty(): continue var pl: Dictionary = DreamDB.PLACES[key] var tief: float = float(pl["size"][1]) var innen: Vector3 = Orte.DIR_VEC[pl["dir"]] * -1.0 var c := Orte.center(DreamDB.PLACES, key) c.y = DreamDB.terrasse(key) var pos := c + innen * (tief * 0.5 - 2.6) var farbe: Color = pl["color"] Prims.box(self, pos + Vector3(0, 0.55, 0), Vector3(2.4, 1.1, 0.9), farbe.darkened(0.35)) Prims.box(self, pos + Vector3(0, 1.18, 0), Vector3(2.8, 0.16, 1.2), farbe.darkened(0.2)) Prims.sphere(self, pos + Vector3(0, 1.7, 0), 0.34, Color(0.85, 0.92, 1.0), 2.0) var lehrt: Array = DreamDB.techniken_von(key) Prims.label(self, pos + Vector3(0, 2.6, 0), TechDB.by_id(str(lehrt[0]))["name"], 0.42, Color(0.85, 0.92, 1)) var n := _marke(pos - innen * 1.8 + Vector3(0, 1.4, 0), "lehre", str(pl["name"])) n.set_meta("arg", key) ## An der Lichtung: hier hängen die Menschen aus, die Hilfe brauchen. func _schwarzes_brett() -> void: var pos := Orte.center(DreamDB.PLACES, "lichtung") + Vector3(-6.0, 0, 9.0) for s: float in [-1.4, 1.4]: Prims.cyl(self, pos + Vector3(s, 1.3, 0), 0.16, 2.6, Color(0.45, 0.38, 0.60)) Prims.box(self, pos + Vector3(0, 2.2, 0), Vector3(3.6, 2.4, 0.18), Color(0.18, 0.16, 0.26)) for i in range(HumanDB.LIST.size()): var x := -1.2 + float(i % 2) * 2.4 var y := 2.8 - float(i / 2) * 1.1 Prims.box(self, pos + Vector3(x, y, 0.12), Vector3(1.1, 0.8, 0.04), Color(0.92, 0.90, 0.80), false, 0.5) Prims.label(self, pos + Vector3(0, 4.2, 0), "Schwarzes Brett", 0.5, Color(1, 0.95, 0.85)) _marke(pos + Vector3(0, 1.6, 1.2), "brett", "Schwarzes Brett") ## Unsichtbarer Punkt, an dem die Interaktion hängt. Um jede Marke bleibt ein ## Stück frei, damit später kein Baum davorsteht. func _marke(pos: Vector3, kind: String, titel: String) -> Node3D: _freihalten.append(Vector3(pos.x, 0.0, pos.z)) var n := Node3D.new() n.position = pos add_child(n) n.add_to_group("interactable") n.set_meta("kind", kind) n.set_meta("arg", "") n.set_meta("titel", titel) return n ## Der Dorfplatz: der Traumbaum in der Mitte, Bänke darum, Laternen am Rand. func _lichtung() -> void: var c := Orte.center(DreamDB.PLACES, "lichtung") Prims.cyl(self, c + Vector3(0, 0.04, 0), 11.0, 0.12, Color(0.60, 0.52, 0.78), false) Prims.cyl(self, c + Vector3(0, 0.14, 0), 3.4, 0.2, Color(0.30, 0.24, 0.40), false) # der Traumbaum Prims.cyl(self, c + Vector3(0, 4.5, 0), 1.1, 9.0, Color(0.36, 0.26, 0.40)) var kronen := [ [Vector3(0, 9.6, 0), 3.6, Color(0.62, 0.55, 0.96)], [Vector3(1.6, 11.4, -0.8), 2.7, Color(0.70, 0.62, 1.0)], [Vector3(-1.4, 12.2, 1.0), 2.4, Color(0.55, 0.78, 1.0)], [Vector3(0.3, 14.0, 0.2), 1.7, Color(0.80, 0.90, 1.0)], ] for k in kronen: var s := Prims.sphere(self, c + k[0], k[1], k[2], 1.3) s.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF var licht := OmniLight3D.new() licht.position = c + Vector3(0, 11.0, 0) licht.omni_range = 46.0 licht.light_energy = 3.2 licht.light_color = Color(0.72, 0.78, 1.0) add_child(licht) for i in range(4): var a := TAU * float(i) / 4.0 + 0.4 var p := c + Vector3(cos(a) * 5.6, 0.42, sin(a) * 5.6) var bank := Prims.box(self, p, Vector3(2.6, 0.16, 0.7), Color(0.46, 0.38, 0.58)) bank.rotation.y = -a Prims.box(self, p + Vector3(0, -0.2, 0), Vector3(0.4, 0.3, 0.6), Color(0.36, 0.30, 0.48), false) for i in range(4): var a := TAU * float(i) / 4.0 _laterne(c + Vector3(cos(a) * 8.8, 0, sin(a) * 8.8)) Prims.label(self, c + Vector3(0, 16.0, 0), str(DreamDB.PLACES["lichtung"]["name"]), 0.6, Color(1, 0.95, 0.85)) func _tor_zur_erde() -> void: var pos := Orte.center(DreamDB.PLACES, "tor") pos.y = DreamDB.hoehe_bei(pos.x, pos.z) # flach und ohne Kollision wie der Platz — eine Stufe hätte den Spieler # vor dem Tor stehen lassen Prims.cyl(self, pos + Vector3(0, 0.05, 0), 5.5, 0.1, Color(0.35, 0.30, 0.50), false) for i in range(10): var a := PI * float(i) / 9.0 Prims.box(self, pos + Vector3(cos(a) * 4.2, sin(a) * 4.2 + 0.6, 0), Vector3(0.7, 0.7, 0.7), Color(0.5, 0.85, 1.0), false, 1.8) var scheibe := Prims.cyl(self, pos + Vector3(0, 2.6, 0), 3.0, 0.2, Color(0.45, 0.75, 1.0, 0.28), false, 1.0) scheibe.rotation.x = PI * 0.5 Prims.label(self, pos + Vector3(0, 6.4, 0), "Tor zur Erde", 0.8, Color(0.7, 0.95, 1)) _marke(pos + Vector3(0, 1.6, 0), "erde", "Tor zur Erde") ## Begrünung nach Regel, nicht von Hand: Poisson-Scheibe über der freien ## Fläche. Nichts wächst auf einem Weg, in einem Haus oder auf dem Dorfplatz. func _gruen() -> void: _freihalten.append(Vector3(spawn.x, 0.0, spawn.z)) var radius := Game.insel_radius() - 4.0 var rng := RandomNumberGenerator.new() rng.seed = 91 var gesetzt: Array[Vector3] = [] var abstand := 9.0 for versuch in range(1400): var a := rng.randf() * TAU var r := sqrt(rng.randf()) * radius var p := Vector3(cos(a) * r, 0.0, sin(a) * r) if p.length() < 20.0: continue var frei := true for g in gesetzt: if g.distance_to(p) < abstand: frei = false break if not frei: continue if not _freie_stelle(p, 3.2): continue gesetzt.append(p) p.y = DreamDB.hoehe_bei(p.x, p.z) _baum(p, rng) func _freie_stelle(p: Vector3, luft: float) -> bool: for frei in _freihalten: if p.distance_to(frei) < 7.0: return false for key in DreamDB.PLACES: var s: Array = DreamDB.PLACES[key]["size"] if float(s[0]) <= 0.0 or not Game.ort_offen(key): continue var c := Orte.center(DreamDB.PLACES, key) if absf(p.x - c.x) < float(s[0]) * 0.5 + luft \ and absf(p.z - c.z) < float(s[1]) * 0.5 + luft: return false for k in DreamDB.NETZ.kanten: if Netz.trifft_rechteck(DreamDB.NETZ.knoten[k[0]], DreamDB.NETZ.knoten[k[1]], p, Vector2(luft * 2.0, luft * 2.0), 0.6): return false return true ## Traumbäume: schlanker Stamm, zwei versetzte Kronen, leuchtende Spitze. func _baum(p: Vector3, rng: RandomNumberGenerator) -> void: var hoehe := rng.randf_range(3.0, 6.0) # Grün gegen den lila Grund -- das macht die Insel erst gemütlich. var ton := Color.from_hsv(0.26 + rng.randf() * 0.12, 0.48 + rng.randf() * 0.18, 0.58 + rng.randf() * 0.26) Prims.cyl(self, p + Vector3(0, hoehe * 0.5, 0), 0.24, hoehe, Color(0.34, 0.26, 0.30)) var k1 := Prims.sphere(self, p + Vector3(0, hoehe + 0.4, 0), rng.randf_range(1.1, 2.0), ton) k1.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF if rng.randf() < 0.6: var k2 := Prims.sphere(self, p + Vector3(rng.randf_range(-0.8, 0.8), hoehe - 0.7, rng.randf_range(-0.8, 0.8)), rng.randf_range(0.8, 1.5), ton.lightened(0.12)) k2.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF if rng.randf() < 0.3: Prims.sphere(self, p + Vector3(0, hoehe + 1.6, 0), 0.28, Color(0.9, 0.95, 1.0), 1.6) ## Am Anfang ist das Dorf leer; mit jedem erfüllten Traum zieht ein Wesen ein. ## Jedes läuft auf dem Hügel -- der Boden sagt ihm, wie hoch. func _wesen() -> void: for i in range(DreamDB.LIST.size()): if not Game.wesen_eingezogen(i): continue var w := Bewohner.new() add_child(w) w.boden = Callable(DreamDB, "hoehe_bei") w.nav_map = nav_map w.setup(DreamDB.LIST[i], i, DreamDB.PLACES) bewohner.append(w) # ansprechbar: ein Wesen flüstert, was in der Wohnung des nächsten Menschen liegt var n := Node3D.new() n.position.y = 1.0 w.add_child(n) n.add_to_group("interactable") n.set_meta("kind", "wesen") n.set_meta("arg", str(i)) n.set_meta("titel", str(DreamDB.LIST[i]["name"])) func _process(delta: float) -> void: if spieler != null: Prims.schnitt(_bauten, spieler.global_position, spieler.cam.global_position) var t := Time.get_ticks_msec() * 0.001 for i in range(_lichter.size()): var n: Node3D = _lichter[i] n.position += Vector3(sin(t * 0.5 + i) * 0.35, cos(t * 0.7 + i * 0.3) * 0.25, cos(t * 0.4 + i * 0.5) * 0.35) * delta