extends Node2D ## Character select: P1 picks with A/D + J, P2 with arrows + O. ## Roster = whatever folders are under characters/ — same scan as the match. var roster: Array = [] # [{path, id, name, portrait, color}] var cursor := [0, 0] var locked := [false, false] var cards: Array = [] var cursors_ui: Array = [] var vs_timer := -1 var CARD_W := 220 var CARD_H := 300 func _ready() -> void: var bg := ColorRect.new() bg.color = Color("#14161c") bg.size = Vector2(1280, 720) add_child(bg) var title := Label.new() title.text = "F O I T I N" title.add_theme_font_size_override("font_size", 64) title.position = Vector2(490, 40) add_child(title) var sub := Label.new() sub.text = "CHOOSE YOUR FIGHTER" sub.add_theme_font_size_override("font_size", 24) sub.modulate = Color(1, 1, 1, 0.6) sub.position = Vector2(520, 120) add_child(sub) for path in CharacterData.scan_roster(): var d = JSON.parse_string(FileAccess.get_file_as_string(path + "/manifest.json")) var entry := {"path": path, "id": d.get("id", ""), "name": d.get("name", "?"), "color": Color(d.get("color", "#888888")), "portrait": MoveData.load_texture_any(path + "/portrait.png")} roster.append(entry) # adaptive grid: shrink cards as the roster grows var per_row := 4 if roster.size() > 16: per_row = 9 CARD_W = 124 CARD_H = 180 elif roster.size() > 8: per_row = 8 CARD_W = 140 CARD_H = 196 per_row = mini(roster.size(), per_row) var gap: int = 40 if roster.size() <= 8 else 14 var total_w: int = per_row * (CARD_W + gap) - gap var x0: float = 640.0 - total_w / 2.0 for i in roster.size(): var card := Node2D.new() card.position = Vector2(x0 + (i % per_row) * (CARD_W + gap), 170 + int(i / float(per_row)) * (CARD_H + 56) as float) add_child(card) var frame := ColorRect.new() frame.color = Color("#22252e") frame.size = Vector2(CARD_W, CARD_H) card.add_child(frame) var accent := ColorRect.new() accent.color = roster[i].color accent.size = Vector2(CARD_W, 8) accent.position = Vector2(0, CARD_H - 8) card.add_child(accent) if roster[i].portrait: var spr := Sprite2D.new() spr.texture = roster[i].portrait spr.centered = false var pscale: float = (CARD_W - 24) / 160.0 spr.scale = Vector2(pscale, pscale) spr.position = Vector2(12, 12) card.add_child(spr) var nm := Label.new() nm.text = roster[i].name nm.add_theme_font_size_override("font_size", 26 if roster.size() <= 8 else 17) nm.position = Vector2(12, CARD_H - 40) card.add_child(nm) cards.append(card) for p in 2: var cur := ColorRect.new() cur.color = Color("#e8c840") if p == 0 else Color("#40a8e8") cur.size = Vector2(CARD_W + 12, 6) add_child(cur) var tag := Label.new() tag.text = "P%d" % (p + 1) tag.add_theme_font_size_override("font_size", 20) cur.add_child(tag) tag.position = Vector2(-2, 8 if p == 0 else -34) cursors_ui.append(cur) var help := Label.new() help.text = "P1: A/D move, J lock in P2: arrows move, O lock in" help.modulate = Color(1, 1, 1, 0.4) help.position = Vector2(440, 660) add_child(help) _place_cursors() func _place_cursors() -> void: for p in 2: var card: Node2D = cards[cursor[p]] cursors_ui[p].position = card.position + Vector2(-6, CARD_H + 14 if p == 0 else -22) cursors_ui[p].modulate.a = 0.5 if locked[p] else 1.0 func _input(event: InputEvent) -> void: if not event is InputEventKey or not event.pressed or event.echo: return if vs_timer >= 0: return match event.keycode: KEY_ESCAPE: get_tree().change_scene_to_file("res://engine/scenes/Title.tscn") return KEY_A: _move(0, -1) KEY_D: _move(0, 1) KEY_J: _lock(0) KEY_LEFT: _move(1, -1) KEY_RIGHT: _move(1, 1) KEY_O: _lock(1) _place_cursors() func _move(p: int, dir: int) -> void: if locked[p]: return cursor[p] = wrapi(cursor[p] + dir, 0, roster.size()) func _lock(p: int) -> void: locked[p] = true if locked[0] and locked[1]: GameState.p1_pick = roster[cursor[0]].id GameState.p2_pick = roster[cursor[1]].id var vs := Label.new() vs.text = "%s VS %s" % [roster[cursor[0]].name, roster[cursor[1]].name] vs.add_theme_font_size_override("font_size", 48) vs.position = Vector2(400, 580) add_child(vs) vs_timer = 60 func _physics_process(_d: float) -> void: if vs_timer > 0: vs_timer -= 1 elif vs_timer == 0: get_tree().change_scene_to_file("res://engine/scenes/Fight.tscn")