Drag & Drop Aktionsleiste und Fähigkeiten-Panel

- Alle Aktionsleisten-Slots frei belegbar (Skills + Consumables)
- Drag & Drop: Items/Skills zwischen Slots verschieben oder rausziehen
- Gelber Highlight-Rand beim Hover über Slots während Drag
- Drag-Icon auf eigener CanvasLayer (Layer 200) für korrektes Z-Order
- Fähigkeiten-Panel (P-Taste): Listet alle Skills, per Drag auf Leiste ziehen
- Skills als Strings in action_bar_items gespeichert (generisches System)
- Cooldown-Anzeige generisch für alle Slot-Typen
- Bugfix: theme_override_constants und Tooltip-Typen in LootWindow

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Andre 2026-03-15 21:03:34 +01:00
parent e682ed65e4
commit d029a37e7f
9 changed files with 466 additions and 77 deletions

View file

@ -23,11 +23,10 @@ Gegner bekämpfen und ihre Charaktere mit verschiedenen Klassen und Ausrüstunge
| RMB gehalten | Kamera drehen, Spieler schaut mit | | RMB gehalten | Kamera drehen, Spieler schaut mit |
| Linksklick auf Gegner | Ziel markieren | | Linksklick auf Gegner | Ziel markieren |
| Rechtsklick auf Gegner | Ziel markieren + Autoattack starten | | Rechtsklick auf Gegner | Ziel markieren + Autoattack starten |
| 1 | Autoattack manuell starten | | 1 9 | Aktionsleiste Slots (Skills + Consumables, frei belegbar) |
| 2 | Heavy Strike (starke Attacke) |
| 3 9 | Aktionsleiste Slots (Consumables/Items) |
| C | Charakter-Panel (Stats + Equipment) | | C | Charakter-Panel (Stats + Equipment) |
| I | Inventar öffnen/schließen | | I | Inventar öffnen/schließen |
| P | Fähigkeiten-Panel (alle Skills, Drag auf Aktionsleiste) |
| Leertaste | Springen | | Leertaste | Springen |
| T | (Test) 10 Schaden am Spieler | | T | (Test) 10 Schaden am Spieler |
@ -306,10 +305,12 @@ Beispiel: Waffe mit 1.5s + 50% Haste → `1.5 / 1.5 = 1.0s`
- Aktiviert automatisch Autoattack danach - Aktiviert automatisch Autoattack danach
### UI & Icons ### UI & Icons
- Aktionsleiste mit 9 Slots (Taste 1-9) - Aktionsleiste mit 9 Slots (Taste 1-9), frei belegbar mit Skills und Consumables
- Drag & Drop: Skills/Tränke zwischen Slots verschieben, aus Leiste rausziehen zum Entfernen
- Fähigkeiten-Panel (P-Taste): Listet alle verfügbaren Skills, per Drag auf Aktionsleiste ziehen
- Icons werden beim Spielstart geladen - Icons werden beim Spielstart geladen
- Cooldown-Anzeige: Dunkle Überlagerung + verbleibende Zeit - Cooldown-Anzeige: Dunkle Überlagerung + verbleibende Zeit
- Alle Slots per Maus klickbar - Gelber Highlight-Rand beim Drag über Slots
--- ---
@ -424,6 +425,8 @@ DungeonCrawler/
├── main_menu.tscn # Hauptmenü Scene ├── main_menu.tscn # Hauptmenü Scene
├── player.gd # Spieler-Script (inkl. Ressourcen, Aktionsleiste) ├── player.gd # Spieler-Script (inkl. Ressourcen, Aktionsleiste)
├── player.tscn # Spieler-Scene ├── player.tscn # Spieler-Scene
├── skill_panel.gd # Fähigkeiten-Panel Script
├── skill_panel.tscn # Fähigkeiten-Panel Scene
├── world.gd # Welt-Script ├── world.gd # Welt-Script
├── world.tscn # Hauptszene ├── world.tscn # Hauptszene
└── PROJEKTDOKU.md # Diese Dokumentation └── PROJEKTDOKU.md # Diese Dokumentation

