feat: modeled market stalls, mega wall ads, traffic lights, awnings

Final detail pass — no gameplay object remains a plain box:

- Markets: box stalls replaced with modeled kit stalls (4 styles seeded),
  hidden box colliders keep the wood acoustics; hand carts at the market
  mouths; standing lanterns between stalls
- Shrines: hanging lanterns on the stone pillars, wooden fences flanking
  the torii approach
- Towers: giant mid-height wall ads on the street faces (the anime-city
  mega-ad look), on top of crown boards and corner columns
- Storefront awnings (kit detail piece) on ~40% of buildings
- Traffic signals at every central-avenue crossing, diagonal from the
  streetlights
- Fantasy Town Kit (CC0) installed at assets/props/town

Verified via street capture (wall-scale glyph ads, pharmacy signs,
banner ads, stalls, carts); movement 11/11, smoke 0, acoustics PASSED.

Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
Nicholas Butzke
2026-07-19 02:50:28 -04:00
co-authored by Claude Fable 5
parent 648872a16d
commit c8a85586bd
15 changed files with 57 additions and 5 deletions
+28
View File
@@ -0,0 +1,28 @@
Fantasy Town Kit (2.0)
Created/distributed by Kenney (www.kenney.nl)
Creation date: 03-08-2025 13:56
------------------------------
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.
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.
+29 -5
View File
@@ -177,6 +177,10 @@ func _kit_fill_side(front_x: float, dir: int, z0: float, z1: float, seed_id: Str
var h: float = KIT_BUILDINGS[key].y * KIT_SCALE var h: float = KIT_BUILDINGS[key].y * KIT_SCALE
_kit_building(key, Vector3(front_x, 0, zc), yaw, "K%s_%d" % [seed_id, idx]) _kit_building(key, Vector3(front_x, 0, zc), yaw, "K%s_%d" % [seed_id, idx])
var face_yaw := -90.0 if dir > 0 else 90.0 # ad quads face the street var face_yaw := -90.0 if dir > 0 else 90.0 # ad quads face the street
# Storefront awning on some buildings (kit detail piece)
if _rng.randi() % 5 < 2:
_kit_prop_path(KIT_DIR + "detail-awning-wide.glb",
Vector3(front_x, 3.1, zc), face_yaw, minf(w * 0.9, 11.0))
# Street-level posters plastered on the facade (graphic density) # Street-level posters plastered on the facade (graphic density)
var posters := 1 + _rng.randi() % 3 var posters := 1 + _rng.randi() % 3
for p in posters: for p in posters:
@@ -467,6 +471,10 @@ func _tower_block(c: Vector3, bx: int, bz: int) -> void:
_box_static(c + Vector3(0, h + 2.4, 0), Vector3(0.6, 4.0, tw - 4.0), METAL, n + "_Crown", "metal") _box_static(c + Vector3(0, h + 2.4, 0), Vector3(0.6, 4.0, tw - 4.0), METAL, n + "_Crown", "metal")
_ad_quad(c + Vector3(0.42, h + 2.4, 0), Vector2(tw - 4.6, 3.3), 90.0, _board_tex()) _ad_quad(c + Vector3(0.42, h + 2.4, 0), Vector2(tw - 4.6, 3.3), 90.0, _board_tex())
_ad_quad(c + Vector3(-0.42, h + 2.4, 0), Vector2(tw - 4.6, 3.3), -90.0, _board_tex()) _ad_quad(c + Vector3(-0.42, h + 2.4, 0), Vector2(tw - 4.6, 3.3), -90.0, _board_tex())
# Giant mid-height wall ad on the tower's street faces (ZZZ-style)
var wall_off := dims.z * s * 0.5 + 0.15
_ad_quad(c + Vector3(0, FLOOR_H + dims.y * s * 0.45, -wall_off).rotated(Vector3.UP, deg_to_rad(yaw)),
Vector2(tw * 0.55, tw * 0.8), yaw + 180.0, _poster_tex())
_spawn_points.append(c + Vector3(0, FLOOR_H + 1.5, (BLOCK - 4.0) * 0.4)) _spawn_points.append(c + Vector3(0, FLOOR_H + 1.5, (BLOCK - 4.0) * 0.4))
@@ -492,14 +500,18 @@ func _shrine_block(c: Vector3, bx: int, bz: int) -> void:
# Hall with slide roof # 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") _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") _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 + real modeled trees (trunk collider under each) # Stone + hanging lanterns, wooden gate fence, real modeled trees
for i in 2: for i in 2:
var lx := -8.0 + 16.0 * float(i) 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.0, 8.0), Vector3(1.0, 2.0, 1.0), Color(0.55, 0.55, 0.52), n + "_Lant%d" % i, "brick")
_kit_prop_path("res://assets/props/town/lantern.glb", c + Vector3(lx, 2.0, 8.0), 0.0, 2.6)
_box_static(c + Vector3(lx, 1.6, -2.0), Vector3(0.8, 3.2, 0.8), TRUNK, n + "_Trunk%d" % i, "wood") _box_static(c + Vector3(lx, 1.6, -2.0), Vector3(0.8, 3.2, 0.8), TRUNK, n + "_Trunk%d" % i, "wood")
_kit_prop_path("res://assets/props/nature/" + ["tree_detailed.glb", "tree_default.glb"][i % 2], _kit_prop_path("res://assets/props/nature/" + ["tree_detailed.glb", "tree_default.glb"][i % 2],
c + Vector3(lx, 0, -2.0), _rng.randf_range(0.0, 360.0), 7.0) c + Vector3(lx, 0, -2.0), _rng.randf_range(0.0, 360.0), 7.0)
_kit_prop_path("res://assets/props/nature/tree_oak.glb", c + Vector3(0, 0, 2.0), _rng.randf_range(0.0, 360.0), 6.0) _kit_prop_path("res://assets/props/nature/tree_oak.glb", c + Vector3(0, 0, 2.0), _rng.randf_range(0.0, 360.0), 6.0)
# Fence gate flanking the torii approach
_kit_prop_path("res://assets/props/town/fence.glb", c + Vector3(-8.0, 0.15, tz - 0.2), 0.0, 3.0)
_kit_prop_path("res://assets/props/town/fence.glb", c + Vector3(8.0, 0.15, tz - 0.2), 0.0, 3.0)
_spawn_points.append(c + Vector3(0, 1.5, 12)) _spawn_points.append(c + Vector3(0, 1.5, 12))
@@ -513,17 +525,26 @@ func _market_block(c: Vector3, bx: int, bz: int) -> void:
for pi in 3: for pi in 3:
var pp := c + Vector3(_rng.randf_range(-9.0, 9.0), 0.1, _rng.randf_range(-12.0, 12.0)) 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) _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 # Modeled stall rows down the middle (kit stalls + carts, box colliders)
var stall_keys := ["stall", "stall-green", "stall-red", "stall-bench"]
for r in 2: for r in 2:
var x := c.x + (-5.0 if r == 0 else 5.0) var x := c.x + (-5.0 if r == 0 else 5.0)
for s in 4: for s in 4:
var z := c.z - 15.0 + float(s) * 10.0 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") var body := _box_static(Vector3(x, 1.3, z), Vector3(3.4, 2.6, 3.0), WOOD, "%s_S%d_%d" % [n, r, s], "wood")
_box_static(Vector3(x, 2.35, z), Vector3(4.0, 0.16, 3.0), for mi in body.find_children("*", "MeshInstance3D", false, false):
SIGN_COLORS[(r + s) % SIGN_COLORS.size()].darkened(0.5), "%s_C%d_%d" % [n, r, s], "wood") mi.visible = false # collider only; the kit stall is the visual
_kit_prop_path("res://assets/props/town/%s.glb" % stall_keys[(r * 4 + s + bx) % stall_keys.size()],
Vector3(x, 0.02, z), 90.0 if r == 0 else -90.0, 3.2)
if s % 2 == 0:
_kit_prop_path("res://assets/props/town/lantern.glb",
Vector3(x + 2.2, 0.02, z + 1.8), 0.0, 3.0)
# Lantern string over each row # Lantern string over each row
for l in 6: 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) _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)
# Hand carts at the market mouths
_kit_prop_path("res://assets/props/town/cart.glb", c + Vector3(0, 0.02, 19.0), _rng.randf_range(-30, 30), 3.0)
_kit_prop_path("res://assets/props/town/cart-high.glb", c + Vector3(2.0, 0.02, -19.0), _rng.randf_range(150, 210), 3.0)
# Vending pair at the north entry # Vending pair at the north entry
for v in 2: 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), _box_static(c + Vector3(-1.5 + 3.0 * float(v), 1.0, -BLOCK * 0.5 + 3.0), Vector3(1.1, 2.0, 0.9),
@@ -700,6 +721,9 @@ func _build_avenues() -> void:
_box_static(Vector3(x, 3.4, z), Vector3(0.26, 6.8, 0.26), METAL, "LampP_%d_%d" % [int(z), int(side)], "metal") _box_static(Vector3(x, 3.4, z), Vector3(0.26, 6.8, 0.26), METAL, "LampP_%d_%d" % [int(z), int(side)], "metal")
_kit_prop_path("res://assets/props/roads/light-curved.glb", _kit_prop_path("res://assets/props/roads/light-curved.glb",
Vector3(x, 0, z), 90.0 if side > 0 else -90.0, lamp_scale) Vector3(x, 0, z), 90.0 if side > 0 else -90.0, lamp_scale)
# Traffic signal on the opposite diagonal corner of each crossing
_kit_prop_path("res://assets/props/roads/light-square-cross.glb",
Vector3(x, 0, z - side * (ROAD_W * 0.5 + 0.8)), 90.0 if side > 0 else -90.0, lamp_scale * 0.8)
for x in lines: for x in lines:
if absf(x) < 1.0: if absf(x) < 1.0:
continue continue