183 lines
5.8 KiB
GDScript
183 lines
5.8 KiB
GDScript
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
|