extends Area3D class_name Projectile # Базовый класс для снарядов @export var speed: float = 120.0 @export var damage: float = 20.0 @export var lifetime: float = 5.0 @export var is_explosive: bool = false @export var explosion_radius: float = 3.0 var direction: Vector3 = Vector3.ZERO var velocity: Vector3 = Vector3.ZERO var owner_unit: Node3D = null var lifetime_timer: float = 0.0 var has_hit: bool = false var previous_position: Vector3 = Vector3.ZERO signal hit(target: Node3D, position: Vector3) signal exploded(position: Vector3) func _ready(): lifetime_timer = lifetime previous_position = global_position # Настраиваем Area3D для обнаружения целей body_entered.connect(_on_body_entered) # Отключаем мониторинг по умолчанию для параболических monitoring = true monitorable = false func _physics_process(delta): if has_hit: return lifetime_timer -= delta if lifetime_timer <= 0: if is_explosive: explode(global_position) else: queue_free() return # Сохраняем предыдущую позицию для raycast previous_position = global_position # Движение снаряда global_position += velocity * delta # Проверка столкновений через raycast check_collisions_raycast() func check_collisions_raycast(): # Используем raycast для проверки столкновений var space_state = get_world_3d().direct_space_state var query = PhysicsRayQueryParameters3D.create(previous_position, global_position) query.exclude = [self, owner_unit] if owner_unit else [self] query.collision_mask = 1 # Только слой земли и объектов var result = space_state.intersect_ray(query) if result: var collider = result.collider var hit_position = result.position # Если это враг, наносим урон if collider.has_method("take_damage"): collider.take_damage(damage) hit.emit(collider, hit_position) on_hit(hit_position) func _on_body_entered(body: Node3D): # Дополнительная проверка через Area3D if has_hit: return if body == owner_unit: return if body is Projectile: return if body.has_method("take_damage"): body.take_damage(damage) hit.emit(body, global_position) on_hit(global_position) func on_hit(position: Vector3): if has_hit: return has_hit = true velocity = Vector3.ZERO # Останавливаем частицы var fire_particles = get_node_or_null("FireParticles") var trail_particles = get_node_or_null("TrailParticles") if fire_particles: fire_particles.emitting = false if trail_particles: trail_particles.emitting = false if is_explosive: explode(position) else: # Простое попадание hit.emit(null, position) # Воспроизводим анимацию попадания play_hit_animation() # Удаляем через небольшую задержку await get_tree().create_timer(0.5).timeout queue_free() func explode(position: Vector3): # Находим все цели в радиусе взрыва var space_state = get_world_3d().direct_space_state var query = PhysicsShapeQueryParameters3D.new() var sphere_shape = SphereShape3D.new() sphere_shape.radius = explosion_radius query.shape = sphere_shape query.transform.origin = position query.collision_mask = 1 var results = space_state.intersect_shape(query) # Наносим урон всем в радиусе for result in results: var collider = result.collider if collider != owner_unit and collider.has_method("take_damage"): var distance = position.distance_to(collider.global_position) var damage_multiplier = 1.0 - (distance / explosion_radius) collider.take_damage(damage * damage_multiplier) exploded.emit(position) play_explosion_animation() # Ждем, пока частицы взрыва отыграют (0.5 секунды) await get_tree().create_timer(0.5).timeout queue_free() func play_hit_animation(): # Создаем простой визуальный эффект попадания var mesh = get_node_or_null("MeshInstance3D") if mesh: # Увеличиваем размер и скрываем var tween = create_tween() tween.tween_property(mesh, "scale", Vector3(2.0, 2.0, 2.0), 0.2) tween.tween_callback(func(): mesh.visible = false) func play_explosion_animation(): # Скрываем основной меш var mesh = get_node_or_null("MeshInstance3D") if mesh: mesh.visible = false # Останавливаем частицы полета var fire_particles = get_node_or_null("FireParticles") var trail_particles = get_node_or_null("TrailParticles") if fire_particles: fire_particles.emitting = false if trail_particles: trail_particles.emitting = false # Воспроизводим частицы взрыва var explosion_particles = get_node_or_null("ExplosionParticles") var explosion_core = get_node_or_null("ExplosionCore") var explosion_smoke = get_node_or_null("ExplosionSmoke") # Масштабируем узлы частиц в зависимости от радиуса взрыва # Это создаст визуальный эффект взрыва нужного размера var scale_factor = explosion_radius / 3.0 # 3.0 - базовый радиус взрыва if explosion_particles: explosion_particles.scale = Vector3.ONE * scale_factor explosion_particles.restart() explosion_particles.emitting = true if explosion_core: explosion_core.scale = Vector3.ONE * scale_factor * 0.6 explosion_core.restart() explosion_core.emitting = true if explosion_smoke: explosion_smoke.scale = Vector3.ONE * scale_factor * 1.2 explosion_smoke.restart() explosion_smoke.emitting = true