110
hud.gd
View file

@ -3,6 +3,15 @@
extends CanvasLayer extends CanvasLayer
signal slot_clicked(slot_index: int) 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_bar = $Control/HealthBar
@onready var health_label = $Control/HealthBar/HealthLabel @onready var health_label = $Control/HealthBar/HealthLabel
@ -84,7 +93,7 @@ func _ready():
action_slots[i].add_child(stack_label) action_slots[i].add_child(stack_label)
slot_stack_labels.append(stack_label) slot_stack_labels.append(stack_label)
# Button für Klicks erstellen # Button für Klicks und Drag erstellen
var button = Button.new() var button = Button.new()
button.name = "SlotButton" button.name = "SlotButton"
button.flat = true button.flat = true
@ -93,10 +102,70 @@ func _ready():
button.modulate = Color(1, 1, 1, 0) # Unsichtbar button.modulate = Color(1, 1, 1, 0) # Unsichtbar
var slot_index = i var slot_index = i
button.pressed.connect(func(): _on_slot_clicked(slot_index)) 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) 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.custom_minimum_size = Vector2(40, 40)
drag_icon.size = Vector2(40, 40)
drag_icon.expand_mode = TextureRect.EXPAND_FIT_WIDTH_PROPORTIONAL
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)
drag_icon.position = get_viewport().get_mouse_position() - Vector2(20, 20)
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 >= 2 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 < 2 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 # Slot-Klick Handler
func _on_slot_clicked(slot_index: int): func _on_slot_clicked(slot_index: int):
if drag_active:
return # Während Drag keine Klicks
set_active_slot(slot_index) set_active_slot(slot_index)
slot_clicked.emit(slot_index) slot_clicked.emit(slot_index)
@ -253,3 +322,42 @@ func set_active_slot(index):
action_slots[active_slot].self_modulate = Color(1, 0.8, 0) action_slots[active_slot].self_modulate = Color(1, 0.8, 0)
await get_tree().create_timer(0.1).timeout await get_tree().create_timer(0.1).timeout
action_slots[active_slot].self_modulate = Color(1, 1, 1) 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 >= 2 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

View file

@ -7,6 +7,11 @@ signal item_selected(item, index: int)
var panel_visible = false var panel_visible = false
var player = null var player = null
# Drag & Drop
var dragging = false
var drag_item: Consumable = null
var drag_icon: TextureRect = null
@onready var panel = $Panel @onready var panel = $Panel
@onready var gold_label = $Panel/VBoxContainer/Header/GoldLabel @onready var gold_label = $Panel/VBoxContainer/Header/GoldLabel
@onready var item_grid = $Panel/VBoxContainer/ScrollContainer/ItemGrid @onready var item_grid = $Panel/VBoxContainer/ScrollContainer/ItemGrid
@ -139,21 +144,64 @@ func _on_slot_clicked(event: InputEvent, index: int, item):
player.inventory.remove_item(item) player.inventory.remove_item(item)
_refresh_inventory() _refresh_inventory()
player._update_action_bar_stacks() player._update_action_bar_stacks()
elif event.button_index == MOUSE_BUTTON_LEFT and event.shift_pressed: elif event.button_index == MOUSE_BUTTON_LEFT:
# Shift+Linksklick: Auf nächsten freien Aktionsleisten-Slot legen # Linksklick: Drag starten
if player: _start_drag(item)
_assign_consumable_to_bar(item)
func _assign_consumable_to_bar(item: Consumable): # Drag & Drop System
# Nächsten freien Slot ab 2 finden func _start_drag(item: Consumable):
for i in range(2, 9): dragging = true
if player.action_bar_items[i] == null: drag_item = item
player.assign_to_action_bar(i, item) # Icon am Mauszeiger erstellen
print(item.item_name + " auf Slot " + str(i + 1) + " gelegt") drag_icon = TextureRect.new()
return drag_icon.texture = item.icon
# Kein freier Slot - auf Slot 2 legen drag_icon.custom_minimum_size = Vector2(40, 40)
player.assign_to_action_bar(2, item) drag_icon.size = Vector2(40, 40)
print(item.item_name + " auf Slot 3 gelegt (überschrieben)") drag_icon.expand_mode = TextureRect.EXPAND_FIT_WIDTH_PROPORTIONAL
drag_icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
drag_icon.mouse_filter = Control.MOUSE_FILTER_IGNORE
# Eigene CanvasLayer damit Icon über allem anderen liegt
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)
drag_icon.position = get_viewport().get_mouse_position() - Vector2(20, 20)
# Highlight auf HUD aktivieren
if player and player.hud:
player.hud.set_drag_active(true)
func _process(_delta):
if dragging and drag_icon:
drag_icon.position = get_viewport().get_mouse_position() - Vector2(20, 20)
# HUD Slots highlighten
if player and player.hud:
player.hud.update_drag_hover(get_viewport().get_mouse_position())
func _input(event):
if not dragging:
return
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and not event.pressed:
# Maus losgelassen - Drop prüfen
_end_drag()
func _end_drag():
if not dragging:
return
# Prüfen ob über einem Action-Slot
if player and player.hud:
var slot_index = player.hud.get_slot_at_position(get_viewport().get_mouse_position())
if slot_index >= 2 and slot_index <= 8 and drag_item:
player.assign_to_action_bar(slot_index, drag_item)
print(drag_item.item_name + " auf Slot " + str(slot_index + 1) + " gelegt")
player.hud.set_drag_active(false)
# Aufräumen
if drag_icon:
var drag_layer = drag_icon.get_parent()
drag_layer.queue_free() # Entfernt DragLayer + Icon
drag_icon = null
dragging = false
drag_item = null
func _get_item_tooltip(item) -> String: func _get_item_tooltip(item) -> String:
if item is Consumable: if item is Consumable:

