Targets and Enemies are killable with the projectile.

pull/28/head
Sascha 2025-08-22 11:42:04 +07:00
parent 4eb4dc1810
commit 739bccd3dd
8 changed files with 129 additions and 38 deletions

@ -1,7 +1,8 @@
[gd_scene load_steps=7 format=4 uid="uid://b0m8q6col4box"]
[gd_scene load_steps=8 format=4 uid="uid://b0m8q6col4box"]
[ext_resource type="Texture2D" uid="uid://0mf0tljb1w6b" path="res://resources/models/adventures/Textures/colormap.png" id="1_4u6j7"]
[ext_resource type="Script" uid="uid://bfl8kwerqb2kv" path="res://scripts/target.gd" id="1_ikhwp"]
[ext_resource type="PackedScene" uid="uid://bghvnlgw38u36" path="res://packed-scenes/explosion.tscn" id="2_e34xa"]
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_l8mi6"]
resource_name = "colormap"
@ -45,7 +46,11 @@ shadow_mesh = SubResource("ArrayMesh_0n375")
points = PackedVector3Array(-0.049999997, -0.010245939, -0.16406348, 0.049999997, 0.009738399, 0.15593648, 0.049999997, 0.03409966, 0.14616756, -0.049999997, 0.035876844, 0.15378544, -0.049999997, -0.16406348, 0.010245939, 0.049999997, -0.1120679, -0.1120679, 0.048056543, 0.12476997, -0.10916965, -0.049999997, 0.16406348, 0.010245939, 0.049999997, -0.1120679, 0.1120679, 0.04830738, 0.12019999, 0.12019999, -0.049999997, -0.11790859, 0.11790859, -0.049999997, 0.11790859, -0.11790859, 0.047874413, -0.010348912, -0.16571234, -0.049999997, -0.11790859, -0.11790859, 0.049999997, -0.15593648, -0.009738399, 0.049999997, 0.15593648, 0.009738399, -0.049999997, 0.11790859, 0.11790859, 0.047874413, -0.010348912, 0.16571234, -0.049999997, -0.010245939, 0.16406348, 0.04830738, 0.12019999, -0.12019999, -0.049999997, 0.010245939, -0.16406348, -0.049999997, 0.16406348, -0.010245939, -0.049999997, -0.16406348, -0.010245939, 0.049999997, -0.15593648, 0.009738399, 0.049999997, 0.15593648, -0.009738399, 0.049999997, 0.009738399, -0.15593648, -0.049999997, 0.010245939, 0.16406348, 0.04778016, -0.036164127, 0.15501685, 0.04778016, -0.036164127, -0.15501685, -0.049999997, -0.12815452, -0.097384594, -0.049999997, -0.12815452, 0.097384594, -0.049999997, -0.097384594, -0.12815452)
[node name="Target" type="RigidBody3D"]
collision_layer = 31
collision_mask = 31
gravity_scale = 0.0
script = ExtResource("1_ikhwp")
explosion_scene = ExtResource("2_e34xa")
[node name="target-large" type="MeshInstance3D" parent="."]
transform = Transform3D(5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0)

@ -1203,6 +1203,29 @@ stream = ExtResource("14_765i1")
[node name="Items" type="Node" parent="."]
[node name="Targets" type="Node" parent="Items"]
[node name="Target" parent="Items/Targets" instance=ExtResource("36_twgab")]
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 23.5, 7, 11)
[node name="Target3" parent="Items/Targets" instance=ExtResource("36_twgab")]
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 20, 6, 12)
[node name="Target2" parent="Items/Targets" instance=ExtResource("36_twgab")]
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 27, 6, -2)
[node name="Target7" parent="Items/Targets" instance=ExtResource("36_twgab")]
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 33, 6, -6)
[node name="Target4" parent="Items/Targets" instance=ExtResource("36_twgab")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 35, 7, -1)
[node name="Target5" parent="Items/Targets" instance=ExtResource("36_twgab")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 48, 6, -5)
[node name="Target6" parent="Items/Targets" instance=ExtResource("36_twgab")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 48, 8, -5)
[node name="torch_mounted" parent="Items" instance=ExtResource("8_8d1n4")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -10.0339, 2.9024, 1.25148)
@ -1257,6 +1280,3 @@ grow_horizontal = 0
[node name="Rogue" parent="." instance=ExtResource("10_twgab")]
transform = Transform3D(0.8, 0, 0, 0, 0.8, 0, 0, 0, 0.8, 23, 6, 20)
jump_speed = 8.0
[node name="Target" parent="." instance=ExtResource("36_twgab")]
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 24, 6, 12)

