Files
client/spacewar/menu/main_menu.gd
T
anekdotin 0c2628a1a6 Reorganize project into autoload/, menu/, ships/, world/, assets/
Move flat top-level files into topic folders and rewrite every
res:// path reference (scenes, scripts, project.godot) accordingly.
Rename node_2d.tscn -> ships/ship.tscn and lowercase
images/ships/Example_ships -> assets/images/ships/example_ships
for naming consistency. Update overview/structure.md to match.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 20:28:15 -04:00

339 lines
11 KiB
GDScript

extends Control
var _name_input: LineEdit
var _casual_btn: Button
var _ranked_btn: Button
const RANK_DATA := [
{"label": "CADET", "color": Color(0.72, 0.48, 0.22)},
{"label": "PILOT", "color": Color(0.78, 0.78, 0.82)},
{"label": "ACE", "color": Color(1.0, 0.82, 0.22)},
{"label": "COMMANDER", "color": Color(0.35, 0.88, 1.0)},
{"label": "ADMIRAL", "color": Color(0.62, 0.28, 1.0)},
{"label": "LEGEND", "color": Color(1.0, 0.28, 0.10)},
]
const CURRENT_RANK := 0 # placeholder until backend exists
func _ready() -> void:
GameConfig.player_race = 0
GameConfig.player_ship_path = ""
_build_ui()
func _build_ui() -> void:
_add_bg()
_add_title()
_add_play_section()
_add_rank_badge()
_add_quit_btn()
_add_version_label()
func _add_bg() -> void:
var bg := TextureRect.new()
bg.texture = load("res://assets/images/background/skybox/2.png")
bg.expand_mode = TextureRect.EXPAND_FIT_WIDTH_PROPORTIONAL
bg.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_COVERED
bg.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
add_child(bg)
var ov := ColorRect.new()
ov.color = Color(0.01, 0.02, 0.09, 0.80)
ov.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
add_child(ov)
func _add_title() -> void:
var title := Label.new()
title.text = "S P A C E W A R"
title.set_anchors_preset(Control.PRESET_TOP_WIDE)
title.offset_top = 72.0
title.offset_bottom = 148.0
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
title.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
title.add_theme_font_size_override("font_size", 56)
title.add_theme_color_override("font_color", Color(0.45, 0.88, 1.0))
add_child(title)
var sub := Label.new()
sub.text = "FIGHT FOR THE GALAXY"
sub.set_anchors_preset(Control.PRESET_TOP_WIDE)
sub.offset_top = 152.0
sub.offset_bottom = 178.0
sub.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
sub.add_theme_font_size_override("font_size", 13)
sub.add_theme_color_override("font_color", Color(0.42, 0.54, 0.70))
add_child(sub)
func _add_play_section() -> void:
const BTN_W := 480.0
const BTN_H := 230.0
const GAP := 40.0
const LEFT_X := (1280.0 - (BTN_W * 2.0 + GAP)) / 2.0 # 140
const TOP_Y := 220.0
_casual_btn = _make_mode_btn(
LEFT_X, TOP_Y, BTN_W, BTN_H,
"CASUAL",
"25 vs 25",
"Drop in, drop out anytime.\nNo rank at stake.",
Color(0.06, 0.22, 0.08),
Color(0.30, 0.90, 0.40),
)
_casual_btn.pressed.connect(_on_play_casual)
add_child(_casual_btn)
_ranked_btn = _make_mode_btn(
LEFT_X + BTN_W + GAP, TOP_Y, BTN_W, BTN_H,
"RANKED",
"5 vs 5",
"Competitive ladder.\nYour rank is on the line.",
Color(0.06, 0.10, 0.26),
Color(0.40, 0.72, 1.00),
)
_ranked_btn.disabled = true
_ranked_btn.pressed.connect(_on_play_ranked)
add_child(_ranked_btn)
var coming_soon := Label.new()
coming_soon.text = "— COMING SOON —"
coming_soon.set_anchor_and_offset(SIDE_LEFT, 0.0, LEFT_X + BTN_W + GAP)
coming_soon.set_anchor_and_offset(SIDE_TOP, 0.0, TOP_Y + BTN_H - 36.0)
coming_soon.set_anchor_and_offset(SIDE_RIGHT, 0.0, LEFT_X + BTN_W * 2.0 + GAP)
coming_soon.set_anchor_and_offset(SIDE_BOTTOM, 0.0, TOP_Y + BTN_H - 8.0)
coming_soon.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
coming_soon.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
coming_soon.add_theme_font_size_override("font_size", 11)
coming_soon.add_theme_color_override("font_color", Color(0.30, 0.36, 0.48, 0.70))
coming_soon.mouse_filter = Control.MOUSE_FILTER_IGNORE
add_child(coming_soon)
# Callsign label
var lbl := Label.new()
lbl.text = "CALLSIGN"
lbl.set_anchor_and_offset(SIDE_LEFT, 0.0, LEFT_X)
lbl.set_anchor_and_offset(SIDE_TOP, 0.0, TOP_Y + BTN_H + 26.0)
lbl.set_anchor_and_offset(SIDE_RIGHT, 0.0, LEFT_X + 95.0)
lbl.set_anchor_and_offset(SIDE_BOTTOM, 0.0, TOP_Y + BTN_H + 62.0)
lbl.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
lbl.add_theme_font_size_override("font_size", 12)
lbl.add_theme_color_override("font_color", Color(0.45, 0.55, 0.70))
add_child(lbl)
_name_input = LineEdit.new()
_name_input.placeholder_text = "Enter your callsign..."
_name_input.set_anchor_and_offset(SIDE_LEFT, 0.0, LEFT_X + 100.0)
_name_input.set_anchor_and_offset(SIDE_TOP, 0.0, TOP_Y + BTN_H + 26.0)
_name_input.set_anchor_and_offset(SIDE_RIGHT, 0.0, LEFT_X + BTN_W * 2.0 + GAP)
_name_input.set_anchor_and_offset(SIDE_BOTTOM, 0.0, TOP_Y + BTN_H + 62.0)
_name_input.add_theme_font_size_override("font_size", 15)
if GameConfig.player_name != "":
_name_input.text = GameConfig.player_name
_name_input.text_changed.connect(_on_name_changed)
add_child(_name_input)
_update_play_buttons()
func _make_mode_btn(
x: float, y: float, w: float, h: float,
mode: String, fmt: String, desc: String,
bg: Color, accent: Color) -> Button:
var btn := Button.new()
btn.text = ""
btn.set_anchor_and_offset(SIDE_LEFT, 0.0, x)
btn.set_anchor_and_offset(SIDE_TOP, 0.0, y)
btn.set_anchor_and_offset(SIDE_RIGHT, 0.0, x + w)
btn.set_anchor_and_offset(SIDE_BOTTOM, 0.0, y + h)
var sn := _flat(bg.lightened(0.05), 12)
sn.set_border_width_all(2)
sn.border_color = accent.darkened(0.45)
var sh := _flat(bg.lightened(0.18), 12)
sh.set_border_width_all(2)
sh.border_color = accent
var sd := _flat(Color(0.04, 0.04, 0.06, 0.5), 12)
sd.set_border_width_all(1)
sd.border_color = Color(0.2, 0.22, 0.28, 0.5)
btn.add_theme_stylebox_override("normal", sn)
btn.add_theme_stylebox_override("hover", sh)
btn.add_theme_stylebox_override("pressed", sh)
btn.add_theme_stylebox_override("disabled", sd)
var vb := VBoxContainer.new()
vb.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
vb.offset_left = 24.0
vb.offset_top = 20.0
vb.offset_right = -24.0
vb.offset_bottom = -20.0
vb.alignment = BoxContainer.ALIGNMENT_CENTER
vb.add_theme_constant_override("separation", 12)
vb.mouse_filter = Control.MOUSE_FILTER_IGNORE
btn.add_child(vb)
var mode_lbl := Label.new()
mode_lbl.text = mode
mode_lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
mode_lbl.add_theme_font_size_override("font_size", 40)
mode_lbl.add_theme_color_override("font_color", accent)
mode_lbl.mouse_filter = Control.MOUSE_FILTER_IGNORE
vb.add_child(mode_lbl)
var fmt_lbl := Label.new()
fmt_lbl.text = fmt
fmt_lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
fmt_lbl.add_theme_font_size_override("font_size", 22)
fmt_lbl.add_theme_color_override("font_color", Color(0.85, 0.90, 0.96))
fmt_lbl.mouse_filter = Control.MOUSE_FILTER_IGNORE
vb.add_child(fmt_lbl)
var sep := HSeparator.new()
sep.add_theme_color_override("color", accent.darkened(0.5))
sep.mouse_filter = Control.MOUSE_FILTER_IGNORE
vb.add_child(sep)
var desc_lbl := Label.new()
desc_lbl.text = desc
desc_lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
desc_lbl.add_theme_font_size_override("font_size", 13)
desc_lbl.add_theme_color_override("font_color", Color(0.55, 0.63, 0.76))
desc_lbl.autowrap_mode = TextServer.AUTOWRAP_WORD
desc_lbl.mouse_filter = Control.MOUSE_FILTER_IGNORE
vb.add_child(desc_lbl)
return btn
func _add_rank_badge() -> void:
const W := 210.0
const H := 60.0
const MARGIN := 16.0
var panel := Panel.new()
panel.set_anchors_preset(Control.PRESET_TOP_RIGHT)
panel.offset_left = -(W + MARGIN)
panel.offset_top = MARGIN
panel.offset_right = -MARGIN
panel.offset_bottom = MARGIN + H
var ps := _flat(Color(0.04, 0.07, 0.16, 0.92), 8)
ps.set_border_width_all(1)
ps.border_color = Color(0.18, 0.36, 0.62, 0.85)
panel.add_theme_stylebox_override("panel", ps)
add_child(panel)
var rank := RANK_DATA[CURRENT_RANK]
# Colored badge block on the left
var badge := Panel.new()
badge.set_anchor_and_offset(SIDE_LEFT, 0.0, 8.0)
badge.set_anchor_and_offset(SIDE_TOP, 0.0, 8.0)
badge.set_anchor_and_offset(SIDE_RIGHT, 0.0, 50.0)
badge.set_anchor_and_offset(SIDE_BOTTOM, 1.0, -8.0)
var bs := _flat(rank.color.darkened(0.45), 5)
bs.set_border_width_all(2)
bs.border_color = rank.color
badge.add_theme_stylebox_override("panel", bs)
panel.add_child(badge)
var init_lbl := Label.new()
init_lbl.text = rank.label.left(1)
init_lbl.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
init_lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
init_lbl.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
init_lbl.add_theme_font_size_override("font_size", 20)
init_lbl.add_theme_color_override("font_color", rank.color)
badge.add_child(init_lbl)
# Rank name
var rank_lbl := Label.new()
rank_lbl.text = rank.label
rank_lbl.set_anchor_and_offset(SIDE_LEFT, 0.0, 58.0)
rank_lbl.set_anchor_and_offset(SIDE_TOP, 0.0, 0.0)
rank_lbl.set_anchor_and_offset(SIDE_RIGHT, 1.0, -8.0)
rank_lbl.set_anchor_and_offset(SIDE_BOTTOM, 0.0, H / 2.0 + 2.0)
rank_lbl.vertical_alignment = VERTICAL_ALIGNMENT_BOTTOM
rank_lbl.add_theme_font_size_override("font_size", 15)
rank_lbl.add_theme_color_override("font_color", rank.color)
panel.add_child(rank_lbl)
var rating_lbl := Label.new()
rating_lbl.text = "Rating: —"
rating_lbl.set_anchor_and_offset(SIDE_LEFT, 0.0, 58.0)
rating_lbl.set_anchor_and_offset(SIDE_TOP, 0.0, H / 2.0)
rating_lbl.set_anchor_and_offset(SIDE_RIGHT, 1.0, -8.0)
rating_lbl.set_anchor_and_offset(SIDE_BOTTOM, 1.0, -6.0)
rating_lbl.vertical_alignment = VERTICAL_ALIGNMENT_TOP
rating_lbl.add_theme_font_size_override("font_size", 11)
rating_lbl.add_theme_color_override("font_color", Color(0.45, 0.54, 0.68))
panel.add_child(rating_lbl)
func _add_quit_btn() -> void:
const W := 110.0
const H := 38.0
const MARGIN := 20.0
var btn := Button.new()
btn.text = "QUIT"
btn.set_anchors_preset(Control.PRESET_BOTTOM_RIGHT)
btn.offset_left = -(W + MARGIN)
btn.offset_top = -(H + MARGIN)
btn.offset_right = -MARGIN
btn.offset_bottom = -MARGIN
btn.add_theme_font_size_override("font_size", 13)
btn.add_theme_color_override("font_color", Color(0.48, 0.54, 0.65))
var sn := _flat(Color(0.05, 0.06, 0.11, 0.80), 6)
sn.set_border_width_all(1)
sn.border_color = Color(0.18, 0.22, 0.33, 0.70)
var sh := _flat(Color(0.10, 0.12, 0.20, 0.90), 6)
sh.set_border_width_all(1)
sh.border_color = Color(0.32, 0.38, 0.52)
btn.add_theme_stylebox_override("normal", sn)
btn.add_theme_stylebox_override("hover", sh)
btn.pressed.connect(func(): get_tree().quit())
add_child(btn)
func _add_version_label() -> void:
var lbl := Label.new()
lbl.text = "v0.1-dev"
lbl.set_anchor_and_offset(SIDE_LEFT, 0.0, 14.0)
lbl.set_anchor_and_offset(SIDE_TOP, 1.0, -30.0)
lbl.set_anchor_and_offset(SIDE_RIGHT, 0.0, 100.0)
lbl.set_anchor_and_offset(SIDE_BOTTOM, 1.0, -10.0)
lbl.add_theme_font_size_override("font_size", 11)
lbl.add_theme_color_override("font_color", Color(0.3, 0.35, 0.45))
add_child(lbl)
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_name_changed(_text: String) -> void:
_update_play_buttons()
func _update_play_buttons() -> void:
var ok := not _name_input.text.strip_edges().is_empty()
_casual_btn.disabled = not ok
_ranked_btn.disabled = true
func _on_play_casual() -> void:
GameConfig.player_name = _name_input.text.strip_edges()
get_tree().change_scene_to_file("res://world/world.tscn")
func _on_play_ranked() -> void:
GameConfig.player_name = _name_input.text.strip_edges()
GameConfig.player_race = 1 # TODO: race selection screen
get_tree().change_scene_to_file("res://world/world.tscn")