View file

@ -50,7 +50,7 @@ func _refresh_display():
for i in range(items.size()): for i in range(items.size()):
var item = items[i] var item = items[i]
var hbox = HBoxContainer.new() var hbox = HBoxContainer.new()
hbox.theme_override_constants = {} hbox.add_theme_constant_override("separation", 4)
# Icon wenn vorhanden # Icon wenn vorhanden
if item.icon: if item.icon:
@ -115,7 +115,10 @@ func _on_loot_all():
_refresh_display() _refresh_display()
func _get_item_tooltip(item: Equipment) -> String: func _get_item_tooltip(item) -> String:
if item is Consumable:
return item.item_name + "\n" + item.get_effect_text()
var tooltip = item.item_name + "\n" var tooltip = item.item_name + "\n"
tooltip += Equipment.get_slot_name(item.slot) + "\n" tooltip += Equipment.get_slot_name(item.slot) + "\n"

161
player.gd
View file

@ -29,9 +29,15 @@ var max_resource = 0 # Klassen-Ressource (Mana/Energie/Wut), 0 = keine
var current_resource = 0 var current_resource = 0
var target = null # Aktuell markierter Gegner var target = null # Aktuell markierter Gegner
# Aktionsleiste: Items/Consumables zugewiesen zu Slots (0-8) # Aktionsleiste: Skills (String) oder Consumables in Slots (0-8)
# Slot 0 = Autoattack, Slot 1 = Heavy Strike, Rest frei für Items # Skills: "autoattack", "heavy_strike" — frei verschiebbar
var action_bar_items: Array = [null, null, null, null, null, null, null, null, null] var action_bar_items: Array = ["autoattack", "heavy_strike", null, null, null, null, null, null, null]
# Alle verfügbaren Skills (für Fähigkeiten-Panel)
var available_skills: Array = [
{"id": "autoattack", "name": "Autoattack", "icon": "res://icons/autoattack_icon.svg", "description": "Greift das Ziel automatisch an.\nSchaden: Waffenschaden + Main-Stat"},
{"id": "heavy_strike", "name": "Heavy Strike", "icon": "res://icons/heavy_strike_icon.svg", "description": "Starker Hieb mit 3s Cooldown.\nSchaden: 10-15 + Main-Stat"},
]
var potion_cooldown: float = 0.0 var potion_cooldown: float = 0.0
const POTION_COOLDOWN_TIME = 1.0 const POTION_COOLDOWN_TIME = 1.0
@ -70,6 +76,7 @@ const HEAVY_STRIKE_RANGE = 4.0
@onready var character_panel = $CharacterPanel @onready var character_panel = $CharacterPanel
@onready var inventory_panel = $InventoryPanel @onready var inventory_panel = $InventoryPanel
@onready var loot_window = $LootWindow @onready var loot_window = $LootWindow
@onready var skill_panel = $SkillPanel
func _ready(): func _ready():
# Stats aus Klasse berechnen # Stats aus Klasse berechnen
@ -81,12 +88,14 @@ func _ready():
hud.update_resource(current_resource, max_resource, get_resource_name()) hud.update_resource(current_resource, max_resource, get_resource_name())
hud.update_level(level, current_xp, xp_to_next_level) hud.update_level(level, current_xp, xp_to_next_level)
hud.set_active_slot(0) hud.set_active_slot(0)
# Icons für Skills setzen # Aktionsleiste initialisieren (Skills + Items)
hud.set_slot_icon(0, "res://icons/autoattack_icon.svg") # Slot 1: Autoattack for i in range(9):
hud.set_slot_icon(1, "res://icons/heavy_strike_icon.svg") # Slot 2: Heavy Strike _refresh_action_slot(i)
# HUD-Klicks verbinden # HUD-Klicks und Drag verbinden
hud.slot_clicked.connect(_on_slot_clicked) hud.slot_clicked.connect(_on_slot_clicked)
hud.slot_drag_removed.connect(_on_slot_drag_removed)
hud.slot_drag_swapped.connect(_on_slot_drag_swapped)
# Inventar Panel initialisieren # Inventar Panel initialisieren
inventory_panel.setup(self) inventory_panel.setup(self)
@ -94,6 +103,9 @@ func _ready():
# Loot Window initialisieren # Loot Window initialisieren
loot_window.setup(self) loot_window.setup(self)
# Skill Panel initialisieren
skill_panel.setup(self)
# Gold im HUD aktualisieren wenn sich Gold ändert # Gold im HUD aktualisieren wenn sich Gold ändert
inventory.gold_changed.connect(func(amount): hud.update_gold(amount)) inventory.gold_changed.connect(func(amount): hud.update_gold(amount))
@ -257,22 +269,80 @@ func _calculate_xp_for_level(target_level: int) -> int:
func _on_slot_clicked(slot_index: int): func _on_slot_clicked(slot_index: int):
_use_action_slot(slot_index) _use_action_slot(slot_index)
# Slot aus Aktionsleiste entfernen (rausgezogen) - Item/Skill bleibt verfügbar
func _on_slot_drag_removed(slot_index: int):
action_bar_items[slot_index] = null
hud.clear_slot_icon(slot_index)
hud.set_slot_stack_count(slot_index, 0)
print("Slot " + str(slot_index + 1) + " geleert")
# Zwei Slots tauschen
func _on_slot_drag_swapped(from_slot: int, to_slot: int):
var temp = action_bar_items[from_slot]
action_bar_items[from_slot] = action_bar_items[to_slot]
action_bar_items[to_slot] = temp
_refresh_action_slot(from_slot)
_refresh_action_slot(to_slot)
# Skill per ID auf Slot legen
func assign_skill_to_action_bar(slot_index: int, skill_id: String):
action_bar_items[slot_index] = skill_id
_refresh_action_slot(slot_index)
print(skill_id + " auf Slot " + str(slot_index + 1) + " gelegt")
# Skill-Info anhand ID holen
func get_skill_info(skill_id: String) -> Dictionary:
for skill in available_skills:
if skill["id"] == skill_id:
return skill
return {}
# Cooldown für einen Slot ermitteln
func _get_slot_cooldown(slot_index: int) -> float:
var entry = action_bar_items[slot_index]
if entry is String:
match entry:
"autoattack":
return global_cooldown
"heavy_strike":
return heavy_strike_cooldown
elif entry is Consumable:
return potion_cooldown
return 0.0
func _refresh_action_slot(slot_index: int):
var entry = action_bar_items[slot_index]
if entry is String:
# Skill
var info = get_skill_info(entry)
if info.size() > 0:
hud.set_slot_icon(slot_index, info["icon"])
else:
hud.clear_slot_icon(slot_index)
hud.set_slot_stack_count(slot_index, 0)
elif entry is Consumable and entry.icon:
hud.set_slot_icon_texture(slot_index, entry.icon)
hud.set_slot_stack_count(slot_index, entry.stack_size)
else:
hud.clear_slot_icon(slot_index)
hud.set_slot_stack_count(slot_index, 0)
func _use_action_slot(slot_index: int): func _use_action_slot(slot_index: int):
match slot_index: var entry = action_bar_items[slot_index]
0: # Autoattack manuell starten if entry is String:
if target != null and global_cooldown <= 0: # Skill ausführen
start_autoattack() match entry:
perform_autoattack() "autoattack":
1: # Heavy Strike if target != null and global_cooldown <= 0:
use_heavy_strike() start_autoattack()
_: # Slots 2-8: Consumables perform_autoattack()
var item = action_bar_items[slot_index] "heavy_strike":
if item is Consumable: use_heavy_strike()
if use_consumable(item): elif entry is Consumable:
# Item aus Inventar entfernen wenn Stack leer if use_consumable(entry):
if item.stack_size <= 0: if entry.stack_size <= 0:
inventory.remove_item(item) inventory.remove_item(entry)
_update_action_bar_stacks() _update_action_bar_stacks()
# Schaden am Spieler abziehen und HP-Leiste aktualisieren # Schaden am Spieler abziehen und HP-Leiste aktualisieren
func take_damage(amount): func take_damage(amount):
@ -539,9 +609,10 @@ func _physics_process(delta):
if potion_cooldown > 0: if potion_cooldown > 0:
potion_cooldown -= delta potion_cooldown -= delta
# HUD Cooldowns aktualisieren # HUD Cooldowns aktualisieren - generisch pro Slot
hud.set_slot_cooldown(0, global_cooldown) # Slot 1: GCD (Autoattack) for i in range(9):
hud.set_slot_cooldown(1, heavy_strike_cooldown) # Slot 2: Heavy Strike CD var cd = _get_slot_cooldown(i)
hud.set_slot_cooldown(i, cd)
# Schwerkraft # Schwerkraft
if not is_on_floor(): if not is_on_floor():
@ -559,36 +630,12 @@ func _physics_process(delta):
if Input.is_action_just_pressed("ui_right_mouse"): if Input.is_action_just_pressed("ui_right_mouse"):
_try_select_target(true) _try_select_target(true)
# Aktionsleiste 1-9 # Aktionsleiste 1-9 — alle generisch über _use_action_slot
if Input.is_action_just_pressed("action_1"): for i in range(9):
hud.set_active_slot(0) var action_name = "action_" + str(i + 1)
if target != null and global_cooldown <= 0: if Input.is_action_just_pressed(action_name):
start_autoattack() hud.set_active_slot(i)
perform_autoattack() _use_action_slot(i)
if Input.is_action_just_pressed("action_2"):
hud.set_active_slot(1)
use_heavy_strike()
if Input.is_action_just_pressed("action_3"):
hud.set_active_slot(2)
_use_action_slot(2)
if Input.is_action_just_pressed("action_4"):
hud.set_active_slot(3)
_use_action_slot(3)
if Input.is_action_just_pressed("action_5"):
hud.set_active_slot(4)
_use_action_slot(4)
if Input.is_action_just_pressed("action_6"):
hud.set_active_slot(5)
_use_action_slot(5)
if Input.is_action_just_pressed("action_7"):
hud.set_active_slot(6)
_use_action_slot(6)
if Input.is_action_just_pressed("action_8"):
hud.set_active_slot(7)
_use_action_slot(7)
if Input.is_action_just_pressed("action_9"):
hud.set_active_slot(8)
_use_action_slot(8)
# TEST: T drücken = 10 Schaden # TEST: T drücken = 10 Schaden
if Input.is_action_just_pressed("test_damage"): if Input.is_action_just_pressed("test_damage"):
@ -603,6 +650,10 @@ func _physics_process(delta):
if Input.is_action_just_pressed("toggle_inventory"): if Input.is_action_just_pressed("toggle_inventory"):
inventory_panel.toggle() inventory_panel.toggle()
# P drücken = Fähigkeiten-Panel öffnen/schließen
if Input.is_action_just_pressed("toggle_skills"):
skill_panel.toggle()
# Eingabe # Eingabe
var input_dir = Vector2.ZERO var input_dir = Vector2.ZERO
if Input.is_action_pressed("move_forward"): if Input.is_action_pressed("move_forward"):