@ -4,6 +4,7 @@ class_name Enemy
@export var player: Player
@export var chasing_range := 30.0
@export var attack_range := 3.0
@export var enemy_reward_gold = 100
@export var attacks := ["Unarmed_Melee_Attack_Punch_A", "Unarmed_Melee_Attack_Punch_B", "Unarmed_Melee_Attack_Kick"]
const ANIM_IWR_PARAM := "parameters/IWR/blend_position"
@onready var navigation_agent: NavigationAgent3D = $NavigationAgent3D
@ -12,6 +13,7 @@ const ANIM_IWR_PARAM := "parameters/IWR/blend_position"
func _ready() -> void:
health = maximum_health
stamina = maximum_stamina
reward_gold = enemy_reward_gold
state_changed.connect(_on_state_changed)
@ -93,7 +95,3 @@ func _on_state_changed(_old_state: States, new_state: States) -> void:
spend_stamina(attack_cost)
if attacks.size() > 0:
anim_state.travel(attacks.pick_random())
States.dead:
# Belohnung vergeben, wenn der Spieler existiert
if player:
player.gold += 100

@ -4,14 +4,13 @@ extends Node
const ENEMY_COUNT_INIT_DELAY := 3.0
const PLAYER_GROUP := "player"
const DEFAULT_SPAWN_MANAGER_PATH := NodePath("../SpawnManager")
@export var player: Player
@export var spawn_manager: Node
@onready var _player: Player = player if player else (get_tree().get_first_node_in_group(PLAYER_GROUP) as Player)
@onready var _spawn_manager: Node = spawn_manager if spawn_manager else get_node_or_null(DEFAULT_SPAWN_MANAGER_PATH)
@onready var _player: Player = get_tree().get_first_node_in_group(PLAYER_GROUP) as Player
@onready var _spawn_manager: Node = get_node_or_null(DEFAULT_SPAWN_MANAGER_PATH)
func _ready() -> void:
if _player == null:
push_warning("GameManager: Kein Player gefunden (Export-Referenz setzen oder Gruppe 'player' verwenden).")
if _spawn_manager == null:
@ -30,3 +29,7 @@ func _schedule_enemy_count_sync() -> void:
await get_tree().create_timer(ENEMY_COUNT_INIT_DELAY).timeout
if _spawn_manager and _spawn_manager.has_method("enemy_count_changed"):
_spawn_manager.call("enemy_count_changed")
func increase_player_gold(amount: int) -> void:
_player.gold += amount
print("Player gets " + str(amount) + " gold and has now " + str(_player.gold) + " in sum!")

@ -26,9 +26,9 @@ func _input(event: InputEvent) -> void:
func _on_attack() -> void:
if not enough_stamina_available(attack_cost):
if not can_spend_stamina(attack_cost):
return
use_stamina(attack_cost)
spend_stamina(attack_cost)
var target_anim: StringName = &"Block_Attack" if state == States.blocking else ATTACK_ANIMS.pick_random()
anim_state.travel(target_anim)

