a94bccc57b
Capital ships & support art: - New Flagship capital ships (world/flagship.gd) spawn a small point-defense formation at each team's spawn marker, independently targeting the nearest enemy in range with a slow unmissable-looking missile and a faster bullet stream -- keeps a team from parking on the enemy spawn and farming respawns. - Per-race/role bullet sprites for turret fire (assets/images/effects/ bullets/), flagship art for both factions, minimap now draws flagship blips, bot_ai awareness tuned to notice them. Settings screen (items 20-21 in CLAUDE.md) -- was a stub, now five real sections shared between the main menu and the in-game pause menu: - Diagnosed and fixed a "movement looks blurry/laggy" report down to three independent causes: physics-tick vs. display-refresh judder (fixed via Godot's built-in physics interpolation, plus a frame-rate-cap + VSync dropdown so each player can match their own monitor), missing mipmaps on every minified ship texture (real GPU sampling shimmer, unrelated to frame timing), and a periodic hitch from the matchmaking heartbeat spinning up a new HTTPRequest thread every 8 seconds instead of reusing one. - Nameplates get their own manual per-frame interpolation, since Godot's physics interpolation only covers Node2D/Node3D, not the Control-based Label they're built from. - Audio: Master/Music/SFX volume sliders backed by a real bus layout (default_bus_layout.tres) -- the project had no volume control at all before this. - Window mode (Fullscreen/Exclusive Fullscreen/Windowed), a colorblind-friendly enemy-color toggle (also fixes the minimap's own separate, inconsistent yellow enemy color), and keyboard rebinding for every action with a live capture UI. - New GameConfig.settings_focused flag (same pattern as chat_focused/ team_select_focused) so a key-rebind capture can't also move the ship or toggle the pause menu underneath the panel. Spawn intro: a ship's first-ever appearance now eases in from a random direction (fast off the start, decelerating into its landing spot) instead of popping into place -- purely a cosmetic sprite offset, so collision/camera/networking are untouched and every peer (including bots) sees the same warp-in. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
144 lines
3.9 KiB
GDScript
144 lines
3.9 KiB
GDScript
extends CanvasLayer
|
||
|
||
var _root: Control
|
||
|
||
|
||
func _ready() -> void:
|
||
layer = 10
|
||
_build_ui()
|
||
_root.visible = false
|
||
|
||
|
||
func _input(event: InputEvent) -> void:
|
||
if event.is_action_pressed("toggle_pause") and not GameConfig.chat_focused \
|
||
and not GameConfig.team_select_focused and not GameConfig.settings_focused:
|
||
_toggle()
|
||
get_viewport().set_input_as_handled()
|
||
|
||
|
||
func is_paused() -> bool:
|
||
return _root.visible
|
||
|
||
|
||
func _toggle() -> void:
|
||
_root.visible = not _root.visible
|
||
|
||
|
||
func _build_ui() -> void:
|
||
_root = Control.new()
|
||
_root.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||
add_child(_root)
|
||
|
||
# Dim overlay — blocks mouse clicks reaching the game
|
||
var overlay := ColorRect.new()
|
||
overlay.color = Color(0.0, 0.0, 0.0, 0.58)
|
||
overlay.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||
overlay.mouse_filter = Control.MOUSE_FILTER_STOP
|
||
_root.add_child(overlay)
|
||
|
||
# Center panel (360 × 490)
|
||
var panel := Panel.new()
|
||
panel.set_anchors_preset(Control.PRESET_CENTER)
|
||
panel.offset_left = -180
|
||
panel.offset_top = -245
|
||
panel.offset_right = 180
|
||
panel.offset_bottom = 245
|
||
|
||
var ps := StyleBoxFlat.new()
|
||
ps.bg_color = Color(0.04, 0.07, 0.16, 0.97)
|
||
ps.set_corner_radius_all(10)
|
||
ps.set_border_width_all(1)
|
||
ps.border_color = Color(0.18, 0.38, 0.65, 0.9)
|
||
panel.add_theme_stylebox_override("panel", ps)
|
||
_root.add_child(panel)
|
||
|
||
var vb := VBoxContainer.new()
|
||
vb.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||
vb.offset_left = 28
|
||
vb.offset_top = 28
|
||
vb.offset_right = -28
|
||
vb.offset_bottom = -28
|
||
vb.add_theme_constant_override("separation", 10)
|
||
panel.add_child(vb)
|
||
|
||
var title := Label.new()
|
||
title.text = "MENU"
|
||
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||
title.add_theme_font_size_override("font_size", 26)
|
||
title.add_theme_color_override("font_color", Color(0.4, 0.85, 1.0))
|
||
vb.add_child(title)
|
||
|
||
vb.add_child(_hsep())
|
||
|
||
var gap := Control.new()
|
||
gap.custom_minimum_size = Vector2(0, 4)
|
||
vb.add_child(gap)
|
||
|
||
_add_btn(vb, "RESUME", true).pressed.connect(_toggle)
|
||
_add_btn(vb, "SETTINGS", true).pressed.connect(_on_settings)
|
||
_add_btn(vb, "SELECT TEAM", true).pressed.connect(_on_select_team)
|
||
|
||
vb.add_child(_hsep())
|
||
|
||
_add_btn(vb, "QUIT TO MENU", true).pressed.connect(_on_quit_to_menu)
|
||
_add_btn(vb, "QUIT TO DESKTOP", true).pressed.connect(_on_quit_to_desktop)
|
||
|
||
|
||
func _add_btn(parent: VBoxContainer, label_text: String, active: bool) -> Button:
|
||
var btn := Button.new()
|
||
btn.text = label_text
|
||
btn.custom_minimum_size = Vector2(0, 54)
|
||
btn.add_theme_font_size_override("font_size", 16)
|
||
|
||
if active:
|
||
btn.add_theme_color_override("font_color", Color(0.92, 0.95, 1.0))
|
||
var sn := _flat(Color(0.07, 0.13, 0.25, 1.0), 6)
|
||
var sh := _flat(Color(0.13, 0.24, 0.42, 1.0), 6)
|
||
sh.set_border_width_all(1)
|
||
sh.border_color = Color(0.3, 0.65, 1.0)
|
||
btn.add_theme_stylebox_override("normal", sn)
|
||
btn.add_theme_stylebox_override("hover", sh)
|
||
else:
|
||
btn.add_theme_color_override("font_color", Color(0.35, 0.40, 0.50))
|
||
var sn := _flat(Color(0.04, 0.06, 0.10, 1.0), 6)
|
||
btn.add_theme_stylebox_override("normal", sn)
|
||
btn.add_theme_stylebox_override("hover", sn)
|
||
|
||
parent.add_child(btn)
|
||
return btn
|
||
|
||
|
||
func _hsep() -> HSeparator:
|
||
var sep := HSeparator.new()
|
||
sep.add_theme_color_override("color", Color(0.18, 0.28, 0.45, 0.7))
|
||
return sep
|
||
|
||
|
||
func _flat(col: Color, radius: int = 0) -> StyleBoxFlat:
|
||
var s := StyleBoxFlat.new()
|
||
s.bg_color = col
|
||
s.set_corner_radius_all(radius)
|
||
return s
|
||
|
||
|
||
func _on_settings() -> void:
|
||
var settings := get_node_or_null("/root/World/SettingsPanel") as SettingsPanel
|
||
if settings:
|
||
settings.open()
|
||
|
||
|
||
func _on_select_team() -> void:
|
||
_root.visible = false
|
||
var team_select := get_node_or_null("/root/World/TeamSelect") as TeamSelect
|
||
if team_select:
|
||
team_select.reopen()
|
||
|
||
|
||
func _on_quit_to_menu() -> void:
|
||
NetworkManager.disconnect_from_game()
|
||
get_tree().change_scene_to_file("res://menu/main_menu.tscn")
|
||
|
||
|
||
func _on_quit_to_desktop() -> void:
|
||
get_tree().quit()
|