View file

@ -7,6 +7,7 @@
[ext_resource type="PackedScene" uid="uid://character_panel" path="res://character_panel.tscn" id="5_char_panel"] [ext_resource type="PackedScene" uid="uid://character_panel" path="res://character_panel.tscn" id="5_char_panel"]
[ext_resource type="PackedScene" uid="uid://inventory_panel" path="res://inventory_panel.tscn" id="6_inv_panel"] [ext_resource type="PackedScene" uid="uid://inventory_panel" path="res://inventory_panel.tscn" id="6_inv_panel"]
[ext_resource type="PackedScene" uid="uid://loot_window" path="res://loot_window.tscn" id="7_loot_win"] [ext_resource type="PackedScene" uid="uid://loot_window" path="res://loot_window.tscn" id="7_loot_win"]
[ext_resource type="PackedScene" uid="uid://skill_panel" path="res://skill_panel.tscn" id="8_skill_panel"]
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_4flbx"] [sub_resource type="CapsuleShape3D" id="CapsuleShape3D_4flbx"]
radius = 0.6 radius = 0.6
@ -36,3 +37,5 @@ transform = Transform3D(2, 0, 0, 0, 1.8126155, 0.84523654, 0, -0.84523654, 1.812
[node name="InventoryPanel" parent="." instance=ExtResource("6_inv_panel")] [node name="InventoryPanel" parent="." instance=ExtResource("6_inv_panel")]
[node name="LootWindow" parent="." instance=ExtResource("7_loot_win")] [node name="LootWindow" parent="." instance=ExtResource("7_loot_win")]
[node name="SkillPanel" parent="." instance=ExtResource("8_skill_panel")]

View file

@ -107,6 +107,11 @@ toggle_inventory={
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":73,"key_label":0,"unicode":105,"location":0,"echo":false,"script":null) "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":73,"key_label":0,"unicode":105,"location":0,"echo":false,"script":null)
] ]
} }
toggle_skills={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":80,"key_label":0,"unicode":112,"location":0,"echo":false,"script":null)
]
}
[physics] [physics]

