# HUD.gd # Verwaltet die Spieler-UI: HP-Leiste, XP-Leiste, Aktionsleiste (Slots 1-9) extends CanvasLayer signal slot_clicked(slot_index: int) signal slot_drag_removed(slot_index: int) signal slot_drag_swapped(from_slot: int, to_slot: int) # Drag & Drop State var drag_active = false var drag_highlight_slot = -1 var drag_from_slot = -1 # Slot von dem aus gedraggt wird var drag_icon: TextureRect = null var drag_item = null # Das gedraggte Consumable @onready var health_bar = $Control/HealthBar @onready var health_label = $Control/HealthBar/HealthLabel # Level/XP UI (wird dynamisch erstellt) var level_label: Label var xp_bar: ProgressBar var gold_label: Label @onready var action_slots = [ $Control/ActionBar/A1, $Control/ActionBar/A2, $Control/ActionBar/A3, $Control/ActionBar/A4, $Control/ActionBar/A5, $Control/ActionBar/A6, $Control/ActionBar/A7, $Control/ActionBar/A8, $Control/ActionBar/A9 ] var active_slot = 0 var slot_icons = [] # TextureRect nodes für Icons var slot_cooldown_overlays = [] # ColorRect für Cooldown-Anzeige var slot_cooldown_labels = [] # Label für Cooldown-Text var slot_stack_labels = [] # Label für Stack-Anzahl # Ressourcen-Bar (Mana/Energie/Wut) var resource_bar: ProgressBar var resource_label: Label # Castbar var castbar: ProgressBar var castbar_label: Label var castbar_container: PanelContainer func _ready(): _create_level_ui() for i in range(9): # Icon erstellen — füllt den ganzen Slot var icon = TextureRect.new() icon.name = "Icon" icon.expand_mode = TextureRect.EXPAND_FIT_WIDTH_PROPORTIONAL icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED icon.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) icon.mouse_filter = Control.MOUSE_FILTER_IGNORE action_slots[i].add_child(icon) slot_icons.append(icon) # Slot-Nummer (oben links, über dem Icon) var slot_number = Label.new() slot_number.name = "SlotNumber" slot_number.text = str(i + 1) slot_number.position = Vector2(2, 0) slot_number.size = Vector2(20, 16) slot_number.add_theme_font_size_override("font_size", 11) slot_number.add_theme_color_override("font_color", Color(1, 1, 1, 0.9)) slot_number.add_theme_color_override("font_shadow_color", Color(0, 0, 0, 1)) slot_number.add_theme_constant_override("shadow_offset_x", 1) slot_number.add_theme_constant_override("shadow_offset_y", 1) slot_number.mouse_filter = Control.MOUSE_FILTER_IGNORE action_slots[i].add_child(slot_number) # Cooldown-Overlay erstellen (dunkle Überlagerung) var cooldown_overlay = ColorRect.new() cooldown_overlay.name = "CooldownOverlay" cooldown_overlay.color = Color(0, 0, 0, 0.7) cooldown_overlay.size = Vector2(50, 50) cooldown_overlay.position = Vector2(0, 0) cooldown_overlay.visible = false cooldown_overlay.mouse_filter = Control.MOUSE_FILTER_IGNORE action_slots[i].add_child(cooldown_overlay) slot_cooldown_overlays.append(cooldown_overlay) # Cooldown-Text erstellen var cooldown_label = Label.new() cooldown_label.name = "CooldownLabel" cooldown_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER cooldown_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER cooldown_label.size = Vector2(50, 50) cooldown_label.position = Vector2(0, 0) cooldown_label.add_theme_font_size_override("font_size", 16) cooldown_label.visible = false cooldown_label.mouse_filter = Control.MOUSE_FILTER_IGNORE action_slots[i].add_child(cooldown_label) slot_cooldown_labels.append(cooldown_label) # Stack-Count Label (unten rechts) var stack_label = Label.new() stack_label.name = "StackLabel" stack_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT stack_label.vertical_alignment = VERTICAL_ALIGNMENT_BOTTOM stack_label.size = Vector2(50, 50) stack_label.position = Vector2(-4, -2) stack_label.add_theme_font_size_override("font_size", 11) stack_label.visible = false stack_label.mouse_filter = Control.MOUSE_FILTER_IGNORE action_slots[i].add_child(stack_label) slot_stack_labels.append(stack_label) # Button für Klicks und Drag erstellen var button = Button.new() button.name = "SlotButton" button.flat = true button.size = Vector2(50, 50) button.position = Vector2(0, 0) button.modulate = Color(1, 1, 1, 0) # Unsichtbar var slot_index = i button.pressed.connect(func(): _on_slot_clicked(slot_index)) button.button_down.connect(func(): _on_slot_drag_start(slot_index)) action_slots[i].add_child(button) # Drag aus Aktionsleiste starten func _on_slot_drag_start(slot_index: int): # Prüfe ob ein Consumable im Slot liegt (wird vom Player gesetzt) # Signal an Player senden um Item abzufragen _start_actionbar_drag(slot_index) func _start_actionbar_drag(slot_index: int): drag_from_slot = slot_index drag_active = true # Icon am Cursor erstellen aus dem aktuellen Slot-Icon var current_texture = slot_icons[slot_index].texture if current_texture == null: drag_active = false drag_from_slot = -1 return drag_icon = TextureRect.new() drag_icon.texture = current_texture drag_icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE drag_icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED drag_icon.mouse_filter = Control.MOUSE_FILTER_IGNORE var drag_layer = CanvasLayer.new() drag_layer.name = "DragLayer" drag_layer.layer = 200 drag_layer.add_child(drag_icon) get_tree().root.add_child(drag_layer) # Größe erst nach add_child setzen drag_icon.custom_minimum_size = Vector2(48, 48) drag_icon.size = Vector2(48, 48) drag_icon.position = get_viewport().get_mouse_position() - Vector2(24, 24) func _process(_delta): if drag_from_slot >= 0 and drag_icon: drag_icon.position = get_viewport().get_mouse_position() - Vector2(20, 20) update_drag_hover(get_viewport().get_mouse_position()) func _input(event): if not drag_active or drag_from_slot < 0: return if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and not event.pressed: _end_actionbar_drag() func _end_actionbar_drag(): var drop_slot = get_slot_at_position(get_viewport().get_mouse_position()) if drop_slot >= 0 and drop_slot <= 8 and drop_slot != drag_from_slot: # Auf anderen Slot verschoben -> swap slot_drag_swapped.emit(drag_from_slot, drop_slot) elif drop_slot < 0 or drop_slot > 8: # Außerhalb gedroppt -> aus Leiste entfernen slot_drag_removed.emit(drag_from_slot) # Aufräumen _clear_drag_highlight() if drag_icon: var drag_layer = drag_icon.get_parent() drag_layer.queue_free() drag_icon = null drag_active = false drag_from_slot = -1 # Slot-Klick Handler func _on_slot_clicked(slot_index: int): if drag_active: return # Während Drag keine Klicks set_active_slot(slot_index) slot_clicked.emit(slot_index) # Icon für einen Slot setzen func set_slot_icon(slot_index: int, icon_path: String): if slot_index >= 0 and slot_index < 9: var texture = load(icon_path) if texture: slot_icons[slot_index].texture = texture else: print("Icon nicht gefunden: ", icon_path) # Cooldown für einen Slot anzeigen (remaining_time in Sekunden) func set_slot_cooldown(slot_index: int, remaining_time: float): if slot_index < 0 or slot_index >= 9: return if remaining_time > 0: slot_cooldown_overlays[slot_index].visible = true slot_cooldown_labels[slot_index].visible = true slot_cooldown_labels[slot_index].text = "%.1f" % remaining_time else: slot_cooldown_overlays[slot_index].visible = false slot_cooldown_labels[slot_index].visible = false # Level/XP UI erstellen func _create_level_ui(): var control = $Control # Ressourcen-Bar (Mana/Energie/Wut) - unter HP-Bar, oben links resource_bar = ProgressBar.new() resource_bar.name = "ResourceBar" resource_bar.position = Vector2(20, 50) resource_bar.size = Vector2(200, 18) resource_bar.show_percentage = false resource_bar.value = 0 resource_bar.visible = false var resource_style = StyleBoxFlat.new() resource_style.bg_color = Color(0.2, 0.3, 0.9, 1.0) resource_bar.add_theme_stylebox_override("fill", resource_style) resource_label = Label.new() resource_label.name = "ResourceLabel" resource_label.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) resource_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER resource_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER resource_bar.add_child(resource_label) control.add_child(resource_bar) # Level Label — wird in update_level() über XPLabel gesetzt, kein eigenes Node nötig level_label = Label.new() # Dummy damit update_level() nicht crasht level_label.name = "LevelLabel" level_label.visible = false control.add_child(level_label) # Gold Label oben links gold_label = Label.new() gold_label.name = "GoldLabel" gold_label.position = Vector2(20, 94) gold_label.add_theme_font_size_override("font_size", 14) gold_label.add_theme_color_override("font_color", Color(1, 0.85, 0, 1)) gold_label.text = "0 Gold" control.add_child(gold_label) # XP Bar — gleiche Breite wie Actionbar (468px), unten mittig, unter der Actionbar xp_bar = ProgressBar.new() xp_bar.name = "XPBar" xp_bar.anchor_left = 0.5 xp_bar.anchor_top = 1.0 xp_bar.anchor_right = 0.5 xp_bar.anchor_bottom = 1.0 xp_bar.offset_left = -234.0 xp_bar.offset_right = 234.0 xp_bar.offset_top = -18.0 xp_bar.offset_bottom = -6.0 xp_bar.show_percentage = false xp_bar.value = 0 xp_bar.mouse_filter = Control.MOUSE_FILTER_IGNORE var xp_style = StyleBoxFlat.new() xp_style.bg_color = Color(0.15, 0.5, 0.9, 1.0) xp_bar.add_theme_stylebox_override("fill", xp_style) var xp_bg = StyleBoxFlat.new() xp_bg.bg_color = Color(0.1, 0.1, 0.15, 0.85) xp_bar.add_theme_stylebox_override("background", xp_bg) # XP Label zentriert in der Bar var xp_label = Label.new() xp_label.name = "XPLabel" xp_label.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) xp_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER xp_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER xp_label.add_theme_font_size_override("font_size", 10) xp_label.add_theme_color_override("font_color", Color(1, 1, 1, 0.9)) xp_label.mouse_filter = Control.MOUSE_FILTER_IGNORE xp_bar.add_child(xp_label) control.add_child(xp_bar) # Castbar — direkt über der Aktionsleiste positioniert _create_castbar() # Gold aktualisieren func update_gold(amount: int): if gold_label: gold_label.text = str(amount) + " Gold" # Ressourcen-Leiste aktualisieren (Mana/Energie/Wut) func update_resource(current: int, maximum: int, resource_name: String): if resource_bar == null: return if maximum <= 0: resource_bar.visible = false return resource_bar.visible = true resource_bar.max_value = maximum resource_bar.value = current resource_label.text = str(current) + " / " + str(maximum) # Farbe je nach Ressourcen-Typ var style = resource_bar.get_theme_stylebox("fill") as StyleBoxFlat if style: match resource_name: "Mana": style.bg_color = Color(0.2, 0.3, 0.9, 1.0) # Blau "Energie": style.bg_color = Color(0.9, 0.8, 0.1, 1.0) # Gelb "Wut": style.bg_color = Color(0.8, 0.15, 0.1, 1.0) # Rot # Icon-Textur direkt setzen (für Consumables) func set_slot_icon_texture(slot_index: int, texture: Texture2D): if slot_index >= 0 and slot_index < 9: slot_icons[slot_index].texture = texture # Slot-Icon entfernen func clear_slot_icon(slot_index: int): if slot_index >= 0 and slot_index < 9: slot_icons[slot_index].texture = null # Stack-Anzahl auf Slot anzeigen func set_slot_stack_count(slot_index: int, count: int): if slot_index < 0 or slot_index >= 9: return if count > 1: slot_stack_labels[slot_index].text = str(count) slot_stack_labels[slot_index].visible = true else: slot_stack_labels[slot_index].visible = false # HP-Leiste und Text aktualisieren func update_health(current_hp, max_hp): health_bar.max_value = max_hp health_bar.value = current_hp health_label.text = str(current_hp) + " / " + str(max_hp) # Level und XP aktualisieren func update_level(level: int, current_xp: int, xp_to_next: int): if level_label: level_label.text = "Lv " + str(level) if xp_bar: xp_bar.max_value = xp_to_next xp_bar.value = current_xp var xp_label = xp_bar.get_node_or_null("XPLabel") if xp_label: xp_label.text = "Lv %d %d / %d XP" % [level, current_xp, xp_to_next] # Aktions-Slot kurz golden hervorheben (0.1s) func set_active_slot(index): action_slots[active_slot].self_modulate = Color(1, 1, 1) active_slot = index action_slots[active_slot].self_modulate = Color(1, 0.8, 0) await get_tree().create_timer(0.1).timeout action_slots[active_slot].self_modulate = Color(1, 1, 1) # Drag & Drop: Highlight aktivieren/deaktivieren func set_drag_active(active: bool): drag_active = active if not active: # Alle Highlights entfernen _clear_drag_highlight() # Drag & Drop: Hover über Slots prüfen und gelben Rand setzen func update_drag_hover(mouse_pos: Vector2): if not drag_active: return var hovered = get_slot_at_position(mouse_pos) if hovered == drag_highlight_slot: return # Keine Änderung # Alten Highlight entfernen _clear_drag_highlight() # Neuen Highlight setzen (nur Slots 2-8) if hovered >= 0 and hovered <= 8: drag_highlight_slot = hovered var style = StyleBoxFlat.new() style.bg_color = Color(0.15, 0.15, 0.15) style.border_color = Color(1, 0.9, 0, 1) # Gelber Rand style.set_border_width_all(3) action_slots[hovered].add_theme_stylebox_override("panel", style) func _clear_drag_highlight(): if drag_highlight_slot >= 0 and drag_highlight_slot < 9: action_slots[drag_highlight_slot].remove_theme_stylebox_override("panel") drag_highlight_slot = -1 # Gibt den Slot-Index zurück wenn mouse_pos über einem Action-Slot liegt, sonst -1 func get_slot_at_position(mouse_pos: Vector2) -> int: for i in range(9): var slot = action_slots[i] var rect = slot.get_global_rect() if rect.has_point(mouse_pos): return i return -1 func _create_castbar(): # Eigene CanvasLayer für die Castbar var castbar_layer = CanvasLayer.new() castbar_layer.name = "CastbarLayer" castbar_layer.layer = 10 add_child(castbar_layer) castbar_container = PanelContainer.new() castbar_container.name = "CastbarContainer" castbar_container.visible = false castbar_container.mouse_filter = Control.MOUSE_FILTER_IGNORE var style = StyleBoxFlat.new() style.bg_color = Color(0.1, 0.1, 0.1, 0.85) style.set_border_width_all(2) style.border_color = Color(0.6, 0.6, 0.6) style.set_corner_radius_all(4) castbar_container.add_theme_stylebox_override("panel", style) var vbox = VBoxContainer.new() vbox.mouse_filter = Control.MOUSE_FILTER_IGNORE castbar_container.add_child(vbox) castbar_label = Label.new() castbar_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER castbar_label.add_theme_font_size_override("font_size", 13) castbar_label.mouse_filter = Control.MOUSE_FILTER_IGNORE vbox.add_child(castbar_label) castbar = ProgressBar.new() castbar.custom_minimum_size = Vector2(300, 16) castbar.show_percentage = false castbar.mouse_filter = Control.MOUSE_FILTER_IGNORE var fill = StyleBoxFlat.new() fill.bg_color = Color(1.0, 0.7, 0.0, 1.0) fill.set_corner_radius_all(2) castbar.add_theme_stylebox_override("fill", fill) var bg = StyleBoxFlat.new() bg.bg_color = Color(0.2, 0.2, 0.2, 1.0) bg.set_corner_radius_all(2) castbar.add_theme_stylebox_override("background", bg) vbox.add_child(castbar) castbar_layer.add_child(castbar_container) # Castbar anzeigen func show_castbar(spell_name: String, cast_time: float): if castbar_container == null: return # Spell-Name übersetzen var display_name = spell_name match spell_name: "frostbolt": display_name = "Frostblitz" castbar_label.text = display_name + " (" + "%.1f" % cast_time + "s)" castbar.max_value = cast_time castbar.value = 0 # Mittig über der Aktionsleiste positionieren var viewport_size = get_viewport().get_visible_rect().size castbar_container.position = Vector2(viewport_size.x / 2 - 160, viewport_size.y - 120) castbar_container.visible = true # Castbar Fortschritt aktualisieren func update_castbar(elapsed: float, total: float): if castbar == null: return castbar.value = elapsed var remaining = total - elapsed if remaining < 0: remaining = 0 castbar_label.text = castbar_label.text.split("(")[0].strip_edges() + " (%.1f" % remaining + "s)" # Castbar verstecken func hide_castbar(): if castbar_container: castbar_container.visible = false