- pipeline/batch_roster.py: model + clip-kit per fighter, renders base+torn, packs, hitboxes, tunes, portraits — sequential batch - --strip flag (mesh removal) added; Mixamo pack models turn out hollow under clothes, so this batch ships base+torn; true clothes-off waits on rigging the wardrobegod nude bases (phrtt2, daphne) - tune_fighters: manifests + default finisher timing for raver/chef/ exec/jinx/elva/umbra; getup move tuned - select screen wraps to a 4-per-row grid Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
142 lines
4.1 KiB
GDScript
142 lines
4.1 KiB
GDScript
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
|
|
|
|
const CARD_W := 220
|
|
const 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": null}
|
|
var pp := ProjectSettings.globalize_path(path + "/portrait.png")
|
|
if FileAccess.file_exists(pp):
|
|
var img := Image.load_from_file(pp)
|
|
if img:
|
|
entry.portrait = ImageTexture.create_from_image(img)
|
|
roster.append(entry)
|
|
|
|
var per_row: int = mini(roster.size(), 4)
|
|
var total_w: int = per_row * (CARD_W + 40) - 40
|
|
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 + 40),
|
|
170 + int(i / float(per_row)) * (CARD_H + 60) 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
|
|
spr.scale = Vector2(1.2, 1.2)
|
|
spr.position = Vector2((CARD_W - 192) / 2.0, 24)
|
|
card.add_child(spr)
|
|
var nm := Label.new()
|
|
nm.text = roster[i].name
|
|
nm.add_theme_font_size_override("font_size", 26)
|
|
nm.position = Vector2(20, CARD_H - 52)
|
|
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_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")
|