feat: parked cars across the city (Kenney Car Kit, CC0)

Streets are now populated: seeded parking lanes hug the curbs of every
N-S street with sedans, taxis, vans, SUVs, hatchbacks, delivery trucks
and the occasional police car (~100+ across the city).

- Wheels attach at the models' wheel-socket empties at spawn
- Metal-tagged box colliders: cars are cover that sounds like cover
- Toonified + darkened base tint (kit albedo clips under the map light)
  with per-car hue jitter for varied paint jobs
- Slight rotation jitter so parking reads natural, not stamped

Verified via street capture (colored cars parked down the avenue, no
clipping/glow); movement 11/11, smoke 0 failures, acoustics ALL PASSED.

Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
Nicholas Butzke
2026-07-19 02:39:35 -04:00
co-authored by Claude Fable 5
parent 04d91dea31
commit 56db9eea59
11 changed files with 82 additions and 0 deletions
@@ -270,6 +270,7 @@ func _build_geometry() -> void:
_build_rail_line()
_build_streets()
_build_avenues()
_build_parked_cars()
if _spawn_points.is_empty():
_spawn_points.append(Vector3(0, 2, 0))
@@ -665,6 +666,59 @@ func _build_avenues() -> void:
Vector3(x, 0, z2), 0.0 if side > 0 else 180.0, lamp_scale)
# ── Parked cars (Kenney Car Kit) ─────────────────────────────────────────────
const CAR_KEYS := ["sedan", "sedan", "taxi", "van", "suv", "hatchback-sports",
"delivery", "sedan-sports", "police"]
const CAR_SCALE := 1.8
## A parked car: metal-tagged collider, kit body, wheels attached to the
## model's wheel sockets, slight per-car tint.
func _parked_car(pos: Vector3, yaw_deg: float) -> void:
var key: String = CAR_KEYS[_rng.randi() % CAR_KEYS.size()]
var body := StaticBody3D.new()
body.name = "Car_%d" % _rng.randi()
body.set_meta("acoustic_material", "metal")
add_child(body)
body.global_position = pos
body.rotation_degrees.y = yaw_deg + _rng.randf_range(-3.0, 3.0)
var shape := CollisionShape3D.new()
var bs := BoxShape3D.new()
bs.size = Vector3(2.8, 2.6, 5.0)
shape.shape = bs
shape.position = Vector3(0, 1.3, 0)
body.add_child(shape)
var inst: Node3D = _kit_scene_at("res://assets/props/cars/" + key + ".glb").instantiate()
inst.scale = Vector3.ONE * CAR_SCALE
inst.position.y = 0.55 # wheel contact at ground after scaling
body.add_child(inst)
var wheel_scene := _kit_scene_at("res://assets/props/cars/wheel-default.glb")
for n in inst.find_children("wheel*", "Node3D", true, false):
if n.get_child_count() == 0 and not (n is MeshInstance3D):
var wheel: Node3D = wheel_scene.instantiate()
n.add_child(wheel)
LevelMaterials.apply_toon_recursive(inst, 0.0)
# Darken under the map lighting (kit albedo clips) + per-car hue
_tint_recursive(inst, Color(0.62, 0.62, 0.66) * Color(1, 1, 1).lerp(Color.from_hsv(_rng.randf(), 0.6, 1.0), 0.35))
## Seeded parking lanes hugging the curbs of the N-S streets.
func _build_parked_cars() -> void:
for x in _street_lines():
for row in GRID:
_rng.seed = hash(Vector2i(int(x), row)) + 913
if _rng.randf() < 0.45:
continue
var z_base := (float(row) - float(GRID - 1) * 0.5) * PITCH
var cars := 1 + _rng.randi() % 3
for i in cars:
var side := 1.0 if _rng.randi() % 2 == 0 else -1.0
var cx := x + side * (ROAD_W * 0.5 - 1.8)
var cz := z_base + _rng.randf_range(-18.0, 18.0)
_parked_car(Vector3(cx, 0.02, cz), 0.0 if _rng.randi() % 2 == 0 else 180.0)
func _kit_prop_path(path: String, pos: Vector3, yaw_deg: float, scale_f: float) -> void:
var inst: Node3D = _kit_scene_at(path).instantiate()
inst.scale = Vector3.ONE * scale_f