Detailed graphics pass: authored textures, humanoid NPCs, HUD feedback, asset pipeline
Full build / Import + headless tests (push) Successful in 16s
Full build / Full desktop build + optional release (push) Skipped

- tools/generate_textures.py (deterministic PIL) produces all authored PNGs:
  brick facade, concrete, asphalt, awning, skyline, vignette, HUD icons,
  access-card art (all <=1024px, power-of-two)
- WorldBuilder: textured facade/glass door/sidewalk/asphalt, striped awning
  with 'STEP LINE', generated skyline backdrop planes
- GameManager: soft sun shadows (4-split parallel); guard in 'guard' group
- NPC avatars: humanoid rig (torso/head/limbs) with archetype props (camera,
  delivery box, performer hat, protest sign, tourist backpack), procedural
  walk bob, head-turns-toward-guard attention cue
- HUD: inspection artifact cards, mode icons (flashlight/door/wand),
  per-escalation vignette, body-camera overlay (corner brackets + REC + clock)
- assets/ import convention documented; test_asset_textures_present_and_pow2
  guards the pipeline (57 tests pass)
- README tech stack updated for Gitea Actions + asset pipeline; screenshot
  gallery regenerated (day + night)
This commit is contained in:
zwitschi committed 2026-10-31 13:33:43 +01:00
1 parent ac3bb7dcd8
commit 031503e161
31 files changed
+636 -57

No files matched your search

+52 -12
View File
@@ -1,5 +1,11 @@
class_name WorldBuilder
## Procedurally assembles a minimal Hollywood Boulevard entrance prototype:
const T_BRICK := preload("res://assets/textures/brick_facade.png")
const T_CONCRETE := preload("res://assets/textures/concrete.png")
const T_ASPHALT := preload("res://assets/textures/asphalt.png")
const T_AWNING := preload("res://assets/textures/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.
@@ -27,14 +33,19 @@ func build() -> void:
_build_crosswalk()
_build_street_furniture()
_build_guard_spawn()
_build_skyline_backdrop()
_build_navmesh()
set_night_mode(false)
func _add_box(parent: Node3D, size: Vector3, pos: Vector3, color: Color, cast_shadow: bool = false) -> MeshInstance3D:
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:
var mesh := BoxMesh.new()
mesh.size = size
var mat := StandardMaterial3D.new()
mat.albedo_color = color
if texture:
mat.albedo_texture = texture
mat.uv1_scale = Vector3(uv_scale.x, uv_scale.y, 1.0)
mesh.material = mat
var mi := MeshInstance3D.new()
mi.mesh = mesh
@@ -56,19 +67,23 @@ func _build_ground() -> void:
func _build_building() -> void:
var building := StaticBody3D.new()
building.name = "Building"
# Main facade
_add_box(building, Vector3(40, 18, 4), Vector3(0, 9, -2), Color(0.35, 0.32, 0.30))
# Main facade (textured brick)
_add_box(building, Vector3(40, 18, 4), Vector3(0, 9, -2), Color(0.85, 0.8, 0.78), false, T_BRICK, Vector2(10, 5))
# Entrance recess + door recess frame
_add_box(building, Vector3(5.4, 6.4, 0.2), Vector3(0, 3.2, -4.0), Color(0.10, 0.10, 0.10))
# Door
_add_box(building, Vector3(3.0, 5.2, 0.3), Vector3(0, 2.9, -4.15), Color(0.16, 0.15, 0.13))
# Door window
_add_box(building, Vector3(1.8, 1.4, 0.34), Vector3(0, 3.8, -4.0), Color(0.5, 0.7, 0.9, 1.0))
_add_box(building, Vector3(5.4, 6.4, 0.2), Vector3(0, 3.2, -4.0), Color(0.08, 0.08, 0.09))
# Door frame and dark door
_add_box(building, Vector3(3.0, 5.2, 0.3), Vector3(0, 2.9, -4.15), Color(0.13, 0.12, 0.11))
# Door glass (semi-transparent)
var glass := _add_box(building, Vector3(1.8, 1.4, 0.34), Vector3(0, 3.8, -4.0), 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
# 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, -4.35), Color(0.1, 0.9, 0.3))
door_indicator.material_override = _lock_indicator_material(Color(0.1, 0.9, 0.3))
# Awning sign bar
_add_box(building, Vector3(8, 0.5, 0.6), Vector3(0, 7.0, -3.6), Color(0.45, 0.44, 0.44))
var col := BoxShape3D.new()
col.size = Vector3(40, 18, 4)
var so := building.create_shape_owner(building)
@@ -96,7 +111,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(0.60, 0.58, 0.56))
_add_box(sidewalk, Vector3(30, 0.18, 8), Vector3(0, 0.09, 8), Color(1, 1, 1), false, T_CONCRETE, Vector2(9, 2.4))
var col := BoxShape3D.new()
col.size = Vector3(30, 0.36, 8)
var so := sidewalk.create_shape_owner(sidewalk)
@@ -107,7 +122,7 @@ 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(0.13, 0.13, 0.14))
_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
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))
@@ -138,6 +153,9 @@ func _build_navmesh() -> void:
root.add_child(region)
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)
awning.name = "Awning"
## Neon-style marquee strip above the door: emissive bars + a label.
var strip_parent := Node3D.new()
strip_parent.name = "Marquee"
@@ -243,6 +261,28 @@ func _plain_material(color: Color) -> StandardMaterial3D:
mat.albedo_color = color
return mat
func _build_skyline_backdrop() -> void:
## Distant procedural skyline on four unshaded planes around the block.
var mat := StandardMaterial3D.new()
mat.albedo_texture = T_SKYLINE
mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
mat.cull_mode = BaseMaterial3D.CULL_DISABLED
const DIST := 95.0
const HEIGHT := 90.0
for i in 4:
var plane := MeshInstance3D.new()
plane.name = "Skyline_%d" % i
var pm := PlaneMesh.new()
pm.size = Vector2(300, HEIGHT)
plane.mesh = pm
plane.material_override = mat
var angle := i * 90.0
plane.position = Vector3(sin(deg_to_rad(angle)) * DIST, HEIGHT / 2.0, cos(deg_to_rad(angle)) * DIST)
plane.rotation_degrees = Vector3(0, -angle + 90.0, 0)
plane.cast_shadow = (0 as GeometryInstance3D.ShadowCastingSetting)
root.add_child(plane)
func _build_guard_spawn() -> void:
var marker := Marker3D.new()
marker.name = "GuardSpawn"