diff --git a/src/states/level/components.rs b/src/states/level/components.rs index 04c1874..c22e036 100644 --- a/src/states/level/components.rs +++ b/src/states/level/components.rs @@ -71,6 +71,7 @@ pub struct WaveState { pub spawning_allowed: bool, pub ball_reached_end: bool, pub is_warming_up: bool, + pub is_queue_locked: bool, //отдельно для большей явности } impl Default for WaveState { @@ -79,6 +80,7 @@ impl Default for WaveState { spawning_allowed: false, ball_reached_end: false, is_warming_up: false, + is_queue_locked: false, } } } @@ -96,4 +98,6 @@ pub struct BallProjectile { pub velocity: Vec2, pub previous_position: Vec2, pub ball_type: BallType, + pub is_fresh: bool, + pub travel_distance: f32, } \ No newline at end of file diff --git a/src/states/level/system_ball.rs b/src/states/level/system_ball.rs index 2f5cac7..c2908a3 100644 --- a/src/states/level/system_ball.rs +++ b/src/states/level/system_ball.rs @@ -11,7 +11,7 @@ pub fn spawn_new_ball( balls: Query<&Ball>, wave: Res, ) { - if !wave.spawning_allowed { + if !wave.spawning_allowed || wave.is_queue_locked { timer.reset(); return; } @@ -209,3 +209,4 @@ pub fn check_wave_completion( } } } + diff --git a/src/states/level/system_cannon.rs b/src/states/level/system_cannon.rs index e2cbb94..3530b7e 100644 --- a/src/states/level/system_cannon.rs +++ b/src/states/level/system_cannon.rs @@ -1,18 +1,17 @@ +use crate::states::level::components::{ + Ball, BallProjectile, BallType, Cannon, LevelMarker, TrackPath, WaveState, +}; +use crate::states::level::system::SLOT_SIZE; use bevy::prelude::*; use bevy::window::PrimaryWindow; -use crate::states::level::components::{Ball, BallProjectile, BallType, Cannon, LevelMarker, TrackPath}; -use crate::states::level::system::SLOT_SIZE; -pub fn setup_cannon( - mut commands: Commands, - asset_server: Res, -) { +pub fn setup_cannon(mut commands: Commands, asset_server: Res) { let texture: Handle = asset_server.load("cannon/cannon.png"); commands.spawn(( LevelMarker, Cannon, - Transform::from_xyz(579.0-(1280.0/2.0), (768.0/2.0)-150.0, 1.0), + Transform::from_xyz(579.0 - (1280.0 / 2.0), (768.0 / 2.0) - 150.0, 1.0), Sprite::from_image(texture), Visibility::Visible, )); @@ -23,13 +22,23 @@ pub fn rotate_cannon( window_query: Query<&Window, With>, camera_query: Query<(&Camera, &GlobalTransform)>, ) { - let Ok(mut cannon_tf) = query.single_mut() else { return }; - let Ok(window) = window_query.single() else { return }; - let Some(cursor_pos) = window.cursor_position() else { return }; - let Ok((camera, camera_tf)) = camera_query.single() else { return }; + let Ok(mut cannon_tf) = query.single_mut() else { + return; + }; + let Ok(window) = window_query.single() else { + return; + }; + let Some(cursor_pos) = window.cursor_position() else { + return; + }; + let Ok((camera, camera_tf)) = camera_query.single() else { + return; + }; // Конвертируем экранные координаты курсора в мировые - let Ok(world_cursor_pos) = camera.viewport_to_world_2d(camera_tf, cursor_pos) else { return }; + let Ok(world_cursor_pos) = camera.viewport_to_world_2d(camera_tf, cursor_pos) else { + return; + }; // Вектор направления от пушки к курсору let cannon_pos = Vec2::new(cannon_tf.translation.x, cannon_tf.translation.y); @@ -46,14 +55,18 @@ pub fn spawn_projectile_from_cannon( mouse_input: Res>, asset_server: Res, ) { - if !mouse_input.just_pressed(MouseButton::Left) { return; } + if !mouse_input.just_pressed(MouseButton::Left) { + return; + } - let Ok(cannon_tf) = cannon_query.single() else { return; }; + let Ok(cannon_tf) = cannon_query.single() else { + return; + }; let ball_type = BallType::First; // TODO: рандом/цикл let image = asset_server.load(ball_type.asset_path()); - let offset = 100.0; + let offset = 100.0; // сдвиг от центра к дулу пушки let direction = cannon_tf.rotation.mul_vec3(Vec3::Y).truncate().normalize(); let spawn_pos_2d = cannon_tf.translation.truncate() + direction * offset; let spawn_pos = spawn_pos_2d.extend(10.0); // z=10, чтобы было поверх трека @@ -71,6 +84,8 @@ pub fn spawn_projectile_from_cannon( velocity: direction * 800.0, previous_position: spawn_pos_2d, ball_type, + is_fresh: true, + travel_distance: 0.0, }, )); } @@ -82,7 +97,11 @@ pub fn move_projectiles( ) { for (entity, mut proj, mut transform) in projectiles.iter_mut() { // Обновляем позицию - let new_pos = proj.previous_position + proj.velocity * time.delta_secs(); + let delta = proj.velocity * time.delta_secs(); + let new_pos = proj.previous_position + delta; + + proj.travel_distance += delta.length(); + transform.translation = new_pos.extend(transform.translation.z); proj.previous_position = new_pos; @@ -93,64 +112,91 @@ pub fn move_projectiles( } } +const HIT_THRESHOLD: f32 = SLOT_SIZE; pub fn detect_projectile_hit( mut commands: Commands, track: Res, + mut wave: ResMut, mut projectiles: Query<(Entity, &mut BallProjectile)>, - mut balls: Query<&mut Ball>, // <-- нужен мутабельный доступ для сдвига + mut balls: Query<(Entity, &mut Ball)>, ) { - for (proj_entity, proj) in projectiles.iter_mut() { - let mut nearest_idx = 0; - let mut min_dist = f32::MAX; + // пока идёт структурное изменение, спавн и другие мутации ждут + if wave.is_queue_locked { + return; + } + + for (proj_entity, mut proj) in projectiles.iter_mut() { + if proj.is_fresh { + proj.is_fresh = false; + continue; + } + + let mut min_dist_any = f32::MAX; + let mut nearest_idx_any = 0; for (idx, &slot_pos) in track.points.iter().enumerate() { let dist = proj.previous_position.distance(slot_pos); - if dist < min_dist { - min_dist = dist; - nearest_idx = idx; + if dist < min_dist_any { + min_dist_any = dist; + nearest_idx_any = idx; + } + } + + // если снаряд далеко от трека - промах + if min_dist_any > HIT_THRESHOLD { + // commands.entity(proj_entity).despawn(); + continue; + } + + let insert_idx; + let target_progress; + + if let Some((_, target_ball)) = balls.iter().find(|(_, b)| b.slot_index == nearest_idx_any) { + // Копируем значения в локальные переменные + target_progress = target_ball.slot_progress; + + // Определяем индекс вставки + insert_idx = if target_progress <= 0.5 { + nearest_idx_any + } else { + nearest_idx_any + 1 + }; + } else { + // Слот пуст — снаряд пролетает мимо + commands.entity(proj_entity).despawn(); + continue; + } + + wave.is_queue_locked = true; + + // сдвигаем очередь + let max_idx = track.points.len() - 1; + let mut to_despawn = Vec::new(); + + + for (entity, mut ball) in balls.iter_mut() { + if ball.slot_index >= insert_idx { + if ball.slot_index < max_idx { + ball.slot_index += 1; + } else { + to_despawn.push(entity); + }; } } - const HIT_THRESHOLD: f32 = SLOT_SIZE; - if min_dist < HIT_THRESHOLD { - - let occupied_slots: Vec = balls.iter() - .map(|ball| ball.slot_index) - .collect(); - - if occupied_slots.contains(&nearest_idx) { - - let left_progress = if nearest_idx > 0 { - balls.iter() - .find(|b| b.slot_index == nearest_idx - 1) - .map(|b| b.slot_progress) - .unwrap_or(0.0) - } else { - 0.0 - }; - - // 2. Сдвигаем правую часть очереди вправо - for mut ball in balls.iter_mut() { - if ball.slot_index >= nearest_idx { - ball.slot_index += 1; - ball.slot_progress = 0.0; - } - } - - commands.entity(proj_entity) - .insert(Ball { - ball_type: proj.ball_type, - slot_index: nearest_idx, - slot_progress: left_progress, - }) - .remove::(); - - println!("Hit! Inserted at slot {}", nearest_idx); - - } else { - commands.entity(proj_entity).despawn(); - println!("Miss: slot {} is empty", nearest_idx); - } + for entity in to_despawn { + commands.entity(entity).despawn(); } + + commands.entity(proj_entity).insert({ + Ball { + ball_type: proj.ball_type, + slot_index: insert_idx, + slot_progress: target_progress, + } + }).remove::(); } -} \ No newline at end of file + + // Разблокировка (структурные изменения завершены) + wave.is_queue_locked = false; +}