This commit is contained in:
Team3
2026-09-16 13:08:59 +02:00
parent 89fad5dc7a
commit e813b5f7aa
17 changed files with 483 additions and 247 deletions

View File

@@ -222,6 +222,9 @@ func _process(roh: float) -> void:
_aura.scale.z = _aura.scale.x
_label.text = beschriftung()
# Auf der Erde tragen nur Fokus, Verfolgte, Mission und wer unter der Maus
# steht ein Schild -- sonst überdecken sich acht Namen im Café.
_label.visible = not mit_mission or Game.markiert.has(str(data["id"]))
_label.modulate = Color(0.55, 0.85, 0.6) if erledigt else (
Color(1, 0.9, 0.6) if aktiv else Color(1, 1, 1))

View File

@@ -89,7 +89,9 @@ func _bebauung() -> void:
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)
var klotz := Prims.box(bau, c + Vector3(0, hoehe * 0.5, 0), Vector3(g.x, hoehe, g.y), ton, false)
# Kollision etwas größer: so schließt sich die Fuge zum Nachbarn, niemand keilt sich hinein
Prims._add_box_body(klotz, Vector3(g.x + 0.9, hoehe, g.y + 0.9))
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),
@@ -188,51 +190,86 @@ func _decke(parent: Node, key: String, cf: Vector3, _groesse: Vector3, farbe: Co
Prims.box(parent, mitte, Vector3(mass.x, 0.2, mass.y), farbe)
## Ein paar Möbel, damit man sieht, wofür ein Raum da ist. Nichts steht im
## Treppenstreifen.
## Ein paar Möbel, damit man sieht, wofür ein Raum da ist. Gestellt wird im
## Rahmen der Tür (quer / zur Tür hin); was in der Türzone, im Treppenstreifen
## oder auf dem Weg von der Tür zur Treppe stünde, bleibt weg.
func _einrichtung(parent: Node, c: Vector3, groesse: Vector3, farbe: Color, key: String,
ort: String = "") -> void:
if ort == "":
ort = key
var w := maxf(0.5, groesse.x * 0.5 - 2.0)
var h := maxf(0.5, groesse.z * 0.5 - 2.0)
var frei := func(v: Vector3, halb: float) -> Vector3:
var p := Orte.frei_von_treppe(HumanDB.PLACES, ort, c + v, halb)
p.y = c.y + v.y
return p
var d: Vector3 = Orte.DIR_VEC[HumanDB.PLACES[ort]["dir"]]
var hd: float = (groesse.z if absf(d.z) > 0.0 else groesse.x) * 0.5
var hq: float = (groesse.x if absf(d.z) > 0.0 else groesse.z) * 0.5
var w := maxf(0.5, hq - 2.0)
var h := maxf(0.5, hd - 2.0)
if key.begins_with("heim_"):
Prims.box(parent, frei.call(Vector3(-w * 0.6, 0.35, -h * 0.5), 1.5), Vector3(1.9, 0.7, 3.0),
# an der Türwand, links und rechts der Tür -- die Rückwand gehört der Treppe
_moebel(parent, ort, c, hq - 1.35, hd - 1.6, 0.35, Vector3(1.9, 0.7, 2.4),
Color(0.85, 0.85, 0.90))
Prims.box(parent, frei.call(Vector3(w * 0.6, 0.75, 0), 1.1), Vector3(1.5, 0.15, 2.2),
farbe.darkened(0.5))
Prims.box(parent, frei.call(Vector3(w * 0.6, 1.05, 0), 0.1), Vector3(0.8, 0.55, 0.1),
Color(0.2, 0.25, 0.3))
if _moebel(parent, ort, c, -(hq - 1.15), hd - 1.6, 0.75, Vector3(1.5, 0.15, 2.2),
farbe.darkened(0.5)):
_moebel(parent, ort, c, -(hq - 1.15), hd - 1.6, 1.05, 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, frei.call(Vector3(-w * 0.5 + float(i) * w * 0.5, 0.4, 0.4), 0.7), 0.7, 0.8,
Color(0.55, 0.40, 0.30))
Prims.box(parent, frei.call(Vector3(0, 0.55, -h * 0.7), 0.4), Vector3(groesse.x - 3.0, 1.1, 0.8),
_moebel(parent, ort, c, -w * 0.5 + float(i) * w * 0.5, 0.4, 0.4,
Vector3(1.4, 0.8, 1.4), Color(0.55, 0.40, 0.30), true)
_moebel(parent, ort, c, -hq * 0.5, -(h * 0.7), 0.55, Vector3(hq - 1.5, 1.1, 0.8),
Color(0.45, 0.32, 0.24))
elif key == "buero":
for i in range(3):
Prims.box(parent, frei.call(Vector3(-w * 0.5 + float(i) * w * 0.5, 0.75, 0), 0.8),
_moebel(parent, ort, c, -w * 0.5 + float(i) * w * 0.5, h * 0.3, 0.75,
Vector3(1.6, 0.15, 1.2), Color(0.75, 0.75, 0.78))
elif key == "baeckerei":
Prims.box(parent, frei.call(Vector3(0, 0.55, h * 0.5), 0.5), Vector3(groesse.x - 3.0, 1.1, 1.0),
_moebel(parent, ort, c, -hq * 0.45, hd - 3.4, 0.55, Vector3(hq - 1.2, 1.1, 1.0),
Color(0.70, 0.55, 0.35))
Prims.box(parent, frei.call(Vector3(0, 0.85, -h * 0.6), 1.3), Vector3(2.6, 1.7, 1.2),
_moebel(parent, ort, c, -(hq - 1.6), -(h * 0.6), 0.85, 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, frei.call(Vector3(0, 0.5, -h * 0.6 + float(i) * h * 0.6), 0.35),
Vector3(groesse.x - 3.0, 0.2, 0.7), Color(0.66, 0.62, 0.58))
Prims.box(parent, frei.call(Vector3(0, 1.1, -h), 0.1), Vector3(groesse.x * 0.55, 1.6, 0.15),
_moebel(parent, ort, c, -hq * 0.4, -h * 0.6 + float(i) * h * 0.6, 0.5,
Vector3(hq - 1.5, 0.2, 0.7), Color(0.66, 0.62, 0.58))
_moebel(parent, ort, c, -hq * 0.4, -h, 1.1, Vector3(hq * 0.8, 1.6, 0.15),
Color(0.15, 0.25, 0.20))
elif key == "werkstatt":
Prims.box(parent, frei.call(Vector3(0, 0.6, 0), 0.7), Vector3(groesse.x - 4.0, 1.2, 1.4),
_moebel(parent, ort, c, -hq * 0.4, 0.0, 0.6, Vector3(hq * 0.8 - 1.0, 1.2, 1.4),
Color(0.52, 0.48, 0.46))
## Ein Möbelstück: aq quer zur Tür, ad zur Tür hin (positiv = zur Tür), Maße
## als (quer, hoch, tief). Steht es im Weg, wird es nicht gestellt.
func _moebel(parent: Node, ort: String, c: Vector3, aq: float, ad: float, y: float,
mass: Vector3, farbe: Color, rund: bool = false) -> bool:
var d: Vector3 = Orte.DIR_VEC[HumanDB.PLACES[ort]["dir"]]
var quer := Vector3(-d.z, 0, d.x)
var pos := c + quer * aq + d * ad
pos.y = c.y + y
var s: Array = HumanDB.PLACES[ort]["size"]
var hd: float = (float(s[1]) if absf(d.z) > 0.0 else float(s[0])) * 0.5
var halb := maxf(mass.x, mass.z) * 0.5
# Türzone: 2,2 tief hinter der Tür, 4 breit
if ad + mass.z * 0.5 > hd - 2.2 and absf(aq) - mass.x * 0.5 < 2.0:
return false
# Treppenstreifen
var r := Orte.treppen_rahmen(HumanDB.PLACES, ort)
var n: Vector3 = r["n"]
var tief_n: float = (mass.z if absf(n.dot(d)) > 0.5 else mass.x) * 0.5
if (pos - c).dot(n) + tief_n > float(r["halb_n"]) - Orte.TREPPE_TIEFE - 0.3:
return false
# Weg von der Tür zur Treppe
var von := Orte.innen_tuer(HumanDB.PLACES, ort)
var bis := Orte.treppe(HumanDB.PLACES, ort, 0)
var flach := Vector3(pos.x, 0, pos.z)
if Geometry3D.get_closest_point_to_segment(flach, von, bis).distance_to(flach) < halb + 0.9:
return false
var welt_mass := mass if absf(d.z) > 0.0 else Vector3(mass.z, mass.y, mass.x)
if rund:
Prims.cyl(parent, pos, mass.x * 0.5, mass.y, farbe)
else:
Prims.box(parent, pos, welt_mass, farbe)
return true
## 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:

View File

@@ -32,6 +32,7 @@ var problem_stand: Dictionary = {} # "human:problem" -> gelungene Anwendung
var ueberwunden: Dictionary = {} # human_id -> {problem -> true}
var gesehen_zaehler: Dictionary = {} # "human:problem" -> wie oft dabeigewesen
var completed: Array = []
var markiert: Dictionary = {} # human_id -> true: wer gerade ein Schild trägt
var time_h: float = 7.0
var day: int = 1
var teleport_lock: float = 0.0

View File

@@ -305,6 +305,7 @@ static func _kreuzung(a: Vector3, b: Vector3, c: Vector3, d: Vector3) -> float:
## volle Breite, die Ost- und Westreihe nur dazwischen -- sonst überlappen die
## Ecken.
static func _block(mitte: Vector3, groesse: Vector2, rng: RandomNumberGenerator) -> void:
var erste := PARZELLEN.size()
var tiefe := minf(RAND_TIEFE, minf(groesse.x, groesse.y) * 0.34)
var seiten := [
["back", Vector3(0, 0, 1), groesse.x, groesse.y],
@@ -346,6 +347,7 @@ static func _block(mitte: Vector3, groesse: Vector2, rng: RandomNumberGenerator)
var ende := mitte + richtung * ((innen.y if laengs else innen.x) * 0.5 - 2.0)
_strasse(start, ende, ROJI)
var flanke := Vector3(1, 0, 0) if laengs else Vector3(0, 0, 1)
_gasse_freischneiden(erste, start, ende, flanke)
var p_breite := (innen.x if laengs else innen.y) * 0.5 - ROJI * 0.5 - 0.8
if p_breite < PARZELLE_MIN * 0.8:
return
@@ -366,6 +368,46 @@ static func _block(mitte: Vector3, groesse: Vector2, rng: RandomNumberGenerator)
})
## Die Gasse läuft durch die Randreihe in den Block. Eine Parzelle, die auf
## ihr steht, wird in zwei Hälften links und rechts der Gasse geteilt --
## sonst stünde ein Haus quer über der Einfahrt.
static func _gasse_freischneiden(ab: int, start: Vector3, ende: Vector3, flanke: Vector3) -> void:
var quer_ist := absf(flanke.x) > 0.0 # Gasse läuft in z, Flanke ist x
var breite := ROJI * 0.5 + 0.8
var g_mitte: float = start.x if quer_ist else start.z
var g_von: float = minf(start.z, ende.z) if quer_ist else minf(start.x, ende.x)
var g_bis: float = maxf(start.z, ende.z) if quer_ist else maxf(start.x, ende.x)
var neu: Array = []
var weg: Array = []
for i in range(ab, PARZELLEN.size()):
var p: Dictionary = PARZELLEN[i]
var pos: Vector3 = p["pos"]
var s: Vector2 = p["size"]
var pf: float = pos.x if quer_ist else pos.z # Lage entlang der Flanke
var pl: float = pos.z if quer_ist else pos.x # Lage entlang der Gasse
var hf: float = (s.x if quer_ist else s.y) * 0.5
var hl: float = (s.y if quer_ist else s.x) * 0.5
if pl + hl < g_von or pl - hl > g_bis:
continue
if pf + hf < g_mitte - breite or pf - hf > g_mitte + breite:
continue
weg.append(i)
for teil in [[pf - hf, g_mitte - breite], [g_mitte + breite, pf + hf]]:
var w: float = float(teil[1]) - float(teil[0])
if w < 3.0:
continue
var m: float = (float(teil[0]) + float(teil[1])) * 0.5
neu.append({
"pos": Vector3(m, 0, pl) if quer_ist else Vector3(pl, 0, m),
"size": Vector2(w, s.y) if quer_ist else Vector2(s.x, w),
"dir": p["dir"],
})
weg.reverse()
for i in weg:
PARZELLEN.remove_at(i)
PARZELLEN.append_array(neu)
# ------------------------------------------------------------ Belegung
static func _park_eintragen() -> void:
@@ -419,10 +461,13 @@ static func _sonderbauten_verteilen() -> void:
## Die Wohnhäuser: gleichmäßig über den Bezirk verteilt, damit die Wege
## nicht alle in derselben Ecke enden.
const WOHNUNG_MIN := 7.0 # schmaler passt kein Treppenhaus mehr neben die Tür
static func _wohnhaeuser_verteilen() -> void:
var frei: Array = []
for i in range(PARZELLEN.size()):
if not PARZELLEN[i].has("belegt"):
var g: Vector2 = PARZELLEN[i]["size"]
if not PARZELLEN[i].has("belegt") and minf(g.x, g.y) >= WOHNUNG_MIN:
frei.append(i)
if frei.is_empty():
return

View File

@@ -72,10 +72,43 @@ func _process(delta: float) -> void:
Game.notiz_sperre = maxf(0.0, Game.notiz_sperre - delta)
Game.situationen_ticken(dt, auf_erde and not Game.schlaeft_ziel())
_fokus_suchen()
_markieren()
_dabeisein()
_hinweis_setzen()
## Wer ein Schild trägt: Mission, Fokus, Verfolgter und wer unter der Maus steht.
func _markieren() -> void:
Game.markiert = {}
if Game.active_mission != "":
Game.markiert[Game.active_mission] = true
if _fokus_mensch != null:
Game.markiert[str(_fokus_mensch.data["id"])] = true
if player.folgt != null and is_instance_valid(player.folgt):
Game.markiert[str(player.folgt.data["id"])] = true
if auf_erde:
var unter_maus := _mensch_unter_maus(get_viewport().get_mouse_position())
if unter_maus != null:
Game.markiert[str(unter_maus.data["id"])] = true
## Der Mensch, der auf dem Bildschirm am nächsten am Punkt steht -- oder null.
func _mensch_unter_maus(pos: Vector2) -> Bewohner:
if not auf_erde:
return null
var bester: Bewohner = null
var beste := 48.0
for m in (welt as EarthWorld).humans:
var w: Vector3 = (m as Node3D).global_position + Vector3(0, 0.8, 0)
if player.cam.is_position_behind(w):
continue
var d := player.cam.unproject_position(w).distance_to(pos)
if d < beste:
beste = d
bester = m
return bester
func _fokus_suchen() -> void:
_fokus = null
_fokus_mensch = null
@@ -96,7 +129,9 @@ func _fokus_suchen() -> void:
if d < bestm:
bestm = d
_fokus_mensch = m
if _fokus_mensch != null and (_fokus == null or bestm < best):
if player.folgt != null and is_instance_valid(player.folgt):
_fokus_mensch = player.folgt # wer sich angehängt hat, ist dabei -- auch auf der Treppe
if _fokus_mensch != null and (_fokus == null or bestm < best or _fokus_mensch == player.folgt):
_fokus = null
elif _fokus != null:
_fokus_mensch = null
@@ -209,25 +244,14 @@ func _notieren() -> void:
## Klick auf einen Menschen: an ihn hängen, noch ein Klick löst wieder.
func _klick(pos: Vector2) -> void:
if not auf_erde:
return
var bester: Bewohner = null
var beste := 48.0
for m in (welt as EarthWorld).humans:
var w: Vector3 = (m as Node3D).global_position + Vector3(0, 0.8, 0)
if player.cam.is_position_behind(w):
continue
var d := player.cam.unproject_position(w).distance_to(pos)
if d < beste:
beste = d
bester = m
var bester := _mensch_unter_maus(pos)
if bester == null:
return
if player.folgt == bester:
player.folgt = null
Game.notified.emit("Du lässt %s wieder los." % bester.data["name"], Color(0.8, 0.85, 1))
else:
player.folgt = bester
player.anhaengen(bester)
Game.notified.emit("Du hängst dich an %s." % bester.data["name"], Color(0.7, 0.9, 1))
@@ -244,7 +268,7 @@ func _verfolgen() -> void:
Game.notified.emit("Geh näher an einen Menschen heran, um ihm zu folgen.",
Color(1, 0.85, 0.5))
return
player.folgt = _fokus_mensch
player.anhaengen(_fokus_mensch)
Game.notified.emit("Du hängst dich an %s." % _fokus_mensch.data["name"],
Color(0.7, 0.9, 1))

View File

@@ -140,7 +140,8 @@ static func treppen_decke(places: Dictionary, key: String) -> Array:
var halb_a := float(r["halb_a"])
var out: Array = []
# Hauptplatte: alles außer dem Streifen an der Treppenwand
out.append([c - n * (TREPPE_TIEFE * 0.5), _xz(a, halb_a * 2.0, halb_n * 2.0 - TREPPE_TIEFE)])
if halb_n * 2.0 - TREPPE_TIEFE > 0.2:
out.append([c - n * (TREPPE_TIEFE * 0.5), _xz(a, halb_a * 2.0, halb_n * 2.0 - TREPPE_TIEFE)])
# Rest des Streifens jenseits des Lochs
var ende: float = _podest_a(r, 1) - PODEST
var rest := ende + halb_a
@@ -176,6 +177,53 @@ static func innen_tuer(places: Dictionary, key: String) -> Vector3:
return c + d * (halb - 1.2)
## Ort, in dessen Grundriss der Punkt liegt -- oder "".
static func ort_bei(places: Dictionary, p: Vector3) -> String:
for key in places:
var s: Array = places[key]["size"]
if float(s[0]) <= 0.0:
continue
var c := center(places, key)
if absf(p.x - c.x) <= float(s[0]) * 0.5 and absf(p.z - c.z) <= float(s[1]) * 0.5:
return key
return ""
## Weg für einen Körper von einem freien Punkt zu einem Menschen: aus dem
## eigenen Haus heraus, über das Netz, durch seine Tür und in seine Etage.
static func route_zu(places: Dictionary, netz: Netz, von: Vector3, nach: Vector3,
nach_ort: String, nach_etage: int) -> Array:
var pts: Array = []
var start := von
var von_ort := ort_bei(places, von)
if von_ort != "" and gebaeude(places, von_ort):
var e := int(round(von.y / ETAGE))
if e > 0:
pts.append(treppe(places, von_ort, e))
pts.append_array(treppen_weg(places, von_ort, e, 0))
pts.append(innen_tuer(places, von_ort))
start = door(places, von_ort)
pts.append(start)
var drin := nach_ort != "" and gebaeude(places, nach_ort)
var ende := door(places, nach_ort) if drin else nach
if netz != null:
var a: int = int(netz.andocken(start)["knoten"])
var b: int = int(netz.andocken(ende)["knoten"])
for idx in netz.pfad(a, b):
pts.append(netz.knoten[idx])
if drin:
pts.append(door(places, nach_ort))
pts.append(innen_tuer(places, nach_ort))
if nach_etage > 0:
pts.append(treppe(places, nach_ort, 0))
pts.append_array(treppen_weg(places, nach_ort, 0, nach_etage))
var ziel := nach
if drin:
ziel.y = hoehe(nach_etage)
pts.append(ziel)
return pts
## Ein Ort ist ein Gebäude, wenn er Grundriss und Tür hat.
static func gebaeude(places: Dictionary, key: String) -> bool:
return places.has(key) and float(places[key]["size"][0]) > 0.0 \

View File

@@ -11,6 +11,7 @@ const GRAVITY := 24.0
const JUMP := 9.0
const FOLGE_ABSTAND := 2.5 # so weit bleibt der Geist auf der Spur zurück
const LEINE := 14.0 # weiter weg reißt die Leine: er wird nachgezogen
const FOLGE_NAH := 5.0 # näher dran braucht es keinen Anlauf
const SCHWEBE := 0.25 # eine Handbreit über dem Boden
const PITCH := -0.96 # 55° von oben, fest wie in Diablo
const CAM_YAW := PI * 0.25 # Blick diagonal über den Bezirk
@@ -28,6 +29,9 @@ var arm: Node3D
var cam: Camera3D
var _dreh_rest := 0.0 # noch zu drehender Winkel (Q)
var _zoom_stufe := 0
var _anlauf: Array = [] # Weg zu dem Menschen, an den man sich gehängt hat
var _stockt := 0.0 # Sekunden ohne Vorankommen
var spruenge := 0 # wie oft die Leine nachziehen musste
var body: Node3D
var _schleier: MeshInstance3D
var _kern: MeshInstance3D
@@ -51,7 +55,7 @@ const FUNKEN_MAX := 6
func _ready() -> void:
var cs := CollisionShape3D.new()
var shape := CapsuleShape3D.new()
shape.radius = 0.4
shape.radius = 0.35 # passt durch die 0,8 m zwischen zwei Häusern
shape.height = 1.6
cs.shape = shape
cs.position.y = 0.8
@@ -164,12 +168,33 @@ func _physics_process(delta: float) -> void:
move_and_slide()
if global_position.y < -400.0:
global_position.y = 2.0
_eingeklemmt(delta, dir.length() > 0.01)
if dir.length() > 0.01:
var ziel := atan2(-dir.x, -dir.z)
body.rotation.y = lerp_angle(body.rotation.y, ziel, 1.0 - pow(0.0005, delta))
## Wer mit gedrückter Taste zwei Sekunden nicht vorankommt, steckt fest --
## dann setzt ihn die Stadt auf die nächste Straße.
func _eingeklemmt(delta: float, will: bool) -> void:
if mode != Mode.GEIST or not will or Vector2(velocity.x, velocity.z).length() > 0.3:
_stockt = 0.0
return
_stockt += delta
if _stockt < 2.0:
return
_stockt = 0.0
var netz: Netz = HumanDB.NETZ
if netz == null or netz.knoten.is_empty():
return
var nah: Vector3 = netz.knoten[int(netz.andocken(global_position)["knoten"])]
global_position = Vector3(nah.x, 0.3, nah.z)
velocity = Vector3.ZERO
spruenge += 1
Game.notified.emit("Eingeklemmt -- du stehst wieder auf der Straße.", Color(0.8, 0.85, 1))
func blickrichtung() -> Vector3:
return -Vector3(sin(yaw), 0, cos(yaw))
@@ -185,11 +210,33 @@ func _fallen(delta: float) -> void:
velocity.y -= GRAVITY * delta
## An einen Menschen hängen. Steht er weit weg, wird erst der Weg zu ihm
## geplant: aus dem Haus, über die Straßen, in seine Wohnung.
func anhaengen(m: Bewohner) -> void:
folgt = m
_anlauf = []
_stockt = 0.0
if global_position.distance_to(m.global_position) > FOLGE_NAH:
var ort: String = m.place if m._path.is_empty() else ""
_anlauf = Orte.route_zu(m.places, m.netz, global_position, m.global_position,
ort, m.etage(ort) if ort != "" else 0)
## Folgen heißt: seiner Spur nachgehen, durch dieselben Türen und Treppen.
## Er läuft so schnell wie nötig, um dranzubleiben -- auch bei hohem Tempo.
func _an_der_leine(delta: float) -> void:
var mensch := folgt as Bewohner
var ziel: Vector3 = mensch.spur_punkt(FOLGE_ABSTAND)
var ziel: Vector3
if not _anlauf.is_empty():
ziel = _anlauf[0]
var bis := ziel - global_position
bis.y = 0.0
if bis.length() < 0.6 and absf(ziel.y - global_position.y) < 1.6:
_anlauf.pop_front()
if _anlauf.is_empty():
ziel = mensch.spur_punkt(FOLGE_ABSTAND)
else:
ziel = _anlauf[0]
var d := ziel - global_position
d.y = 0.0
var tempo := maxf(SPEED_GEIST, Bewohner.GEHTEMPO * Game.zeit_faktor() * 1.4)
@@ -203,9 +250,17 @@ func _an_der_leine(delta: float) -> void:
velocity.z = 0.0
_fallen(delta)
move_and_slide()
if global_position.distance_to(mensch.global_position) > LEINE:
if d.length() > 0.6 and Vector2(velocity.x, velocity.z).length() < 0.3:
_stockt += delta
else:
_stockt = 0.0
var zu_weit: bool = _anlauf.is_empty() \
and global_position.distance_to(mensch.global_position) > LEINE
if _stockt > 2.0 or zu_weit:
global_position = ziel # hängen geblieben: die Leine zieht ihn nach
velocity = Vector3.ZERO
_stockt = 0.0
spruenge += 1
## Schweben, Funken, Schweif und Modusfarbe -- der Zustand ist am Geist ablesbar.

View File

@@ -113,7 +113,8 @@ static func room(parent: Node, center: Vector3, size: Vector3, wall_c: Color,
var h := size.y
var waende: Array = []
if with_floor:
box(parent, center + Vector3(0, -0.1, 0), Vector3(size.x, 0.2, size.z), floor_c)
# 3 cm über dem Gelände, sonst flimmert der Boden gegen die Insel
box(parent, center + Vector3(0, -0.07, 0), Vector3(size.x, 0.2, size.z), floor_c)
var seiten: Array[Vector3] = [Vector3.FORWARD, Vector3.BACK, Vector3.LEFT, Vector3.RIGHT]
for d in seiten:
var quer := absf(d.z) > 0.0 # Wand liegt entlang der X-Achse
@@ -184,6 +185,9 @@ static func schnitt(bauten: Array, spieler: Vector3, kamera: Vector3) -> void:
b["meshes"] = _meshes_von(b)
for mi in b["meshes"]:
(mi as GeometryInstance3D).transparency = 0.82 if im_weg else 0.0
(mi as GeometryInstance3D).cast_shadow = \
GeometryInstance3D.SHADOW_CASTING_SETTING_OFF if im_weg \
else GeometryInstance3D.SHADOW_CASTING_SETTING_ON
var etagen: Array = b["etagen"]
var pf := clampi(int(floor((spieler.y - c.y + 0.6) / Orte.ETAGE)), 0, maxi(0, etagen.size() - 1))
for f in range(etagen.size()):