# GDScript class_name Projectile extends Node3D signal hit(collider: Object) @export var initial_speed: float = 50.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 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() 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 _detach_to_world() _align_visual_to_direction() func _forward_basis() -> Vector3: return -global_transform.basis.z func _align_visual_to_direction() -> void: look_at(global_transform.origin + velocity_dir, Vector3.UP) # Godot richtet -Z nach vorne aus; wenn das Mesh +X als "vorne" hat, um -90° um Y korrigieren if mesh_uses_x_forward: rotate_y(deg_to_rad(-90.0)) func _detach_to_world() -> void: var tree := get_tree() var target_parent: Node = tree.current_scene if tree.current_scene != null else tree.root if target_parent == null: return var gt := global_transform var p := get_parent() if p and p != target_parent: p.remove_child(self) target_parent.add_child(self) global_transform = gt func _on_hit(collider: Object) -> void: if speed == 0.0: return speed = 0.0 emit_signal("hit", 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) else: queue_free() 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)