ajhahn.de
← Theria commits

Commit

Theria

feat: make the connect menu a readable, sized-up card

ajhahnde · Jun 2026 · 4c1807677a77830827cf9e7c2b2ded29e2bd6f24 · parent: b2f512d · view on GitHub →

modified CHANGELOG.md
@@ -37,10 +37,11 @@ protocol version.
- A practice match can now be set up entirely from the connect screen: a hero picker lists
every hero of both tribes, and the choice drives the same tribe-versus-tribe seating as the
command line's `--hero`, so picking a side no longer needs a flag. On the field, each hero
now wears a distinct shade of its team colour, so three squadmates read apart at a glance
instead of sharing one flat colour. Presentation and menu only; the simulation and the
netcode protocol are unchanged.
command line's `--hero`, so picking a side no longer needs a flag. The screen now renders as
a framed card over an opaque backdrop, so its controls read clearly instead of floating over
the arena. On the field, each hero now wears a distinct shade of its team colour, so three
squadmates read apart at a glance instead of sharing one flat colour. Presentation and menu
only; the simulation and the netcode protocol are unchanged.
- Bots now position to their hero's stance instead of all closing in the same way: the
skirmishers (Cheetah, Chameleon) kite — they hold their ranged form and keep an enemy
inside their skillshot band, backing off a point-blank attacker and closing on a distant
modified src/client/connect_menu.gd
@@ -21,6 +21,21 @@ signal join_requested(address: String)
## also chooses the match-up — the same role `--hero` fills on the command line.
signal practice_requested(hero: String)
## Menu styling. An opaque backdrop covers the whole viewport so the debug map and its
## jungle camps — drawn behind the menu in world space — do not bleed through the otherwise
## transparent controls; the card sits on top as a framed panel, so the menu reads as UI
## rather than floating text over the arena.
const BACKDROP_COLOR := Color(0.07, 0.08, 0.10)
const CARD_COLOR := Color(0.13, 0.14, 0.17)
const CARD_PADDING := 52.0
const CARD_CORNER := 14
const CARD_MIN_WIDTH := 680.0
## The menu-wide base font size; every control inherits it, the title overrides larger.
const BASE_FONT_SIZE := 28
const TITLE_FONT_SIZE := 72
const BUTTON_MIN_SIZE := Vector2(560, 76)
const ADDRESS_MIN_WIDTH := 380.0
## The address used when the player leaves the field blank. The driver injects its
## own default so the menu and the `--join` flag resolve to one value.
var default_address := "127.0.0.1"
@@ -40,30 +55,59 @@ var _hero_picker: OptionButton
func _ready() -> void:
set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
# A menu-wide theme bumps the base font so every control — the labels, the dropdown, the
# buttons, the address field — scales up together; the title overrides larger still.
var ui_theme := Theme.new()
ui_theme.default_font_size = BASE_FONT_SIZE
theme = ui_theme
# An opaque backdrop, behind everything, so the world drawn in screen space behind the
# menu does not show through the transparent controls. Ignores the mouse so it never
# eats a click meant for a button below it in the tree.
var backdrop := ColorRect.new()
backdrop.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
backdrop.color = BACKDROP_COLOR
backdrop.mouse_filter = Control.MOUSE_FILTER_IGNORE
add_child(backdrop)
var center := CenterContainer.new()
center.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
add_child(center)
# A framed card so the controls read against a solid panel rather than the arena. The
# stylebox is set explicitly so the look does not depend on the active editor theme.
var card := PanelContainer.new()
card.add_theme_stylebox_override("panel", _card_style())
card.custom_minimum_size = Vector2(CARD_MIN_WIDTH, 0)
center.add_child(card)
var box := VBoxContainer.new()
box.add_theme_constant_override("separation", 16)
center.add_child(box)
box.add_theme_constant_override("separation", 18)
card.add_child(box)
var title := Label.new()
title.text = "Theria"
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
title.add_theme_font_size_override("font_size", TITLE_FONT_SIZE)
box.add_child(title)
var pick_label := Label.new()
pick_label.text = "Practice hero"
box.add_child(pick_label)
_hero_picker = OptionButton.new()
_populate_heroes()
box.add_child(_hero_picker)
var practice_button := Button.new()
practice_button.text = "Practice (single machine)"
practice_button.custom_minimum_size = BUTTON_MIN_SIZE
practice_button.pressed.connect(_on_practice_pressed)
box.add_child(practice_button)
var host_button := Button.new()
host_button.text = "Host a match"
host_button.custom_minimum_size = BUTTON_MIN_SIZE
host_button.pressed.connect(_on_host_pressed)
box.add_child(host_button)
@@ -72,7 +116,8 @@ func _ready() -> void:
_address_field = LineEdit.new()
_address_field.placeholder_text = default_address
_address_field.custom_minimum_size = Vector2(220, 0)
_address_field.custom_minimum_size = Vector2(ADDRESS_MIN_WIDTH, 0)
_address_field.size_flags_horizontal = Control.SIZE_EXPAND_FILL
join_row.add_child(_address_field)
var join_button := Button.new()
@@ -81,6 +126,16 @@ func _ready() -> void:
join_row.add_child(join_button)
## The card's background: a solid dark panel with rounded corners and inner padding, so the
## controls sit on their own surface clear of the arena behind the menu.
func _card_style() -> StyleBoxFlat:
var style := StyleBoxFlat.new()
style.bg_color = CARD_COLOR
style.set_corner_radius_all(CARD_CORNER)
style.set_content_margin_all(CARD_PADDING)
return style
## Fills the hero picker from the tribe rosters — one item per hero, labelled
## "Tribe — Hero", carrying its kit id as metadata — and selects `default_hero` (or the
## first hero when none was injected). Reading `AbilityData.TRIBE` keeps the menu's roster