feat: Akiba Crossing — parametric Akihabara rebuild of the urban map
Complete new approach after the hand-placed layout shipped misaligned: every element now derives from a street grid and its building's footprint, so alignment is structural, not luck. District structure (modeled on Akihabara's Chuo-dori / Denki-gai / JR viaduct): - 16m avenue flanked by continuous mid-rise rows; buildings defined ONLY by z-interval + floor count (4m quantized floors), packed edge-to-edge - _building() derives everything from the footprint: parapet lips on the roof rim, inset glass storefront, sidewalk canopy, stacked per-floor sign bands, a solid vertical corner sign column with glowing segments, evenly spaced roof AC units, rooftop billboard on 4+ floor buildings - Brick rail viaduct crosses the whole block on piers with an under-track vending arcade; sidewalk-aligned slide ramps with handrails reach the deck on both flanks - Denki-gai alley cut through both rows (wallrun walls + fire escape platforms climbing per floor); red SEGA-style landmark + blue electronics building anchor the north corners - Matched streetlight pairs every 16m with arms over the road; zebra crossings at the alley mouth and south block; spawns on sidewalks/deck facing the map center Movement: viaduct high lane + roof-hop onto the 2-floor row, canopy lane above each sidewalk, alley wallruns + fire escapes, grapple anchors on lamp arms / sign columns / viaduct edge / billboards, slide ramps. Acoustics: facades concrete, storefronts glass, canopies wood, viaduct brick, signs/AC/vending/lights/escapes metal. Renamed in the level selector: "Akiba Crossing". Verified via aerial and street captures (aligned rows, clean canyon, viaduct framing); movement 11/11, smoke 0 failures, acoustics ALL PASSED. Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Fable 5
parent
233f88aee3
commit
a7fa7f207c
@@ -1,5 +1,5 @@
|
|||||||
[map]
|
[map]
|
||||||
name="Neon Alley"
|
name="Akiba Crossing"
|
||||||
scene_path="res://scenes/maps/neon_alley/neon_alley.tscn"
|
scene_path="res://scenes/maps/neon_alley/neon_alley.tscn"
|
||||||
color1=Color(1.0, 0.3, 0.55, 1.0)
|
color1=Color(1.0, 0.3, 0.55, 1.0)
|
||||||
color2=Color(0.2, 0.15, 0.45, 1.0)
|
color2=Color(0.2, 0.15, 0.45, 1.0)
|
||||||
|
|||||||
@@ -1,55 +1,71 @@
|
|||||||
extends TestLevelBuilder
|
extends TestLevelBuilder
|
||||||
|
|
||||||
## Neon Alley — an urban-Japan block built for the movement kit.
|
## Akiba Crossing — an Akihabara-styled block, rebuilt parametrically.
|
||||||
##
|
##
|
||||||
## Layout (X east, -Z north):
|
## Real-district structure this mimics (Chuo-dori + Denki-gai + JR viaduct):
|
||||||
## - Main street runs N-S: the long sightline, crossed by a pedestrian
|
## - A wide N-S avenue flanked by continuous mid-rise retail rows
|
||||||
## bridge (underpass = cover, deck = high ground, ramps = slide entries)
|
## - Buildings quantized to 4m floors, packed edge-to-edge with designed
|
||||||
## - West: wooden machiya row houses — jumpable awnings, sloped slide
|
## alley cuts (the narrow perpendicular Denki-gai streets)
|
||||||
## roofs, warm acoustics (shots through them stay readable)
|
## - An elevated rail viaduct crossing the whole block on brick piers,
|
||||||
## - East: concrete towers with glass storefronts, a brick wallrun alley
|
## with an arcade of vending machines beneath the arches
|
||||||
## between them, and a metal fire-escape route to the roofs
|
## - Stacked floor-by-floor sign bands, vertical corner sign columns and
|
||||||
## - North: shrine plaza — torii gate crossbeam is a grapple anchor
|
## rooftop billboards — every sign is derived from its building's
|
||||||
## - South: market stalls + vending machines = scattered mid cover
|
## footprint, so nothing floats or clips
|
||||||
## - Power pylons with crossarms chain a grapple route down the street
|
|
||||||
##
|
##
|
||||||
## Every surface carries an acoustic material (wood/brick/concrete/metal/
|
## Movement: viaduct deck = high lane (slide ramps at both avenue flanks),
|
||||||
## glass) so occlusion and reflections describe the map truthfully.
|
## 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.
|
||||||
|
##
|
||||||
|
## Acoustics: facades concrete, storefronts glass, canopies wood, viaduct
|
||||||
|
## brick, signs/AC/vending/lights/escapes metal.
|
||||||
|
|
||||||
# Palette — sunset-neon cel look.
|
const FLOOR_H := 4.0 # one story
|
||||||
const ASPHALT := Color(0.24, 0.24, 0.31)
|
const AVE_HALF := 8.0 # avenue half-width (x in [-8, 8])
|
||||||
const PAVING := Color(0.4, 0.38, 0.44)
|
const WALK_W := 3.0 # sidewalk width
|
||||||
const CONCRETE := Color(0.52, 0.5, 0.55)
|
const FRONT_E := AVE_HALF + WALK_W # east row front plane x = +11
|
||||||
const CONCRETE_DARK := Color(0.38, 0.37, 0.44)
|
const FRONT_W := -AVE_HALF - WALK_W # west row front plane x = -11
|
||||||
const WOOD := Color(0.52, 0.34, 0.2)
|
const ROW_DEPTH := 14.0 # building depth away from the avenue
|
||||||
const WOOD_DARK := Color(0.36, 0.24, 0.15)
|
const EDGE_Z := 38.0 # block half-length
|
||||||
const VERMILION := Color(0.88, 0.26, 0.16) # torii / shrine red
|
const VIADUCT_Z0 := -26.0 # viaduct strip (z)
|
||||||
const METAL := Color(0.42, 0.46, 0.52)
|
const VIADUCT_Z1 := -18.0
|
||||||
const GLASS := Color(0.6, 0.78, 0.82)
|
const VIADUCT_TOP := 8.0 # deck walking height (2 floors)
|
||||||
const ROOF_TILE := Color(0.25, 0.3, 0.42)
|
const ALLEY_Z0 := 8.0 # Denki-gai alley cut (z)
|
||||||
|
const ALLEY_Z1 := 12.0
|
||||||
|
|
||||||
const NEON_COLORS: Array[Color] = [
|
# Palette — dusk cel. Facades muted so the signage carries the color.
|
||||||
Color(1.0, 0.25, 0.55), Color(0.2, 0.9, 0.95), Color(1.0, 0.75, 0.2),
|
const ASPHALT := Color(0.3, 0.3, 0.37)
|
||||||
Color(0.55, 0.4, 1.0), Color(0.3, 1.0, 0.5),
|
const PAVING := Color(0.46, 0.44, 0.5)
|
||||||
|
const STRIPE := Color(0.85, 0.85, 0.88)
|
||||||
|
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
|
||||||
|
]
|
||||||
|
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 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 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),
|
||||||
|
Color(0.6, 0.45, 1.0), Color(0.35, 1.0, 0.55), Color(0.95, 0.95, 0.98),
|
||||||
]
|
]
|
||||||
|
|
||||||
const SPAWNS: Array[Vector3] = [
|
var _spawn_points: Array[Vector3] = []
|
||||||
Vector3(0, 2, 30), Vector3(-20, 2, -26), Vector3(20, 2, -24),
|
|
||||||
Vector3(-20, 2, 24), Vector3(24, 2, 20), Vector3(0, 7, 1),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
## Full cel treatment: every structure gets a FLAT toon color (no greybox
|
## Full cel treatment (flat toon color + prop-scale ink outlines).
|
||||||
## grid texture) plus an ink outline hull scaled to its size — the same
|
|
||||||
## inked-silhouette language as the characters. Ground-scale slabs skip the
|
|
||||||
## outline (a hull around an 80m floor just z-fights the sidewalks).
|
|
||||||
func _box_static(pos: Vector3, size: Vector3, color: Color, node_name: String = "",
|
func _box_static(pos: Vector3, size: Vector3, color: Color, node_name: String = "",
|
||||||
acoustic: String = "") -> StaticBody3D:
|
acoustic: String = "") -> StaticBody3D:
|
||||||
var body := super._box_static(pos, size, color, node_name, acoustic)
|
var body := super._box_static(pos, size, color, node_name, acoustic)
|
||||||
for mi in body.find_children("*", "MeshInstance3D", false, false):
|
for mi in body.find_children("*", "MeshInstance3D", false, false):
|
||||||
mi.mesh.surface_set_material(0, LevelMaterials.flat(color))
|
mi.mesh.surface_set_material(0, LevelMaterials.flat(color))
|
||||||
# Ink outlines on prop-scale pieces only: on building-scale boxes the
|
|
||||||
# inflated hull plates read as smears at close range.
|
|
||||||
if maxf(size.x, maxf(size.y, size.z)) < 10.0:
|
if maxf(size.x, maxf(size.y, size.z)) < 10.0:
|
||||||
var w := clampf(maxf(size.x, maxf(size.y, size.z)) * 0.008, 0.02, 0.05)
|
var w := clampf(maxf(size.x, maxf(size.y, size.z)) * 0.008, 0.02, 0.05)
|
||||||
mi.material_overlay = LevelMaterials.outline(w)
|
mi.material_overlay = LevelMaterials.outline(w)
|
||||||
@@ -58,8 +74,6 @@ func _box_static(pos: Vector3, size: Vector3, color: Color, node_name: String =
|
|||||||
|
|
||||||
func _build_environment() -> void:
|
func _build_environment() -> void:
|
||||||
var env := LevelEnvironment.add_to(self, "sunset")
|
var env := LevelEnvironment.add_to(self, "sunset")
|
||||||
# The sunset sky is too dark to light street canyons — use a fixed dusk
|
|
||||||
# lavender ambient instead so the map stays readable and neon still pops.
|
|
||||||
env.environment.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR
|
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_color = Color(0.58, 0.54, 0.66)
|
||||||
env.environment.ambient_light_energy = 3.0
|
env.environment.ambient_light_energy = 3.0
|
||||||
@@ -67,205 +81,207 @@ func _build_environment() -> void:
|
|||||||
var sun := get_node_or_null("Sun")
|
var sun := get_node_or_null("Sun")
|
||||||
if sun:
|
if sun:
|
||||||
sun.light_energy = 3.0
|
sun.light_energy = 3.0
|
||||||
sun.rotation_degrees = Vector3(-38, 55, 0) # low warm sun down the street
|
sun.rotation_degrees = Vector3(-38, 55, 0)
|
||||||
|
|
||||||
|
|
||||||
func _build_geometry() -> void:
|
func _build_geometry() -> void:
|
||||||
_build_environment()
|
_build_environment()
|
||||||
_build_lighting()
|
_build_lighting()
|
||||||
_build_streets()
|
_build_ground()
|
||||||
_build_perimeter()
|
_build_row(1) # east row (front plane x=+11, faces -X)
|
||||||
_build_machiya_row()
|
_build_row(-1) # west row
|
||||||
_build_east_block()
|
_build_viaduct()
|
||||||
_build_bridge()
|
_build_streetlights()
|
||||||
_build_shrine_plaza()
|
_build_crosswalks()
|
||||||
_build_market()
|
_spawn_points.append_array([
|
||||||
_build_pylons()
|
Vector3(0, 1.5, 34), Vector3(-9.5, 1.5, 20), Vector3(9.5, 1.5, -6),
|
||||||
_build_neon_dressing()
|
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:
|
func _spawn_player(pid: int) -> CharacterBody3D:
|
||||||
var player := super._spawn_player(pid)
|
var player := super._spawn_player(pid)
|
||||||
var s: Vector3 = SPAWNS[randi() % SPAWNS.size()]
|
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.5, 1.5), 0, randf_range(-1.5, 1.5))
|
player.position = s + Vector3(randf_range(-1.0, 1.0), 0, randf_range(-1.0, 1.0))
|
||||||
# Face the map center so nobody spawns staring at the perimeter wall.
|
|
||||||
player.rotation.y = atan2(player.position.x, player.position.z)
|
player.rotation.y = atan2(player.position.x, player.position.z)
|
||||||
return player
|
return player
|
||||||
|
|
||||||
|
|
||||||
# ── Ground & perimeter ───────────────────────────────────────────────────────
|
# ── Ground ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
func _build_streets() -> void:
|
func _build_ground() -> void:
|
||||||
# Asphalt base
|
_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),
|
||||||
_box_static(Vector3(0, -0.5, 0), Vector3(104, 1.0, 74), ASPHALT, "Street_Asphalt")
|
ASPHALT, "Ground")
|
||||||
# Lighter paving strip down the main street (readability of the lane)
|
# Raised sidewalks flanking the avenue, full length
|
||||||
_box_static(Vector3(0, 0.02, 0), Vector3(12, 0.04, 74), PAVING, "Street_Main")
|
_box_static(Vector3(-AVE_HALF - WALK_W * 0.5, 0.08, 0), Vector3(WALK_W, 0.16, EDGE_Z * 2.0), PAVING, "Sidewalk_W")
|
||||||
# Sidewalks in front of both districts
|
_box_static(Vector3(AVE_HALF + WALK_W * 0.5, 0.08, 0), Vector3(WALK_W, 0.16, EDGE_Z * 2.0), PAVING, "Sidewalk_E")
|
||||||
_box_static(Vector3(-8.5, 0.08, 0), Vector3(3, 0.16, 74), CONCRETE_DARK, "Sidewalk_W")
|
|
||||||
_box_static(Vector3(8.5, 0.08, 0), Vector3(3, 0.16, 74), CONCRETE_DARK, "Sidewalk_E")
|
|
||||||
|
|
||||||
|
|
||||||
func _build_perimeter() -> void:
|
# ── Building rows (the aligned heart of the map) ─────────────────────────────
|
||||||
# Brick block walls boxing the arena — all wallrunnable.
|
|
||||||
_box_static(Vector3(0, 7, -37), Vector3(104, 14, 0.8), Color(0.42, 0.3, 0.28), "Perimeter_N", "brick")
|
## side: +1 = east row, -1 = west row. Buildings are defined ONLY by their
|
||||||
_box_static(Vector3(0, 7, 37), Vector3(104, 14, 0.8), Color(0.42, 0.3, 0.28), "Perimeter_S", "brick")
|
## z-interval and floor count; every facade element derives from those.
|
||||||
_box_static(Vector3(-52, 7, 0), Vector3(0.8, 14, 74), Color(0.42, 0.3, 0.28), "Perimeter_W", "brick")
|
func _build_row(side: int) -> void:
|
||||||
_box_static(Vector3(52, 7, 0), Vector3(0.8, 14, 74), Color(0.42, 0.3, 0.28), "Perimeter_E", "brick")
|
# 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]))
|
||||||
|
|
||||||
|
|
||||||
# ── West: machiya row (wood) ─────────────────────────────────────────────────
|
## 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
|
||||||
|
|
||||||
func _build_machiya_row() -> void:
|
_box_static(Vector3(cx, h * 0.5, zc), Vector3(ROW_DEPTH, h, w), facade, n, "concrete")
|
||||||
var xs := [-16.0, -27.0, -38.0]
|
|
||||||
|
# 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():
|
for i in xs.size():
|
||||||
var x: float = xs[i]
|
_box_static(Vector3(xs[i], (VIADUCT_TOP - 1.0) * 0.5, zc), Vector3(2.0, VIADUCT_TOP - 1.0, depth - 2.0),
|
||||||
var z := -14.0 + float(i) * 3.0
|
BRICK, "Viaduct_Pier%d" % i, "brick")
|
||||||
_machiya(Vector3(x, 0, z), i)
|
# 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")
|
||||||
|
|
||||||
# Fourth house south side, rotated feel via different depth
|
# Under-arch vending arcade (Akihabara's radio-parts stalls under the
|
||||||
_machiya(Vector3(-22.0, 0, 16.0), 3)
|
# 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")
|
||||||
|
|
||||||
# Connecting awning walkway between house 1 and 2 (jump route at h=2.6)
|
# Slide ramps up to the deck along both sidewalks (aligned to the walk)
|
||||||
_box_static(Vector3(-21.5, 2.6, -12.5), Vector3(6, 0.25, 2.2), WOOD_DARK, "Awning_Link", "wood")
|
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")
|
||||||
|
|
||||||
|
|
||||||
## One two-story wooden row house: body, awning, balcony, flat roof section
|
# ── Street furniture ─────────────────────────────────────────────────────────
|
||||||
## plus a sloped slide roof. Roofs are reachable (awning -> balcony -> roof).
|
|
||||||
func _machiya(base: Vector3, idx: int) -> void:
|
func _build_streetlights() -> void:
|
||||||
var n := "Machiya%d" % idx
|
# Matched pairs of avenue lights every 16m, arms reaching over the road.
|
||||||
# Body (two floors)
|
var z := -10.0
|
||||||
_box_static(base + Vector3(0, 3.0, 0), Vector3(8, 6.0, 7), WOOD if idx % 2 == 0 else WOOD_DARK, n + "_Body", "wood")
|
while z <= EDGE_Z - 4.0:
|
||||||
# Street awning (first jump step, h 2.5)
|
for side: float in [-1.0, 1.0]:
|
||||||
_box_static(base + Vector3(4.6, 2.5, 0), Vector3(1.6, 0.22, 7), VERMILION.darkened(0.25), n + "_Awning", "wood")
|
var x: float = side * (AVE_HALF + 0.6)
|
||||||
# Balcony on the second floor (second step, h 4.4)
|
_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(base + Vector3(4.4, 4.4, -1.5), Vector3(1.2, 0.22, 3.2), WOOD_DARK, n + "_Balcony", "wood")
|
_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")
|
||||||
# Flat roof deck
|
_neon_box(Vector3(x - side * 2.8, 6.2, z), Vector3(0.9, 0.18, 0.4), Color(1.0, 0.95, 0.8))
|
||||||
_box_static(base + Vector3(0, 6.15, 1.4), Vector3(8, 0.3, 4.2), ROOF_TILE, n + "_Roof", "wood")
|
z += 16.0
|
||||||
# Sloped slide roof falling toward the street (25°) — slide entry from deck
|
|
||||||
_ramp_static(base + Vector3(2.6, 6.7, -2.2), Vector3(8.6, 0.25, 3.4),
|
|
||||||
Vector3(0, 0, -25), ROOF_TILE.lightened(0.12), n + "_SlideRoof", "wood")
|
|
||||||
|
|
||||||
|
|
||||||
# ── East: concrete towers, glass storefronts, wallrun alley ──────────────────
|
func _build_crosswalks() -> void:
|
||||||
|
# Zebra stripes across the avenue at the alley mouth and the south block.
|
||||||
func _build_east_block() -> void:
|
for cz in [10.0, 28.0]:
|
||||||
# Tower A (north-east) with billboard roof
|
var stripes := 7
|
||||||
_box_static(Vector3(20, 5.5, -22), Vector3(16, 11, 12), CONCRETE, "TowerA", "concrete")
|
for s in stripes:
|
||||||
_glass_front(Vector3(11.8, 1.6, -22), Vector3(0.25, 3.2, 9), "TowerA_Storefront")
|
var x := -AVE_HALF + 1.0 + (2.0 * (AVE_HALF - 1.0)) * float(s) / float(stripes - 1)
|
||||||
# Billboard: metal frame + angled panel on the roof (grapple anchor + cover)
|
_box_static(Vector3(x, 0.03, cz), Vector3(0.9, 0.06, 3.6), STRIPE, "Zebra_%d_%d" % [int(cz), s])
|
||||||
_box_static(Vector3(17, 12.2, -25), Vector3(0.5, 2.6, 7), METAL, "Billboard_Frame", "metal")
|
|
||||||
_ramp_static(Vector3(18.2, 12.6, -25), Vector3(0.4, 3.2, 7.4), Vector3(0, 0, -18),
|
|
||||||
Color(0.9, 0.9, 0.95), "Billboard_Panel", "metal")
|
|
||||||
|
|
||||||
# Tower B (south-east), shorter, fire-escape route up its face
|
|
||||||
_box_static(Vector3(20, 4.5, 8), Vector3(16, 9, 14), CONCRETE_DARK, "TowerB", "concrete")
|
|
||||||
_glass_front(Vector3(11.8, 1.6, 8), Vector3(0.25, 3.2, 11), "TowerB_Storefront")
|
|
||||||
var esc_y := [2.2, 4.4, 6.6, 8.8]
|
|
||||||
for i in esc_y.size():
|
|
||||||
_box_static(Vector3(12.6, esc_y[i], 13.5 - float(i) * 2.6),
|
|
||||||
Vector3(2.2, 0.18, 2.2), METAL, "FireEscape_%d" % i, "metal")
|
|
||||||
|
|
||||||
# Wallrun alley between the towers (brick faces, 4.5m apart)
|
|
||||||
_box_static(Vector3(20, 4, -13.5), Vector3(15, 8, 0.6), Color(0.45, 0.32, 0.3), "Alley_N", "brick")
|
|
||||||
_box_static(Vector3(20, 4, -9.0), Vector3(15, 8, 0.6), Color(0.45, 0.32, 0.3), "Alley_S", "brick")
|
|
||||||
# AC units in the alley to vault over / take cover behind
|
|
||||||
_box_static(Vector3(16, 0.8, -11.2), Vector3(1.8, 1.6, 1.6), METAL, "Alley_AC_1", "metal")
|
|
||||||
_box_static(Vector3(24, 0.8, -11.2), Vector3(1.8, 1.6, 1.6), METAL, "Alley_AC_2", "metal")
|
|
||||||
|
|
||||||
# Rooftop AC clusters (high-ground cover)
|
|
||||||
_box_static(Vector3(23, 11.75, -19), Vector3(2.4, 1.5, 2.4), METAL, "RoofAC_A1", "metal")
|
|
||||||
_box_static(Vector3(16, 9.75, 11), Vector3(2.2, 1.5, 2.2), METAL, "RoofAC_B1", "metal")
|
|
||||||
|
|
||||||
# Shipping container by the south-east corner (metal cover + jump step)
|
|
||||||
_box_static(Vector3(38, 1.3, 26), Vector3(6.2, 2.6, 2.6), Color(0.75, 0.35, 0.25), "Container", "metal")
|
|
||||||
|
|
||||||
|
|
||||||
func _glass_front(pos: Vector3, size: Vector3, node_name: String) -> void:
|
# ── Neon (visual only) ───────────────────────────────────────────────────────
|
||||||
_box_static(pos, size, GLASS, node_name, "glass")
|
|
||||||
|
|
||||||
|
|
||||||
# ── Pedestrian bridge over the main street ───────────────────────────────────
|
|
||||||
|
|
||||||
func _build_bridge() -> void:
|
|
||||||
# Deck at h=5 spanning the street; underpass below is drive-through cover.
|
|
||||||
_box_static(Vector3(0, 5.0, 0), Vector3(21, 0.4, 4.5), CONCRETE, "Bridge_Deck", "concrete")
|
|
||||||
# Railings (thin metal — reflections ring off them)
|
|
||||||
_box_static(Vector3(0, 5.75, -2.1), Vector3(21, 1.1, 0.12), METAL, "Bridge_Rail_N", "metal")
|
|
||||||
_box_static(Vector3(0, 5.75, 2.1), Vector3(21, 1.1, 0.12), METAL, "Bridge_Rail_S", "metal")
|
|
||||||
# Slide ramps up both ends (20° — sprint in, slide out)
|
|
||||||
_ramp_static(Vector3(-13.2, 2.45, 0), Vector3(7.8, 0.35, 4.5), Vector3(0, 0, -20),
|
|
||||||
CONCRETE_DARK, "Bridge_Ramp_W", "concrete")
|
|
||||||
_ramp_static(Vector3(13.2, 2.45, 0), Vector3(7.8, 0.35, 4.5), Vector3(0, 0, 20),
|
|
||||||
CONCRETE_DARK, "Bridge_Ramp_E", "concrete")
|
|
||||||
# Support pillars (cover in the underpass)
|
|
||||||
_box_static(Vector3(-5, 2.4, 0), Vector3(0.8, 4.8, 0.8), CONCRETE_DARK, "Bridge_Pillar_W", "concrete")
|
|
||||||
_box_static(Vector3(5, 2.4, 0), Vector3(0.8, 4.8, 0.8), CONCRETE_DARK, "Bridge_Pillar_E", "concrete")
|
|
||||||
|
|
||||||
|
|
||||||
# ── North: shrine plaza ──────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
func _build_shrine_plaza() -> void:
|
|
||||||
# Paved plaza
|
|
||||||
_box_static(Vector3(-6, 0.06, -28), Vector3(28, 0.12, 14), PAVING.lightened(0.08), "Plaza_Paving")
|
|
||||||
|
|
||||||
# Torii gate: two pillars + double crossbeam at h≈7 (the grapple anchor)
|
|
||||||
_box_static(Vector3(-12, 3.5, -22), Vector3(0.9, 7, 0.9), VERMILION, "Torii_Pillar_W", "wood")
|
|
||||||
_box_static(Vector3(0, 3.5, -22), Vector3(0.9, 7, 0.9), VERMILION, "Torii_Pillar_E", "wood")
|
|
||||||
_box_static(Vector3(-6, 7.0, -22), Vector3(15, 0.7, 1.2), VERMILION, "Torii_Beam_Top", "wood")
|
|
||||||
_box_static(Vector3(-6, 5.8, -22), Vector3(13, 0.5, 1.0), VERMILION.darkened(0.15), "Torii_Beam_Low", "wood")
|
|
||||||
|
|
||||||
# Shrine hall (wood, walk-around cover) with sloped roof to slide off
|
|
||||||
_box_static(Vector3(-6, 1.9, -31), Vector3(9, 3.8, 5), WOOD_DARK, "Shrine_Hall", "wood")
|
|
||||||
_ramp_static(Vector3(-6, 4.4, -29.4), Vector3(10, 0.3, 4.4), Vector3(-22, 0, 0),
|
|
||||||
VERMILION.darkened(0.35), "Shrine_Roof", "wood")
|
|
||||||
|
|
||||||
# Stone lanterns flanking the approach (small brick cover)
|
|
||||||
for i in 2:
|
|
||||||
var x := -14.0 + float(i) * 16.0
|
|
||||||
_box_static(Vector3(x, 1.0, -26), Vector3(1.1, 2.0, 1.1), Color(0.55, 0.55, 0.52), "Lantern_%d" % i, "brick")
|
|
||||||
|
|
||||||
|
|
||||||
# ── South: market street ─────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
func _build_market() -> void:
|
|
||||||
# Wooden stalls with canopy tops (low cover; canopy is a jump step)
|
|
||||||
var stalls := [Vector3(-8, 0, 26), Vector3(-2, 0, 30), Vector3(6, 0, 25), Vector3(12, 0, 30)]
|
|
||||||
for i in stalls.size():
|
|
||||||
var p: Vector3 = stalls[i]
|
|
||||||
_box_static(p + Vector3(0, 1.0, 0), Vector3(3.4, 2.0, 2.0), WOOD, "Stall%d_Body" % i, "wood")
|
|
||||||
_box_static(p + Vector3(0, 2.35, 0), Vector3(4.2, 0.18, 2.8),
|
|
||||||
NEON_COLORS[i % NEON_COLORS.size()].darkened(0.45), "Stall%d_Canopy" % i, "wood")
|
|
||||||
|
|
||||||
# Vending machines (classic urban-japan metal cover)
|
|
||||||
var vends := [Vector3(-10.5, 0, 12), Vector3(10.4, 0, -4), Vector3(-10.5, 0, -18), Vector3(14, 0, 22)]
|
|
||||||
for i in vends.size():
|
|
||||||
_box_static(vends[i] + Vector3(0, 1.0, 0), Vector3(1.1, 2.0, 0.9),
|
|
||||||
[Color(0.85, 0.3, 0.3), Color(0.3, 0.55, 0.85), Color(0.9, 0.9, 0.9), Color(0.3, 0.75, 0.5)][i],
|
|
||||||
"Vending_%d" % i, "metal")
|
|
||||||
|
|
||||||
|
|
||||||
# ── Grapple pylons down the main street ──────────────────────────────────────
|
|
||||||
|
|
||||||
func _build_pylons() -> void:
|
|
||||||
# Power poles with crossarms over the street: chained grapple swings.
|
|
||||||
for i in 3:
|
|
||||||
var z := -18.0 + float(i) * 18.0
|
|
||||||
var side := 1.0 if i % 2 == 0 else -1.0
|
|
||||||
var x := 7.5 * side
|
|
||||||
_box_static(Vector3(x, 5.5, z), Vector3(0.5, 11, 0.5), WOOD_DARK, "Pylon_%d" % i, "wood")
|
|
||||||
# Crossarm reaching over the street — THE grapple target
|
|
||||||
_box_static(Vector3(x - 4.0 * side, 10.2, z), Vector3(8.5, 0.35, 0.35), WOOD_DARK, "Pylon_%d_Arm" % i, "wood")
|
|
||||||
# Hanging cable segment between arm tip and next pole (visual line)
|
|
||||||
var wire := MeshInstance3D.new()
|
|
||||||
var wm := BoxMesh.new()
|
|
||||||
wm.size = Vector3(0.06, 0.06, 16.0)
|
|
||||||
wm.material = LevelMaterials.flat(Color(0.08, 0.07, 0.1)) # ink line in the sky
|
|
||||||
wire.mesh = wm
|
|
||||||
wire.position = Vector3(x - 7.5 * side, 10.0, z + 9.0)
|
|
||||||
add_child(wire)
|
|
||||||
|
|
||||||
|
|
||||||
# ── Neon dressing (visual only, no collision) ────────────────────────────────
|
|
||||||
|
|
||||||
func _neon_mat(color: Color) -> StandardMaterial3D:
|
func _neon_mat(color: Color) -> StandardMaterial3D:
|
||||||
var m := StandardMaterial3D.new()
|
var m := StandardMaterial3D.new()
|
||||||
@@ -285,32 +301,3 @@ func _neon_box(pos: Vector3, size: Vector3, color: Color) -> void:
|
|||||||
mi.mesh = bm
|
mi.mesh = bm
|
||||||
mi.position = pos
|
mi.position = pos
|
||||||
add_child(mi)
|
add_child(mi)
|
||||||
|
|
||||||
|
|
||||||
func _build_neon_dressing() -> void:
|
|
||||||
# Vertical stacked sign columns on the tower faces (kanji-board vibe)
|
|
||||||
for i in 4:
|
|
||||||
var col: Color = NEON_COLORS[i % NEON_COLORS.size()]
|
|
||||||
var z := -27.0 + float(i) * 3.4
|
|
||||||
_neon_box(Vector3(11.6, 6.0 - float(i % 2), z), Vector3(0.15, 2.6, 1.1), col)
|
|
||||||
for i in 3:
|
|
||||||
var col: Color = NEON_COLORS[(i + 2) % NEON_COLORS.size()]
|
|
||||||
_neon_box(Vector3(11.6, 5.2 + float(i % 2) * 1.8, 4.0 + float(i) * 3.6), Vector3(0.15, 2.2, 1.0), col)
|
|
||||||
|
|
||||||
# Horizontal shop signs above the machiya awnings
|
|
||||||
for i in 3:
|
|
||||||
var x := -16.0 - float(i) * 11.0
|
|
||||||
_neon_box(Vector3(x, 3.3, -14.0 + float(i) * 3.0 + 3.6), Vector3(5.0, 0.7, 0.12),
|
|
||||||
NEON_COLORS[(i + 1) % NEON_COLORS.size()])
|
|
||||||
|
|
||||||
# Lantern string along the market street (small warm glows)
|
|
||||||
for i in 8:
|
|
||||||
_neon_box(Vector3(-10.0 + float(i) * 3.4, 3.4, 22.0 + sin(float(i)) * 1.2),
|
|
||||||
Vector3(0.35, 0.5, 0.35), Color(1.0, 0.62, 0.25))
|
|
||||||
|
|
||||||
# Billboard face glow
|
|
||||||
_neon_box(Vector3(18.55, 12.6, -25), Vector3(0.1, 2.8, 6.8), Color(0.95, 0.3, 0.5))
|
|
||||||
|
|
||||||
# Underpass strip lights (so the bridge shadow isn't a black hole)
|
|
||||||
_neon_box(Vector3(0, 4.7, -1.9), Vector3(20, 0.08, 0.08), Color(0.2, 0.9, 0.95))
|
|
||||||
_neon_box(Vector3(0, 4.7, 1.9), Vector3(20, 0.08, 0.08), Color(1.0, 0.25, 0.55))
|
|
||||||
|
|||||||
Reference in New Issue
Block a user