Feat/visual audio overhaul #21
@@ -43,6 +43,10 @@ func _process(_delta: float) -> bool:
|
||||
if cam:
|
||||
var aerial := Camera3D.new()
|
||||
current_scene.add_child(aerial)
|
||||
if _scene.contains("neon_alley"):
|
||||
aerial.far = 2000.0
|
||||
aerial.global_position = Vector3(0, 400, 300)
|
||||
else:
|
||||
aerial.global_position = Vector3(0, 55, 42)
|
||||
aerial.look_at(Vector3(0, 0, -5), Vector3.UP)
|
||||
aerial.current = true
|
||||
|
||||
@@ -1,56 +1,65 @@
|
||||
extends TestLevelBuilder
|
||||
|
||||
## Akiba Crossing — an Akihabara-styled block, rebuilt parametrically.
|
||||
## Akiba Crossing — a 10x10-block Akihabara-styled city (100 blocks).
|
||||
##
|
||||
## Real-district structure this mimics (Chuo-dori + Denki-gai + JR viaduct):
|
||||
## - A wide N-S avenue flanked by continuous mid-rise retail rows
|
||||
## - Buildings quantized to 4m floors, packed edge-to-edge with designed
|
||||
## alley cuts (the narrow perpendicular Denki-gai streets)
|
||||
## - An elevated rail viaduct crossing the whole block on brick piers,
|
||||
## with an arcade of vending machines beneath the arches
|
||||
## - Stacked floor-by-floor sign bands, vertical corner sign columns and
|
||||
## rooftop billboards — every sign is derived from its building's
|
||||
## footprint, so nothing floats or clips
|
||||
## The map is a street grid: 48m buildable cells on a 64m pitch with 16m
|
||||
## streets. A hand-authored layout assigns each cell a block type so the
|
||||
## city stays varied ("S" retail is the fabric; landmarks break it up):
|
||||
## S standard retail T landmark tower J shrine courtyard
|
||||
## M market street P plaza (2x2 centre = the Crossing) C construction
|
||||
## A brick rail viaduct runs east-west across the whole city, and a brick
|
||||
## wall rings the district.
|
||||
##
|
||||
## Movement: viaduct deck = high lane (slide ramps at both avenue flanks),
|
||||
## canopy lane above each sidewalk, roof-hop route across quantized roofs,
|
||||
## fire escapes in the alleys, wallrun alley walls, grapple anchors on
|
||||
## streetlight arms / sign columns / viaduct edge / billboards.
|
||||
## Everything is parametric: buildings derive parapets, storefronts,
|
||||
## canopies, per-floor sign bands, window bands, corner sign columns, roof
|
||||
## AC/water tanks/antennas from their footprint; per-block variation comes
|
||||
## from a seeded RNG so no two blocks repeat exactly.
|
||||
##
|
||||
## Acoustics: facades concrete, storefronts glass, canopies wood, viaduct
|
||||
## brick, signs/AC/vending/lights/escapes metal.
|
||||
## Collision & acoustics: bodies/canopies/columns/furniture are static and
|
||||
## tagged (concrete/glass/wood/brick/metal); pure decoration (signs,
|
||||
## windows, wires) is render-only to keep physics light.
|
||||
|
||||
const FLOOR_H := 4.0 # one story
|
||||
const AVE_HALF := 8.0 # avenue half-width (x in [-8, 8])
|
||||
const WALK_W := 3.0 # sidewalk width
|
||||
const FRONT_E := AVE_HALF + WALK_W # east row front plane x = +11
|
||||
const FRONT_W := -AVE_HALF - WALK_W # west row front plane x = -11
|
||||
const ROW_DEPTH := 14.0 # building depth away from the avenue
|
||||
const EDGE_Z := 38.0 # block half-length
|
||||
const VIADUCT_Z0 := -26.0 # viaduct strip (z)
|
||||
const VIADUCT_Z1 := -18.0
|
||||
const VIADUCT_TOP := 8.0 # deck walking height (2 floors)
|
||||
const ALLEY_Z0 := 8.0 # Denki-gai alley cut (z)
|
||||
const ALLEY_Z1 := 12.0
|
||||
const BLOCK := 48.0
|
||||
const STREET := 16.0
|
||||
const PITCH := BLOCK + STREET
|
||||
const GRID := 10
|
||||
const CITY_HALF := GRID * PITCH * 0.5 # 320
|
||||
const FLOOR_H := 4.0
|
||||
const RAIL_ROW := 2 # viaduct runs along the street south of row 2
|
||||
const RAIL_TOP := 9.0
|
||||
|
||||
# Palette — dusk cel. Facades muted so the signage carries the color.
|
||||
const LAYOUT: Array[String] = [
|
||||
"SMSTSSMSTS",
|
||||
"STSSJSSCSS",
|
||||
"SSMSPSSTSM",
|
||||
"MSTSSSTSSS",
|
||||
"SSSTPPTSMS",
|
||||
"SJSTPPTSSS",
|
||||
"SSSSTPTSJS",
|
||||
"TSMSSSSSTS",
|
||||
"SSJSSTSSMS",
|
||||
"SMSSTSMSST",
|
||||
]
|
||||
|
||||
# Palette
|
||||
const ASPHALT := Color(0.3, 0.3, 0.37)
|
||||
const PAVING := Color(0.46, 0.44, 0.5)
|
||||
const STRIPE := Color(0.85, 0.85, 0.88)
|
||||
const PAVING := Color(0.47, 0.45, 0.51)
|
||||
const STRIPE := Color(0.72, 0.72, 0.75)
|
||||
const FACADES: Array[Color] = [
|
||||
Color(0.74, 0.71, 0.66), # cream
|
||||
Color(0.62, 0.63, 0.67), # cool gray
|
||||
Color(0.68, 0.6, 0.5), # tan
|
||||
Color(0.55, 0.6, 0.66), # blue-gray
|
||||
Color(0.71, 0.66, 0.6), # warm gray
|
||||
Color(0.74, 0.71, 0.66), Color(0.62, 0.63, 0.67), Color(0.68, 0.6, 0.5),
|
||||
Color(0.55, 0.6, 0.66), Color(0.71, 0.66, 0.6), Color(0.6, 0.55, 0.58),
|
||||
]
|
||||
const SEGA_RED := Color(0.78, 0.16, 0.14)
|
||||
const ELEC_BLUE := Color(0.2, 0.38, 0.62)
|
||||
const GLASS := Color(0.62, 0.8, 0.84)
|
||||
const WINDOW := Color(0.16, 0.2, 0.28)
|
||||
const CANOPY := Color(0.5, 0.34, 0.22)
|
||||
const METAL := Color(0.44, 0.48, 0.54)
|
||||
const BRICK := Color(0.5, 0.34, 0.3)
|
||||
const INK := Color(0.08, 0.07, 0.1)
|
||||
const WOOD := Color(0.52, 0.34, 0.2)
|
||||
const VERMILION := Color(0.88, 0.26, 0.16)
|
||||
const LEAF := Color(0.3, 0.55, 0.32)
|
||||
const TRUNK := Color(0.4, 0.28, 0.18)
|
||||
|
||||
const SIGN_COLORS: Array[Color] = [
|
||||
Color(1.0, 0.3, 0.5), Color(0.25, 0.9, 0.95), Color(1.0, 0.8, 0.2),
|
||||
@@ -58,9 +67,11 @@ const SIGN_COLORS: Array[Color] = [
|
||||
]
|
||||
|
||||
var _spawn_points: Array[Vector3] = []
|
||||
var _rng := RandomNumberGenerator.new()
|
||||
|
||||
|
||||
## Full cel treatment (flat toon color + prop-scale ink outlines).
|
||||
# ── Cel-styled statics + render-only decoration ──────────────────────────────
|
||||
|
||||
func _box_static(pos: Vector3, size: Vector3, color: Color, node_name: String = "",
|
||||
acoustic: String = "") -> StaticBody3D:
|
||||
var body := super._box_static(pos, size, color, node_name, acoustic)
|
||||
@@ -72,232 +83,372 @@ func _box_static(pos: Vector3, size: Vector3, color: Color, node_name: String =
|
||||
return body
|
||||
|
||||
|
||||
func _build_environment() -> void:
|
||||
var env := LevelEnvironment.add_to(self, "sunset")
|
||||
env.environment.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR
|
||||
env.environment.ambient_light_color = Color(0.58, 0.54, 0.66)
|
||||
env.environment.ambient_light_energy = 3.0
|
||||
env.environment.fog_density = 0.0006
|
||||
var sun := get_node_or_null("Sun")
|
||||
if sun:
|
||||
sun.light_energy = 3.0
|
||||
sun.rotation_degrees = Vector3(-38, 55, 0)
|
||||
|
||||
|
||||
func _build_geometry() -> void:
|
||||
_build_environment()
|
||||
_build_lighting()
|
||||
_build_ground()
|
||||
_build_row(1) # east row (front plane x=+11, faces -X)
|
||||
_build_row(-1) # west row
|
||||
_build_viaduct()
|
||||
_build_streetlights()
|
||||
_build_crosswalks()
|
||||
_spawn_points.append_array([
|
||||
Vector3(0, 1.5, 34), Vector3(-9.5, 1.5, 20), Vector3(9.5, 1.5, -6),
|
||||
Vector3(0, VIADUCT_TOP + 1.5, -22), Vector3(-9.5, 1.5, -32), Vector3(9.5, 1.5, 30),
|
||||
])
|
||||
|
||||
|
||||
func _spawn_player(pid: int) -> CharacterBody3D:
|
||||
var player := super._spawn_player(pid)
|
||||
var s: Vector3 = _spawn_points[randi() % _spawn_points.size()] if not _spawn_points.is_empty() else Vector3(0, 2, 30)
|
||||
player.position = s + Vector3(randf_range(-1.0, 1.0), 0, randf_range(-1.0, 1.0))
|
||||
player.rotation.y = atan2(player.position.x, player.position.z)
|
||||
return player
|
||||
|
||||
|
||||
# ── Ground ───────────────────────────────────────────────────────────────────
|
||||
|
||||
func _build_ground() -> void:
|
||||
_box_static(Vector3(0, -0.5, 0), Vector3(2.0 * (absf(FRONT_E) + ROW_DEPTH) + 4.0, 1.0, 2.0 * EDGE_Z + 4.0),
|
||||
ASPHALT, "Ground")
|
||||
# Raised sidewalks flanking the avenue, full length
|
||||
_box_static(Vector3(-AVE_HALF - WALK_W * 0.5, 0.08, 0), Vector3(WALK_W, 0.16, EDGE_Z * 2.0), PAVING, "Sidewalk_W")
|
||||
_box_static(Vector3(AVE_HALF + WALK_W * 0.5, 0.08, 0), Vector3(WALK_W, 0.16, EDGE_Z * 2.0), PAVING, "Sidewalk_E")
|
||||
|
||||
|
||||
# ── Building rows (the aligned heart of the map) ─────────────────────────────
|
||||
|
||||
## side: +1 = east row, -1 = west row. Buildings are defined ONLY by their
|
||||
## z-interval and floor count; every facade element derives from those.
|
||||
func _build_row(side: int) -> void:
|
||||
# z0, z1, floors, facade color index (-1 = landmark red / -2 = blue)
|
||||
var plots: Array = [
|
||||
[-EDGE_Z, VIADUCT_Z0, 5 if side == 1 else 4, -1 if side == 1 else 3], # north of viaduct
|
||||
[VIADUCT_Z1, -6.0, 3 if side == 1 else 2, 0 if side == 1 else -2], # mid segment
|
||||
[-6.0, ALLEY_Z0, 2 if side == 1 else 3, 1 if side == 1 else 4], # short building (roof-hop)
|
||||
[ALLEY_Z1, 24.0, 4 if side == 1 else 5, 2 if side == 1 else 0], # south of alley
|
||||
[24.0, EDGE_Z, 3 if side == 1 else 3, 4 if side == 1 else 2], # south corner
|
||||
]
|
||||
for i in plots.size():
|
||||
var p: Array = plots[i]
|
||||
var color: Color
|
||||
match int(p[3]):
|
||||
-1: color = SEGA_RED
|
||||
-2: color = ELEC_BLUE
|
||||
_: color = FACADES[int(p[3])]
|
||||
_building(side, float(p[0]), float(p[1]), int(p[2]), color, "%s%d" % ["E" if side == 1 else "W", i])
|
||||
|
||||
# Fire escapes climb the alley faces (both rows, north side of the alley)
|
||||
_fire_escape(side, ALLEY_Z0, int(plots[2][2]))
|
||||
|
||||
|
||||
## One retail building: body, parapet, glass storefront, canopy, stacked
|
||||
## floor sign bands, a vertical corner sign column, aligned roof AC units,
|
||||
## and a rooftop billboard on 4+ floor buildings.
|
||||
func _building(side: int, z0: float, z1: float, floors: int, facade: Color, id: String) -> void:
|
||||
var w := z1 - z0
|
||||
var zc := (z0 + z1) * 0.5
|
||||
var h := float(floors) * FLOOR_H
|
||||
var front := float(side) * FRONT_E
|
||||
var cx := front + float(side) * ROW_DEPTH * 0.5
|
||||
var n := "Bldg_" + id
|
||||
|
||||
_box_static(Vector3(cx, h * 0.5, zc), Vector3(ROW_DEPTH, h, w), facade, n, "concrete")
|
||||
|
||||
# Parapet lips on the roof rim (walkable cover on the roof lane)
|
||||
var pt := 0.5
|
||||
_box_static(Vector3(front + float(side) * 0.2, h + pt * 0.5, zc), Vector3(0.4, pt, w), facade.darkened(0.15), n + "_ParapetF")
|
||||
_box_static(Vector3(front + float(side) * (ROW_DEPTH - 0.2), h + pt * 0.5, zc), Vector3(0.4, pt, w), facade.darkened(0.15), n + "_ParapetB")
|
||||
for e in 2:
|
||||
var ze := z0 + 0.2 if e == 0 else z1 - 0.2
|
||||
_box_static(Vector3(cx, h + pt * 0.5, ze), Vector3(ROW_DEPTH, pt, 0.4), facade.darkened(0.15), n + "_ParapetS%d" % e)
|
||||
|
||||
# Ground floor: inset glass storefront + wooden canopy over the sidewalk
|
||||
var face := front - float(side) * 0.12
|
||||
_box_static(Vector3(face, 2.0, zc), Vector3(0.25, 3.0, w - 1.6), GLASS, n + "_Storefront", "glass")
|
||||
_box_static(Vector3(front - float(side) * 1.1, 3.7, zc), Vector3(2.2, 0.2, w - 1.0), CANOPY, n + "_Canopy", "wood")
|
||||
|
||||
# Stacked sign bands at every floor line above the ground floor
|
||||
for f in range(1, floors):
|
||||
var y := float(f) * FLOOR_H + 0.9
|
||||
_neon_box(Vector3(front - float(side) * 0.1, y, zc), Vector3(0.16, 1.3, w - 2.6),
|
||||
SIGN_COLORS[(f + id.hash()) % SIGN_COLORS.size()])
|
||||
|
||||
# Vertical corner sign column (solid, grapple-able) with glowing segments
|
||||
if floors >= 3:
|
||||
var zs := z0 + 1.3
|
||||
var col_h := h - FLOOR_H
|
||||
_box_static(Vector3(front - float(side) * 0.45, FLOOR_H + col_h * 0.5 - 1.0, zs),
|
||||
Vector3(0.7, col_h, 1.4), METAL, n + "_SignCol", "metal")
|
||||
var segs := floors - 1
|
||||
for s2 in segs:
|
||||
var sy := FLOOR_H + 0.6 + (col_h - 1.2) * (float(s2) + 0.5) / float(segs) - 1.0
|
||||
_neon_box(Vector3(front - float(side) * 0.85, sy, zs), Vector3(0.12, 2.2, 1.2),
|
||||
SIGN_COLORS[(s2 + 1 + id.hash()) % SIGN_COLORS.size()])
|
||||
|
||||
# Roof AC units, evenly spaced along the roof midline
|
||||
var count := int(w / 6.0)
|
||||
for a in count:
|
||||
var za := z0 + w * (float(a) + 0.5) / float(count)
|
||||
_box_static(Vector3(cx, h + 0.8, za), Vector3(1.7, 1.6, 1.7), METAL, n + "_AC%d" % a, "metal")
|
||||
|
||||
# Rooftop billboard on tall buildings, facing the avenue
|
||||
if floors >= 4:
|
||||
var bb_h := 3.2
|
||||
var bz := zc
|
||||
_box_static(Vector3(front + float(side) * 2.0, h + bb_h * 0.5 + pt, bz),
|
||||
Vector3(0.5, bb_h, minf(w - 4.0, 10.0)), METAL, n + "_BillboardFrame", "metal")
|
||||
_neon_box(Vector3(front + float(side) * 1.7, h + bb_h * 0.5 + pt, bz),
|
||||
Vector3(0.12, bb_h - 0.6, minf(w - 4.6, 9.4)),
|
||||
SIGN_COLORS[id.hash() % SIGN_COLORS.size()])
|
||||
|
||||
|
||||
## Metal fire-escape platforms climbing an alley-side face, one per floor.
|
||||
func _fire_escape(side: int, alley_z: float, floors: int) -> void:
|
||||
var front := float(side) * FRONT_E
|
||||
for f in range(1, floors + 1):
|
||||
var y := float(f) * FLOOR_H - 0.4
|
||||
var xo := front + float(side) * (1.5 + float(f - 1) * 2.6)
|
||||
_box_static(Vector3(xo, y, alley_z - 1.3), Vector3(2.4, 0.16, 2.4), METAL,
|
||||
"Escape_%s_%d" % ["E" if side == 1 else "W", f], "metal")
|
||||
|
||||
|
||||
# ── Rail viaduct (brick, crosses the whole block) ────────────────────────────
|
||||
|
||||
func _build_viaduct() -> void:
|
||||
var depth := VIADUCT_Z1 - VIADUCT_Z0
|
||||
var zc := (VIADUCT_Z0 + VIADUCT_Z1) * 0.5
|
||||
var full_w := 2.0 * (absf(FRONT_E) + ROW_DEPTH)
|
||||
# Deck slab
|
||||
_box_static(Vector3(0, VIADUCT_TOP - 0.5, zc), Vector3(full_w, 1.0, depth), BRICK, "Viaduct_Deck", "brick")
|
||||
# Low side walls along both deck edges (slide-under cover up top)
|
||||
_box_static(Vector3(0, VIADUCT_TOP + 0.55, VIADUCT_Z0 + 0.3), Vector3(full_w, 1.1, 0.6), BRICK.darkened(0.1), "Viaduct_Wall_N", "brick")
|
||||
_box_static(Vector3(0, VIADUCT_TOP + 0.55, VIADUCT_Z1 - 0.3), Vector3(full_w, 1.1, 0.6), BRICK.darkened(0.1), "Viaduct_Wall_S", "brick")
|
||||
# Brick piers every 10m (the under-track arcade), avenue kept clear
|
||||
var xs := [-20.0, -14.0, 14.0, 20.0]
|
||||
for i in xs.size():
|
||||
_box_static(Vector3(xs[i], (VIADUCT_TOP - 1.0) * 0.5, zc), Vector3(2.0, VIADUCT_TOP - 1.0, depth - 2.0),
|
||||
BRICK, "Viaduct_Pier%d" % i, "brick")
|
||||
# Avenue-edge piers carry the deck across the road
|
||||
_box_static(Vector3(-AVE_HALF - 1.0, (VIADUCT_TOP - 1.0) * 0.5, zc), Vector3(2.0, VIADUCT_TOP - 1.0, depth - 2.0), BRICK, "Viaduct_PierW", "brick")
|
||||
_box_static(Vector3(AVE_HALF + 1.0, (VIADUCT_TOP - 1.0) * 0.5, zc), Vector3(2.0, VIADUCT_TOP - 1.0, depth - 2.0), BRICK, "Viaduct_PierE", "brick")
|
||||
|
||||
# Under-arch vending arcade (Akihabara's radio-parts stalls under the
|
||||
# tracks): aligned vending machines between the piers
|
||||
var vend_colors := [Color(0.85, 0.3, 0.3), Color(0.3, 0.55, 0.85), Color(0.92, 0.92, 0.95), Color(0.3, 0.75, 0.5)]
|
||||
var vx := [-17.0, 17.0, -23.0, 23.0]
|
||||
for i in vx.size():
|
||||
_box_static(Vector3(vx[i], 1.0, VIADUCT_Z1 - 1.6), Vector3(1.1, 2.0, 0.9),
|
||||
vend_colors[i % vend_colors.size()], "ArcadeVending_%d" % i, "metal")
|
||||
|
||||
# Slide ramps up to the deck along both sidewalks (aligned to the walk)
|
||||
var ramp_len := 18.0
|
||||
var ramp_ang := rad_to_deg(atan2(VIADUCT_TOP, ramp_len))
|
||||
for side: float in [-1.0, 1.0]:
|
||||
var rx: float = side * (AVE_HALF + WALK_W * 0.5)
|
||||
var rz := VIADUCT_Z1 + ramp_len * 0.5
|
||||
var r := _ramp_static(Vector3(rx, VIADUCT_TOP * 0.5, rz),
|
||||
Vector3(WALK_W, 0.4, sqrt(ramp_len * ramp_len + VIADUCT_TOP * VIADUCT_TOP)),
|
||||
Vector3(-ramp_ang, 0, 0), PAVING.darkened(0.1),
|
||||
"ViaductRamp_%s" % ("E" if side > 0 else "W"), "concrete")
|
||||
# Handrail (thin metal ink line along the ramp edge)
|
||||
var rail := _ramp_static(Vector3(rx + side * (WALK_W * 0.5 - 0.06), VIADUCT_TOP * 0.5 + 0.55, rz),
|
||||
Vector3(0.12, 1.0, sqrt(ramp_len * ramp_len + VIADUCT_TOP * VIADUCT_TOP) - 0.8),
|
||||
Vector3(-ramp_ang, 0, 0), METAL, "ViaductRail_%s" % ("E" if side > 0 else "W"), "metal")
|
||||
|
||||
|
||||
# ── Street furniture ─────────────────────────────────────────────────────────
|
||||
|
||||
func _build_streetlights() -> void:
|
||||
# Matched pairs of avenue lights every 16m, arms reaching over the road.
|
||||
var z := -10.0
|
||||
while z <= EDGE_Z - 4.0:
|
||||
for side: float in [-1.0, 1.0]:
|
||||
var x: float = side * (AVE_HALF + 0.6)
|
||||
_box_static(Vector3(x, 3.25, z), Vector3(0.28, 6.5, 0.28), METAL, "Lamp_%d_%s" % [int(z), "E" if side > 0 else "W"], "metal")
|
||||
_box_static(Vector3(x - side * 1.5, 6.35, z), Vector3(3.0, 0.22, 0.22), METAL, "LampArm_%d_%s" % [int(z), "E" if side > 0 else "W"], "metal")
|
||||
_neon_box(Vector3(x - side * 2.8, 6.2, z), Vector3(0.9, 0.18, 0.4), Color(1.0, 0.95, 0.8))
|
||||
z += 16.0
|
||||
|
||||
|
||||
func _build_crosswalks() -> void:
|
||||
# Zebra stripes across the avenue at the alley mouth and the south block.
|
||||
for cz in [10.0, 28.0]:
|
||||
var stripes := 7
|
||||
for s in stripes:
|
||||
var x := -AVE_HALF + 1.0 + (2.0 * (AVE_HALF - 1.0)) * float(s) / float(stripes - 1)
|
||||
_box_static(Vector3(x, 0.03, cz), Vector3(0.9, 0.06, 3.6), STRIPE, "Zebra_%d_%d" % [int(cz), s])
|
||||
|
||||
|
||||
# ── Neon (visual only) ───────────────────────────────────────────────────────
|
||||
|
||||
func _neon_mat(color: Color) -> StandardMaterial3D:
|
||||
func _deco_box(pos: Vector3, size: Vector3, color: Color, emissive: bool = false) -> void:
|
||||
var mi := MeshInstance3D.new()
|
||||
var bm := BoxMesh.new()
|
||||
bm.size = size
|
||||
if emissive:
|
||||
var m := StandardMaterial3D.new()
|
||||
m.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
||||
m.albedo_color = color
|
||||
m.emission_enabled = true
|
||||
m.emission = color
|
||||
m.emission_energy_multiplier = 2.2
|
||||
return m
|
||||
|
||||
|
||||
func _neon_box(pos: Vector3, size: Vector3, color: Color) -> void:
|
||||
var mi := MeshInstance3D.new()
|
||||
var bm := BoxMesh.new()
|
||||
bm.size = size
|
||||
bm.material = _neon_mat(color)
|
||||
bm.material = m
|
||||
else:
|
||||
bm.material = LevelMaterials.flat(color)
|
||||
mi.mesh = bm
|
||||
mi.position = pos
|
||||
add_child(mi)
|
||||
|
||||
|
||||
# ── Environment ──────────────────────────────────────────────────────────────
|
||||
|
||||
func _build_environment() -> void:
|
||||
var env := LevelEnvironment.add_to(self, "sunset")
|
||||
env.environment.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR
|
||||
env.environment.ambient_light_color = Color(0.58, 0.54, 0.66)
|
||||
env.environment.ambient_light_energy = 3.0
|
||||
env.environment.fog_density = 0.0011 # city haze for depth over 600m
|
||||
var sun := get_node_or_null("Sun")
|
||||
if sun:
|
||||
sun.light_energy = 3.0
|
||||
sun.rotation_degrees = Vector3(-38, 55, 0)
|
||||
sun.directional_shadow_max_distance = 220.0
|
||||
|
||||
|
||||
func _build_geometry() -> void:
|
||||
_build_environment()
|
||||
_build_lighting()
|
||||
# Ground: one asphalt slab for the whole district
|
||||
_box_static(Vector3(0, -0.5, 0), Vector3(CITY_HALF * 2.0 + 16.0, 1.0, CITY_HALF * 2.0 + 16.0), ASPHALT, "Ground")
|
||||
# Perimeter wall
|
||||
for i in 4:
|
||||
var horiz := i < 2
|
||||
var s := 1.0 if i % 2 == 0 else -1.0
|
||||
var p := (CITY_HALF + 6.0) * s
|
||||
_box_static(Vector3(0.0 if horiz else p, 8.0, p if horiz else 0.0),
|
||||
Vector3(CITY_HALF * 2.0 + 14.0 if horiz else 1.0, 16.0, 1.0 if horiz else CITY_HALF * 2.0 + 14.0),
|
||||
BRICK.darkened(0.1), "CityWall_%d" % i, "brick")
|
||||
|
||||
for bz in GRID:
|
||||
for bx in GRID:
|
||||
_build_block(bx, bz)
|
||||
|
||||
_build_rail_line()
|
||||
_build_avenues()
|
||||
|
||||
if _spawn_points.is_empty():
|
||||
_spawn_points.append(Vector3(0, 2, 0))
|
||||
|
||||
|
||||
func _spawn_player(pid: int) -> CharacterBody3D:
|
||||
var player := super._spawn_player(pid)
|
||||
var s: Vector3 = _spawn_points[randi() % _spawn_points.size()]
|
||||
player.position = s + Vector3(randf_range(-1.0, 1.0), 0, randf_range(-1.0, 1.0))
|
||||
player.rotation.y = atan2(player.position.x, player.position.z)
|
||||
return player
|
||||
|
||||
|
||||
# ── Grid plumbing ────────────────────────────────────────────────────────────
|
||||
|
||||
func _cell_center(bx: int, bz: int) -> Vector3:
|
||||
return Vector3((float(bx) - float(GRID - 1) * 0.5) * PITCH, 0.0, (float(bz) - float(GRID - 1) * 0.5) * PITCH)
|
||||
|
||||
|
||||
func _build_block(bx: int, bz: int) -> void:
|
||||
var c := _cell_center(bx, bz)
|
||||
_rng.seed = hash(Vector2i(bx, bz)) + 77
|
||||
var t := LAYOUT[bz][bx]
|
||||
# Sidewalk apron under the whole cell
|
||||
_box_static(c + Vector3(0, 0.08, 0), Vector3(BLOCK + 4.0, 0.16, BLOCK + 4.0), PAVING, "Walk_%d_%d" % [bx, bz])
|
||||
match t:
|
||||
"S": _standard_block(c, bx, bz)
|
||||
"T": _tower_block(c, bx, bz)
|
||||
"J": _shrine_block(c, bx, bz)
|
||||
"M": _market_block(c, bx, bz)
|
||||
"P": _plaza_block(c, bx, bz)
|
||||
"C": _construction_block(c, bx, bz)
|
||||
|
||||
|
||||
# ── Buildings (shared parametric unit) ───────────────────────────────────────
|
||||
|
||||
## front_x: world x of the facade plane. dir: +1 body extends +x, -1 extends -x.
|
||||
func _building(front_x: float, dir: int, z0: float, z1: float, depth: float,
|
||||
floors: int, facade: Color, id: String) -> void:
|
||||
var w := z1 - z0
|
||||
var zc := (z0 + z1) * 0.5
|
||||
var h := float(floors) * FLOOR_H
|
||||
var cx := front_x + float(dir) * depth * 0.5
|
||||
var n := "B" + id
|
||||
|
||||
_box_static(Vector3(cx, h * 0.5, zc), Vector3(depth, h, w), facade, n, "concrete")
|
||||
|
||||
# Parapet ring
|
||||
var pt := 0.5
|
||||
_box_static(Vector3(front_x + float(dir) * 0.2, h + pt * 0.5, zc), Vector3(0.4, pt, w), facade.darkened(0.15), n + "pf")
|
||||
_box_static(Vector3(front_x + float(dir) * (depth - 0.2), h + pt * 0.5, zc), Vector3(0.4, pt, w), facade.darkened(0.15), n + "pb")
|
||||
for e in 2:
|
||||
_box_static(Vector3(cx, h + pt * 0.5, (z0 + 0.2) if e == 0 else (z1 - 0.2)), Vector3(depth, pt, 0.4), facade.darkened(0.15), n + "ps%d" % e)
|
||||
|
||||
# Ground floor: glass storefront + canopy
|
||||
_box_static(Vector3(front_x - float(dir) * 0.12, 2.0, zc), Vector3(0.25, 3.0, w - 1.6), GLASS, n + "g", "glass")
|
||||
_box_static(Vector3(front_x - float(dir) * 1.1, 3.7, zc), Vector3(2.2, 0.2, w - 1.0), CANOPY, n + "c", "wood")
|
||||
|
||||
# Upper floors: sign band + window band per floor (render-only)
|
||||
for f in range(1, floors):
|
||||
var y := float(f) * FLOOR_H
|
||||
_deco_box(Vector3(front_x - float(dir) * 0.1, y + 0.9, zc), Vector3(0.16, 1.3, w - 2.6),
|
||||
SIGN_COLORS[(f + id.hash()) % SIGN_COLORS.size()], true)
|
||||
_deco_box(Vector3(front_x - float(dir) * 0.08, y + 2.6, zc), Vector3(0.12, 1.4, w - 2.0), WINDOW)
|
||||
|
||||
# Corner sign column with glowing segments
|
||||
if floors >= 3:
|
||||
var zs := z0 + 1.3
|
||||
var col_h := h - FLOOR_H
|
||||
_box_static(Vector3(front_x - float(dir) * 0.45, FLOOR_H + col_h * 0.5 - 1.0, zs),
|
||||
Vector3(0.7, col_h, 1.4), METAL, n + "sc", "metal")
|
||||
for s2 in floors - 1:
|
||||
var sy := FLOOR_H - 1.0 + 0.6 + (col_h - 1.2) * (float(s2) + 0.5) / float(floors - 1)
|
||||
_deco_box(Vector3(front_x - float(dir) * 0.85, sy, zs), Vector3(0.12, 2.2, 1.2),
|
||||
SIGN_COLORS[(s2 + 1 + id.hash()) % SIGN_COLORS.size()], true)
|
||||
|
||||
# Roof furniture: AC units always; water tank or antenna by seed
|
||||
var count := maxi(1, int(w / 8.0))
|
||||
for a in count:
|
||||
var za := z0 + w * (float(a) + 0.5) / float(count)
|
||||
_box_static(Vector3(cx + float(dir) * 1.5, h + 0.8, za), Vector3(1.7, 1.6, 1.7), METAL, n + "ac%d" % a, "metal")
|
||||
match _rng.randi() % 3:
|
||||
0: # water tank on legs
|
||||
_box_static(Vector3(cx - float(dir) * 2.5, h + 1.9, zc), Vector3(2.4, 2.4, 2.4), METAL.lightened(0.15), n + "wt", "metal")
|
||||
1: # antenna mast (grapple pole)
|
||||
_box_static(Vector3(cx - float(dir) * 2.5, h + 2.6, zc), Vector3(0.3, 5.2, 0.3), METAL, n + "an", "metal")
|
||||
_:
|
||||
pass
|
||||
|
||||
# Rooftop billboard on tall buildings
|
||||
if floors >= 4:
|
||||
var bw := minf(w - 4.0, 10.0)
|
||||
_box_static(Vector3(front_x + float(dir) * 2.0, h + 2.1, zc), Vector3(0.5, 3.2, bw), METAL, n + "bf", "metal")
|
||||
_deco_box(Vector3(front_x + float(dir) * 1.7, h + 2.1, zc), Vector3(0.12, 2.6, bw - 0.6),
|
||||
SIGN_COLORS[id.hash() % SIGN_COLORS.size()], true)
|
||||
|
||||
|
||||
# ── Block types ──────────────────────────────────────────────────────────────
|
||||
|
||||
## Retail fabric: two building pairs back-to-back facing the E/W streets,
|
||||
## seeded heights/colors; ~half the blocks get a mid-block alley with fire
|
||||
## escapes (wallrun + climb route).
|
||||
func _standard_block(c: Vector3, bx: int, bz: int) -> void:
|
||||
var half := BLOCK * 0.5
|
||||
var alley := _rng.randi() % 2 == 0
|
||||
var gap := 4.0 if alley else 0.0
|
||||
var depth := (BLOCK - gap) * 0.5
|
||||
for side in 2:
|
||||
var dirv := 1 if side == 0 else -1 # 0: faces west street, body +x
|
||||
var front := c.x - float(dirv) * half
|
||||
# Two buildings along z per face
|
||||
var split := c.z + _rng.randf_range(-6.0, 6.0)
|
||||
var f1 := 2 + _rng.randi() % 4
|
||||
var f2 := 2 + _rng.randi() % 4
|
||||
var col1 := FACADES[_rng.randi() % FACADES.size()]
|
||||
var col2 := FACADES[_rng.randi() % FACADES.size()]
|
||||
_building(front, dirv, c.z - half, split, depth, f1, col1, "%d_%d_%d0" % [bx, bz, side])
|
||||
_building(front, dirv, split, c.z + half, depth, f2, col2, "%d_%d_%d1" % [bx, bz, side])
|
||||
if alley:
|
||||
# Fire escapes climbing into the mid-block alley
|
||||
for f in range(1, 3):
|
||||
var y := float(f) * FLOOR_H - 0.4
|
||||
_box_static(c + Vector3(-2.0 + float(f) * 1.3, y, 4.0), Vector3(2.2, 0.16, 2.2), METAL,
|
||||
"Esc_%d_%d_%d" % [bx, bz, f], "metal")
|
||||
if _rng.randi() % 3 == 0:
|
||||
_spawn_points.append(c + Vector3(0, 1.5, 0))
|
||||
|
||||
|
||||
## Landmark: an 8-10 floor tower with a retail podium and mega-billboards.
|
||||
func _tower_block(c: Vector3, bx: int, bz: int) -> void:
|
||||
var floors := 8 + _rng.randi() % 3
|
||||
var h := float(floors) * FLOOR_H
|
||||
var col := SEGA_RED if _rng.randi() % 3 == 0 else (ELEC_BLUE if _rng.randi() % 2 == 0 else FACADES[_rng.randi() % FACADES.size()])
|
||||
var n := "Tower_%d_%d" % [bx, bz]
|
||||
# Podium (1 floor, walkable roof ring)
|
||||
_box_static(c + Vector3(0, FLOOR_H * 0.5, 0), Vector3(BLOCK - 4.0, FLOOR_H, BLOCK - 4.0), col.darkened(0.15), n + "_Podium", "concrete")
|
||||
_box_static(c + Vector3(0, 2.0, -(BLOCK - 4.0) * 0.5 + 0.1), Vector3(BLOCK - 8.0, 3.0, 0.25), GLASS, n + "_Glass", "glass")
|
||||
# Tower shaft
|
||||
var tw := BLOCK - 20.0
|
||||
_box_static(c + Vector3(0, FLOOR_H + (h - FLOOR_H) * 0.5, 0), Vector3(tw, h - FLOOR_H, tw), col, n, "concrete")
|
||||
# Window + sign bands on two faces
|
||||
for f in range(2, floors):
|
||||
var y := float(f) * FLOOR_H
|
||||
_deco_box(c + Vector3(-tw * 0.5 - 0.1, y + 1.7, 0), Vector3(0.14, 2.0, tw - 3.0), WINDOW)
|
||||
_deco_box(c + Vector3(tw * 0.5 + 0.1, y + 1.7, 0), Vector3(0.14, 2.0, tw - 3.0), WINDOW)
|
||||
# Mega vertical sign on the tower corner
|
||||
_box_static(c + Vector3(-tw * 0.5 - 0.5, FLOOR_H + (h - FLOOR_H) * 0.45, -tw * 0.5 + 1.5),
|
||||
Vector3(0.8, (h - FLOOR_H) * 0.8, 1.6), METAL, n + "_SignCol", "metal")
|
||||
for s2 in floors - 2:
|
||||
var sy := FLOOR_H * 2.0 + (h - FLOOR_H * 3.0) * float(s2) / float(maxi(floors - 3, 1))
|
||||
_deco_box(c + Vector3(-tw * 0.5 - 0.95, sy, -tw * 0.5 + 1.5), Vector3(0.14, 2.4, 1.4),
|
||||
SIGN_COLORS[s2 % SIGN_COLORS.size()], true)
|
||||
# Rooftop crown billboard both directions
|
||||
_box_static(c + Vector3(0, h + 2.4, 0), Vector3(0.6, 4.0, tw - 4.0), METAL, n + "_Crown", "metal")
|
||||
_deco_box(c + Vector3(0.45, h + 2.4, 0), Vector3(0.12, 3.4, tw - 4.6), SIGN_COLORS[(bx + bz) % SIGN_COLORS.size()], true)
|
||||
_deco_box(c + Vector3(-0.45, h + 2.4, 0), Vector3(0.12, 3.4, tw - 4.6), SIGN_COLORS[(bx + bz + 2) % SIGN_COLORS.size()], true)
|
||||
_spawn_points.append(c + Vector3(0, FLOOR_H + 1.5, (BLOCK - 4.0) * 0.4))
|
||||
|
||||
|
||||
## Shrine courtyard: torii, hall with sloped roof, lanterns, trees — the
|
||||
## quiet green break in the city fabric.
|
||||
func _shrine_block(c: Vector3, bx: int, bz: int) -> void:
|
||||
var n := "Shrine_%d_%d" % [bx, bz]
|
||||
# Courtyard paving + low wall ring
|
||||
_box_static(c + Vector3(0, 0.12, 0), Vector3(BLOCK - 6.0, 0.1, BLOCK - 6.0), PAVING.lightened(0.1), n + "_Court")
|
||||
for i in 4:
|
||||
var horiz := i < 2
|
||||
var s := 1.0 if i % 2 == 0 else -1.0
|
||||
var off := (BLOCK - 6.0) * 0.5 * s
|
||||
_box_static(c + Vector3(0.0 if horiz else off, 0.7, off if horiz else 0.0),
|
||||
Vector3(BLOCK - 6.0 if horiz else 0.6, 1.4, 0.6 if horiz else BLOCK - 6.0),
|
||||
Color(0.55, 0.55, 0.52), n + "_Wall%d" % i, "brick")
|
||||
# Torii gate at the south entry
|
||||
var tz := c.z + (BLOCK - 6.0) * 0.5
|
||||
_box_static(c + Vector3(-4.0, 3.5, tz), Vector3(0.8, 7.0, 0.8), VERMILION, n + "_PillarW", "wood")
|
||||
_box_static(c + Vector3(4.0, 3.5, tz), Vector3(0.8, 7.0, 0.8), VERMILION, n + "_PillarE", "wood")
|
||||
_box_static(c + Vector3(0, 7.0, tz), Vector3(11.0, 0.7, 1.1), VERMILION, n + "_BeamTop", "wood")
|
||||
_box_static(c + Vector3(0, 5.8, tz), Vector3(9.4, 0.5, 0.9), VERMILION.darkened(0.15), n + "_BeamLow", "wood")
|
||||
# Hall with slide roof
|
||||
_box_static(c + Vector3(0, 2.0, -10.0), Vector3(12.0, 4.0, 8.0), WOOD.darkened(0.2), n + "_Hall", "wood")
|
||||
_ramp_static(c + Vector3(0, 4.9, -8.2), Vector3(13.0, 0.3, 5.6), Vector3(-24, 0, 0), VERMILION.darkened(0.35), n + "_Roof", "wood")
|
||||
# Stone lanterns + trees (trunk static, canopy deco)
|
||||
for i in 2:
|
||||
var lx := -8.0 + 16.0 * float(i)
|
||||
_box_static(c + Vector3(lx, 1.0, 8.0), Vector3(1.0, 2.0, 1.0), Color(0.55, 0.55, 0.52), n + "_Lant%d" % i, "brick")
|
||||
_box_static(c + Vector3(lx, 1.6, -2.0), Vector3(0.7, 3.2, 0.7), TRUNK, n + "_Trunk%d" % i, "wood")
|
||||
_deco_box(c + Vector3(lx, 4.6, -2.0), Vector3(4.2, 3.4, 4.2), LEAF)
|
||||
_spawn_points.append(c + Vector3(0, 1.5, 12))
|
||||
|
||||
|
||||
## Market street: two dense stall rows with lantern strings — low cover maze.
|
||||
func _market_block(c: Vector3, bx: int, bz: int) -> void:
|
||||
var n := "Mkt_%d_%d" % [bx, bz]
|
||||
# Back walls: modest 2-floor buildings on east/west faces
|
||||
_building(c.x - BLOCK * 0.5, 1, c.z - BLOCK * 0.5, c.z + BLOCK * 0.5, 10.0, 2,
|
||||
FACADES[_rng.randi() % FACADES.size()], "%d_%dmw" % [bx, bz])
|
||||
_building(c.x + BLOCK * 0.5, -1, c.z - BLOCK * 0.5, c.z + BLOCK * 0.5, 10.0, 2,
|
||||
FACADES[_rng.randi() % FACADES.size()], "%d_%dme" % [bx, bz])
|
||||
# Stall rows down the middle
|
||||
for r in 2:
|
||||
var x := c.x + (-5.0 if r == 0 else 5.0)
|
||||
for s in 4:
|
||||
var z := c.z - 15.0 + float(s) * 10.0
|
||||
_box_static(Vector3(x, 1.0, z), Vector3(3.2, 2.0, 2.2), WOOD, "%s_S%d_%d" % [n, r, s], "wood")
|
||||
_box_static(Vector3(x, 2.35, z), Vector3(4.0, 0.16, 3.0),
|
||||
SIGN_COLORS[(r + s) % SIGN_COLORS.size()].darkened(0.5), "%s_C%d_%d" % [n, r, s], "wood")
|
||||
# Lantern string over each row
|
||||
for l in 6:
|
||||
_deco_box(Vector3(x, 3.6, c.z - 17.5 + float(l) * 7.0), Vector3(0.32, 0.45, 0.32), Color(1.0, 0.62, 0.25), true)
|
||||
# Vending pair at the north entry
|
||||
for v in 2:
|
||||
_box_static(c + Vector3(-1.5 + 3.0 * float(v), 1.0, -BLOCK * 0.5 + 3.0), Vector3(1.1, 2.0, 0.9),
|
||||
[Color(0.85, 0.3, 0.3), Color(0.3, 0.55, 0.85)][v], "%s_V%d" % [n, v], "metal")
|
||||
_spawn_points.append(c + Vector3(0, 1.5, 0))
|
||||
|
||||
|
||||
## Plaza: open ground; the exact centre cell hosts the giant screen tower.
|
||||
func _plaza_block(c: Vector3, bx: int, bz: int) -> void:
|
||||
var n := "Plaza_%d_%d" % [bx, bz]
|
||||
# Planter cover ring
|
||||
for i in 4:
|
||||
var a := TAU * float(i) / 4.0 + PI / 4.0
|
||||
var p := c + Vector3(cos(a), 0, sin(a)) * (BLOCK * 0.32)
|
||||
_box_static(Vector3(p.x, 0.55, p.z), Vector3(4.0, 1.1, 4.0), Color(0.5, 0.48, 0.52), "%s_Pl%d" % [n, i], "brick")
|
||||
_deco_box(Vector3(p.x, 1.5, p.z), Vector3(3.2, 1.0, 3.2), LEAF)
|
||||
# The Crossing screen tower on the single centre-most plaza cell
|
||||
if bx == 5 and bz == 4:
|
||||
_box_static(c + Vector3(0, 9.0, 0), Vector3(6.0, 18.0, 6.0), METAL.darkened(0.2), n + "_ScreenTower", "metal")
|
||||
for face in 4:
|
||||
var a2 := TAU * float(face) / 4.0
|
||||
var off := Vector3(cos(a2), 0, sin(a2)) * 3.2
|
||||
var flat := absf(off.x) > absf(off.z)
|
||||
_deco_box(c + off + Vector3(0, 11.0, 0),
|
||||
Vector3(0.14 if flat else 4.6, 6.0, 4.6 if flat else 0.14),
|
||||
SIGN_COLORS[face % SIGN_COLORS.size()], true)
|
||||
_spawn_points.append(c + Vector3(BLOCK * 0.2, 1.5, -BLOCK * 0.2))
|
||||
|
||||
|
||||
## Construction site: crane (grapple mast + jib), container stacks, ramps
|
||||
## and scaffold platforms — the vertical playground block.
|
||||
func _construction_block(c: Vector3, bx: int, bz: int) -> void:
|
||||
var n := "Site_%d_%d" % [bx, bz]
|
||||
# Unfinished frame: slab stack with open floors (wallrun edges)
|
||||
for lvl in 3:
|
||||
_box_static(c + Vector3(-8, float(lvl) * 6.0 + 5.7, -8), Vector3(24.0, 0.6, 24.0), Color(0.6, 0.58, 0.56), "%s_Slab%d" % [n, lvl], "concrete")
|
||||
for px in 2:
|
||||
for pz in 2:
|
||||
_box_static(c + Vector3(-8.0 + (-10.0 + 20.0 * float(px)), 9.0, -8.0 + (-10.0 + 20.0 * float(pz))),
|
||||
Vector3(1.0, 18.0, 1.0), Color(0.6, 0.58, 0.56), "%s_Col%d%d" % [n, px, pz], "concrete")
|
||||
# Access ramp to first slab
|
||||
_ramp_static(c + Vector3(6.0, 3.0, -8.0), Vector3(4.0, 0.4, 13.5), Vector3(0, 0, -26), METAL, n + "_Ramp", "metal")
|
||||
# Crane: mast + jib over the street (THE grapple anchor)
|
||||
_box_static(c + Vector3(14, 14.0, 14), Vector3(1.6, 28.0, 1.6), Color(0.9, 0.6, 0.15), n + "_CraneMast", "metal")
|
||||
_box_static(c + Vector3(4, 27.5, 14), Vector3(22.0, 1.2, 1.2), Color(0.9, 0.6, 0.15), n + "_CraneJib", "metal")
|
||||
_deco_box(c + Vector3(-6.0, 24.0, 14), Vector3(0.12, 6.5, 0.12), INK_COLOR)
|
||||
# Container stacks (jump steps + cover)
|
||||
var cc := [Color(0.75, 0.35, 0.25), Color(0.3, 0.55, 0.85), Color(0.35, 0.65, 0.4)]
|
||||
for i in 3:
|
||||
_box_static(c + Vector3(10, 1.3 + 2.6 * float(mini(i, 1)), -12.0 + float(i) * 3.2), Vector3(6.2, 2.6, 2.6),
|
||||
cc[i], "%s_Cont%d" % [n, i], "metal")
|
||||
_spawn_points.append(c + Vector3(14, 1.5, -14))
|
||||
|
||||
const INK_COLOR := Color(0.08, 0.07, 0.1)
|
||||
|
||||
|
||||
# ── Rail viaduct across the whole city ───────────────────────────────────────
|
||||
|
||||
func _build_rail_line() -> void:
|
||||
var z := (float(RAIL_ROW) - float(GRID - 1) * 0.5) * PITCH + PITCH * 0.5 # street south of RAIL_ROW
|
||||
var full := CITY_HALF * 2.0 + 12.0
|
||||
_box_static(Vector3(0, RAIL_TOP - 0.5, z), Vector3(full, 1.0, 7.0), BRICK, "Rail_Deck", "brick")
|
||||
_box_static(Vector3(0, RAIL_TOP + 0.55, z - 3.2), Vector3(full, 1.1, 0.5), BRICK.darkened(0.1), "Rail_WallN", "brick")
|
||||
_box_static(Vector3(0, RAIL_TOP + 0.55, z + 3.2), Vector3(full, 1.1, 0.5), BRICK.darkened(0.1), "Rail_WallS", "brick")
|
||||
# Piers at every block line (streets cross beneath between them)
|
||||
var i := 0
|
||||
var x := -CITY_HALF + PITCH * 0.5
|
||||
while x <= CITY_HALF:
|
||||
_box_static(Vector3(x, (RAIL_TOP - 1.0) * 0.5, z), Vector3(2.2, RAIL_TOP - 1.0, 5.4), BRICK, "Rail_Pier%d" % i, "brick")
|
||||
# vending pair under alternating arches
|
||||
if i % 2 == 0:
|
||||
_box_static(Vector3(x + 4.0, 1.0, z + 1.8), Vector3(1.1, 2.0, 0.9), Color(0.85, 0.3, 0.3), "Rail_V%da" % i, "metal")
|
||||
_box_static(Vector3(x + 5.6, 1.0, z + 1.8), Vector3(1.1, 2.0, 0.9), Color(0.3, 0.55, 0.85), "Rail_V%db" % i, "metal")
|
||||
i += 1
|
||||
x += PITCH
|
||||
# Slide ramps to the deck at the two main-avenue crossings
|
||||
for sx in [-PITCH * 0.5 - 4.0, PITCH * 0.5 + 4.0]:
|
||||
var ang := rad_to_deg(atan2(RAIL_TOP, 16.0))
|
||||
_ramp_static(Vector3(sx, RAIL_TOP * 0.5, z + 3.5 + 8.0), Vector3(3.0, 0.4, sqrt(256.0 + RAIL_TOP * RAIL_TOP)),
|
||||
Vector3(-ang, 0, 0), PAVING.darkened(0.1), "Rail_Ramp_%d" % int(sx), "concrete")
|
||||
_spawn_points.append(Vector3(0, RAIL_TOP + 1.5, z))
|
||||
|
||||
|
||||
# ── Main avenues: lights + crosswalks ────────────────────────────────────────
|
||||
|
||||
func _build_avenues() -> void:
|
||||
# The two central avenues (x=0 and z=0) get matched streetlights and
|
||||
# zebra crossings at every block line.
|
||||
var zline := -CITY_HALF + PITCH * 0.5
|
||||
while zline <= CITY_HALF:
|
||||
for side: float in [-1.0, 1.0]:
|
||||
var x := side * (STREET * 0.5 + 0.6)
|
||||
_box_static(Vector3(x, 3.25, zline), Vector3(0.28, 6.5, 0.28), METAL, "LampA_%d_%d" % [int(zline), int(side)], "metal")
|
||||
_box_static(Vector3(x - side * 1.5, 6.35, zline), Vector3(3.0, 0.22, 0.22), METAL, "LampArmA_%d_%d" % [int(zline), int(side)], "metal")
|
||||
_deco_box(Vector3(x - side * 2.8, 6.2, zline), Vector3(0.9, 0.18, 0.4), Color(1.0, 0.95, 0.8), true)
|
||||
# Crosswalk across the x=0 avenue at each block line
|
||||
for s in 5:
|
||||
var sxx := -6.0 + 3.0 * float(s)
|
||||
_deco_box(Vector3(sxx, 0.03, zline + PITCH * 0.5 - 4.0), Vector3(0.9, 0.06, 3.2), STRIPE)
|
||||
zline += PITCH
|
||||
var xline := -CITY_HALF + PITCH * 0.5
|
||||
while xline <= CITY_HALF:
|
||||
for side: float in [-1.0, 1.0]:
|
||||
var z2 := side * (STREET * 0.5 + 0.6)
|
||||
_box_static(Vector3(xline, 3.25, z2), Vector3(0.28, 6.5, 0.28), METAL, "LampB_%d_%d" % [int(xline), int(side)], "metal")
|
||||
xline += PITCH
|
||||
|
||||
Reference in New Issue
Block a user