pull/28/head
Sascha Woitschetzki 2025-08-18 18:46:17 +07:00
parent 0a09dc4beb
commit 78a6df0fdf
8 changed files with 216 additions and 100 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

@ -2,12 +2,12 @@ class_name Chest
extends ItemInteractable
# Aktionstexte
const ACTION_OPEN := "open chest"
const ACTION_CLOSE := "close chest"
const ACTION_COLLECT := "collect gold"
const ACTION_OPEN_CHEST: StringName = &"open chest"
const ACTION_CLOSE_CHEST: StringName = &"close chest"
const ACTION_COLLECT: StringName = &"collect gold"
# Animationsnamen
const ANIM_OPEN := "open"
const ANIM_CLOSE := "close"
const ANIM_TAKE_GOLD: StringName = &"take_gold"
const ANIM_TAKE_GOLD := "take_gold"
@export var gold_amount: int = 10

@ -1,8 +1,23 @@
class_name HitBox extends Area3D
class_name HitBox
extends Area3D
func _init() -> void:
collision_layer = 2
collision_mask = 2
## Konstanten statt magischer Zahlen für bessere Lesbarkeit/Wartbarkeit
const COLLISION_LAYER: int = 2
const COLLISION_MASK: int = 2
const DEFAULT_DAMAGE: int = 0
func get_damage() -> int:
return owner.damage
func _ready() -> void:
# Initialisierung im _ready, wenn der Node sicher im Baum ist
collision_layer = COLLISION_LAYER
collision_mask = COLLISION_MASK
## Liefert den Schaden des Owners, falls dieser eine entsprechende API anbietet.
## Erwartet eine Methode `get_damage()` am Owner. Fällt sonst auf DEFAULT_DAMAGE zurück.
func get_owner_damage() -> int:
if owner == null:
return DEFAULT_DAMAGE
if owner.has_method("get_damage"):
return int(owner.call("get_damage"))
return DEFAULT_DAMAGE

@ -1,38 +1,37 @@
class_name HurtBox extends Area3D
class_name HurtBox
extends Area3D
const COLLISION_LAYER: int = 0
const COLLISION_MASK: int = 2
const DEFAULT_COOLDOWN_S: float = 1.0
var can_take_damage: bool = true
var cooldown_time: float = 1.0 # Cooldown duration in seconds
func _init() -> void:
collision_layer = 0
collision_mask = 2
@export var damage_cooldown_s: float = DEFAULT_COOLDOWN_S
func _ready() -> void:
connect("area_entered", Callable(self, "_on_area_entered"))
collision_layer = COLLISION_LAYER
collision_mask = COLLISION_MASK
area_entered.connect(_on_area_entered)
monitoring = true
func _on_area_entered(hitbox) -> void:
func _on_area_entered(other_area: Area3D) -> void:
if not can_take_damage:
return
if not hitbox is HitBox:
print("Unexpected type:", hitbox)
if not (other_area is HitBox):
push_warning("Unerwarteter Typ: %s" % [other_area])
return
var hitbox := other_area as HitBox
if hitbox.owner == owner:
print("Hitbox owner is the same as hurtbox owner, ignoring hit.")
# Eigenen Treffer ignorieren
return
if owner != null and owner.has_method("take_damage"):
owner.call("take_damage", hitbox.get_owner_damage())
start_damage_cooldown()
if hitbox == null or not hitbox is HitBox:
print("Unexpected or null hitbox:", hitbox)
return
if owner.has_method("take_damage"):
owner.call("take_damage", hitbox.get_damage())
start_cooldown()
func start_cooldown() -> void:
func start_damage_cooldown() -> void:
can_take_damage = false
await get_tree().create_timer(cooldown_time).timeout
await get_tree().create_timer(damage_cooldown_s).timeout
can_take_damage = true