@ -4,35 +4,48 @@ extends Node3D
signal hit(collider: Object)
@export var initial_speed: float = 50.0
@export var damage: float = 3.0
@export var damage: int = 3
@export var lifetime_after_hit: float = 3.0
@export var mesh_uses_x_forward: bool = true # Mesh zeigt mit +X nach vorne
@export var hit_mask: int = 0xFFFFFFFF # Kollisions-Layer, die das Projektil treffen darf
const DAMAGE_METHOD_NAME := "take_damage"
var speed: float = 0.0
var velocity_dir: Vector3 = Vector3.ZERO
var _last_pos: Vector3 = Vector3.ZERO
@onready var ray_cast: RayCast3D = $RayCast3D
func _ready() -> void:
_last_pos = global_position
func _physics_process(delta: float) -> void:
if speed <= 0.0:
return
var move_dir := velocity_dir if velocity_dir != Vector3.ZERO else _forward_basis()
global_position += move_dir * speed * delta
if ray_cast and ray_cast.is_colliding():
var collider: Object = ray_cast.get_collider()
var next_pos := global_position + move_dir * speed * delta
var space := get_world_3d().direct_space_state
var query := PhysicsRayQueryParameters3D.create(global_position, next_pos)
query.exclude = [self]
query.collision_mask = hit_mask
var result := space.intersect_ray(query)
if result:
var collider: Object = result.collider
global_position = result.position
_on_hit(collider)
else:
global_position = next_pos
_last_pos = global_position
func shoot(new_direction: Vector3 = Vector3.ZERO) -> void:
velocity_dir = new_direction.normalized() if new_direction != Vector3.ZERO else _forward_basis()
speed = initial_speed
# Beim Abfeuern aus der Waffen-Hierarchie lösen, damit Maus-/Kamerabewegung den Pfeil nicht beeinflusst
_detach_to_world()
# Visuelle Ausrichtung konsistent zur Flugrichtung setzen
_align_visual_to_direction()
if ray_cast:
ray_cast.enabled = true
func _forward_basis() -> Vector3:
@ -63,10 +76,12 @@ func _on_hit(collider: Object) -> void:
if speed == 0.0:
return
speed = 0.0
if ray_cast:
ray_cast.enabled = false
emit_signal("hit", collider)
_apply_damage_if_supported(collider)
var target := _find_damage_receiver(collider)
_log_hit(target, collider)
_apply_damage_to_target(target)
if lifetime_after_hit > 0.0:
var timer := get_tree().create_timer(lifetime_after_hit)
timer.timeout.connect(queue_free)
@ -74,6 +89,32 @@ func _on_hit(collider: Object) -> void:
queue_free()
func _apply_damage_if_supported(collider: Object) -> void:
if collider and collider.has_method("take_damage"):
collider.call("take_damage", damage)
func _apply_damage_to_target(target: Object) -> void:
if target:
target.call(DAMAGE_METHOD_NAME, damage)
func _find_damage_receiver(start: Object) -> Object:
# Viele Kollisionen treffen Child-Nodes (Mesh, CollisionShape). Hochlaufen bis ein Node mit take_damage gefunden wird.
var node := start as Node
while node:
if node.has_method(DAMAGE_METHOD_NAME):
return node
node = node.get_parent()
return null
func _log_hit(target: Object, collider: Object) -> void:
var collider_desc := _describe_node(collider)
if target:
var target_desc := _describe_node(target)
print("Projectile hit: %s -> applying %d damage to %s" % [collider_desc, damage, target_desc])
else:
print("Projectile hit: %s -> no damage receiver found" % [collider_desc])
func _describe_node(obj: Object) -> String:
var n := obj as Node
if n:
return "%s [%s]" % [n.name, n.get_class()]
return str(obj)

@ -1,16 +1,16 @@
class_name Target
extends RigidBody3D
const MAX_HEALTH: int = 10
var current_health: int = MAX_HEALTH
@export var max_health: int = 3
var current_health: int = max_health
signal damaged(amount: int)
signal died()
@export var explosion_scene: PackedScene
@export var explosion_lifetime: float = 3.0
@export var reward_gold: int = 1
func is_dead() -> bool:
return current_health <= 0
func take_damage(amount: int) -> void:
if amount <= 0:
return
@ -20,10 +20,31 @@ func take_damage(amount: int) -> void:
current_health = max(0, current_health - amount)
emit_signal("damaged", amount)
if was_alive and is_dead():
emit_signal("died")
die()
func die() -> void:
# Optional: Explosion/Animation/Sound abspielen, dann entfernen
print("Target destroyed!")
# Erhöhe das Gold des Spielers beim Tod dieses Targets
if reward_gold > 0 and GameManager:
GameManager.increase_player_gold(reward_gold)
_spawn_explosion()
queue_free()
func _spawn_explosion() -> void:
if explosion_scene == null:
return
var explosion := explosion_scene.instantiate()
var tree := get_tree()
var parent: Node = tree.current_scene if tree.current_scene != null else tree.root
parent.add_child(explosion)
if explosion is Node3D:
(explosion as Node3D).global_transform = global_transform
# Optional: Partikel/Animation starten, falls der Effekt so etwas anbietet
if explosion.has_method("start"):
explosion.call("start")
# Aufräumen, falls der Effekt sich nicht selbst entfernt
if explosion_lifetime > 0.0:
var timer := tree.create_timer(explosion_lifetime)
timer.timeout.connect(explosion.queue_free)

@ -15,6 +15,7 @@ const STAMINA_TICK_SECONDS := 0.1
@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
@ -94,7 +95,6 @@ var stamina: int:
# Stamina Regeneration
var stamina_timer: float = 0.0
func _process(delta: float) -> void:
stamina_timer += delta
if stamina_timer >= STAMINA_TICK_SECONDS:
@ -130,6 +130,9 @@ func die() -> void:
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!")