class_name Unit extends CharacterBody3D # Konstante(n) const ANIM_BLOCK_HIT := "Block_Hit" const STAMINA_TICK_SECONDS := 0.1 # Exportierte Eigenschaften (balancing) @export var maximum_health: int = 10 @export var maximum_stamina: int = 50 @export var stamina_regeneration_rate: int = 2 @export var speed: float = 4.0 @export var acceleration: float = 4.0 @export var jump_speed: float = 8.0 @export var jump_cost: int = 20 @export var attack_cost: int = 10 @export var damage: int = 1 @export var model: Node3D @export var reward_gold: int = 3 # Animation @onready var anim_tree: AnimationTree = $AnimationTree @onready var anim_state: AnimationNodeStateMachinePlayback = anim_tree.get("parameters/playback") # Animation-Sets @export var hit_animations: PackedStringArray = ["Hit_A", "Hit_B"] @export var death_animations: PackedStringArray = ["Death_A", "Death_B"] # Zustände enum States { idle, walking, running, chasing, jumping, attacking, blocking, dead, aiming } # Signale signal name_changed(unit_name: String) signal health_changed(current_health: int, maximum_health: int) signal stamina_changed(current_stamina: int, maximum_stamina: int) signal state_changed(old_state: States, new_state: States) signal unit_died(unit: Unit) # Zufall für Animationsauswahl var rng := RandomNumberGenerator.new() # Interne Backing-Felder var _state: States = States.idle var _unit_name: String = "" var _health: int = maximum_health var _stamina: int = maximum_stamina var gravity: float = ProjectSettings.get_setting("physics/3d/default_gravity") # Eigenschaften mit validen Getter/Setter (ohne Rekursion) var state: States: get: return _state set(value): if value == _state: return var old: States = _state _state = value state_changed.emit(old, _state) @export var unit_name: String: get: return _unit_name set(value): _unit_name = value name_changed.emit(_unit_name, str(_state)) var health: int: get: return _health set(value): if is_dead(): push_warning("Fehler in Health-Handling. Unit bereits tot!") return _health = clampi(value, 0, maximum_health) health_changed.emit(_health, maximum_health) if hit_animations.size() > 0 and _health > 0: anim_state.travel(random_string(hit_animations)) if _health <= 0: die() var stamina: int: get: return _stamina set(value): _stamina = clampi(value, 0, maximum_stamina) stamina_changed.emit(_stamina, maximum_stamina) # Stamina Regeneration var stamina_timer: float = 0.0 func _process(delta: float) -> void: stamina_timer += delta if stamina_timer >= STAMINA_TICK_SECONDS: stamina += stamina_regeneration_rate stamina_timer = 0.0 func is_dead() -> bool: return _state == States.dead func take_damage(damage_amount: int) -> void: if is_dead(): print(_unit_name + " kann keinen Schaden nehmen -> bereits tot!") return if _state == States.blocking: anim_state.travel(ANIM_BLOCK_HIT) return health = _health - damage_amount func can_spend_stamina(stamina_needed: int) -> bool: return stamina_needed <= _stamina func spend_stamina(stamina_needed: int) -> void: stamina = _stamina - stamina_needed func die() -> void: if not is_dead(): state = States.dead unit_died.emit(self) if death_animations.size() > 0: anim_state.travel(random_string(death_animations)) # Erhöhe das Gold des Spielers beim Tod dieses Targets if reward_gold > 0 and GameManager: GameManager.increase_player_gold(reward_gold) else: print(_unit_name + " kann nicht sterben -> bereits tot!") func remove_unit() -> void: if self is Player: var player := self as Player player.player_game_over() else: queue_free() # Hilfsfunktion: zufälliger String aus PackedStringArray func random_string(arr: PackedStringArray) -> String: if arr.is_empty(): return "" if rng.seed == 0: rng.randomize() var idx := rng.randi_range(0, arr.size() - 1) return arr[idx]