@ -1,12 +1,32 @@
class_name InteractionArea extends Area3D
class_name InteractionArea
extends Area3D
@export var action_name := "interact"
const DEFAULT_ACTION: StringName = &"interact"
@export var action_name: StringName = DEFAULT_ACTION
var interact: Callable = func():
# Neuer klarer Name
var on_interact: Callable = func() -> void:
pass
# Rückwärtskompatibles Alias für bestehenden Code (z. B. interaction_area.interact = _on_interact)
var interact: Callable:
set(value):
on_interact = value
get:
return on_interact
func set_interact_callback(callback: Callable) -> void:
on_interact = callback
func _on_body_entered(_body: Node3D) -> void:
InteractionManager.register_area(self)
func _on_body_exited(_body: Node3D) -> void:
InteractionManager.unregister_area(self)
func _exit_tree() -> void:
InteractionManager.unregister_area(self)

@ -1,43 +1,67 @@
extends Node2D
const ACTION_INTERACT := "interact"
@onready var player: Player = get_tree().get_first_node_in_group("player")
@onready var label: Label = $Label
var base_text: String
var active_areas := []
var can_interact := true
var base_prompt_text: String = ""
var areas_in_range: Array[InteractionArea] = []
var can_interact: bool = true
func _ready() -> void:
var interact_keys = InputMap.action_get_events("interact")
base_text = interact_keys[0].as_text() + " to "
print(base_text)
base_prompt_text = _build_base_prompt_text()
label.hide()
func register_area(area: InteractionArea) -> void:
active_areas.push_back(area)
areas_in_range.push_back(area)
func unregister_area(area: InteractionArea) -> void:
var index = active_areas.find(area)
var index := areas_in_range.find(area)
if index != -1:
active_areas.remove_at(index)
areas_in_range.remove_at(index)
func _process(_delta: float) -> void:
if active_areas.size() > 0 and can_interact:
active_areas.sort_custom(_sort_by_distance_to_player)
var active_area = active_areas[0] as InteractionArea
label.text = base_text + active_area.action_name
if not can_interact:
label.hide()
return
var nearest := _get_nearest_area()
if nearest:
label.text = "%s%s" % [base_prompt_text, nearest.action_name]
label.show()
else:
label.hide()
func _sort_by_distance_to_player(area1: Area3D, area2: Area3D) -> bool:
var area1_to_player = player.global_position.distance_to(area1.global_position)
var area2_to_player = player.global_position.distance_to(area2.global_position)
return area1_to_player < area2_to_player
func _input(event: InputEvent) -> void:
if event.is_action_pressed("interact") and can_interact:
if active_areas.size() > 0:
can_interact = false
label.hide()
await active_areas[0].interact.call()
can_interact = true
if not (can_interact and event.is_action_pressed(ACTION_INTERACT)):
return
var nearest := _get_nearest_area()
if nearest:
can_interact = false
label.hide()
await nearest.interact.call()
can_interact = true
func _get_nearest_area() -> InteractionArea:
if areas_in_range.is_empty():
return null
areas_in_range.sort_custom(self._compare_by_distance_to_player)
return areas_in_range[0]
func _compare_by_distance_to_player(a: InteractionArea, b: InteractionArea) -> bool:
var d1 := player.global_position.distance_to(a.global_position)
var d2 := player.global_position.distance_to(b.global_position)
return d1 < d2
func _build_base_prompt_text() -> String:
var events := InputMap.action_get_events(ACTION_INTERACT)
if events.is_empty():
return "Interact with "
return "%s to " % [events[0].as_text()]

