121 lines
3.3 KiB
GDScript
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)
|