From 739bccd3dd505866bd09741b62e0f778d2916958 Mon Sep 17 00:00:00 2001 From: Sascha Date: Fri, 22 Aug 2025 11:42:04 +0200 Subject: [PATCH] Targets and Enemies are killable with the projectile. --- packed-scenes/target.tscn | 7 +++- scenes/game.tscn | 26 ++++++++++++-- scripts/enemy.gd | 6 ++-- scripts/game_manager.gd | 11 +++--- scripts/knight.gd | 4 +-- scripts/projectile.gd | 73 ++++++++++++++++++++++++++++++--------- scripts/target.gd | 35 +++++++++++++++---- scripts/unit.gd | 5 ++- 8 files changed, 129 insertions(+), 38 deletions(-) diff --git a/packed-scenes/target.tscn b/packed-scenes/target.tscn index 77be9ef..4551a10 100644 --- a/packed-scenes/target.tscn +++ b/packed-scenes/target.tscn @@ -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) diff --git a/scenes/game.tscn b/scenes/game.tscn index 6298011..00b8229 100644 --- a/scenes/game.tscn +++ b/scenes/game.tscn @@ -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) diff --git a/scripts/enemy.gd b/scripts/enemy.gd index 466e55d..f469eac 100644 --- a/scripts/enemy.gd +++ b/scripts/enemy.gd @@ -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 diff --git a/scripts/game_manager.gd b/scripts/game_manager.gd index a0a8de3..f7bbf4f 100644 --- a/scripts/game_manager.gd +++ b/scripts/game_manager.gd @@ -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!") diff --git a/scripts/knight.gd b/scripts/knight.gd index 7bbc919..00c7408 100644 --- a/scripts/knight.gd +++ b/scripts/knight.gd @@ -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) diff --git a/scripts/projectile.gd b/scripts/projectile.gd index ac664d4..7048869 100644 --- a/scripts/projectile.gd +++ b/scripts/projectile.gd @@ -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) diff --git a/scripts/target.gd b/scripts/target.gd index 598b6dc..8b12e21 100644 --- a/scripts/target.gd +++ b/scripts/target.gd @@ -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) diff --git a/scripts/unit.gd b/scripts/unit.gd index fa3c03c..0fcc599 100644 --- a/scripts/unit.gd +++ b/scripts/unit.gd @@ -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!")