extends Unit class_name Player # Класс игрока с управлением WASD @export var mouse_sensitivity: float = 0.003 @export var dash_speed: float = 25.0 # Скорость рывка @export var dash_duration: float = 0.2 # Длительность рывка @export var dash_cooldown: float = 1.0 # Перезарядка рывка @export var straight_projectile_scene: PackedScene = null # Сцена прямого снаряда @export var parabolic_projectile_scene: PackedScene = null # Сцена параболического снаряда @export var projectile_damage: float = 20.0 @export var projectile_speed: float = 20.0 @export var explosion_radius: float = 3.0 var camera_angle: float = 0.0 var is_dashing: bool = false var dash_timer: float = 0.0 var dash_cooldown_timer: float = 0.0 var dash_direction: Vector3 = Vector3.ZERO signal dash_started signal dash_ended signal dash_cooldown_changed(remaining_time: float) func _ready(): super._ready() # Игрок белый var mesh_instance = get_node_or_null("MeshInstance3D") if mesh_instance: var material = StandardMaterial3D.new() material.albedo_color = Color.WHITE mesh_instance.material_override = material func _input(event): # Обработка рывка if event.is_action_pressed("dash") and dash_cooldown_timer <= 0.0 and not is_dashing: perform_dash() # Обработка стрельбы (левая кнопка мыши) if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.pressed: shoot_straight_projectile() # Обработка броска (правая кнопка мыши) if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_RIGHT and event.pressed: throw_parabolic_projectile() func _physics_process(delta): # Обновляем таймеры var previous_cooldown = dash_cooldown_timer if dash_cooldown_timer > 0.0: dash_cooldown_timer -= delta dash_cooldown_timer = max(0.0, dash_cooldown_timer) # Эмитим сигнал только если значение изменилось if abs(previous_cooldown - dash_cooldown_timer) > 0.01: dash_cooldown_changed.emit(dash_cooldown_timer) # Поворачиваем игрока в сторону курсора мыши update_rotation_to_mouse() # Получаем направление движения от клавиатуры var input_dir = Input.get_vector("move_left", "move_right", "move_forward", "move_back") var direction = Vector3(input_dir.x, 0, input_dir.y) # Обработка рывка if is_dashing: dash_timer -= delta if dash_timer <= 0.0: end_dash() else: # Во время рывка двигаемся с увеличенной скоростью velocity.x = dash_direction.x * dash_speed velocity.z = dash_direction.z * dash_speed else: # Обычное движение if direction.length() > 0: velocity.x = direction.x * speed velocity.z = direction.z * speed else: velocity.x = move_toward(velocity.x, 0, speed) velocity.z = move_toward(velocity.z, 0, speed) # Применяем гравитацию if not is_on_floor(): velocity.y -= 9.8 * delta else: velocity.y = 0 super._physics_process(delta) func update_rotation_to_mouse(): # Получаем камеру из сцены var camera = get_viewport().get_camera_3d() if not camera: return # Получаем позицию курсора мыши на экране var mouse_pos = get_viewport().get_mouse_position() # Создаем луч от камеры через позицию курсора var from = camera.project_ray_origin(mouse_pos) var to = from + camera.project_ray_normal(mouse_pos) * 1000.0 # Создаем запрос для raycast var space_state = get_world_3d().direct_space_state var query = PhysicsRayQueryParameters3D.create(from, to) query.collision_mask = 1 # Слой земли # Выполняем raycast var result = space_state.intersect_ray(query) if result: # Получаем точку пересечения var target_point = result.position # Поворачиваем игрока в сторону курсора (только по оси Y) var look_direction = (target_point - global_position) look_direction.y = 0 # Игнорируем вертикальную составляющую if look_direction.length() > 0.1: look_at(global_position + look_direction, Vector3.UP) func perform_dash(): # Получаем направление движения var input_dir = Input.get_vector("move_left", "move_right", "move_forward", "move_back") var direction = Vector3(input_dir.x, 0, input_dir.y) # Если игрок не двигается, рывок вперед (по направлению взгляда или вверх) if direction.length() < 0.1: direction = Vector3(0, 0, -1) # Вперед по умолчанию # Нормализуем направление dash_direction = direction.normalized() # Запускаем рывок is_dashing = true dash_timer = dash_duration dash_cooldown_timer = dash_cooldown dash_started.emit() func end_dash(): is_dashing = false dash_timer = 0.0 dash_ended.emit() func shoot_straight_projectile(): if not straight_projectile_scene: return # Получаем точку прицеливания через raycast var target_point = get_mouse_target_point() if not target_point: return # Создаем снаряд var projectile = straight_projectile_scene.instantiate() as StraightProjectile if not projectile: return # Устанавливаем параметры projectile.owner_unit = self projectile.damage = projectile_damage projectile.speed = projectile_speed projectile.is_explosive = false # Направление полета var shoot_position = global_position + Vector3.UP * 1.0 # Немного выше игрока var direction = (target_point - shoot_position).normalized() projectile.direction = direction # Позиция и поворот projectile.global_position = shoot_position projectile.look_at(shoot_position + direction, Vector3.UP) # Добавляем в сцену var scene_root = get_tree().current_scene if not scene_root: scene_root = get_tree().root.get_child(get_tree().root.get_child_count() - 1) scene_root.add_child(projectile) func throw_parabolic_projectile(): if not parabolic_projectile_scene: # Пробуем загрузить вручную parabolic_projectile_scene = load("res://scenes/parabolic_projectile.tscn") as PackedScene if not parabolic_projectile_scene: return # Получаем точку прицеливания через raycast (на плоскости) var target_point = get_mouse_target_point() # Убеждаемся, что целевая точка на уровне земли (Y = 0) # Если кликнули на противника или другой объект, используем его X и Z, но Y ставим на землю target_point.y = 0.0 # Позиция запуска var throw_position = global_position + Vector3.UP * 1.0 # Создаем снаряд var projectile = parabolic_projectile_scene.instantiate() if not projectile: return # Устанавливаем параметры ДО добавления в сцену projectile.owner_unit = self projectile.damage = projectile_damage projectile.speed = projectile_speed projectile.is_explosive = true projectile.explosion_radius = explosion_radius # Устанавливаем позицию projectile.global_position = throw_position # Целевая позиция (на уровне земли) - устанавливаем ДО _ready() projectile.target_position = target_point # Добавляем в сцену (после установки всех параметров) var scene_root = get_tree().current_scene if not scene_root: scene_root = get_tree().root.get_child(get_tree().root.get_child_count() - 1) scene_root.add_child(projectile) func get_mouse_target_point() -> Vector3: # Получаем камеру var camera = get_viewport().get_camera_3d() if not camera: return global_position + transform.basis.z * -10.0 # Получаем позицию курсора мыши var mouse_pos = get_viewport().get_mouse_position() # Создаем луч от камеры var from = camera.project_ray_origin(mouse_pos) var ray_dir = camera.project_ray_normal(mouse_pos) var to = from + ray_dir * 1000.0 # Raycast var space_state = get_world_3d().direct_space_state var query = PhysicsRayQueryParameters3D.create(from, to) query.collision_mask = 1 var result = space_state.intersect_ray(query) if result: return result.position # Если не попали ни во что, вычисляем точку на плоскости земли if ray_dir.y < -0.01: # Луч направлен вниз var t = -from.y / ray_dir.y return from + ray_dir * t # Если луч не направлен вниз, используем точку перед игроком return global_position + transform.basis.z * -10.0