SchildDerStaerke/scripts/projectile.gd

121 lines
3.3 KiB
GDScript

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