@ -2,6 +2,7 @@ class_name Item
extends Node3D
const DEFAULT_MAX_HEALTH: int = 10
const MIN_MAX_HEALTH: int = 1
signal name_changed(item_name: String)
signal health_changed(current_health: int, maximum_health: int)
signal state_changed(new_state: States)
@ -29,27 +30,30 @@ enum States {
get:
return _maximum_health
set(value):
value = max(1, value)
if _maximum_health == value:
return
_maximum_health = value
if _health > _maximum_health:
_health = _maximum_health
if _initialized:
health_changed.emit(_health, _maximum_health)
var new_max: int = max(MIN_MAX_HEALTH, value)
var max_changed: bool = (_maximum_health != new_max)
if max_changed:
_maximum_health = new_max
# Gesundheitswert bei Bedarf anpassen
var clamped_health: int = clamp(_health, 0, _maximum_health)
var health_changed_local: bool = (clamped_health != _health)
if health_changed_local:
_health = clamped_health
if _initialized and (max_changed or health_changed_local):
_emit_health_signal()
@export var health: int = DEFAULT_MAX_HEALTH:
get:
return _health
set(value):
value = clamp(value, 0, maximum_health)
if _health == value:
var clamped := clampi(value, 0, maximum_health)
if _health == clamped:
return
_health = value
_health = clamped
if _initialized:
health_changed.emit(_health, maximum_health)
if _health <= 0:
state = States.destroyed
_emit_health_signal()
_handle_death_if_needed()
@export var state: States = States.idle:
get:
@ -70,21 +74,40 @@ var _state: States = States.idle
func _ready() -> void:
_initialized = true
name_changed.emit(item_name)
health_changed.emit(health, maximum_health)
_emit_initial_signals()
func apply_damage(amount: int) -> void:
# Öffentliche API
func take_damage(amount: int) -> void:
# Primäre API (kompatibel zu HurtBox)
if amount <= 0:
return
health = health - amount
func take_damage(damage_amount: int) -> void:
# Rückwärtskompatibel: Bitte künftig apply_damage verwenden.
apply_damage(damage_amount)
func apply_damage(damage_amount: int) -> void:
# Veraltet: bitte take_damage verwenden (Alias zur Rückwärtskompatibilität)
take_damage(damage_amount)
func destroy_item() -> void:
recalculate_navigation_map.emit(self)
queue_free()
queue_free()
# Private Helfer
func _emit_initial_signals() -> void:
# Bewahrt Startverhalten (Name + Health); State wird nicht zusätzlich emittiert
name_changed.emit(item_name)
_emit_health_signal()
func _emit_health_signal() -> void:
health_changed.emit(_health, _maximum_health)
func _handle_death_if_needed() -> void:
if _health <= 0:
state = States.destroyed

@ -1,24 +1,59 @@
class_name ItemInteractable extends Item
class_name ItemInteractable
extends Item
@onready var interaction_area: InteractionArea = $InteractionArea
@onready var animation_player: AnimationPlayer = $AnimationPlayer
# Einheitliche Standardtexte/-anim-Namen (können in Subklassen überschrieben werden)
const ACTION_OPEN: StringName = &"open"
const ACTION_CLOSE: StringName = &"close"
const ANIM_OPEN: StringName = &"open"
const ANIM_CLOSE: StringName = &"close"
#func _ready() -> void:
#state_changed.connect(Callable(self, "_on_state_changed"))
#interaction_area.interact = Callable(self, "_on_interact")
#func _on_interact() -> void:
#match state:
#States.closed:
#state = States.opened
#interaction_area.action_name = "open"
#States.opened:
#state = States.closed
#interaction_area.action_name = "close"
#
#func _on_state_changed(new_state: States) -> void:
#match new_state:
#States.opened:
#animation_player.play("open")
#States.closed:
#animation_player.play("close")
func _ready() -> void:
# Nur verbinden, wenn noch nicht verbunden (verhindert Doppelverbindungen in Subklassen)
if not state_changed.is_connected(_on_state_changed):
state_changed.connect(_on_state_changed)
# Standardaktion initial setzen
_update_action_name_for_state(state)
func _on_interact() -> void:
# Standardverhalten: zwischen opened/closed toggeln
match state:
States.closed:
state = States.opened
States.opened:
state = States.closed
_:
pass
_update_action_name_for_state(state)
func _on_state_changed(new_state: States) -> void:
_play_animation_for_state(new_state)
_update_action_name_for_state(new_state)
func _play_animation_for_state(new_state: States) -> void:
if animation_player == null:
return
match new_state:
States.opened:
animation_player.play(ANIM_OPEN)
States.closed:
animation_player.play(ANIM_CLOSE)
_:
pass
func _update_action_name_for_state(s: States) -> void:
if interaction_area == null:
return
match s:
States.closed:
interaction_area.action_name = ACTION_OPEN
States.opened:
interaction_area.action_name = ACTION_CLOSE
_:
pass