v0.3.0: rendering pass, generated props, selection model and Web build
Rendering and presentation - Shared surface-material module (src/game/materials.gd) with per-surface roughness/metallic/uv profiles; ACES tonemapping, glow, fog and phase-driven sky energy. WorldBuilder rebuilt on those profiles with facade windows that light up, door trim/handles, curb, lane dashes, bollards, bench, bin and planter foliage. - NPC silhouettes: per-role clothing palettes, caps/hoods/backpacks/sunglasses, hands and shoes, arm/leg walk swing. - Six generated low-poly GLB props (bollards, bench, bin, planter, newspaper box, hydrant) built by tools/model_pipeline.py, loaded by WorldBuilder with a procedural fallback; ADR-0012. Selection and movement - Focus ranking by view angle then distance (NpcFocus.rank_indices); Tab and the mouse wheel cycle nearby people initially, right click clears focus with a re-acquire grace, HUD shows the candidate count, Shift walks at half speed. ADR-0011. Windowed smoke extended to cover wheel cycling and clearing. QA tooling - screen_designs.py catalogue grown to 19 screens (8 scene, 11 UI) plus a `list` subcommand; draft generation keeps existing AI references unless --force. Web export - New Web preset using the non-threaded Godot template (static hosting, no COOP/COEP). tools/build_releases.sh exports build/web and packages build/step-line-web-<ver>.zip; publish_release.sh uploads it with the desktop artifacts. test_web_export.gd covers the preset and release wiring. Verification - Headless suite 177 -> 181, Python tooling 53 tests, all eight smokes pass (including the windowed focus/wheel smoke). Web bundle serves index.html, index.wasm (35 MB) and index.pck (30 MB) with HTTP 200 from a static server. - Version 0.3.0; README, PLAN, RELEASING, TODO/DONE and MEMORY updated.
This commit is contained in:
1 parent
f763cff744
commit
afa1d6beec
48 files changed
+2602
-147
No files matched your search
+179
-41
@@ -1,13 +1,21 @@
|
||||
class_name WorldBuilder
|
||||
## Procedurally assembles a minimal Hollywood Boulevard entrance prototype:
|
||||
|
||||
const Materials := preload("res://src/game/materials.gd")
|
||||
const T_BRICK := preload("res://assets/textures/ai/game_ready/brick_facade.png")
|
||||
const T_CONCRETE := preload("res://assets/textures/ai/game_ready/concrete.png")
|
||||
const T_ASPHALT := preload("res://assets/textures/ai/game_ready/asphalt.png")
|
||||
const T_AWNING := preload("res://assets/textures/ai/game_ready/awning.png")
|
||||
const T_SKYLINE := preload("res://assets/textures/skyline.png")
|
||||
## steps, building facade, restricted door, sidewalk, pavement, and street edge.
|
||||
## No art assets required; all geometry is BoxMesh with flat materials.
|
||||
## Surfaces use the shared PBR profiles in `src/game/materials.gd`; street
|
||||
## furniture comes from the generated GLB props (tools/model_pipeline.py) with a
|
||||
## procedural fallback so a headless checkout without imports still builds.
|
||||
|
||||
## Generated prop models, one `.glb` per street-furniture piece.
|
||||
const MODEL_DIR := "res://assets/models/generated/"
|
||||
const MODEL_PROPS := ["bollard", "bench", "trash_bin", "planter_box",
|
||||
"newspaper_box", "fire_hydrant"]
|
||||
|
||||
var root: Node3D = null
|
||||
## Door lock indicator mesh near the handle; toggled by GameManager.
|
||||
@@ -19,6 +27,12 @@ var _lamp_omni: Array[OmniLight3D] = []
|
||||
var _lamp_bulb_mats: Array[StandardMaterial3D] = []
|
||||
var marquee_label: Label3D = null
|
||||
var _marquee_emissive: Array[StandardMaterial3D] = []
|
||||
## Facade windows that light up after dark.
|
||||
var _window_mats: Array[StandardMaterial3D] = []
|
||||
## Names of the generated props that actually loaded (procedural fallbacks are
|
||||
## not listed), so tests and the debug overlay can report real model usage.
|
||||
var loaded_models: Array[String] = []
|
||||
var fallback_props: Array[String] = []
|
||||
|
||||
func _init(root_node: Node3D) -> void:
|
||||
root = root_node
|
||||
@@ -33,19 +47,21 @@ func build() -> void:
|
||||
_build_street()
|
||||
_build_crosswalk()
|
||||
_build_street_furniture()
|
||||
_build_street_detail()
|
||||
_build_guard_spawn()
|
||||
_build_navmesh()
|
||||
set_night_mode(false)
|
||||
|
||||
func _add_box(parent: Node3D, size: Vector3, pos: Vector3, color: Color, cast_shadow: bool = false,
|
||||
texture: Texture2D = null, uv_scale: Vector2 = Vector2(1, 1)) -> MeshInstance3D:
|
||||
texture: Texture2D = null, uv_scale: Vector2 = Vector2(1, 1),
|
||||
role: String = "concrete") -> MeshInstance3D:
|
||||
var mesh := BoxMesh.new()
|
||||
mesh.size = size
|
||||
var mat := StandardMaterial3D.new()
|
||||
mat.albedo_color = color
|
||||
var mat: StandardMaterial3D
|
||||
if texture:
|
||||
mat.albedo_texture = texture
|
||||
mat.uv1_scale = Vector3(uv_scale.x, uv_scale.y, 1.0)
|
||||
mat = Materials.textured(texture, color, uv_scale, role)
|
||||
else:
|
||||
mat = Materials.flat(color, role)
|
||||
mesh.material = mat
|
||||
var mi := MeshInstance3D.new()
|
||||
mi.mesh = mesh
|
||||
@@ -59,7 +75,7 @@ func _build_ground() -> void:
|
||||
ground.name = "Ground"
|
||||
# Large enough that the ground meets the distant skyline ring (no gap of sky
|
||||
# under the buildings when looking down the boulevard).
|
||||
_add_box(root, Vector3(500, 0.5, 500), Vector3(0, -0.25, 20), Color(0.22, 0.24, 0.26))
|
||||
_add_box(root, Vector3(500, 0.5, 500), Vector3(0, -0.25, 20), Color(0.22, 0.24, 0.26), false, null, Vector2.ONE, "asphalt")
|
||||
var col := BoxShape3D.new()
|
||||
col.size = Vector3(500, 1, 500)
|
||||
var shape_owner := ground.create_shape_owner(ground)
|
||||
@@ -70,22 +86,27 @@ func _build_building() -> void:
|
||||
var building := StaticBody3D.new()
|
||||
building.name = "Building"
|
||||
# Main facade (textured brick)
|
||||
_add_box(building, Vector3(40, 18, 4), Vector3(0, 9, -6), Color(0.85, 0.8, 0.78), false, T_BRICK, Vector2(10, 5))
|
||||
_add_box(building, Vector3(40, 18, 4), Vector3(0, 9, -6), Color(0.80, 0.76, 0.74), false, T_BRICK, Vector2(22, 10), "brick")
|
||||
# Stone base course so the facade has a footing instead of meeting the steps dead-on.
|
||||
_add_box(building, Vector3(40, 0.8, 0.35), Vector3(0, 0.4, -3.85), Color(0.58, 0.56, 0.53), false, null, Vector2.ONE, "stone")
|
||||
# Entrance recess + door recess frame
|
||||
_add_box(building, Vector3(5.4, 6.4, 0.2), Vector3(0, 3.2, -3.9), Color(0.08, 0.08, 0.09))
|
||||
_add_box(building, Vector3(5.4, 6.4, 0.2), Vector3(0, 3.2, -3.9), Color(0.08, 0.08, 0.09), false, null, Vector2.ONE, "stone")
|
||||
# Door frame columns and lintel, so the entrance is framed rather than painted on.
|
||||
for side in [-1.55, 1.55]:
|
||||
_add_box(building, Vector3(0.28, 5.2, 0.42), Vector3(side, 2.9, -3.86), Color(0.30, 0.28, 0.26), true, null, Vector2.ONE, "stone")
|
||||
_add_box(building, Vector3(3.4, 0.3, 0.42), Vector3(0, 5.55, -3.86), Color(0.30, 0.28, 0.26), true, null, Vector2.ONE, "stone")
|
||||
# Door frame and dark door
|
||||
_add_box(building, Vector3(3.0, 5.2, 0.3), Vector3(0, 2.9, -3.98), Color(0.13, 0.12, 0.11))
|
||||
_add_box(building, Vector3(3.0, 5.2, 0.3), Vector3(0, 2.9, -3.98), Color(0.13, 0.12, 0.11), true, null, Vector2.ONE, "painted_metal")
|
||||
# Door glass (semi-transparent)
|
||||
var glass := _add_box(building, Vector3(1.8, 1.4, 0.34), Vector3(0, 3.8, -3.9), Color(0.6, 0.75, 0.9, 0.55))
|
||||
var glass_mat := StandardMaterial3D.new()
|
||||
glass_mat.albedo_color = Color(0.6, 0.75, 0.9, 0.45)
|
||||
glass_mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
||||
glass_mat.roughness = 0.15
|
||||
glass_mat.metallic = 0.1
|
||||
glass.material_override = glass_mat
|
||||
glass.material_override = Materials.glass(Color(0.55, 0.7, 0.88, 0.55))
|
||||
# Door handles: two slim metal bars where the guard reaches for the lock.
|
||||
for side in [-0.42, 0.42]:
|
||||
_add_box(building, Vector3(0.06, 0.5, 0.06), Vector3(side, 2.6, -3.79), Color(0.75, 0.74, 0.70), false, null, Vector2.ONE, "metal")
|
||||
# Door lock indicator (green = open, red = locked), placed beside the handle
|
||||
door_indicator = _add_box(building, Vector3(0.18, 0.18, 0.24), Vector3(-1.8, 1.7, -3.82), Color(0.1, 0.9, 0.3))
|
||||
door_indicator.material_override = _lock_indicator_material(Color(0.1, 0.9, 0.3))
|
||||
_build_facade_windows(building)
|
||||
var col := BoxShape3D.new()
|
||||
col.size = Vector3(40, 18, 4)
|
||||
var so := building.create_shape_owner(building)
|
||||
@@ -93,6 +114,24 @@ func _build_building() -> void:
|
||||
building.shape_owner_set_transform(so, Transform3D(Basis(), Vector3(0, 9, -6)))
|
||||
root.add_child(building)
|
||||
|
||||
## Regular window bands above the entrance. Panes are dark glass by day and
|
||||
## light up after dark, which gives the facade depth at night.
|
||||
func _build_facade_windows(parent: Node3D) -> void:
|
||||
var columns := [-17.0, -13.6, -10.2, -6.8, 6.8, 10.2, 13.6, 17.0]
|
||||
for y in [9.2, 12.4]:
|
||||
for x in columns:
|
||||
# Recessed reveal, then the pane: gives the window a dark frame edge.
|
||||
_add_box(parent, Vector3(1.96, 2.46, 0.1), Vector3(x, y, -3.99),
|
||||
Color(0.22, 0.21, 0.20), false, null, Vector2.ONE, "stone")
|
||||
var pane := _add_box(parent, Vector3(1.7, 2.2, 0.12),
|
||||
Vector3(x, y, -3.94), Color(1, 1, 1))
|
||||
var pane_mat := Materials.window_pane()
|
||||
pane.material_override = pane_mat
|
||||
_window_mats.append(pane_mat)
|
||||
# Sill below the frame.
|
||||
_add_box(parent, Vector3(2.1, 0.12, 0.3), Vector3(x, y - 1.3, -3.86),
|
||||
Color(0.55, 0.53, 0.50), false, null, Vector2.ONE, "stone")
|
||||
|
||||
func _build_steps() -> void:
|
||||
# Steps have collision so NPCs and the guard can walk up the entrance.
|
||||
# The ground and building provide the actual collision boundaries.
|
||||
@@ -125,7 +164,7 @@ func _build_steps() -> void:
|
||||
func _build_sidewalk() -> void:
|
||||
var sidewalk := StaticBody3D.new()
|
||||
sidewalk.name = "Sidewalk"
|
||||
_add_box(sidewalk, Vector3(30, 0.18, 8), Vector3(0, 0.09, 8), Color(1, 1, 1), false, T_CONCRETE, Vector2(9, 2.4))
|
||||
_add_box(sidewalk, Vector3(30, 0.18, 8), Vector3(0, 0.09, 8), Color(0.74, 0.74, 0.72), false, T_CONCRETE, Vector2(9, 2.4), "concrete")
|
||||
var col := BoxShape3D.new()
|
||||
col.size = Vector3(30, 0.36, 8)
|
||||
var so := sidewalk.create_shape_owner(sidewalk)
|
||||
@@ -136,10 +175,14 @@ func _build_sidewalk() -> void:
|
||||
func _build_street() -> void:
|
||||
var street := StaticBody3D.new()
|
||||
street.name = "Street"
|
||||
_add_box(street, Vector3(120, 0.1, 26), Vector3(0, 0.05, 22), Color(1, 1, 1), false, T_ASPHALT, Vector2(30, 7))
|
||||
# Center dashes
|
||||
# The roadway reads darker than the sidewalk; the raw asphalt texture tinted
|
||||
# white looked like more pavement.
|
||||
_add_box(street, Vector3(120, 0.1, 26), Vector3(0, 0.05, 22), Color(0.52, 0.53, 0.57), false, T_ASPHALT, Vector2(30, 7), "asphalt")
|
||||
# Center dashes along the street (x varied so they form a lane line, not a stack).
|
||||
for i in range(-40, 41, 4):
|
||||
_add_box(street, Vector3(0.2, 0.02, 1.2), Vector3(i * 0.0, 0.11, 22), Color(0.85, 0.85, 0.30))
|
||||
_add_box(street, Vector3(0.2, 0.02, 1.2), Vector3(float(i), 0.11, 22), Color(0.85, 0.85, 0.30), false, null, Vector2.ONE, "concrete")
|
||||
# Curb between the sidewalk and the roadway.
|
||||
_add_box(street, Vector3(120, 0.22, 0.5), Vector3(0, 0.11, 12.1), Color(0.62, 0.61, 0.58), false, null, Vector2.ONE, "concrete")
|
||||
var col := BoxShape3D.new()
|
||||
col.size = Vector3(120, 0.2, 26)
|
||||
var so := street.create_shape_owner(street)
|
||||
@@ -215,7 +258,7 @@ var _skyline_mat: StandardMaterial3D = null
|
||||
|
||||
func _build_marquee() -> void:
|
||||
## Striped awning above the door, then the neon sign strip + label.
|
||||
var awning := _add_box(root, Vector3(10.5, 0.08, 1.8), Vector3(0, 6.55, -3.7), Color(1, 1, 1), false, T_AWNING)
|
||||
var awning := _add_box(root, Vector3(10.5, 0.08, 1.8), Vector3(0, 6.55, -3.7), Color(1, 1, 1), true, T_AWNING, Vector2(4, 1), "awning")
|
||||
awning.name = "Awning"
|
||||
## Neon-style marquee strip above the door: emissive bars + a label.
|
||||
var strip_parent := Node3D.new()
|
||||
@@ -244,8 +287,8 @@ func _build_street_furniture() -> void:
|
||||
for side in [-7.5, 7.5]:
|
||||
var lamp := Node3D.new()
|
||||
lamp.name = "Lamp_%d" % int(side)
|
||||
_add_box(lamp, Vector3(0.22, 5.0, 0.22), Vector3(side, 2.5, 8.5), Color(0.16, 0.16, 0.18))
|
||||
_add_box(lamp, Vector3(1.6, 0.15, 0.15), Vector3(side + (0.8 if side < 0 else -0.8), 4.9, 8.5), Color(0.16, 0.16, 0.18))
|
||||
_add_box(lamp, Vector3(0.22, 5.0, 0.22), Vector3(side, 2.5, 8.5), Color(0.16, 0.16, 0.18), true, null, Vector2.ONE, "metal")
|
||||
_add_box(lamp, Vector3(1.6, 0.15, 0.15), Vector3(side + (0.8 if side < 0 else -0.8), 4.9, 8.5), Color(0.16, 0.16, 0.18), true, null, Vector2.ONE, "metal")
|
||||
var bulb := _add_box(lamp, Vector3(0.16, 0.16, 0.5), Vector3(side + (0.8 if side < 0 else -0.8), 4.85, 8.5), Color(1, 1, 0.85))
|
||||
var bulb_mat := _emissive_material(Color(1.0, 1.0, 0.8))
|
||||
bulb.material_override = bulb_mat
|
||||
@@ -253,17 +296,15 @@ func _build_street_furniture() -> void:
|
||||
_lamp_bulb_mats.append(bulb_mat)
|
||||
var omni := OmniLight3D.new()
|
||||
omni.position = Vector3(side + (0.8 if side < 0 else -0.8), 4.7, 8.5)
|
||||
omni.omni_range = 14.0
|
||||
omni.omni_range = 12.0
|
||||
omni.light_energy = 0.0
|
||||
omni.light_color = Color(1, 0.93, 0.75)
|
||||
lamp.add_child(omni)
|
||||
_lamp_omni.append(omni)
|
||||
root.add_child(lamp)
|
||||
_street_lamps.append(bulb)
|
||||
# Planters beside the steps.
|
||||
for side in [-4.6, 4.6]:
|
||||
var planter := _add_box(root, Vector3(1.1, 0.7, 1.1), Vector3(side, 0.35, 1.0), Color(0.42, 0.34, 0.28))
|
||||
planter.name = "Planter_%d" % int(side)
|
||||
# Planters beside the steps were flat boxes that clipped the treads; the
|
||||
# generated planter_box props replace them in _build_street_detail().
|
||||
# Low-poly trees along the street front.
|
||||
var trunk_color := Color(0.35, 0.25, 0.18)
|
||||
var leaf_color := Color(0.16, 0.42, 0.2)
|
||||
@@ -280,7 +321,7 @@ func _build_street_furniture() -> void:
|
||||
trunk_mesh.bottom_radius = 0.14
|
||||
trunk_mesh.height = 1.4
|
||||
trunk.mesh = trunk_mesh
|
||||
trunk.material_override = _plain_material(trunk_color)
|
||||
trunk.material_override = _plain_material(trunk_color, "wood")
|
||||
trunk.position = Vector3(x, 0.7, z)
|
||||
tree.add_child(trunk)
|
||||
var crown := MeshInstance3D.new()
|
||||
@@ -289,11 +330,108 @@ func _build_street_furniture() -> void:
|
||||
crown_mesh.bottom_radius = 0.85
|
||||
crown_mesh.height = 2.2
|
||||
crown.mesh = crown_mesh
|
||||
crown.material_override = _plain_material(leaf_color)
|
||||
crown.material_override = _plain_material(leaf_color, "foliage")
|
||||
crown.position = Vector3(x, 2.1, z)
|
||||
tree.add_child(crown)
|
||||
root.add_child(tree)
|
||||
|
||||
## Street furniture built from the generated GLB props (tools/model_pipeline.py).
|
||||
## The meshes are real 3D models (bollards, benches, bins, planters, newspaper
|
||||
## boxes, a hydrant) rather than scaled boxes, and every placement is fixed so
|
||||
## the street looks the same on every run.
|
||||
func _build_street_detail() -> void:
|
||||
var detail := Node3D.new()
|
||||
detail.name = "StreetDetail"
|
||||
root.add_child(detail)
|
||||
# Bollards flank the foot of the steps.
|
||||
for x in [-5.6, -4.2, 4.2, 5.6]:
|
||||
_add_prop_model(detail, "bollard", Vector3(x, 0.18, 4.7))
|
||||
# Benches face the boulevard.
|
||||
_add_prop_model(detail, "bench", Vector3(-8.8, 0.18, 6.4), 180.0)
|
||||
_add_prop_model(detail, "bench", Vector3(8.8, 0.18, 6.4), 180.0)
|
||||
# Bins where people leave the steps.
|
||||
_add_prop_model(detail, "trash_bin", Vector3(-6.6, 0.18, 4.9))
|
||||
_add_prop_model(detail, "trash_bin", Vector3(6.6, 0.18, 4.9))
|
||||
# Planters break up the long sidewalk run.
|
||||
_add_prop_model(detail, "planter_box", Vector3(-11.8, 0.18, 8.8))
|
||||
_add_prop_model(detail, "planter_box", Vector3(11.8, 0.18, 8.8))
|
||||
# Newspaper boxes and a hydrant along the curb.
|
||||
_add_prop_model(detail, "newspaper_box", Vector3(-9.6, 0.18, 11.0), -12.0)
|
||||
_add_prop_model(detail, "newspaper_box", Vector3(-8.9, 0.18, 11.0), -12.0)
|
||||
_add_prop_model(detail, "fire_hydrant", Vector3(8.4, 0.18, 11.2))
|
||||
|
||||
## Place one generated prop. Falls back to simple primitives when the `.glb` is
|
||||
## missing (fresh checkout before `godot --import`) so the world still builds.
|
||||
func _add_prop_model(parent: Node3D, prop: String, pos: Vector3,
|
||||
rot_y_deg: float = 0.0) -> Node3D:
|
||||
var holder := Node3D.new()
|
||||
holder.name = "Prop_%s_%d" % [prop, parent.get_child_count()]
|
||||
holder.position = pos
|
||||
holder.rotation_degrees.y = rot_y_deg
|
||||
var model := _load_prop_scene(prop)
|
||||
if model != null:
|
||||
holder.add_child(model)
|
||||
loaded_models.append(prop)
|
||||
else:
|
||||
holder.add_child(_fallback_prop(prop))
|
||||
fallback_props.append(prop)
|
||||
parent.add_child(holder)
|
||||
return holder
|
||||
|
||||
func _load_prop_scene(prop: String) -> Node3D:
|
||||
var path := MODEL_DIR + prop + ".glb"
|
||||
if not ResourceLoader.exists(path):
|
||||
return null
|
||||
var res: Resource = ResourceLoader.load(path)
|
||||
if res is PackedScene:
|
||||
var node: Node = (res as PackedScene).instantiate()
|
||||
if node is Node3D:
|
||||
return node as Node3D
|
||||
node.queue_free()
|
||||
return null
|
||||
|
||||
## Rough primitive stand-in per prop, used only when the model is unavailable.
|
||||
func _fallback_prop(prop: String) -> Node3D:
|
||||
var node := Node3D.new()
|
||||
match prop:
|
||||
"bollard":
|
||||
_add_cylinder_mesh(node, 0.09, 0.92, Vector3(0, 0.46, 0), Color(0.16, 0.17, 0.19), "metal")
|
||||
"bench":
|
||||
_add_box(node, Vector3(1.8, 0.08, 0.5), Vector3(0, 0.46, 0), Color(0.42, 0.28, 0.17), true, null, Vector2.ONE, "wood")
|
||||
_add_box(node, Vector3(1.8, 0.42, 0.07), Vector3(0, 0.72, -0.22), Color(0.42, 0.28, 0.17), true, null, Vector2.ONE, "wood")
|
||||
for x in [-0.72, 0.72]:
|
||||
_add_box(node, Vector3(0.09, 0.46, 0.46), Vector3(x, 0.23, 0), Color(0.16, 0.17, 0.19), true, null, Vector2.ONE, "metal")
|
||||
"trash_bin":
|
||||
_add_cylinder_mesh(node, 0.27, 0.78, Vector3(0, 0.39, 0), Color(0.22, 0.30, 0.24), "painted_metal")
|
||||
_add_cylinder_mesh(node, 0.29, 0.06, Vector3(0, 0.81, 0), Color(0.16, 0.17, 0.19), "metal")
|
||||
"planter_box":
|
||||
_add_box(node, Vector3(0.96, 0.54, 0.96), Vector3(0, 0.27, 0), Color(0.52, 0.51, 0.48), true, null, Vector2.ONE, "stone")
|
||||
_add_cylinder_mesh(node, 0.30, 0.55, Vector3(0, 0.86, 0), Color(0.18, 0.42, 0.20), "foliage")
|
||||
"newspaper_box":
|
||||
_add_box(node, Vector3(0.42, 0.72, 0.34), Vector3(0, 0.49, 0), Color(0.16, 0.27, 0.45), true, null, Vector2.ONE, "painted_metal")
|
||||
_add_box(node, Vector3(0.44, 0.10, 0.36), Vector3(0, 0.90, 0), Color(0.85, 0.72, 0.28), true, null, Vector2.ONE, "painted_metal")
|
||||
"fire_hydrant":
|
||||
_add_cylinder_mesh(node, 0.15, 0.62, Vector3(0, 0.31, 0), Color(0.6, 0.12, 0.12), "painted_metal")
|
||||
_add_box(node, Vector3(0.52, 0.10, 0.12), Vector3(0, 0.42, 0), Color(0.75, 0.74, 0.70), true, null, Vector2.ONE, "metal")
|
||||
_:
|
||||
_add_box(node, Vector3(0.5, 0.5, 0.5), Vector3(0, 0.25, 0), Color(0.5, 0.5, 0.5))
|
||||
return node
|
||||
|
||||
func _add_cylinder_mesh(parent: Node3D, radius: float, height: float,
|
||||
pos: Vector3, color: Color, role: String = "metal") -> MeshInstance3D:
|
||||
var mi := MeshInstance3D.new()
|
||||
var mesh := CylinderMesh.new()
|
||||
mesh.top_radius = radius
|
||||
mesh.bottom_radius = radius
|
||||
mesh.height = height
|
||||
mesh.radial_segments = 12
|
||||
mi.mesh = mesh
|
||||
mi.position = pos
|
||||
mi.material_override = Materials.flat(color, role)
|
||||
mi.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_ON
|
||||
parent.add_child(mi)
|
||||
return mi
|
||||
|
||||
func _build_crosswalk() -> void:
|
||||
# White crosswalk stripes in front of the entrance on the street.
|
||||
for i in 8:
|
||||
@@ -305,24 +443,24 @@ func set_night_mode(night: bool) -> void:
|
||||
for mat in _lamp_bulb_mats:
|
||||
mat.emission_energy_multiplier = (2.0 if night else 0.0)
|
||||
for omni in _lamp_omni:
|
||||
omni.light_energy = (3.0 if night else 0.0)
|
||||
# Two lamps at full energy washed the whole sidewalk grey; keep visible
|
||||
# pools of light instead of flat night-time brightness.
|
||||
omni.light_energy = (1.8 if night else 0.0)
|
||||
for mat in _marquee_emissive:
|
||||
mat.emission_energy_multiplier = (2.0 if night else 0.2)
|
||||
# Light most, not all, of the windows: a fully lit grid reads as a light box
|
||||
# instead of an occupied building.
|
||||
for i in _window_mats.size():
|
||||
var lit := night and i % 3 != 2
|
||||
_window_mats[i].emission_energy_multiplier = (1.1 if lit else 0.0)
|
||||
if marquee_label:
|
||||
marquee_label.modulate.a = (1.0 if night else 0.9)
|
||||
|
||||
func _emissive_material(color: Color) -> StandardMaterial3D:
|
||||
var mat := StandardMaterial3D.new()
|
||||
mat.albedo_color = color
|
||||
mat.emission_enabled = true
|
||||
mat.emission = color
|
||||
mat.emission_energy_multiplier = 1.0
|
||||
return mat
|
||||
return Materials.emissive(color)
|
||||
|
||||
func _plain_material(color: Color) -> StandardMaterial3D:
|
||||
var mat := StandardMaterial3D.new()
|
||||
mat.albedo_color = color
|
||||
return mat
|
||||
func _plain_material(color: Color, role: String = "concrete") -> StandardMaterial3D:
|
||||
return Materials.flat(color, role)
|
||||
|
||||
func _build_guard_spawn() -> void:
|
||||
var marker := Marker3D.new()
|
||||
|
||||
Reference in new issue
Block a user