inventory angefangen
This commit is contained in:
parent
04f22183ce
commit
1616431d1c
8 changed files with 749 additions and 59 deletions
|
|
@ -23,7 +23,9 @@ Gegner bekämpfen und ihre Charaktere mit verschiedenen Klassen und Ausrüstunge
|
|||
| RMB gehalten | Kamera drehen, Spieler schaut mit |
|
||||
| Linksklick auf Gegner | Ziel markieren |
|
||||
| Rechtsklick auf Gegner | Ziel markieren + Autoattack starten |
|
||||
| 1 – 9 | Aktionsleiste Slot auswählen |
|
||||
| 1 | Autoattack manuell starten |
|
||||
| 2 | Heavy Strike (starke Attacke) |
|
||||
| 3 – 9 | Aktionsleiste Slots (noch frei) |
|
||||
| Leertaste | Springen |
|
||||
| T | (Test) 10 Schaden am Spieler |
|
||||
|
||||
|
|
@ -198,6 +200,31 @@ Definiert einen Angriff — Schaden kommt von der ausgerüsteten Waffe.
|
|||
- Ohne Waffe: 1 Schaden, 1.5s Cooldown, 1.5 Reichweite
|
||||
- Mit Waffe: Schaden = zufällig zwischen min/max, Speed und Range von der Waffe
|
||||
|
||||
### Skills
|
||||
|
||||
#### Autoattack (Taste 1)
|
||||
- **Schaden**: 1 (unbewaffnet) oder Waffenschaden
|
||||
- **Cooldown**: 1.5s (unbewaffnet) oder Waffengeschwindigkeit
|
||||
- **Reichweite**: 1.5 (unbewaffnet) oder Waffenreichweite
|
||||
- **Icon**: Faust mit grünem Kreispfeil
|
||||
- Manuell per Taste 1 starten oder automatisch per Rechtsklick
|
||||
|
||||
#### Heavy Strike (Taste 2)
|
||||
- **Schaden**: 10-15 (zufällig)
|
||||
- **Cooldown**: 3 Sekunden
|
||||
- **Reichweite**: 2.0
|
||||
- **Icon**: Schwert mit roten Schlaglinien
|
||||
- Manuell aktivierbare starke Attacke für höheren Schaden
|
||||
|
||||
### UI & Icons
|
||||
- Aktionsleiste zeigt Icons für Skills an
|
||||
- Icons werden dynamisch beim Spielstart geladen
|
||||
- Slot 1: Autoattack Icon (Faust)
|
||||
- Slot 2: Heavy Strike Icon (Schwert)
|
||||
- Slots 3-9: Verfügbar für weitere Skills
|
||||
- **Mausklick**: Alle Slots sind per Maus klickbar
|
||||
- **Cooldown-Anzeige**: Dunkle Überlagerung mit verbleibender Zeit in Sekunden
|
||||
|
||||
### Schadenstypen (geplant)
|
||||
- **PHYSICAL** – normaler Schaden
|
||||
- **FIRE** – Feuerschaden (z.B. Magier)
|
||||
|
|
|
|||
182
enemy.gd
182
enemy.gd
|
|
@ -2,26 +2,95 @@
|
|||
# Steuert den Gegner: KI-Bewegung zum Spieler, Angriff, HP, Zielanzeige
|
||||
extends CharacterBody3D
|
||||
|
||||
signal enemy_died(spawn_position: Vector3, xp_reward: int)
|
||||
|
||||
const SPEED = 3.0
|
||||
const PATROL_SPEED = 1.5
|
||||
const GRAVITY = 9.8
|
||||
const ATTACK_DAMAGE = 5
|
||||
const ATTACK_RANGE = 1.5
|
||||
const ATTACK_COOLDOWN = 2.0
|
||||
const AGGRO_RANGE = 8.0 # Entfernung ab der der Gegner angreift
|
||||
const PATROL_RADIUS = 5.0 # Radius um Spawn-Position für Patrol
|
||||
const PATROL_WAIT_TIME = 2.0 # Wartezeit am Patrol-Punkt
|
||||
|
||||
var max_hp = 50
|
||||
var current_hp = 50
|
||||
var target = null # Ziel des Gegners (normalerweise der Spieler)
|
||||
# Level-Differenz Konstanten
|
||||
const LEVEL_DIFF_DAMAGE_MOD = 0.1 # 10% mehr/weniger Schaden pro Level-Differenz
|
||||
const MAX_LEVEL_DIFF_MOD = 0.5 # Maximal 50% Modifikation
|
||||
|
||||
enum State { PATROL, CHASE, ATTACK }
|
||||
|
||||
# Stats-System
|
||||
@export var level: int = 1
|
||||
@export var base_strength: int = 8
|
||||
@export var base_stamina: int = 10
|
||||
@export var base_armor: int = 5 # Rüstung reduziert Nahkampfschaden
|
||||
|
||||
# Berechnete Stats
|
||||
var strength: int = 8
|
||||
var stamina: int = 10
|
||||
var armor: int = 5
|
||||
var max_hp: int = 100
|
||||
var current_hp: int = 100
|
||||
var attack_damage: int = 5
|
||||
|
||||
# XP-Belohnung (skaliert mit Level)
|
||||
var xp_reward: int = 25
|
||||
|
||||
var target = null # Spieler-Referenz (wird von World gesetzt)
|
||||
var can_attack = true
|
||||
var spawn_position: Vector3 # Ursprüngliche Spawn-Position
|
||||
var current_state = State.PATROL
|
||||
var patrol_target: Vector3 # Aktuelles Patrol-Ziel
|
||||
var is_waiting = false # Ob Gegner am Patrol-Punkt wartet
|
||||
|
||||
@onready var health_label = $HealthLabel
|
||||
|
||||
func _ready():
|
||||
_calculate_stats()
|
||||
current_hp = max_hp
|
||||
health_label.visible = false
|
||||
_update_label()
|
||||
spawn_position = global_position
|
||||
_pick_new_patrol_target()
|
||||
|
||||
# Stats basierend auf Level berechnen
|
||||
func _calculate_stats():
|
||||
var levels_gained = level - 1
|
||||
strength = base_strength + levels_gained * 2
|
||||
stamina = base_stamina + levels_gained * 3
|
||||
armor = base_armor + levels_gained * 2
|
||||
|
||||
# HP = Stamina * 10
|
||||
max_hp = stamina * 10
|
||||
# Schaden = Stärke / 2
|
||||
attack_damage = int(strength * 0.5) + 2
|
||||
# XP = 25 * Level
|
||||
xp_reward = 25 * level
|
||||
|
||||
print("Enemy Stats (Lv", level, ") - STR:", strength, " STA:", stamina, " ARM:", armor, " HP:", max_hp, " DMG:", attack_damage)
|
||||
|
||||
# Schaden mit Rüstung und Level-Differenz berechnen
|
||||
func calculate_incoming_damage(raw_damage: int, attacker_level: int, is_melee: bool) -> int:
|
||||
var damage = float(raw_damage)
|
||||
|
||||
# Rüstung reduziert nur Nahkampfschaden
|
||||
if is_melee:
|
||||
# Rüstungsreduktion: armor / (armor + 50) = Prozent Reduktion
|
||||
# Bei 5 Rüstung: 5/55 = ~9% Reduktion
|
||||
# Bei 20 Rüstung: 20/70 = ~29% Reduktion
|
||||
var armor_reduction = float(armor) / (float(armor) + 50.0)
|
||||
damage = damage * (1.0 - armor_reduction)
|
||||
|
||||
# Level-Differenz Modifikator
|
||||
var level_diff = attacker_level - level
|
||||
var level_mod = clamp(level_diff * LEVEL_DIFF_DAMAGE_MOD, -MAX_LEVEL_DIFF_MOD, MAX_LEVEL_DIFF_MOD)
|
||||
damage = damage * (1.0 + level_mod)
|
||||
|
||||
return maxi(1, int(damage)) # Mindestens 1 Schaden
|
||||
|
||||
# HP-Label Text aktualisieren
|
||||
func _update_label():
|
||||
health_label.text = str(current_hp) + " / " + str(max_hp)
|
||||
health_label.text = "Lv" + str(level) + " " + str(current_hp) + "/" + str(max_hp)
|
||||
|
||||
# HP-Label anzeigen (wenn Gegner markiert wird)
|
||||
func show_health():
|
||||
|
|
@ -31,16 +100,26 @@ func show_health():
|
|||
func hide_health():
|
||||
health_label.visible = false
|
||||
|
||||
# Schaden nehmen und Label aktualisieren
|
||||
# Schaden nehmen und Label aktualisieren (alte Methode für Kompatibilität)
|
||||
func take_damage(amount):
|
||||
current_hp -= amount
|
||||
_update_label()
|
||||
if current_hp <= 0:
|
||||
die()
|
||||
|
||||
# Schaden mit vollem Schadenssystem (Rüstung, Level-Differenz)
|
||||
func take_damage_from(raw_damage: int, attacker_level: int, is_melee: bool = true):
|
||||
var final_damage = calculate_incoming_damage(raw_damage, attacker_level, is_melee)
|
||||
print("Eingehender Schaden: ", raw_damage, " -> ", final_damage, " (nach Rüstung/Level)")
|
||||
take_damage(final_damage)
|
||||
|
||||
# Gegner aus der Szene entfernen
|
||||
func die():
|
||||
print("Gegner besiegt!")
|
||||
print("Gegner besiegt! +", xp_reward, " XP")
|
||||
# XP an Spieler geben
|
||||
if target and target.has_method("gain_xp"):
|
||||
target.gain_xp(xp_reward)
|
||||
enemy_died.emit(spawn_position, xp_reward)
|
||||
queue_free()
|
||||
|
||||
func _physics_process(delta):
|
||||
|
|
@ -51,29 +130,84 @@ func _physics_process(delta):
|
|||
move_and_slide()
|
||||
return
|
||||
|
||||
var distance = global_position.distance_to(target.global_position)
|
||||
# Prüfe Distanz zum Spieler für Aggro
|
||||
var distance_to_player = global_position.distance_to(target.global_position)
|
||||
|
||||
if distance <= ATTACK_RANGE:
|
||||
# In Reichweite: angreifen
|
||||
velocity.x = 0
|
||||
velocity.z = 0
|
||||
if can_attack:
|
||||
_attack()
|
||||
else:
|
||||
# Direkt auf Ziel zubewegen
|
||||
var direction = (target.global_position - global_position)
|
||||
direction.y = 0
|
||||
direction = direction.normalized()
|
||||
velocity.x = direction.x * SPEED
|
||||
velocity.z = direction.z * SPEED
|
||||
look_at(Vector3(target.global_position.x, global_position.y, target.global_position.z))
|
||||
# State-Wechsel basierend auf Distanz
|
||||
match current_state:
|
||||
State.PATROL:
|
||||
if distance_to_player <= AGGRO_RANGE:
|
||||
current_state = State.CHASE
|
||||
print("Gegner hat Spieler entdeckt!")
|
||||
else:
|
||||
_do_patrol()
|
||||
State.CHASE:
|
||||
if distance_to_player <= ATTACK_RANGE:
|
||||
current_state = State.ATTACK
|
||||
else:
|
||||
_chase_player()
|
||||
State.ATTACK:
|
||||
if distance_to_player > ATTACK_RANGE:
|
||||
current_state = State.CHASE
|
||||
else:
|
||||
velocity.x = 0
|
||||
velocity.z = 0
|
||||
if can_attack:
|
||||
_attack()
|
||||
|
||||
move_and_slide()
|
||||
|
||||
# Neues Patrol-Ziel in der Nähe der Spawn-Position wählen
|
||||
func _pick_new_patrol_target():
|
||||
var angle = randf() * TAU # Zufälliger Winkel
|
||||
var distance = randf_range(2.0, PATROL_RADIUS)
|
||||
patrol_target = spawn_position + Vector3(cos(angle) * distance, 0, sin(angle) * distance)
|
||||
|
||||
# Patrol-Verhalten: Zufällig herumlaufen
|
||||
func _do_patrol():
|
||||
if is_waiting:
|
||||
return
|
||||
|
||||
var distance_to_patrol = global_position.distance_to(patrol_target)
|
||||
|
||||
if distance_to_patrol <= 0.5:
|
||||
# Am Ziel angekommen, warten und neues Ziel wählen
|
||||
velocity.x = 0
|
||||
velocity.z = 0
|
||||
_wait_at_patrol_point()
|
||||
else:
|
||||
# Zum Patrol-Ziel laufen
|
||||
var direction = (patrol_target - global_position)
|
||||
direction.y = 0
|
||||
direction = direction.normalized()
|
||||
velocity.x = direction.x * PATROL_SPEED
|
||||
velocity.z = direction.z * PATROL_SPEED
|
||||
look_at(Vector3(patrol_target.x, global_position.y, patrol_target.z))
|
||||
|
||||
# Am Patrol-Punkt warten
|
||||
func _wait_at_patrol_point():
|
||||
is_waiting = true
|
||||
await get_tree().create_timer(PATROL_WAIT_TIME).timeout
|
||||
is_waiting = false
|
||||
_pick_new_patrol_target()
|
||||
|
||||
# Spieler verfolgen
|
||||
func _chase_player():
|
||||
var direction = (target.global_position - global_position)
|
||||
direction.y = 0
|
||||
direction = direction.normalized()
|
||||
velocity.x = direction.x * SPEED
|
||||
velocity.z = direction.z * SPEED
|
||||
look_at(Vector3(target.global_position.x, global_position.y, target.global_position.z))
|
||||
|
||||
# Angriff mit Cooldown
|
||||
func _attack():
|
||||
can_attack = false
|
||||
target.take_damage(ATTACK_DAMAGE)
|
||||
print("Gegner greift an!")
|
||||
# Gegner verwendet auch das Schadenssystem mit Level-Differenz
|
||||
if target.has_method("take_damage_from"):
|
||||
target.take_damage_from(attack_damage, level, true)
|
||||
else:
|
||||
target.take_damage(attack_damage)
|
||||
print("Gegner (Lv", level, ") greift an: ", attack_damage, " Schaden")
|
||||
await get_tree().create_timer(ATTACK_COOLDOWN).timeout
|
||||
can_attack = true
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
[gd_scene format=3 uid="uid://cvojaeanxugfj"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://tosl2au4emxt" path="res://assets/kenney_blocky-characters_20/Models/GLB format/character-d.glb" id="1_7k104"]
|
||||
[ext_resource type="Script" uid="uid://bg5qs3pcfp7p7" path="res://enemy.gd" id="2_enemy"]
|
||||
|
||||
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_4gyqm"]
|
||||
radius = 0.6
|
||||
|
|
@ -11,6 +12,7 @@ radius = 0.6
|
|||
height = 3.0
|
||||
|
||||
[node name="Enemy" type="CharacterBody3D" unique_id=332011146]
|
||||
script = ExtResource("2_enemy")
|
||||
|
||||
[node name="character-d2" parent="." unique_id=846574684 instance=ExtResource("1_7k104")]
|
||||
transform = Transform3D(-1, 0, -8.742278e-08, 0, 1, 0, 8.742278e-08, 0, -1, 0, 0, 0)
|
||||
|
|
|
|||
122
hud.gd
122
hud.gd
|
|
@ -1,9 +1,15 @@
|
|||
# HUD.gd
|
||||
# Verwaltet die Spieler-UI: HP-Leiste, Aktionsleiste (Slots 1-9)
|
||||
# Verwaltet die Spieler-UI: HP-Leiste, XP-Leiste, Aktionsleiste (Slots 1-9)
|
||||
extends CanvasLayer
|
||||
|
||||
signal slot_clicked(slot_index: int)
|
||||
|
||||
@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
|
||||
@onready var action_slots = [
|
||||
$Control/ActionBar/A1,
|
||||
$Control/ActionBar/A2,
|
||||
|
|
@ -17,6 +23,112 @@ extends CanvasLayer
|
|||
]
|
||||
|
||||
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
|
||||
|
||||
func _ready():
|
||||
_create_level_ui()
|
||||
|
||||
for i in range(9):
|
||||
# Icon erstellen
|
||||
var icon = TextureRect.new()
|
||||
icon.name = "Icon"
|
||||
icon.expand_mode = TextureRect.EXPAND_FIT_WIDTH_PROPORTIONAL
|
||||
icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
||||
icon.custom_minimum_size = Vector2(40, 40)
|
||||
icon.position = Vector2(5, 5)
|
||||
action_slots[i].add_child(icon)
|
||||
slot_icons.append(icon)
|
||||
|
||||
# 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)
|
||||
|
||||
# Button für Klicks 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))
|
||||
action_slots[i].add_child(button)
|
||||
|
||||
# Slot-Klick Handler
|
||||
func _on_slot_clicked(slot_index: int):
|
||||
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
|
||||
|
||||
# Level Label
|
||||
level_label = Label.new()
|
||||
level_label.name = "LevelLabel"
|
||||
level_label.position = Vector2(20, 55)
|
||||
level_label.add_theme_font_size_override("font_size", 14)
|
||||
level_label.text = "Level 1"
|
||||
control.add_child(level_label)
|
||||
|
||||
# XP Bar
|
||||
xp_bar = ProgressBar.new()
|
||||
xp_bar.name = "XPBar"
|
||||
xp_bar.position = Vector2(80, 55)
|
||||
xp_bar.size = Vector2(140, 18)
|
||||
xp_bar.show_percentage = false
|
||||
xp_bar.value = 0
|
||||
|
||||
# XP Bar Farbe (blau)
|
||||
var xp_style = StyleBoxFlat.new()
|
||||
xp_style.bg_color = Color(0.2, 0.4, 0.9, 1.0)
|
||||
xp_bar.add_theme_stylebox_override("fill", xp_style)
|
||||
|
||||
control.add_child(xp_bar)
|
||||
|
||||
# HP-Leiste und Text aktualisieren
|
||||
func update_health(current_hp, max_hp):
|
||||
|
|
@ -24,6 +136,14 @@ func update_health(current_hp, 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
|
||||
|
||||
# Aktions-Slot kurz golden hervorheben (0.1s)
|
||||
func set_active_slot(index):
|
||||
action_slots[active_slot].self_modulate = Color(1, 1, 1)
|
||||
|
|
|
|||
408
player.gd
408
player.gd
|
|
@ -6,19 +6,216 @@ const SPEED = 5.0
|
|||
const JUMP_VELOCITY = 4.5
|
||||
const GRAVITY = 9.8
|
||||
|
||||
# Charakter-Klasse und Level-System
|
||||
@export var character_class: CharacterClass
|
||||
var level: int = 1
|
||||
var current_xp: int = 0
|
||||
var xp_to_next_level: int = 100 # XP benötigt für Level 2
|
||||
|
||||
# Aktuelle Stats (berechnet aus Klasse + Level)
|
||||
var strength: int = 10
|
||||
var agility: int = 10
|
||||
var intelligence: int = 10
|
||||
var stamina: int = 10
|
||||
var armor: int = 0 # Rüstung aus Ausrüstung
|
||||
|
||||
# Level-Differenz Konstanten
|
||||
const LEVEL_DIFF_DAMAGE_MOD = 0.1 # 10% mehr/weniger Schaden pro Level-Differenz
|
||||
const MAX_LEVEL_DIFF_MOD = 0.5 # Maximal 50% Modifikation
|
||||
|
||||
var max_hp = 100
|
||||
var current_hp = 100
|
||||
var can_attack = true
|
||||
var target = null # Aktuell markierter Gegner
|
||||
var equipped_weapon = null # Ausgerüstete Waffe (null = unbewaffnet, Schaden = 1)
|
||||
|
||||
# Equipment System
|
||||
var equipment: Dictionary = {
|
||||
Equipment.Slot.HEAD: null,
|
||||
Equipment.Slot.CHEST: null,
|
||||
Equipment.Slot.HANDS: null,
|
||||
Equipment.Slot.LEGS: null,
|
||||
Equipment.Slot.FEET: null,
|
||||
Equipment.Slot.WEAPON: null,
|
||||
Equipment.Slot.OFFHAND: null
|
||||
}
|
||||
|
||||
# Inventar System
|
||||
var inventory: Inventory = Inventory.new()
|
||||
|
||||
# Global Cooldown System (GCD) - gilt für alle Aktionen inkl. Autoattack
|
||||
var global_cooldown = 0.0
|
||||
const BASE_GCD = 1.5 # Basis-GCD in Sekunden (wird durch Haste modifiziert)
|
||||
var haste: float = 0.0 # Angriffsgeschwindigkeits-Bonus (0.1 = 10% schneller)
|
||||
|
||||
# Autoattack System
|
||||
var autoattack_active = false # Ob Autoattack aktiv ist
|
||||
|
||||
# Skills System - individuelle Cooldowns (zusätzlich zum GCD)
|
||||
var heavy_strike_cooldown = 0.0
|
||||
const HEAVY_STRIKE_DAMAGE_MIN = 10
|
||||
const HEAVY_STRIKE_DAMAGE_MAX = 15
|
||||
const HEAVY_STRIKE_COOLDOWN = 3.0
|
||||
const HEAVY_STRIKE_RANGE = 4.0
|
||||
|
||||
@onready var camera_pivot = $CameraPivot
|
||||
@onready var camera = $CameraPivot/Camera3D
|
||||
@onready var hud = $HUD
|
||||
@onready var character_panel = $CharacterPanel
|
||||
@onready var inventory_panel = $InventoryPanel
|
||||
|
||||
func _ready():
|
||||
# Stats aus Klasse berechnen
|
||||
_calculate_stats()
|
||||
current_hp = max_hp
|
||||
|
||||
hud.update_health(current_hp, max_hp)
|
||||
hud.update_level(level, current_xp, xp_to_next_level)
|
||||
hud.set_active_slot(0)
|
||||
# Icons für Skills setzen
|
||||
hud.set_slot_icon(0, "res://icons/autoattack_icon.svg") # Slot 1: Autoattack
|
||||
hud.set_slot_icon(1, "res://icons/heavy_strike_icon.svg") # Slot 2: Heavy Strike
|
||||
|
||||
# HUD-Klicks verbinden
|
||||
hud.slot_clicked.connect(_on_slot_clicked)
|
||||
|
||||
# Inventar Panel initialisieren
|
||||
inventory_panel.setup(self)
|
||||
|
||||
# Stats basierend auf Klasse und Level berechnen
|
||||
func _calculate_stats():
|
||||
if character_class == null:
|
||||
# Fallback ohne Klasse
|
||||
strength = 10
|
||||
agility = 10
|
||||
intelligence = 10
|
||||
stamina = 10
|
||||
max_hp = 100
|
||||
return
|
||||
|
||||
# Stats = Basis + (Level-1) * Zuwachs pro Level
|
||||
var levels_gained = level - 1
|
||||
strength = character_class.base_strength + int(levels_gained * character_class.strength_per_level)
|
||||
agility = character_class.base_agility + int(levels_gained * character_class.agility_per_level)
|
||||
intelligence = character_class.base_intelligence + int(levels_gained * character_class.intelligence_per_level)
|
||||
stamina = character_class.base_stamina + int(levels_gained * character_class.stamina_per_level)
|
||||
|
||||
# HP aus Stamina berechnen
|
||||
max_hp = stamina * CharacterClass.HP_PER_STAMINA
|
||||
|
||||
# Equipment-Boni hinzufügen
|
||||
_apply_equipment_stats()
|
||||
|
||||
print("Stats berechnet - STR: ", strength, " AGI: ", agility, " INT: ", intelligence, " STA: ", stamina, " ARM: ", armor, " HP: ", max_hp)
|
||||
|
||||
# Equipment-Stats auf Charakter anwenden
|
||||
func _apply_equipment_stats():
|
||||
armor = 0
|
||||
haste = 0.0
|
||||
var bonus_str = 0
|
||||
var bonus_agi = 0
|
||||
var bonus_int = 0
|
||||
var bonus_sta = 0
|
||||
|
||||
for slot in equipment.keys():
|
||||
var item = equipment[slot]
|
||||
if item != null:
|
||||
armor += item.armor
|
||||
haste += item.haste
|
||||
bonus_str += item.strength
|
||||
bonus_agi += item.agility
|
||||
bonus_int += item.intelligence
|
||||
bonus_sta += item.stamina
|
||||
|
||||
strength += bonus_str
|
||||
agility += bonus_agi
|
||||
intelligence += bonus_int
|
||||
stamina += bonus_sta
|
||||
|
||||
# HP neu berechnen mit Equipment-Stamina
|
||||
max_hp = stamina * CharacterClass.HP_PER_STAMINA
|
||||
|
||||
# Equipment anlegen
|
||||
func equip_item(item: Equipment) -> Equipment:
|
||||
var old_item = equipment[item.slot]
|
||||
equipment[item.slot] = item
|
||||
_calculate_stats()
|
||||
# HP proportional anpassen
|
||||
if max_hp > 0:
|
||||
current_hp = mini(current_hp, max_hp)
|
||||
hud.update_health(current_hp, max_hp)
|
||||
character_panel.update_stats(self)
|
||||
print("Ausgerüstet: ", item.item_name, " in Slot ", Equipment.get_slot_name(item.slot))
|
||||
return old_item
|
||||
|
||||
# Equipment ablegen
|
||||
func unequip_slot(slot: Equipment.Slot) -> Equipment:
|
||||
var old_item = equipment[slot]
|
||||
if old_item == null:
|
||||
return null
|
||||
equipment[slot] = null
|
||||
_calculate_stats()
|
||||
current_hp = mini(current_hp, max_hp)
|
||||
hud.update_health(current_hp, max_hp)
|
||||
character_panel.update_stats(self)
|
||||
print("Abgelegt: ", old_item.item_name)
|
||||
return old_item
|
||||
|
||||
# Ausgerüstete Waffe holen
|
||||
func get_equipped_weapon() -> Equipment:
|
||||
return equipment[Equipment.Slot.WEAPON]
|
||||
|
||||
# Main-Stat für Schadensberechnung holen
|
||||
func get_main_stat() -> int:
|
||||
if character_class == null:
|
||||
return 10
|
||||
match character_class.main_stat:
|
||||
CharacterClass.MainStat.STRENGTH:
|
||||
return strength
|
||||
CharacterClass.MainStat.AGILITY:
|
||||
return agility
|
||||
CharacterClass.MainStat.INTELLIGENCE:
|
||||
return intelligence
|
||||
return 10
|
||||
|
||||
# XP erhalten und Level-Up prüfen
|
||||
func gain_xp(amount: int):
|
||||
current_xp += amount
|
||||
print("+" , amount, " XP (", current_xp, "/", xp_to_next_level, ")")
|
||||
|
||||
while current_xp >= xp_to_next_level:
|
||||
_level_up()
|
||||
|
||||
hud.update_level(level, current_xp, xp_to_next_level)
|
||||
|
||||
# Level-Up durchführen
|
||||
func _level_up():
|
||||
current_xp -= xp_to_next_level
|
||||
level += 1
|
||||
xp_to_next_level = _calculate_xp_for_level(level + 1)
|
||||
|
||||
# Stats neu berechnen
|
||||
_calculate_stats()
|
||||
|
||||
# HP vollständig auffüllen bei Level-Up
|
||||
current_hp = max_hp
|
||||
|
||||
hud.update_health(current_hp, max_hp)
|
||||
# Character Panel aktualisieren falls offen
|
||||
character_panel.update_stats(self)
|
||||
print("LEVEL UP! Jetzt Level ", level, " - HP voll aufgefüllt!")
|
||||
|
||||
# XP-Kurve: Jedes Level braucht mehr XP
|
||||
func _calculate_xp_for_level(target_level: int) -> int:
|
||||
return 100 * target_level # Level 2: 100, Level 3: 200, etc.
|
||||
|
||||
# Handler für HUD-Slot-Klicks
|
||||
func _on_slot_clicked(slot_index: int):
|
||||
match slot_index:
|
||||
0: # Autoattack manuell starten
|
||||
if target != null and global_cooldown <= 0:
|
||||
start_autoattack()
|
||||
perform_autoattack()
|
||||
1: # Heavy Strike
|
||||
use_heavy_strike()
|
||||
|
||||
# Schaden am Spieler abziehen und HP-Leiste aktualisieren
|
||||
func take_damage(amount):
|
||||
|
|
@ -27,6 +224,28 @@ func take_damage(amount):
|
|||
if current_hp <= 0:
|
||||
die()
|
||||
|
||||
# Schaden mit Rüstung und Level-Differenz berechnen
|
||||
func calculate_incoming_damage(raw_damage: int, attacker_level: int, is_melee: bool) -> int:
|
||||
var damage = float(raw_damage)
|
||||
|
||||
# Rüstung reduziert nur Nahkampfschaden
|
||||
if is_melee and armor > 0:
|
||||
var armor_reduction = float(armor) / (float(armor) + 50.0)
|
||||
damage = damage * (1.0 - armor_reduction)
|
||||
|
||||
# Level-Differenz Modifikator (Gegner höheres Level = mehr Schaden)
|
||||
var level_diff = attacker_level - level
|
||||
var level_mod = clamp(level_diff * LEVEL_DIFF_DAMAGE_MOD, -MAX_LEVEL_DIFF_MOD, MAX_LEVEL_DIFF_MOD)
|
||||
damage = damage * (1.0 + level_mod)
|
||||
|
||||
return maxi(1, int(damage))
|
||||
|
||||
# Schaden mit vollem Schadenssystem nehmen
|
||||
func take_damage_from(raw_damage: int, attacker_level: int, is_melee: bool = true):
|
||||
var final_damage = calculate_incoming_damage(raw_damage, attacker_level, is_melee)
|
||||
print("Spieler nimmt Schaden: ", raw_damage, " -> ", final_damage, " (nach Rüstung/Level)")
|
||||
take_damage(final_damage)
|
||||
|
||||
# HP heilen und HP-Leiste aktualisieren
|
||||
func heal(amount):
|
||||
current_hp = clamp(current_hp + amount, 0, max_hp)
|
||||
|
|
@ -35,23 +254,66 @@ func heal(amount):
|
|||
func die():
|
||||
print("Spieler gestorben!")
|
||||
|
||||
# Schaden basierend auf ausgerüsteter Waffe (unbewaffnet = 1)
|
||||
# Schaden basierend auf ausgerüsteter Waffe + Main-Stat Skalierung
|
||||
func get_attack_damage() -> int:
|
||||
if equipped_weapon == null:
|
||||
return 1
|
||||
return randi_range(equipped_weapon.min_damage, equipped_weapon.max_damage)
|
||||
var weapon = get_equipped_weapon()
|
||||
var base_damage: int
|
||||
if weapon == null:
|
||||
# Unbewaffneter Schaden klassenabhängig
|
||||
if character_class:
|
||||
base_damage = randi_range(character_class.unarmed_min_damage, character_class.unarmed_max_damage)
|
||||
else:
|
||||
base_damage = 1
|
||||
else:
|
||||
base_damage = randi_range(weapon.min_damage, weapon.max_damage)
|
||||
|
||||
# Reichweite basierend auf ausgerüsteter Waffe (unbewaffnet = 1.5)
|
||||
# Schaden skaliert mit Main-Stat
|
||||
var stat_bonus = int(get_main_stat() * CharacterClass.DAMAGE_PER_MAIN_STAT)
|
||||
return base_damage + stat_bonus
|
||||
|
||||
# Aktuellen GCD berechnen (mit Haste-Modifikator)
|
||||
func get_current_gcd() -> float:
|
||||
var weapon = get_equipped_weapon()
|
||||
var base_speed: float
|
||||
if weapon == null:
|
||||
# Unbewaffnete Angriffsgeschwindigkeit klassenabhängig
|
||||
if character_class:
|
||||
base_speed = character_class.unarmed_attack_speed
|
||||
else:
|
||||
base_speed = BASE_GCD
|
||||
else:
|
||||
base_speed = weapon.attack_speed
|
||||
|
||||
# Haste reduziert den GCD: GCD = Basis / (1 + Haste)
|
||||
# Bei 0.5 Haste (50%): 1.5s / 1.5 = 1.0s
|
||||
return base_speed / (1.0 + haste)
|
||||
|
||||
# DPS berechnen (für Anzeige)
|
||||
func get_dps() -> float:
|
||||
var weapon = get_equipped_weapon()
|
||||
var avg_damage: float
|
||||
if weapon == null:
|
||||
# Unbewaffneter Durchschnittsschaden klassenabhängig
|
||||
if character_class:
|
||||
avg_damage = (character_class.unarmed_min_damage + character_class.unarmed_max_damage) / 2.0
|
||||
else:
|
||||
avg_damage = 1.0
|
||||
else:
|
||||
avg_damage = (weapon.min_damage + weapon.max_damage) / 2.0
|
||||
|
||||
var stat_bonus = get_main_stat() * CharacterClass.DAMAGE_PER_MAIN_STAT
|
||||
var total_damage = avg_damage + stat_bonus
|
||||
var gcd = get_current_gcd()
|
||||
|
||||
# DPS = Schaden / GCD
|
||||
return total_damage / gcd
|
||||
|
||||
# Reichweite basierend auf ausgerüsteter Waffe (unbewaffnet = 3.0)
|
||||
func get_attack_range() -> float:
|
||||
if equipped_weapon == null:
|
||||
return 1.5
|
||||
return equipped_weapon.range
|
||||
|
||||
# Angriffsgeschwindigkeit basierend auf ausgerüsteter Waffe (unbewaffnet = 1.5s)
|
||||
func get_attack_cooldown() -> float:
|
||||
if equipped_weapon == null:
|
||||
return 1.5
|
||||
return equipped_weapon.attack_speed
|
||||
var weapon = get_equipped_weapon()
|
||||
if weapon == null:
|
||||
return 3.0
|
||||
return weapon.weapon_range
|
||||
|
||||
# Ziel markieren — start_attack=true startet sofort die Autoattack
|
||||
func set_target(new_target, start_attack: bool = false):
|
||||
|
|
@ -60,29 +322,80 @@ func set_target(new_target, start_attack: bool = false):
|
|||
target = new_target
|
||||
target.show_health()
|
||||
print("Ziel markiert: ", target.name)
|
||||
if start_attack and can_attack:
|
||||
autoattack()
|
||||
if start_attack:
|
||||
start_autoattack()
|
||||
if global_cooldown <= 0:
|
||||
perform_autoattack()
|
||||
|
||||
# Autoattack: greift wiederholt an solange Ziel gültig ist
|
||||
func autoattack():
|
||||
# Ziel komplett aufheben und Autoattack stoppen
|
||||
func clear_target():
|
||||
if target != null and is_instance_valid(target):
|
||||
target.hide_health()
|
||||
target = null
|
||||
autoattack_active = false
|
||||
print("Ziel aufgehoben, Autoattack gestoppt")
|
||||
|
||||
# Autoattack aktivieren
|
||||
func start_autoattack():
|
||||
autoattack_active = true
|
||||
print("Autoattack aktiviert")
|
||||
|
||||
# Autoattack deaktivieren
|
||||
func stop_autoattack():
|
||||
autoattack_active = false
|
||||
print("Autoattack deaktiviert")
|
||||
|
||||
# Führt einen Autoattack aus (wird vom GCD-System aufgerufen)
|
||||
func perform_autoattack():
|
||||
if target == null or not is_instance_valid(target):
|
||||
target = null
|
||||
return
|
||||
if not can_attack:
|
||||
autoattack_active = false
|
||||
return
|
||||
|
||||
var distance = global_position.distance_to(target.global_position)
|
||||
if distance <= get_attack_range():
|
||||
can_attack = false
|
||||
var dmg = get_attack_damage()
|
||||
target.take_damage(dmg)
|
||||
print("Autoattack: ", dmg, " Schaden")
|
||||
await get_tree().create_timer(get_attack_cooldown()).timeout
|
||||
can_attack = true
|
||||
autoattack()
|
||||
# Neues Schadenssystem mit Rüstung und Level-Differenz
|
||||
if target.has_method("take_damage_from"):
|
||||
target.take_damage_from(dmg, level, true) # true = Nahkampf
|
||||
else:
|
||||
target.take_damage(dmg)
|
||||
print("Autoattack: ", dmg, " Schaden (GCD: %.2fs, DPS: %.1f)" % [get_current_gcd(), get_dps()])
|
||||
# GCD auslösen basierend auf Waffengeschwindigkeit + Haste
|
||||
trigger_global_cooldown()
|
||||
|
||||
# Global Cooldown auslösen (basierend auf Waffe + Haste)
|
||||
func trigger_global_cooldown():
|
||||
global_cooldown = get_current_gcd()
|
||||
|
||||
# Heavy Strike: Starker Angriff mit Cooldown
|
||||
func use_heavy_strike():
|
||||
if target == null or not is_instance_valid(target):
|
||||
print("Kein Ziel für Heavy Strike!")
|
||||
return
|
||||
|
||||
# Nur Skill-eigener Cooldown Check (kein GCD-Check!)
|
||||
if heavy_strike_cooldown > 0:
|
||||
print("Heavy Strike noch im Cooldown: ", "%.1f" % heavy_strike_cooldown, "s")
|
||||
return
|
||||
|
||||
var distance = global_position.distance_to(target.global_position)
|
||||
if distance > HEAVY_STRIKE_RANGE:
|
||||
print("Ziel zu weit entfernt für Heavy Strike!")
|
||||
return
|
||||
|
||||
var base_damage = randi_range(HEAVY_STRIKE_DAMAGE_MIN, HEAVY_STRIKE_DAMAGE_MAX)
|
||||
var stat_bonus = int(get_main_stat() * CharacterClass.DAMAGE_PER_MAIN_STAT)
|
||||
var damage = base_damage + stat_bonus
|
||||
# Neues Schadenssystem mit Rüstung und Level-Differenz
|
||||
if target.has_method("take_damage_from"):
|
||||
target.take_damage_from(damage, level, true) # true = Nahkampf
|
||||
else:
|
||||
await get_tree().create_timer(0.5).timeout
|
||||
autoattack()
|
||||
target.take_damage(damage)
|
||||
heavy_strike_cooldown = HEAVY_STRIKE_COOLDOWN
|
||||
trigger_global_cooldown() # GCD zurücksetzen damit Autoattack nicht sofort feuert
|
||||
start_autoattack() # Autoattack nach Skill automatisch aktivieren
|
||||
print("Heavy Strike! ", damage, " Rohschaden")
|
||||
|
||||
# Raycast von der Kamera auf Mausposition — trifft Gegner mit take_damage()
|
||||
func _try_select_target(start_attack: bool = false):
|
||||
|
|
@ -96,8 +409,28 @@ func _try_select_target(start_attack: bool = false):
|
|||
var result = space_state.intersect_ray(query)
|
||||
if result and result.collider.has_method("take_damage"):
|
||||
set_target(result.collider, start_attack)
|
||||
elif not start_attack:
|
||||
# Nur bei Linksklick ins Leere: Ziel deselektieren und Autoattack stoppen
|
||||
# Rechtsklick wird für Kameradrehung verwendet
|
||||
clear_target()
|
||||
|
||||
func _physics_process(delta):
|
||||
# Global Cooldown herunterzählen (gilt für alle Aktionen)
|
||||
if global_cooldown > 0:
|
||||
global_cooldown -= delta
|
||||
|
||||
# Wenn GCD bereit und Autoattack aktiv, versuche anzugreifen
|
||||
if global_cooldown <= 0 and autoattack_active:
|
||||
perform_autoattack()
|
||||
|
||||
# Skill-Cooldowns herunterzählen
|
||||
if heavy_strike_cooldown > 0:
|
||||
heavy_strike_cooldown -= delta
|
||||
|
||||
# HUD Cooldowns aktualisieren
|
||||
hud.set_slot_cooldown(0, global_cooldown) # Slot 1: GCD (Autoattack)
|
||||
hud.set_slot_cooldown(1, heavy_strike_cooldown) # Slot 2: Heavy Strike CD
|
||||
|
||||
# Schwerkraft
|
||||
if not is_on_floor():
|
||||
velocity.y -= GRAVITY * delta
|
||||
|
|
@ -117,10 +450,12 @@ func _physics_process(delta):
|
|||
# Aktionsleiste 1-9
|
||||
if Input.is_action_just_pressed("action_1"):
|
||||
hud.set_active_slot(0)
|
||||
if target != null:
|
||||
autoattack()
|
||||
if target != null and global_cooldown <= 0:
|
||||
start_autoattack()
|
||||
perform_autoattack()
|
||||
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)
|
||||
if Input.is_action_just_pressed("action_4"):
|
||||
|
|
@ -140,6 +475,15 @@ func _physics_process(delta):
|
|||
if Input.is_action_just_pressed("test_damage"):
|
||||
take_damage(10)
|
||||
|
||||
# C drücken = Charakter-Panel öffnen/schließen
|
||||
if Input.is_action_just_pressed("toggle_character"):
|
||||
character_panel.update_stats(self)
|
||||
character_panel.toggle()
|
||||
|
||||
# I drücken = Inventar öffnen/schließen
|
||||
if Input.is_action_just_pressed("toggle_inventory"):
|
||||
inventory_panel.toggle()
|
||||
|
||||
# Eingabe
|
||||
var input_dir = Vector2.ZERO
|
||||
if Input.is_action_pressed("move_forward"):
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@
|
|||
[ext_resource type="PackedScene" uid="uid://01rrtitc6yh1" path="res://assets/kenney_blocky-characters_20/Models/GLB format/character-b.glb" id="2_hqtel"]
|
||||
[ext_resource type="Script" uid="uid://bwtwon54po4w3" path="res://camera_pivot.gd" id="2_onrkg"]
|
||||
[ext_resource type="PackedScene" uid="uid://bej3excyoxrdh" path="res://hud.tscn" id="4_hqtel"]
|
||||
[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"]
|
||||
|
||||
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_4flbx"]
|
||||
radius = 0.6
|
||||
|
|
@ -27,3 +29,7 @@ script = ExtResource("2_onrkg")
|
|||
transform = Transform3D(2, 0, 0, 0, 1.8126155, 0.84523654, 0, -0.84523654, 1.8126155, 0, 5, 5)
|
||||
|
||||
[node name="HUD" parent="." unique_id=1901284390 instance=ExtResource("4_hqtel")]
|
||||
|
||||
[node name="CharacterPanel" parent="." instance=ExtResource("5_char_panel")]
|
||||
|
||||
[node name="InventoryPanel" parent="." instance=ExtResource("6_inv_panel")]
|
||||
|
|
|
|||
|
|
@ -97,6 +97,16 @@ ui_right_mouse={
|
|||
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":2,"position":Vector2(411, 19),"global_position":Vector2(420, 67),"factor":1.0,"button_index":2,"canceled":false,"pressed":true,"double_click":false,"script":null)
|
||||
]
|
||||
}
|
||||
toggle_character={
|
||||
"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":67,"key_label":0,"unicode":99,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
toggle_inventory={
|
||||
"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":73,"key_label":0,"unicode":105,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
|
||||
[physics]
|
||||
|
||||
|
|
|
|||
49
world.gd
49
world.gd
|
|
@ -2,10 +2,57 @@
|
|||
# Initialisiert die Spielwelt: weist dem Gegner den Spieler als Ziel zu
|
||||
extends Node3D
|
||||
|
||||
const ENEMY_SCENE = preload("res://enemy.tscn")
|
||||
const CLASS_SELECTION_MENU = preload("res://class_selection_menu.tscn")
|
||||
const RESPAWN_TIME = 5.0
|
||||
|
||||
# Startausrüstung
|
||||
const STARTER_WEAPON = preload("res://equipment/iron_sword.tres")
|
||||
const STARTER_CHEST = preload("res://equipment/leather_chest.tres")
|
||||
|
||||
@onready var player = $Player
|
||||
|
||||
func _ready():
|
||||
var player = get_node("Player")
|
||||
# Klassenauswahl-Menü anzeigen
|
||||
var menu = CLASS_SELECTION_MENU.instantiate()
|
||||
add_child(menu)
|
||||
menu.class_selected.connect(_on_class_selected)
|
||||
|
||||
# Klasse ausgewählt: Spieler initialisieren
|
||||
func _on_class_selected(character_class: CharacterClass):
|
||||
player.character_class = character_class
|
||||
|
||||
# Startausrüstung geben
|
||||
player.equip_item(STARTER_WEAPON)
|
||||
player.equip_item(STARTER_CHEST)
|
||||
|
||||
player._calculate_stats()
|
||||
player.current_hp = player.max_hp
|
||||
player.hud.update_health(player.current_hp, player.max_hp)
|
||||
print("Klasse gewählt: ", character_class.class_name_de)
|
||||
|
||||
# Jetzt Gegner initialisieren
|
||||
var enemy = get_node("Enemy")
|
||||
_setup_enemy(enemy)
|
||||
|
||||
# Gegner initialisieren und Signal verbinden
|
||||
func _setup_enemy(enemy):
|
||||
if enemy and player:
|
||||
enemy.target = player
|
||||
enemy.enemy_died.connect(_on_enemy_died)
|
||||
else:
|
||||
print("Fehler: Player oder Enemy nicht gefunden!")
|
||||
|
||||
# Gegner gestorben: Nach 5 Sekunden respawnen
|
||||
func _on_enemy_died(spawn_position: Vector3, _xp_reward: int):
|
||||
print("Respawn in ", RESPAWN_TIME, " Sekunden...")
|
||||
await get_tree().create_timer(RESPAWN_TIME).timeout
|
||||
_spawn_enemy(spawn_position)
|
||||
|
||||
# Neuen Gegner an Position spawnen
|
||||
func _spawn_enemy(position: Vector3):
|
||||
var new_enemy = ENEMY_SCENE.instantiate()
|
||||
add_child(new_enemy)
|
||||
new_enemy.global_position = position
|
||||
_setup_enemy(new_enemy)
|
||||
print("Gegner respawned!")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue