- main_menu.tscn: Vollbild dunkler Hintergrund, Banner-Slot oben, Buttons zentriert, Einstellungen als styled Overlay-Panel - main_menu.gd: Banner automatisch geladen wenn Banner.png vorhanden, sonst Fallback-Titeltext; @onready-Pfade auf neue Struktur angepasst - class_selection_menu.gd: CLASS_DATA von const auf var geändert Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
218 lines
7.5 KiB
GDScript
218 lines
7.5 KiB
GDScript
# ClassSelectionMenu.gd
|
|
# Menü zur Auswahl der Charakterklasse beim Spielstart
|
|
extends CanvasLayer
|
|
|
|
signal class_selected(character_class: CharacterClass)
|
|
|
|
const WARRIOR_CLASS = preload("res://classes/warrior.tres")
|
|
const ROGUE_CLASS = preload("res://classes/rogue.tres")
|
|
const MAGE_CLASS = preload("res://classes/mage.tres")
|
|
|
|
const WARRIOR_PORTRAIT = preload("res://assets/Icons/Warrior.png")
|
|
const ROGUE_PORTRAIT = preload("res://assets/Icons/Schurke.png")
|
|
const MAGE_PORTRAIT = preload("res://assets/Icons/Magier.png")
|
|
|
|
var CLASS_DATA = [
|
|
{
|
|
"key": "warrior",
|
|
"name": "Krieger",
|
|
"res": null, # filled in _ready
|
|
"portrait": null,
|
|
"description": "Meister des Nahkampfs.\nHohe Rüstung und rohe Stärke.\nNutzt Wut als Ressource.",
|
|
"lore": "Einst ein einfacher Söldner, der die falschen Leute bestahl. Er überlebte das Massaker an seiner Kompanie als Einziger — nicht durch Glück, sondern weil er zu wütend war, um zu sterben. Seitdem trägt er die Narben seiner Gefallenen auf der Haut und ihre Namen auf der Klinge.",
|
|
"flavor": "\"Die Toten schreien nicht. Ich schreie für sie.\""
|
|
},
|
|
{
|
|
"key": "rogue",
|
|
"name": "Schurke",
|
|
"res": null,
|
|
"portrait": null,
|
|
"description": "Schnell und tödlich.\nNah- und Fernkampf.\nNutzt Energie als Ressource.",
|
|
"lore": "Aufgewachsen in den Kanalrohren der Unterstadt, lernte sie früh: Wer zuerst gesehen wird, verliert. Die Gilde der Stille formte sie zur Waffe — bis sie erkannte, dass sie selbst das Ziel war. Seitdem arbeitet sie für sich. Und für einen sehr hohen Preis.",
|
|
"flavor": "\"Ich war schon hier, bevor du mich bemerkt hast.\""
|
|
},
|
|
{
|
|
"key": "mage",
|
|
"name": "Magier",
|
|
"res": null,
|
|
"portrait": null,
|
|
"description": "Meister der Arkanen Künste.\nElementarmagie und Kontrolle.\nNutzt Mana als Ressource.",
|
|
"lore": "Er war Hofgelehrter, bis er in verbotenen Archiven Worte las, die nicht für Sterbliche bestimmt waren. Die Akademie verbrannte seine Bücher. Das Feuer übernahm er — und alles andere, was sie ihm zu nehmen versuchten. Wissen hat seinen Preis. Er zahlt ihn gerne.",
|
|
"flavor": "\"Das Universum ist Text. Ich lerne noch, es umzuschreiben.\""
|
|
}
|
|
]
|
|
|
|
var selected_class: CharacterClass = null
|
|
var _cards: Array = []
|
|
|
|
@onready var card_container = $Background/CardContainer
|
|
@onready var start_btn = $Background/BottomBar/StartBtn
|
|
|
|
# Farben für Karten-Zustände
|
|
const COLOR_CARD_DEFAULT = Color(0.10, 0.08, 0.13, 1.0)
|
|
const COLOR_CARD_HOVER = Color(0.14, 0.11, 0.18, 1.0)
|
|
const COLOR_CARD_SELECTED = Color(0.18, 0.13, 0.25, 1.0)
|
|
const COLOR_BORDER_DEFAULT = Color(0.25, 0.20, 0.35, 1.0)
|
|
const COLOR_BORDER_SELECTED = Color(0.85, 0.65, 0.10, 1.0)
|
|
|
|
func _ready():
|
|
get_tree().paused = true
|
|
process_mode = Node.PROCESS_MODE_ALWAYS
|
|
|
|
CLASS_DATA[0]["res"] = WARRIOR_CLASS
|
|
CLASS_DATA[0]["portrait"] = WARRIOR_PORTRAIT
|
|
CLASS_DATA[1]["res"] = ROGUE_CLASS
|
|
CLASS_DATA[1]["portrait"] = ROGUE_PORTRAIT
|
|
CLASS_DATA[2]["res"] = MAGE_CLASS
|
|
CLASS_DATA[2]["portrait"] = MAGE_PORTRAIT
|
|
|
|
for i in CLASS_DATA.size():
|
|
var card = _build_card(CLASS_DATA[i], i)
|
|
card_container.add_child(card)
|
|
_cards.append(card)
|
|
|
|
start_btn.pressed.connect(_on_start_pressed)
|
|
start_btn.disabled = true
|
|
|
|
|
|
func _build_card(data: Dictionary, idx: int) -> Panel:
|
|
var card = Panel.new()
|
|
card.custom_minimum_size = Vector2(340, 580)
|
|
card.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
|
|
var style = StyleBoxFlat.new()
|
|
style.bg_color = COLOR_CARD_DEFAULT
|
|
style.border_width_left = 2
|
|
style.border_width_top = 2
|
|
style.border_width_right = 2
|
|
style.border_width_bottom = 2
|
|
style.border_color = COLOR_BORDER_DEFAULT
|
|
style.corner_radius_top_left = 10
|
|
style.corner_radius_top_right = 10
|
|
style.corner_radius_bottom_right = 10
|
|
style.corner_radius_bottom_left = 10
|
|
card.add_theme_stylebox_override("panel", style)
|
|
card.set_meta("style", style)
|
|
card.set_meta("idx", idx)
|
|
|
|
var vbox = VBoxContainer.new()
|
|
vbox.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT, Control.PRESET_MODE_MINSIZE, 16)
|
|
vbox.add_theme_constant_override("separation", 14)
|
|
card.add_child(vbox)
|
|
|
|
# Portrait
|
|
var portrait = TextureRect.new()
|
|
portrait.texture = data["portrait"]
|
|
portrait.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
|
portrait.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
|
portrait.custom_minimum_size = Vector2(0, 280)
|
|
portrait.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
portrait.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
|
vbox.add_child(portrait)
|
|
|
|
# Klassenname
|
|
var name_label = Label.new()
|
|
name_label.text = data["name"]
|
|
name_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
name_label.add_theme_font_size_override("font_size", 26)
|
|
vbox.add_child(name_label)
|
|
|
|
# Trennlinie
|
|
var sep = HSeparator.new()
|
|
vbox.add_child(sep)
|
|
|
|
# Lore
|
|
var lore = Label.new()
|
|
lore.text = data["lore"]
|
|
lore.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
lore.add_theme_font_size_override("font_size", 12)
|
|
lore.modulate = Color(0.85, 0.82, 0.75, 1.0)
|
|
lore.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
|
lore.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
|
vbox.add_child(lore)
|
|
|
|
# Trennlinie 2
|
|
var sep2 = HSeparator.new()
|
|
vbox.add_child(sep2)
|
|
|
|
# Gameplay Beschreibung
|
|
var desc = Label.new()
|
|
desc.text = data["description"]
|
|
desc.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
desc.add_theme_font_size_override("font_size", 13)
|
|
desc.modulate = Color(0.7, 0.85, 0.7, 1.0)
|
|
desc.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
|
vbox.add_child(desc)
|
|
|
|
# Flavor Text
|
|
var flavor = Label.new()
|
|
flavor.text = data["flavor"]
|
|
flavor.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
flavor.add_theme_font_size_override("font_size", 11)
|
|
flavor.modulate = Color(0.7, 0.6, 0.4, 1.0)
|
|
flavor.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
|
vbox.add_child(flavor)
|
|
|
|
# Stats
|
|
var stats = Label.new()
|
|
var char_class: CharacterClass = data["res"]
|
|
stats.text = "STR %d | AGI %d | INT %d | STA %d" % [
|
|
char_class.base_strength, char_class.base_agility,
|
|
char_class.base_intelligence, char_class.base_stamina
|
|
]
|
|
stats.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
stats.add_theme_font_size_override("font_size", 12)
|
|
stats.modulate = Color(0.8, 0.8, 0.8, 1.0)
|
|
vbox.add_child(stats)
|
|
|
|
# Auswählen-Button
|
|
var btn = Button.new()
|
|
btn.text = "Auswählen"
|
|
btn.add_theme_font_size_override("font_size", 16)
|
|
btn.pressed.connect(_on_card_selected.bind(idx))
|
|
vbox.add_child(btn)
|
|
|
|
# Hover
|
|
card.mouse_entered.connect(_on_card_hover.bind(idx, true))
|
|
card.mouse_exited.connect(_on_card_hover.bind(idx, false))
|
|
card.mouse_filter = Control.MOUSE_FILTER_STOP
|
|
|
|
return card
|
|
|
|
|
|
func _on_card_selected(idx: int):
|
|
selected_class = CLASS_DATA[idx]["res"]
|
|
for i in _cards.size():
|
|
var s: StyleBoxFlat = _cards[i].get_meta("style")
|
|
if i == idx:
|
|
s.bg_color = COLOR_CARD_SELECTED
|
|
s.border_color = COLOR_BORDER_SELECTED
|
|
s.border_width_left = 3
|
|
s.border_width_top = 3
|
|
s.border_width_right = 3
|
|
s.border_width_bottom = 3
|
|
else:
|
|
s.bg_color = COLOR_CARD_DEFAULT
|
|
s.border_color = COLOR_BORDER_DEFAULT
|
|
s.border_width_left = 2
|
|
s.border_width_top = 2
|
|
s.border_width_right = 2
|
|
s.border_width_bottom = 2
|
|
start_btn.disabled = false
|
|
|
|
|
|
func _on_card_hover(idx: int, hovering: bool):
|
|
var card = _cards[idx]
|
|
var s: StyleBoxFlat = card.get_meta("style")
|
|
var is_selected = (selected_class == CLASS_DATA[idx]["res"])
|
|
if is_selected:
|
|
return
|
|
s.bg_color = COLOR_CARD_HOVER if hovering else COLOR_CARD_DEFAULT
|
|
|
|
|
|
func _on_start_pressed():
|
|
if selected_class == null:
|
|
return
|
|
class_selected.emit(selected_class)
|
|
get_tree().paused = false
|
|
queue_free()
|