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,154 @@
|
||||
class_name SettingsPanel
|
||||
extends CanvasLayer
|
||||
## Settings: mouse look sensitivity and HUD font scale, persisted through the
|
||||
## `Save` autoload into `user://settings.json`.
|
||||
##
|
||||
## Emits `applied(settings)` so the game manager can push values into the guard
|
||||
## and HUD as soon as the player confirms.
|
||||
|
||||
signal applied(settings: Dictionary)
|
||||
signal closed()
|
||||
|
||||
var _root: Control = null
|
||||
var _sensitivity: HSlider = null
|
||||
var _font_scale: HSlider = null
|
||||
var _sensitivity_value: Label = null
|
||||
var _font_scale_value: Label = null
|
||||
var settings: Dictionary = {}
|
||||
|
||||
func _ready() -> void:
|
||||
layer = 22
|
||||
_build()
|
||||
hide()
|
||||
|
||||
func _init() -> void:
|
||||
layer = 22
|
||||
_build()
|
||||
hide()
|
||||
|
||||
func _build() -> void:
|
||||
_root = Control.new()
|
||||
_root.name = "SettingsRoot"
|
||||
_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.02, 0.03, 0.05, 0.9)
|
||||
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.custom_minimum_size = Vector2(420, 0)
|
||||
column.add_theme_constant_override("separation", 12)
|
||||
center.add_child(column)
|
||||
|
||||
var title := Label.new()
|
||||
title.text = "Settings"
|
||||
title.add_theme_font_size_override("font_size", 32)
|
||||
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
column.add_child(title)
|
||||
|
||||
var sens_row := VBoxContainer.new()
|
||||
column.add_child(sens_row)
|
||||
_sensitivity_value = _add_label_row(sens_row, "Mouse look sensitivity")
|
||||
_sensitivity = _add_slider(sens_row, 0.0005, 0.0060, 0.0001)
|
||||
_sensitivity.value_changed.connect(_on_sensitivity_changed)
|
||||
|
||||
var font_row := VBoxContainer.new()
|
||||
column.add_child(font_row)
|
||||
_font_scale_value = _add_label_row(font_row, "HUD font scale")
|
||||
_font_scale = _add_slider(font_row, 0.75, 1.75, 0.05)
|
||||
_font_scale.value_changed.connect(_on_font_scale_changed)
|
||||
|
||||
var row := HBoxContainer.new()
|
||||
row.alignment = BoxContainer.ALIGNMENT_CENTER
|
||||
row.add_theme_constant_override("separation", 12)
|
||||
column.add_child(row)
|
||||
|
||||
var save_button := Button.new()
|
||||
save_button.name = "ApplyButton"
|
||||
save_button.text = "Apply"
|
||||
save_button.pressed.connect(_on_apply)
|
||||
row.add_child(save_button)
|
||||
|
||||
var cancel_button := Button.new()
|
||||
cancel_button.name = "CancelButton"
|
||||
cancel_button.text = "Cancel"
|
||||
cancel_button.pressed.connect(_on_cancel)
|
||||
row.add_child(cancel_button)
|
||||
|
||||
func _add_label_row(parent: Control, text: String) -> Label:
|
||||
var row := HBoxContainer.new()
|
||||
parent.add_child(row)
|
||||
var label := Label.new()
|
||||
label.text = text
|
||||
label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
row.add_child(label)
|
||||
var value := Label.new()
|
||||
value.add_theme_color_override("font_color", Color(0.75, 0.8, 0.9))
|
||||
row.add_child(value)
|
||||
return value
|
||||
|
||||
func _add_slider(parent: Control, min_v: float, max_v: float, step: float) -> HSlider:
|
||||
var slider := HSlider.new()
|
||||
slider.min_value = min_v
|
||||
slider.max_value = max_v
|
||||
slider.step = step
|
||||
slider.custom_minimum_size = Vector2(420, 24)
|
||||
parent.add_child(slider)
|
||||
return slider
|
||||
|
||||
func open(current: Dictionary) -> void:
|
||||
settings = current.duplicate(true)
|
||||
var sens: float = float(settings.get("look_sensitivity", 0.0022))
|
||||
var scale: float = float(settings.get("hud_font_scale", 1.0))
|
||||
if _sensitivity:
|
||||
_sensitivity.set_value_no_signal(clampf(sens, _sensitivity.min_value, _sensitivity.max_value))
|
||||
if _font_scale:
|
||||
_font_scale.set_value_no_signal(clampf(scale, _font_scale.min_value, _font_scale.max_value))
|
||||
_sync_labels()
|
||||
visible = true
|
||||
_root.visible = true
|
||||
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
|
||||
|
||||
func close() -> void:
|
||||
visible = false
|
||||
_root.visible = false
|
||||
|
||||
func _sync_labels() -> void:
|
||||
if _sensitivity_value and _sensitivity:
|
||||
_sensitivity_value.text = "%.4f" % _sensitivity.value
|
||||
if _font_scale_value and _font_scale:
|
||||
_font_scale_value.text = "%.2fx" % _font_scale.value
|
||||
|
||||
func _on_sensitivity_changed(value: float) -> void:
|
||||
settings["look_sensitivity"] = value
|
||||
_sync_labels()
|
||||
|
||||
func _on_font_scale_changed(value: float) -> void:
|
||||
settings["hud_font_scale"] = value
|
||||
_sync_labels()
|
||||
|
||||
func _on_apply() -> void:
|
||||
if Save:
|
||||
Save.save_settings(settings)
|
||||
applied.emit(settings)
|
||||
close()
|
||||
closed.emit()
|
||||
|
||||
func _on_cancel() -> void:
|
||||
close()
|
||||
closed.emit()
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if not visible:
|
||||
return
|
||||
if event is InputEventKey and event.pressed and not event.echo and event.keycode == KEY_ESCAPE:
|
||||
_on_cancel()
|
||||
get_viewport().set_input_as_handled()
|
||||
Reference in new issue
Block a user