120
skill_panel.gd Normal file
View file

@ -0,0 +1,120 @@
# SkillPanel.gd
# Zeigt alle verfügbaren Fähigkeiten an, von hier aus auf Aktionsleiste ziehen
extends CanvasLayer
var panel_visible = false
var player = null
# Drag State
var dragging = false
var drag_skill_id: String = ""
var drag_icon: TextureRect = null
@onready var panel = $Panel
@onready var skill_list = $Panel/VBoxContainer/ScrollContainer/SkillList
func _ready():
panel.visible = false
func setup(p):
player = p
func toggle():
panel_visible = !panel_visible
panel.visible = panel_visible
if panel_visible:
_refresh_skills()
func _refresh_skills():
if player == null:
return
for child in skill_list.get_children():
child.queue_free()
for skill in player.available_skills:
var hbox = HBoxContainer.new()
hbox.add_theme_constant_override("separation", 8)
# Icon
var icon_rect = TextureRect.new()
var tex = load(skill["icon"])
if tex:
icon_rect.texture = tex
icon_rect.expand_mode = TextureRect.EXPAND_FIT_WIDTH_PROPORTIONAL
icon_rect.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
icon_rect.custom_minimum_size = Vector2(36, 36)
icon_rect.mouse_filter = Control.MOUSE_FILTER_IGNORE
hbox.add_child(icon_rect)
# Name + Beschreibung
var label = Label.new()
label.text = skill["name"]
label.add_theme_font_size_override("font_size", 14)
label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
label.mouse_filter = Control.MOUSE_FILTER_IGNORE
hbox.add_child(label)
# Tooltip
hbox.tooltip_text = skill["name"] + "\n" + skill["description"]
hbox.custom_minimum_size = Vector2(0, 40)
hbox.mouse_filter = Control.MOUSE_FILTER_STOP
# Drag starten bei Linksklick
var skill_id = skill["id"]
var skill_icon_path = skill["icon"]
hbox.gui_input.connect(_on_skill_input.bind(skill_id, skill_icon_path))
skill_list.add_child(hbox)
func _on_skill_input(event: InputEvent, skill_id: String, icon_path: String):
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.pressed:
_start_drag(skill_id, icon_path)
func _start_drag(skill_id: String, icon_path: String):
dragging = true
drag_skill_id = skill_id
var tex = load(icon_path)
drag_icon = TextureRect.new()
drag_icon.texture = tex
drag_icon.custom_minimum_size = Vector2(40, 40)
drag_icon.size = Vector2(40, 40)
drag_icon.expand_mode = TextureRect.EXPAND_FIT_WIDTH_PROPORTIONAL
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)
drag_icon.position = get_viewport().get_mouse_position() - Vector2(20, 20)
if player and player.hud:
player.hud.set_drag_active(true)
func _process(_delta):
if dragging and drag_icon:
drag_icon.position = get_viewport().get_mouse_position() - Vector2(20, 20)
if player and player.hud:
player.hud.update_drag_hover(get_viewport().get_mouse_position())
func _input(event):
if not dragging:
return
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and not event.pressed:
_end_drag()
func _end_drag():
if not dragging:
return
if player and player.hud:
var slot_index = player.hud.get_slot_at_position(get_viewport().get_mouse_position())
if slot_index >= 0 and slot_index <= 8 and drag_skill_id != "":
player.assign_skill_to_action_bar(slot_index, drag_skill_id)
player.hud.set_drag_active(false)
if drag_icon:
var drag_layer = drag_icon.get_parent()
drag_layer.queue_free()
drag_icon = null
dragging = false
drag_skill_id = ""

