Feat/visual audio overhaul #21

Merged
Dotts merged 31 commits from feat/visual-audio-overhaul into main 2026-07-21 14:09:18 -07:00
44 changed files with 188 additions and 46 deletions
Showing only changes of commit 67040ba12d - Show all commits
+28
View File
@@ -0,0 +1,28 @@
City Kit Commercial (2.1)
Created/distributed by Kenney (www.kenney.nl)
Creation date: 21-07-2025 18:16
------------------------------
License: (Creative Commons Zero, CC0)
http://creativecommons.org/publicdomain/zero/1.0/
You can use this content for personal, educational, and commercial purposes.
Support by crediting 'Kenney' or 'www.kenney.nl' (this is not a requirement)
------------------------------
• Website : www.kenney.nl
• Donate : www.kenney.nl/donate
• Patreon : patreon.com/kenney
Follow on social media for updates:
• Twitter: twitter.com/KenneyNL
• BlueSky: kenney.bsky.social
• Instagram: instagram.com/kenney_nl
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+160 -46
View File
@@ -42,8 +42,8 @@ const LAYOUT: Array[String] = [
]
# Palette
const ASPHALT := Color(0.3, 0.3, 0.37)
const PAVING := Color(0.47, 0.45, 0.51)
const ASPHALT := Color(0.38, 0.38, 0.45)
const PAVING := Color(0.56, 0.54, 0.6)
const STRIPE := Color(0.72, 0.72, 0.75)
const FACADES: Array[Color] = [
Color(0.74, 0.71, 0.66), Color(0.62, 0.63, 0.67), Color(0.68, 0.6, 0.5),
@@ -69,6 +69,117 @@ const SIGN_COLORS: Array[Color] = [
var _spawn_points: Array[Vector3] = []
var _rng := RandomNumberGenerator.new()
# ── Kenney City Kit catalog (CC0) — base sizes measured at import scale ─────
const KIT_DIR := "res://assets/props/citykit/"
const KIT_SCALE := 13.0
## name -> Vector3(facade_width, height, depth) at import scale
const KIT_BUILDINGS := {
"building-a": Vector3(0.884, 1.293, 0.94), "building-b": Vector3(0.97, 1.293, 0.94),
"building-c": Vector3(0.884, 0.893, 1.09), "building-d": Vector3(0.84, 1.293, 0.9),
"building-e": Vector3(1.64, 0.893, 1.008), "building-f": Vector3(0.84, 1.693, 1.03),
"building-g": Vector3(0.97, 1.693, 0.922), "building-h": Vector3(0.884, 1.293, 1.008),
"building-i": Vector3(1.24, 1.68, 1.302), "building-j": Vector3(2.084, 1.693, 1.34),
"building-k": Vector3(2.084, 1.47, 0.942), "building-l": Vector3(1.37, 2.27, 1.402),
"building-n": Vector3(2.32, 2.48, 1.82),
}
const KIT_SKYSCRAPERS := {
"building-skyscraper-a": Vector3(1.36, 2.88, 1.36), "building-skyscraper-b": Vector3(1.36, 4.48, 1.36),
"building-skyscraper-c": Vector3(1.28, 4.08, 1.388), "building-skyscraper-d": Vector3(1.28, 5.47, 1.388),
"building-skyscraper-e": Vector3(1.295, 4.08, 1.242),
}
var _kit_cache: Dictionary = {}
func _kit_scene(key: String) -> PackedScene:
if not _kit_cache.has(key):
_kit_cache[key] = load(KIT_DIR + key + ".glb")
return _kit_cache[key]
## Small visual-only kit prop (parasols, awnings): no collider needed.
func _kit_prop(key: String, pos: Vector3, yaw_deg: float, scale_f: float) -> void:
var inst: Node3D = _kit_scene(key).instantiate()
inst.scale = Vector3.ONE * scale_f
add_child(inst)
inst.global_position = pos
inst.rotation_degrees.y = yaw_deg
LevelMaterials.apply_toon_recursive(inst, 0.02)
## Place a kit model with a box collider matching its scaled bounds.
## front_pos: centre of the facade at ground level. yaw_deg: 0 faces +Z.
func _kit_building(key: String, front_pos: Vector3, yaw_deg: float, node_name: String,
scale_f: float = KIT_SCALE) -> float:
var dims: Vector3 = KIT_BUILDINGS.get(key, KIT_SKYSCRAPERS.get(key, Vector3.ONE))
var w := dims.x * scale_f
var h := dims.y * scale_f
var d := dims.z * scale_f
var fwd := Vector3(sin(deg_to_rad(yaw_deg)), 0, cos(deg_to_rad(yaw_deg)))
var center := front_pos - fwd * d * 0.5
var body := StaticBody3D.new()
body.name = node_name
body.set_meta("acoustic_material", "concrete")
add_child(body)
body.global_position = center
body.rotation_degrees.y = yaw_deg
var shape := CollisionShape3D.new()
var bs := BoxShape3D.new()
bs.size = Vector3(w, h, d)
shape.shape = bs
shape.position = Vector3(0, h * 0.5, 0)
body.add_child(shape)
var inst: Node3D = _kit_scene(key).instantiate()
inst.scale = Vector3.ONE * scale_f
body.add_child(inst)
LevelMaterials.apply_toon_recursive(inst, 0.0)
return w
## Fill one street-facing edge of a lot with kit buildings + Akiba signage.
func _kit_fill_side(front_x: float, dir: int, z0: float, z1: float, seed_id: String) -> void:
var yaw := -90.0 if dir > 0 else 90.0 # face the street (-x for east row)
var cursor := z0
var idx := 0
var keys := KIT_BUILDINGS.keys()
while z1 - cursor > 10.0:
var key: String = keys[_rng.randi() % keys.size()]
var w: float = KIT_BUILDINGS[key].x * KIT_SCALE
if w > z1 - cursor:
# find any model that still fits, else stop
var fits := false
for k2 in keys:
if KIT_BUILDINGS[k2].x * KIT_SCALE <= z1 - cursor:
key = k2
w = KIT_BUILDINGS[k2].x * KIT_SCALE
fits = true
break
if not fits:
break
var zc := cursor + w * 0.5
var h: float = KIT_BUILDINGS[key].y * KIT_SCALE
_kit_building(key, Vector3(front_x, 0, zc), yaw, "K%s_%d" % [seed_id, idx])
# Akiba signage layer, aligned to the model's measured bounds
if h >= 14.0:
var zs := cursor + 1.2
var col_h := h * 0.55
_box_static(Vector3(front_x - float(dir) * 0.45, h * 0.35, zs), Vector3(0.7, col_h, 1.4),
METAL, "K%s_%d_sc" % [seed_id, idx], "metal")
var segs := maxi(2, int(col_h / 3.4))
for s2 in segs:
var sy := h * 0.35 - col_h * 0.5 + col_h * (float(s2) + 0.5) / float(segs)
_deco_box(Vector3(front_x - float(dir) * 0.85, sy, zs), Vector3(0.12, 2.2, 1.2),
SIGN_COLORS[(s2 + idx + seed_id.hash()) % SIGN_COLORS.size()], true)
if h >= 20.0:
var bw := minf(w - 4.0, 10.0)
_box_static(Vector3(front_x + float(dir) * 2.0, h + 1.9, zc), Vector3(0.5, 3.0, bw),
METAL, "K%s_%d_bf" % [seed_id, idx], "metal")
_deco_box(Vector3(front_x + float(dir) * 1.7, h + 1.9, zc), Vector3(0.12, 2.4, bw - 0.6),
SIGN_COLORS[(idx + seed_id.hash()) % SIGN_COLORS.size()], true)
cursor += w
idx += 1
# ── Cel-styled statics + render-only decoration ──────────────────────────────
@@ -106,13 +217,18 @@ func _deco_box(pos: Vector3, size: Vector3, color: Color, emissive: bool = false
func _build_environment() -> void:
var env := LevelEnvironment.add_to(self, "sunset")
# Calibrated for the kit's bright albedo textures (they clip to white at
# the energies the old dark flat palette needed).
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
env.environment.ambient_light_energy = 1.7
env.environment.fog_density = 0.0007 # light city haze for depth over 600m
# Ambient occlusion grounds the modeled facade detail (ZZZ-style depth)
env.environment.ssao_enabled = true
env.environment.ssao_intensity = 2.0
var sun := get_node_or_null("Sun")
if sun:
sun.light_energy = 3.0
sun.light_energy = 2.2
sun.rotation_degrees = Vector3(-38, 55, 0)
sun.directional_shadow_max_distance = 220.0
@@ -241,22 +357,11 @@ func _building(front_x: float, dir: int, z0: float, z1: float, depth: float,
## 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
# Detailed kit buildings fill both street-facing edges
_kit_fill_side(c.x - half, 1, c.z - half, c.z + half, "%d_%dW" % [bx, bz])
_kit_fill_side(c.x + half, -1, c.z - half, c.z + half, "%d_%dE" % [bx, bz])
# Fire escapes into the mid-block gap on some blocks
if _rng.randi() % 2 == 0:
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,
@@ -267,29 +372,36 @@ func _standard_block(c: Vector3, bx: int, bz: int) -> void:
## 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)
var col := SEGA_RED if _rng.randi() % 3 == 0 else (ELEC_BLUE if _rng.randi() % 2 == 0 else FACADES[_rng.randi() % FACADES.size()])
# Podium (1 floor, walkable roof ring) with glass front
_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
# Detailed kit skyscraper on the podium
var keys := KIT_SKYSCRAPERS.keys()
var key: String = keys[_rng.randi() % keys.size()]
var dims: Vector3 = KIT_SKYSCRAPERS[key]
var s := (BLOCK - 22.0) / dims.x # shaft footprint ~26m
var h := dims.y * s + FLOOR_H
var yaw := float(90 * (_rng.randi() % 4))
var body := StaticBody3D.new()
body.name = n
body.set_meta("acoustic_material", "concrete")
add_child(body)
body.global_position = c + Vector3(0, FLOOR_H, 0)
body.rotation_degrees.y = yaw
var shape := CollisionShape3D.new()
var bs := BoxShape3D.new()
bs.size = Vector3(dims.x * s, dims.y * s, dims.z * s)
shape.shape = bs
shape.position = Vector3(0, dims.y * s * 0.5, 0)
body.add_child(shape)
var inst: Node3D = _kit_scene(key).instantiate()
inst.scale = Vector3.ONE * s
body.add_child(inst)
LevelMaterials.apply_toon_recursive(inst, 0.0)
# Rooftop crown billboard both directions (skyline wayfinding)
var tw := dims.x * s
_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)
@@ -330,11 +442,13 @@ func _shrine_block(c: Vector3, bx: int, bz: int) -> void:
## 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])
# Back walls: detailed kit buildings on east/west faces
_kit_fill_side(c.x - BLOCK * 0.5, 1, c.z - BLOCK * 0.5, c.z + BLOCK * 0.5, "%d_%dmw" % [bx, bz])
_kit_fill_side(c.x + BLOCK * 0.5, -1, c.z - BLOCK * 0.5, c.z + BLOCK * 0.5, "%d_%dme" % [bx, bz])
# Cafe parasols scattered among the stalls
for pi in 3:
var pp := c + Vector3(_rng.randf_range(-9.0, 9.0), 0.1, _rng.randf_range(-12.0, 12.0))
_kit_prop(["detail-parasol-a", "detail-parasol-b"][pi % 2], pp, _rng.randf_range(0.0, 360.0), 6.0)
# Stall rows down the middle
for r in 2:
var x := c.x + (-5.0 if r == 0 else 5.0)