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
@@ -0,0 +1,140 @@
|
||||
class_name StartMenu
|
||||
extends CanvasLayer
|
||||
## Startup menu: pick a venue, start the shift, open settings, or quit.
|
||||
## Keyboard and mouse both work. Replaces `V` as the player-facing venue path
|
||||
## (the `V` bind stays as a debug shortcut).
|
||||
|
||||
signal venue_selected(scenario_id: String)
|
||||
signal settings_requested()
|
||||
signal quit_requested()
|
||||
|
||||
const VENUES := [
|
||||
{"id": "hollywood_building", "label": "Hollywood Boulevard", "blurb": "Building entrance, tourists and late-night trouble."},
|
||||
{"id": "joke_shop", "label": "The Joke Shop", "blurb": "Guest list, IDs and wand screening."},
|
||||
{"id": "tv_studio", "label": "Late Night TV Studio", "blurb": "Credentials, filming boundary, celebrity arrivals."},
|
||||
]
|
||||
|
||||
var _index := 0
|
||||
var _buttons: Array[Button] = []
|
||||
var _root: Control = null
|
||||
|
||||
func _ready() -> void:
|
||||
layer = 20
|
||||
_build()
|
||||
hide()
|
||||
|
||||
func _build() -> void:
|
||||
_root = Control.new()
|
||||
_root.name = "StartMenuRoot"
|
||||
_root.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
_root.mouse_filter = Control.MOUSE_FILTER_STOP
|
||||
add_child(_root)
|
||||
|
||||
var bg := ColorRect.new()
|
||||
bg.color = Color(0.03, 0.04, 0.07, 0.96)
|
||||
bg.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
bg.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
_root.add_child(bg)
|
||||
|
||||
var center := CenterContainer.new()
|
||||
center.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
_root.add_child(center)
|
||||
|
||||
var column := VBoxContainer.new()
|
||||
column.name = "Column"
|
||||
column.custom_minimum_size = Vector2(460, 0)
|
||||
column.add_theme_constant_override("separation", 14)
|
||||
center.add_child(column)
|
||||
|
||||
var title := Label.new()
|
||||
title.text = "STEP LINE"
|
||||
title.add_theme_font_size_override("font_size", 44)
|
||||
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
column.add_child(title)
|
||||
|
||||
var subtitle := Label.new()
|
||||
subtitle.text = "Pick a venue to start your shift."
|
||||
subtitle.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
subtitle.add_theme_color_override("font_color", Color(0.72, 0.75, 0.82))
|
||||
column.add_child(subtitle)
|
||||
|
||||
for i in VENUES.size():
|
||||
var venue: Dictionary = VENUES[i]
|
||||
var button := Button.new()
|
||||
button.name = "Venue_%s" % venue["id"]
|
||||
button.text = "%s\n%s" % [venue["label"], venue["blurb"]]
|
||||
button.alignment = HORIZONTAL_ALIGNMENT_LEFT
|
||||
button.custom_minimum_size = Vector2(460, 62)
|
||||
button.pressed.connect(_on_venue_pressed.bind(i))
|
||||
column.add_child(button)
|
||||
_buttons.append(button)
|
||||
|
||||
var spacer := Control.new()
|
||||
spacer.custom_minimum_size = Vector2(0, 10)
|
||||
column.add_child(spacer)
|
||||
|
||||
var row := HBoxContainer.new()
|
||||
row.alignment = BoxContainer.ALIGNMENT_CENTER
|
||||
row.add_theme_constant_override("separation", 12)
|
||||
column.add_child(row)
|
||||
|
||||
var settings_button := Button.new()
|
||||
settings_button.name = "SettingsButton"
|
||||
settings_button.text = "Settings"
|
||||
settings_button.pressed.connect(func(): settings_requested.emit())
|
||||
row.add_child(settings_button)
|
||||
|
||||
var quit_button := Button.new()
|
||||
quit_button.name = "QuitButton"
|
||||
quit_button.text = "Quit"
|
||||
quit_button.pressed.connect(func(): quit_requested.emit())
|
||||
row.add_child(quit_button)
|
||||
|
||||
var hint := Label.new()
|
||||
hint.text = "Up/Down to choose, Enter to start, Esc to quit"
|
||||
hint.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
hint.add_theme_color_override("font_color", Color(0.55, 0.58, 0.65))
|
||||
column.add_child(hint)
|
||||
|
||||
_focus_index(0)
|
||||
|
||||
func _focus_index(i: int) -> void:
|
||||
if _buttons.is_empty():
|
||||
return
|
||||
_index = clampi(i, 0, _buttons.size() - 1)
|
||||
_buttons[_index].grab_focus()
|
||||
|
||||
func open() -> void:
|
||||
visible = true
|
||||
_root.visible = true
|
||||
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
|
||||
_focus_index(_index)
|
||||
|
||||
func close() -> void:
|
||||
visible = false
|
||||
_root.visible = false
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if not visible:
|
||||
return
|
||||
if event is InputEventKey and event.pressed and not event.echo:
|
||||
match event.keycode:
|
||||
KEY_UP, KEY_W:
|
||||
_focus_index(_index - 1)
|
||||
get_viewport().set_input_as_handled()
|
||||
KEY_DOWN, KEY_S:
|
||||
_focus_index(_index + 1)
|
||||
get_viewport().set_input_as_handled()
|
||||
KEY_ENTER, KEY_KP_ENTER, KEY_SPACE:
|
||||
_on_venue_pressed(_index)
|
||||
get_viewport().set_input_as_handled()
|
||||
KEY_ESCAPE:
|
||||
quit_requested.emit()
|
||||
get_viewport().set_input_as_handled()
|
||||
|
||||
func _on_venue_pressed(i: int) -> void:
|
||||
if i < 0 or i >= VENUES.size():
|
||||
return
|
||||
var venue: Dictionary = VENUES[i]
|
||||
close()
|
||||
venue_selected.emit(str(venue["id"]))
|
||||
Reference in new issue
Block a user