48
skill_panel.tscn Normal file
View file

@ -0,0 +1,48 @@
[gd_scene load_steps=2 format=3 uid="uid://skill_panel"]
[ext_resource type="Script" path="res://skill_panel.gd" id="1"]
[node name="SkillPanel" type="CanvasLayer"]
script = ExtResource("1")
[node name="Panel" type="Panel" parent="."]
anchors_preset = 0
offset_left = 250
offset_top = 100
offset_right = 530
offset_bottom = 400
custom_minimum_size = Vector2(280, 300)
[node name="VBoxContainer" type="VBoxContainer" parent="Panel"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 10
offset_top = 10
offset_right = -10
offset_bottom = -10
[node name="TitleLabel" type="Label" parent="Panel/VBoxContainer"]
layout_mode = 2
text = "Fähigkeiten"
horizontal_alignment = 1
theme_override_font_sizes/font_size = 18
[node name="HSeparator" type="HSeparator" parent="Panel/VBoxContainer"]
layout_mode = 2
[node name="HintLabel" type="Label" parent="Panel/VBoxContainer"]
layout_mode = 2
text = "Linksklick + Ziehen auf Aktionsleiste"
horizontal_alignment = 1
theme_override_font_sizes/font_size = 10
modulate = Color(0.7, 0.7, 0.7, 1)
[node name="ScrollContainer" type="ScrollContainer" parent="Panel/VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
[node name="SkillList" type="VBoxContainer" parent="Panel/VBoxContainer/ScrollContainer"]
layout_mode = 2
size_flags_horizontal = 3