SchildDerStaerke/scripts/unit.gd

152 lines
3.7 KiB
GDScript

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
# 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))
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]