Add session shell and close world presentation follow-ups
Session shell - Startup venue menu (src/game/ui/start_menu.gd): keyboard + mouse, replaces the live-shift boot and V as the player-facing venue path (V stays a debug bind). - Pause overlay on Esc (src/game/ui/pause_overlay.gd): resume, restart shift, settings, quit to menu. - Settings panel (src/game/ui/settings_panel.gd): mouse look sensitivity and HUD font scale, persisted via Save into user://settings.json and loaded on boot. - Shift review "Run another shift" now returns to the venue menu. - New smoke: src/test/smoke_session_shell.gd (menu -> shift -> pause -> settings -> menu), passes headless and windowed. World and presentation - Skyline backdrop restored with a transparent asset and an alpha-scissor unshaded material; fixes the opaque black slab from the earlier attempt and the unimported texture path. - Steps and landing are a StaticBody3D with per-tread colliders, so the guard and NPCs walk up instead of clipping through. - Marquee Label3D scaled up (72 -> 160) so STEP LINE reads from the sidewalk. - Focus torus and name tag enlarged for 2-3 m readability. - Routine traffic despawns after an idle lifetime (NpcAvatar.ephemeral), so the 16-NPC cap no longer fills with standers; authored events stay for the shift. Docs and tests - PLAN.md test/CI section corrected to the custom headless runner and Gitea Actions. - README controls table, smoke checklist, test count, and status lists refreshed. - TODO/DONE/MEMORY updated; completed sections moved to DONE.md. - Headless suite 77 -> 88 passing (settings round-trip, step colliders, skyline alpha, session-shell UI behavior, traffic lifetime).
This commit is contained in:
1 parent
f11b441c07
commit
be429cab29
20 files changed
+989
-90
No files matched your search
@@ -5,6 +5,7 @@ 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.
|
||||
|
||||
@@ -25,6 +26,7 @@ func _init(root_node: Node3D) -> void:
|
||||
func build() -> void:
|
||||
_build_ground()
|
||||
_build_building()
|
||||
_build_skyline_backdrop()
|
||||
_build_marquee()
|
||||
_build_steps()
|
||||
_build_sidewalk()
|
||||
@@ -90,10 +92,9 @@ func _build_building() -> void:
|
||||
root.add_child(building)
|
||||
|
||||
func _build_steps() -> void:
|
||||
# Steps are visual only (no collider) so NPCs and the guard can walk up the
|
||||
# entrance on the flat navigation mesh; the ground and building provide the
|
||||
# actual collision boundaries.
|
||||
var steps := Node3D.new()
|
||||
# Steps have collision so NPCs and the guard can walk up the entrance.
|
||||
# The ground and building provide the actual collision boundaries.
|
||||
var steps := StaticBody3D.new()
|
||||
steps.name = "Steps"
|
||||
var step_w := 8.0
|
||||
var tread := 0.6
|
||||
@@ -101,9 +102,22 @@ func _build_steps() -> void:
|
||||
for i in 4:
|
||||
var y: float = i * height
|
||||
var z: float = 4.0 * tread - i * tread
|
||||
_add_box(steps, Vector3(step_w - i * 0.35, height, tread), Vector3(0, y + height / 2.0, z), Color(0.55, 0.52, 0.5))
|
||||
# Landing at the top
|
||||
_add_box(steps, Vector3(9, 0.2, 3), Vector3(0, 4 * height + 0.1, -1.5), Color(0.5, 0.47, 0.45))
|
||||
var w: float = step_w - i * 0.35
|
||||
_add_box(steps, Vector3(w, height, tread), Vector3(0, y + height / 2.0, z), Color(0.55, 0.52, 0.5))
|
||||
# Add collision shape to each tread
|
||||
var tread_col := BoxShape3D.new()
|
||||
tread_col.size = Vector3(w, height, tread)
|
||||
var owner_id := steps.create_shape_owner(steps)
|
||||
steps.shape_owner_add_shape(owner_id, tread_col)
|
||||
steps.shape_owner_set_transform(owner_id, Transform3D(Basis(), Vector3(0, y + height / 2.0, z)))
|
||||
# Landing at the top with collision
|
||||
var landing_y: float = 4 * height + 0.1
|
||||
_add_box(steps, Vector3(9, 0.2, 3), Vector3(0, landing_y, -1.5), Color(0.5, 0.47, 0.45))
|
||||
var landing_col := BoxShape3D.new()
|
||||
landing_col.size = Vector3(9, 0.2, 3)
|
||||
var landing_owner := steps.create_shape_owner(steps)
|
||||
steps.shape_owner_add_shape(landing_owner, landing_col)
|
||||
steps.shape_owner_set_transform(landing_owner, Transform3D(Basis(), Vector3(0, landing_y, -1.5)))
|
||||
root.add_child(steps)
|
||||
|
||||
func _build_sidewalk() -> void:
|
||||
@@ -150,6 +164,33 @@ func _build_navmesh() -> void:
|
||||
region.navigation_mesh = mesh
|
||||
root.add_child(region)
|
||||
|
||||
func _build_skyline_backdrop() -> void:
|
||||
## Skyline silhouette backdrop behind the building. The texture has a
|
||||
## transparent sky, so only the building silhouettes show; the material must
|
||||
## be set up for alpha or the plane renders as an opaque slab (ADR/TODO).
|
||||
var backdrop := Node3D.new()
|
||||
backdrop.name = "SkylineBackdrop"
|
||||
var plane := MeshInstance3D.new()
|
||||
var mesh := PlaneMesh.new()
|
||||
mesh.size = Vector2(240, 60) # wide, low horizon
|
||||
plane.mesh = mesh
|
||||
var mat := StandardMaterial3D.new()
|
||||
mat.albedo_texture = T_SKYLINE
|
||||
mat.albedo_texture_repeat = true
|
||||
mat.uv1_scale = Vector3(2.0, 1.0, 1.0)
|
||||
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA_SCISSOR
|
||||
mat.alpha_scissor_threshold = 0.3
|
||||
mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
||||
mat.cull_mode = BaseMaterial3D.CULL_DISABLED
|
||||
plane.material_override = mat
|
||||
plane.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
|
||||
plane.position = Vector3(0, 18, -60) # behind and above the building
|
||||
backdrop.add_child(plane)
|
||||
root.add_child(backdrop)
|
||||
_skyline_mat = mat
|
||||
|
||||
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)
|
||||
@@ -167,9 +208,9 @@ func _build_marquee() -> void:
|
||||
marquee_label.text = "STEP LINE"
|
||||
marquee_label.position = Vector3(0, 7.3, -3.55)
|
||||
marquee_label.modulate = Color(1, 0.6, 0.9)
|
||||
marquee_label.font_size = 72
|
||||
marquee_label.font_size = 160
|
||||
marquee_label.pixel_size = 0.004
|
||||
marquee_label.outline_size = 12
|
||||
marquee_label.outline_size = 24
|
||||
marquee_label.outline_modulate = Color(0.9, 0.15, 0.5)
|
||||
marquee_label.billboard = BaseMaterial3D.BILLBOARD_ENABLED
|
||||
marquee_label.no_depth_test = false
|
||||
@@ -264,6 +305,11 @@ func _plain_material(color: Color) -> StandardMaterial3D:
|
||||
func _build_guard_spawn() -> void:
|
||||
var marker := Marker3D.new()
|
||||
marker.name = "GuardSpawn"
|
||||
# Guard spawns on the landing at the top of the steps, facing the street.
|
||||
# The landing is at y=4*height+0.1 where height=0.16, so y=0.76
|
||||
# The guard's head offset is 1.6m, so spawn at y=0.76-1.6=-0.84 to place feet on ground
|
||||
# But we want the guard at eye-level with the sidewalk, so adjust to y=0.1 for feet
|
||||
# Then the head will be at y=1.7 (1.6 offset + 0.1 base)
|
||||
marker.position = Vector3(0, 0.1, -2.0)
|
||||
root.add_child(marker)
|
||||
|
||||
|
||||
Reference in new issue
Block a user