0c2628a1a6
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>
339 lines
11 KiB
GDScript
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")
|