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>
This commit is contained in:
@@ -0,0 +1,246 @@
|
||||
extends CanvasLayer
|
||||
|
||||
const RACES := [
|
||||
{"id": 1, "name": "TERRAN FEDERATION", "sub": "Speed and balanced firepower", "color": Color(0.30, 0.55, 1.00)},
|
||||
{"id": 2, "name": "THE COLLECTIVE", "sub": "Heavy armor, brute force", "color": Color(1.00, 0.45, 0.18)},
|
||||
{"id": 3, "name": "IRON ORDER", "sub": "Precision strike force", "color": Color(0.28, 0.90, 0.42)},
|
||||
]
|
||||
|
||||
const SHIPS := [
|
||||
{
|
||||
"name": "WARBIRD",
|
||||
"role": "All-Round Fighter",
|
||||
"desc": "The most versatile ship in the fleet. Solid speed, reliable firepower, and enough armor to stay in the fight. Forgiving to fly and effective in any situation — the go-to choice when you're not sure what you're up against.",
|
||||
"path": "res://assets/images/ships/example_ships/1.png",
|
||||
},
|
||||
{
|
||||
"name": "JAVELIN",
|
||||
"role": "Fast Interceptor",
|
||||
"desc": "Built for speed above everything else. Light armor means you have to pick your fights carefully, but nothing can outrun you. Best used for hit-and-run strikes, hunting weakened targets, and escaping when things go wrong.",
|
||||
"path": "res://assets/images/ships/example_ships/1B.png",
|
||||
},
|
||||
{
|
||||
"name": "SPIDER",
|
||||
"role": "Heavy Support",
|
||||
"desc": "Slow but extremely durable. High-volume fire makes it devastating in sustained engagements. Anchors a team by holding zones and suppressing enemy movements. Stay near allies — alone you're a target, together you're a wall.",
|
||||
"path": "res://assets/images/ships/example_ships/2a.png",
|
||||
},
|
||||
{
|
||||
"name": "LANCASTER",
|
||||
"role": "Stealth Bomber",
|
||||
"desc": "Medium speed with high burst damage potential. Built for patience — get into position, strike hard, and fade before the enemy can respond. Excels at flanking and ambushes. Requires discipline to play but punishes mistakes severely.",
|
||||
"path": "res://assets/images/ships/example_ships/3b.png",
|
||||
},
|
||||
]
|
||||
|
||||
# Row layout constants (screen is 1280×800)
|
||||
const ROW_X1 := 120.0
|
||||
const ROW_X2 := 1160.0
|
||||
const ROW_H := 130.0
|
||||
const ROW_GAP := 10.0
|
||||
const ROW_START := 195.0
|
||||
const IMG_W := 130.0
|
||||
|
||||
var _root: Control
|
||||
var _content: Control
|
||||
var _offered: Array = []
|
||||
var _chosen_race: Dictionary = {}
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
layer = 20
|
||||
_offered = _pick_two_races()
|
||||
_build_root()
|
||||
_show_race_selection()
|
||||
|
||||
|
||||
func _pick_two_races() -> Array:
|
||||
var pool := RACES.duplicate()
|
||||
pool.shuffle()
|
||||
return [pool[0], pool[1]]
|
||||
|
||||
|
||||
func _build_root() -> void:
|
||||
_root = Control.new()
|
||||
_root.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
add_child(_root)
|
||||
|
||||
var overlay := ColorRect.new()
|
||||
overlay.color = Color(0.0, 0.0, 0.0, 0.65)
|
||||
overlay.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
overlay.mouse_filter = Control.MOUSE_FILTER_STOP
|
||||
_root.add_child(overlay)
|
||||
|
||||
|
||||
func _swap_content() -> Control:
|
||||
if _content:
|
||||
_content.queue_free()
|
||||
_content = Control.new()
|
||||
_content.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
_content.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
_root.add_child(_content)
|
||||
return _content
|
||||
|
||||
|
||||
# ── Race selection ───────────────────────────────────────────────────────────
|
||||
|
||||
func _show_race_selection() -> void:
|
||||
var c := _swap_content()
|
||||
|
||||
_label(c, "CHOOSE YOUR FACTION", 32, Color(0.42, 0.87, 1.0),
|
||||
0, 105, 1280, 160, HORIZONTAL_ALIGNMENT_CENTER)
|
||||
_label(c, "This match: %s vs %s" % [_offered[0].name, _offered[1].name],
|
||||
13, Color(0.45, 0.58, 0.72),
|
||||
0, 170, 1280, 200, HORIZONTAL_ALIGNMENT_CENTER)
|
||||
|
||||
var left_btn := _make_race_btn(_offered[0])
|
||||
_place(left_btn, 150, 225, 590, 540)
|
||||
left_btn.pressed.connect(_on_race_chosen.bind(_offered[0]))
|
||||
c.add_child(left_btn)
|
||||
|
||||
var right_btn := _make_race_btn(_offered[1])
|
||||
_place(right_btn, 690, 225, 1130, 540)
|
||||
right_btn.pressed.connect(_on_race_chosen.bind(_offered[1]))
|
||||
c.add_child(right_btn)
|
||||
|
||||
|
||||
func _on_race_chosen(race: Dictionary) -> void:
|
||||
_chosen_race = race
|
||||
GameConfig.player_race = race.id
|
||||
_show_ship_selection()
|
||||
|
||||
|
||||
# ── Ship selection ───────────────────────────────────────────────────────────
|
||||
|
||||
func _show_ship_selection() -> void:
|
||||
var c := _swap_content()
|
||||
|
||||
_label(c, "SELECT YOUR SHIP", 30, Color(0.42, 0.87, 1.0),
|
||||
0, 90, 1280, 145, HORIZONTAL_ALIGNMENT_CENTER)
|
||||
_label(c, "Playing as — " + _chosen_race.name, 14, _chosen_race.color,
|
||||
0, 155, 1280, 185, HORIZONTAL_ALIGNMENT_CENTER)
|
||||
|
||||
for i in SHIPS.size():
|
||||
var y := ROW_START + i * (ROW_H + ROW_GAP)
|
||||
var btn := _make_ship_row(SHIPS[i], _chosen_race.color)
|
||||
_place(btn, ROW_X1, y, ROW_X2, y + ROW_H)
|
||||
btn.pressed.connect(_on_ship_chosen.bind(SHIPS[i]))
|
||||
c.add_child(btn)
|
||||
|
||||
|
||||
func _on_ship_chosen(ship: Dictionary) -> void:
|
||||
GameConfig.player_ship_path = ship.path
|
||||
GameConfig.team_selected.emit()
|
||||
queue_free()
|
||||
|
||||
|
||||
# ── Button builders ──────────────────────────────────────────────────────────
|
||||
|
||||
func _make_race_btn(race: Dictionary) -> Button:
|
||||
var btn := Button.new()
|
||||
btn.text = ""
|
||||
var col: Color = race.color
|
||||
|
||||
var sn := _flat(Color(col.r * 0.14, col.g * 0.14, col.b * 0.14, 0.96), 12)
|
||||
sn.set_border_width_all(2)
|
||||
sn.border_color = Color(col.r * 0.38, col.g * 0.38, col.b * 0.38)
|
||||
var sh := _flat(Color(col.r * 0.24, col.g * 0.24, col.b * 0.24, 0.96), 12)
|
||||
sh.set_border_width_all(2)
|
||||
sh.border_color = col
|
||||
btn.add_theme_stylebox_override("normal", sn)
|
||||
btn.add_theme_stylebox_override("hover", sh)
|
||||
btn.add_theme_stylebox_override("pressed", sh)
|
||||
|
||||
var vb := VBoxContainer.new()
|
||||
vb.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
vb.offset_left = 20; vb.offset_top = 24; vb.offset_right = -20; vb.offset_bottom = -24
|
||||
vb.alignment = BoxContainer.ALIGNMENT_CENTER
|
||||
vb.add_theme_constant_override("separation", 16)
|
||||
vb.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
btn.add_child(vb)
|
||||
|
||||
var name_lbl := Label.new()
|
||||
name_lbl.text = race.name
|
||||
name_lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
name_lbl.add_theme_font_size_override("font_size", 28)
|
||||
name_lbl.add_theme_color_override("font_color", col)
|
||||
name_lbl.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
vb.add_child(name_lbl)
|
||||
|
||||
var sep := HSeparator.new()
|
||||
sep.add_theme_color_override("color", Color(col.r * 0.4, col.g * 0.4, col.b * 0.4))
|
||||
sep.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
vb.add_child(sep)
|
||||
|
||||
var sub_lbl := Label.new()
|
||||
sub_lbl.text = race.sub
|
||||
sub_lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
sub_lbl.add_theme_font_size_override("font_size", 15)
|
||||
sub_lbl.add_theme_color_override("font_color", Color(0.60, 0.70, 0.82))
|
||||
sub_lbl.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
vb.add_child(sub_lbl)
|
||||
|
||||
var hint := Label.new()
|
||||
hint.text = "CLICK TO SELECT"
|
||||
hint.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
hint.add_theme_font_size_override("font_size", 11)
|
||||
hint.add_theme_color_override("font_color", Color(col.r * 0.5, col.g * 0.5, col.b * 0.5))
|
||||
hint.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
vb.add_child(hint)
|
||||
|
||||
return btn
|
||||
|
||||
|
||||
func _make_ship_row(ship: Dictionary, accent: Color) -> Button:
|
||||
var btn := Button.new()
|
||||
btn.text = ""
|
||||
|
||||
var sn := _flat(Color(0.06, 0.09, 0.17, 0.95), 8)
|
||||
sn.set_border_width_all(1)
|
||||
sn.border_color = Color(0.18, 0.28, 0.48, 0.6)
|
||||
var sh := _flat(Color(0.10, 0.16, 0.28, 0.97), 8)
|
||||
sh.set_border_width_all(2)
|
||||
sh.border_color = accent
|
||||
btn.add_theme_stylebox_override("normal", sn)
|
||||
btn.add_theme_stylebox_override("hover", sh)
|
||||
btn.add_theme_stylebox_override("pressed", sh)
|
||||
|
||||
var tex := TextureRect.new()
|
||||
tex.texture = load(ship.path)
|
||||
tex.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
tex.offset_left = 8; tex.offset_top = 6; tex.offset_right = -8; tex.offset_bottom = -6
|
||||
tex.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
||||
tex.expand_mode = TextureRect.EXPAND_FIT_HEIGHT_PROPORTIONAL
|
||||
tex.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
btn.add_child(tex)
|
||||
|
||||
return btn
|
||||
|
||||
|
||||
# ── Helpers ──────────────────────────────────────────────────────────────────
|
||||
|
||||
func _label(parent: Control, text: String, font_size: int, color: Color,
|
||||
x1: float, y1: float, x2: float, y2: float,
|
||||
align: HorizontalAlignment = HORIZONTAL_ALIGNMENT_LEFT) -> void:
|
||||
var lbl := Label.new()
|
||||
lbl.text = text
|
||||
lbl.horizontal_alignment = align
|
||||
lbl.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
lbl.add_theme_font_size_override("font_size", font_size)
|
||||
lbl.add_theme_color_override("font_color", color)
|
||||
_place(lbl, x1, y1, x2, y2)
|
||||
parent.add_child(lbl)
|
||||
|
||||
|
||||
func _place(node: Control, x1: float, y1: float, x2: float, y2: float) -> void:
|
||||
node.set_anchor_and_offset(SIDE_LEFT, 0.0, x1)
|
||||
node.set_anchor_and_offset(SIDE_TOP, 0.0, y1)
|
||||
node.set_anchor_and_offset(SIDE_RIGHT, 0.0, x2)
|
||||
node.set_anchor_and_offset(SIDE_BOTTOM, 0.0, y2)
|
||||
|
||||
|
||||
func _flat(col: Color, radius: int = 0) -> StyleBoxFlat:
|
||||
var s := StyleBoxFlat.new()
|
||||
s.bg_color = col
|
||||
s.set_corner_radius_all(radius)
|
||||
return s
|
||||
Reference in New Issue
Block a user