feat: add Kenney road and nature assets and implement map builder script

This commit is contained in:
Nicholas Butzke
2026-07-19 02:32:06 -04:00
parent 67040ba12d
commit f62f4f2503
81 changed files with 194 additions and 24 deletions
+23
View File
@@ -0,0 +1,23 @@
Nature Kit (2.1)
Created/distributed by Kenney (www.kenney.nl)
Creation date: 29-04-2020 01:02
------------------------------
License: (Creative Commons Zero, CC0)
http://creativecommons.org/publicdomain/zero/1.0/
This content is free to use in personal, educational and commercial projects.␍
Support us by crediting Kenney or www.kenney.nl (this is not mandatory)
------------------------------
Donate: http://support.kenney.nl
Request: http://request.kenney.nl
Patreon: http://patreon.com/kenney/
Follow on Twitter for updates:
http://twitter.com/KenneyNL
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.

After

Width:  |  Height:  |  Size: 8.5 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.
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.
+36
View File
@@ -0,0 +1,36 @@
extends SceneTree
## Dev tool: print AABBs of prop models used by the city builder.
const PATHS := [
"res://assets/props/roads/road-straight.glb",
"res://assets/props/roads/road-crossroad-line.glb",
"res://assets/props/roads/road-crossing.glb",
"res://assets/props/roads/light-curved.glb",
"res://assets/props/roads/light-curved-double.glb",
"res://assets/props/roads/construction-barrier.glb",
"res://assets/props/roads/construction-cone.glb",
"res://assets/props/nature/tree_default.glb",
"res://assets/props/nature/tree_detailed.glb",
"res://assets/props/nature/tree_oak.glb",
"res://assets/props/nature/tree_tall.glb",
]
func _initialize() -> void:
for p in PATHS:
var scene = load(p)
if scene == null:
print("measure: FAILED ", p)
continue
var inst: Node3D = scene.instantiate()
get_root().add_child(inst)
var aabb := AABB()
var first := true
for mi in inst.find_children("*", "MeshInstance3D", true, false):
var b: AABB = mi.global_transform * mi.get_aabb()
aabb = b if first else aabb.merge(b)
first = false
print("measure: %s size=%s min=%s" % [p.get_file(), aabb.size, aabb.position])
inst.queue_free()
quit()
+1
View File
@@ -0,0 +1 @@
uid://dd4g4l7mg66ro
+134 -24
View File
@@ -134,9 +134,25 @@ func _kit_building(key: String, front_pos: Vector3, yaw_deg: float, node_name: S
inst.scale = Vector3.ONE * scale_f
body.add_child(inst)
LevelMaterials.apply_toon_recursive(inst, 0.0)
# Subtle per-building tint so repeated kit models read as different shops
_tint_recursive(inst, Color(1, 1, 1).lerp(Color.from_hsv(_rng.randf(), 0.3, 1.0), 0.16))
return w
func _tint_recursive(node: Node, tint: Color) -> void:
if node is MeshInstance3D:
var mi := node as MeshInstance3D
var sc: int = mi.mesh.get_surface_count() if mi.mesh else 0
for s in sc:
var m := mi.get_surface_override_material(s)
if m is ShaderMaterial:
var c = m.get_shader_parameter("albedo_color")
if c is Color:
m.set_shader_parameter("albedo_color", c * tint)
for child in node.get_children():
_tint_recursive(child, tint)
## 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)
@@ -252,6 +268,7 @@ func _build_geometry() -> void:
_build_block(bx, bz)
_build_rail_line()
_build_streets()
_build_avenues()
if _spawn_points.is_empty():
@@ -430,12 +447,14 @@ func _shrine_block(c: Vector3, bx: int, bz: int) -> void:
# 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)
# Stone lanterns + real modeled trees (trunk collider under each)
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)
_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],
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)
_spawn_points.append(c + Vector3(0, 1.5, 12))
@@ -470,12 +489,12 @@ func _market_block(c: Vector3, bx: int, bz: int) -> void:
## 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
# Planter cover ring with real trees
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)
_kit_prop_path("res://assets/props/nature/tree_oak.glb", Vector3(p.x, 1.0, p.z), float(i) * 90.0, 4.5)
# 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")
@@ -511,6 +530,13 @@ func _construction_block(c: Vector3, bx: int, bz: int) -> void:
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")
# Cones + barriers scattered around the site entrance
for i in 4:
_kit_prop_path("res://assets/props/roads/construction-cone.glb",
c + Vector3(-16.0 + float(i) * 4.0, 0.1, 18.0), 0.0, 8.0)
for i in 2:
_kit_prop_path("res://assets/props/roads/construction-barrier.glb",
c + Vector3(-14.0 + float(i) * 12.0, 0.1, 14.0), float(i * 30), 8.0)
_spawn_points.append(c + Vector3(14, 1.5, -14))
const INK_COLOR := Color(0.08, 0.07, 0.1)
@@ -543,26 +569,110 @@ func _build_rail_line() -> void:
_spawn_points.append(Vector3(0, RAIL_TOP + 1.5, z))
# ── Main avenues: lights + crosswalks ────────────────────────────────────────
# ── Streets: real road meshes with markings ──────────────────────────────────
const ROAD_W := 12.0 # visual road width between sidewalk aprons
## Street lines run between block columns/rows at multiples of PITCH.
func _street_lines() -> Array[float]:
var lines: Array[float] = []
for i in GRID - 1:
lines.append((float(i) - float(GRID) * 0.5 + 1.0) * PITCH)
return lines
func _road_piece(key: String, pos: Vector3, yaw_deg: float, sc: Vector3) -> void:
var inst: Node3D = _kit_scene_at("res://assets/props/roads/" + key + ".glb").instantiate()
inst.scale = sc
add_child(inst)
inst.global_position = pos
inst.rotation_degrees.y = yaw_deg
LevelMaterials.apply_toon_recursive(inst, 0.0)
# The kit's pale concrete clips to white under the map lighting
_tint_recursive(inst, Color(0.55, 0.55, 0.6))
var _path_cache: Dictionary = {}
func _kit_scene_at(path: String) -> PackedScene:
if not _path_cache.has(path):
_path_cache[path] = load(path)
return _path_cache[path]
func _build_streets() -> void:
var lines := _street_lines()
var seg := PITCH - ROAD_W # straight run between intersections
var y := 0.02
# Intersections with line markings
for x in lines:
for z in lines:
_road_piece("road-crossroad-line", Vector3(x, y, z), 0.0, Vector3(ROAD_W, 2.0, ROAD_W))
# Straight segments (stretched single meshes) + edge stubs to the wall
for x in lines:
for i in lines.size() + 1:
var z0 := -CITY_HALF if i == 0 else lines[i - 1] + ROAD_W * 0.5
var z1 := CITY_HALF if i == lines.size() else lines[i] - ROAD_W * 0.5
if z1 - z0 < 2.0:
continue
_road_piece("road-straight", Vector3(x, y, (z0 + z1) * 0.5), 0.0, Vector3(ROAD_W, 2.0, z1 - z0))
for z in lines:
for i in lines.size() + 1:
var x0 := -CITY_HALF if i == 0 else lines[i - 1] + ROAD_W * 0.5
var x1 := CITY_HALF if i == lines.size() else lines[i] - ROAD_W * 0.5
if x1 - x0 < 2.0:
continue
_road_piece("road-straight", Vector3((x0 + x1) * 0.5, y, z), 90.0, Vector3(ROAD_W, 2.0, x1 - x0))
# Overhead cable bundles across east-west streets (urban-Japan clutter)
for z in lines:
var x := -CITY_HALF + 24.0
while x < CITY_HALF - 24.0:
if absf(x) > 14.0: # keep the central avenue sky clear
var h := 9.5 + fmod(absf(x), 3.0) * 0.4
for s3 in 3:
var t0 := float(s3) / 3.0
var t1 := float(s3 + 1) / 3.0
var sag0 := sin(t0 * PI) * 0.7
var sag1 := sin(t1 * PI) * 0.7
var za := z - STREET * 0.5 + STREET * t0
var zb := z - STREET * 0.5 + STREET * t1
var mid := Vector3(x, h - (sag0 + sag1) * 0.5, (za + zb) * 0.5)
_deco_box(mid, Vector3(0.07, 0.07, zb - za + 0.1), INK_COLOR)
x += 48.0
else:
x += 48.0
# ── Main avenues: kit streetlights ───────────────────────────────────────────
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:
var lines := _street_lines()
var lamp_scale := 10.0
for z in lines:
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:
# Double-arm lights along the central N-S avenue at every block line
var x := side * (ROAD_W * 0.5 + 0.4)
_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",
Vector3(x, 0, z), 90.0 if side > 0 else -90.0, lamp_scale)
for x in lines:
if absf(x) < 1.0:
continue
# Single lights down the central E-W avenue
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
var z2 := side * (ROAD_W * 0.5 + 0.4)
_kit_prop_path("res://assets/props/roads/light-curved.glb",
Vector3(x, 0, z2), 0.0 if side > 0 else 180.0, lamp_scale)
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
add_child(inst)
inst.global_position = pos
inst.rotation_degrees.y = yaw_deg
# Nature-kit foliage uses vertex-colored materials that the toon swap
# mangles (cyan trees) — keep original materials; the faceted low-poly
# look already reads cel under this lighting.
if not path.contains("/nature/"):
LevelMaterials.apply_toon_recursive(inst, 0.0)