class_name Projectile extends Area3D const HEIGHT: float = 1 const MAX_STRETCH: float = 0.75 @export var _speed_stretch_factor: float = 100 @export var _hit_particles_scene: PackedScene @export var _destroy_particles_scene: PackedScene @export_group("References") @export var _model_base: Node3D @export var _model_mesh: MeshInstance3D var _start_position: Vector3 var _velocity: Vector3 var _lifetime: float var _size: float = 0.25 var _life_timer: float func _ready() -> void: _life_timer = _lifetime global_position = _start_position body_entered.connect(_on_body_entered) _model_mesh.rotation = Vector3( randf_range(0, TAU), randf_range(0, TAU), randf_range(0, TAU) ) _model_mesh.scale = Vector3.ONE * _size func _physics_process(delta: float) -> void: if _life_timer <= 0: queue_free() _life_timer -= delta global_position += _velocity * delta look_at(global_position + _velocity, Vector3.UP, true) var speed_squash := clampf( _velocity.length() / _speed_stretch_factor, 0, MAX_STRETCH ) _model_base.scale = Vector3(1 - speed_squash, 1 - speed_squash, 1 + speed_squash) func init(velocity: Vector3, start_position: Vector3, lifetime: float = 10) -> void: _velocity = velocity _start_position = start_position _lifetime = lifetime func hit(velocity: Vector3) -> void: var hit_particles := ( _hit_particles_scene.instantiate() as Particles3DSelfDestructing ) get_tree().get_root().add_child(hit_particles) hit_particles.init(global_position, velocity) set_velocity(velocity) func set_velocity(velocity: Vector3) -> void: _velocity = velocity _life_timer = _lifetime func _destroy() -> void: var destroy_particles := ( _destroy_particles_scene.instantiate() as Particles3DSelfDestructing ) get_tree().get_root().add_child(destroy_particles) destroy_particles.init(global_position) queue_free() func _on_body_entered(node: Node3D) -> void: if node is Player: queue_free() var player := node as Player player.stats.damage() return if node is CollisionObject3D: var collision_node := node as CollisionObject3D if collision_node.collision_layer & 1: _destroy() return if node is CSGCombiner3D: var collision_node := node as CSGCombiner3D if collision_node.collision_layer & 1: _destroy() return