From 562b340469c31605d785cd66db3203ac98a853b4 Mon Sep 17 00:00:00 2001 From: nquidox Date: Sat, 11 Apr 2026 18:09:50 +0300 Subject: [PATCH 1/2] old code remove --- src/states/level/components_cannon.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/states/level/components_cannon.rs b/src/states/level/components_cannon.rs index fc40ee8..6113c20 100644 --- a/src/states/level/components_cannon.rs +++ b/src/states/level/components_cannon.rs @@ -27,12 +27,6 @@ impl CannonState { } pub fn cycle_next(&mut self) { - // self.next_type = match self.next_type { - // BallType::First => BallType::Second, - // BallType::Second => BallType::Third, - // BallType::Third => BallType::Forth, - // BallType::Forth => BallType::First, - // }; let cur = self.current_type; self.current_type = self.next_type; self.next_type = cur; From 8d673fb3aad83c7973352d30e85e7918379062d0 Mon Sep 17 00:00:00 2001 From: nquidox Date: Sat, 11 Apr 2026 19:40:22 +0300 Subject: [PATCH 2/2] check and remove matches + const refactor --- src/states/level/components.rs | 6 ++ src/states/level/components_ball.rs | 13 ++-- src/states/level/constants.rs | 14 ++++ src/states/level/mod.rs | 6 ++ src/states/level/plugin.rs | 50 +++++++------- src/states/level/system.rs | 5 +- src/states/level/system_ball.rs | 103 +++++++++++++++++++++++----- src/states/level/system_cannon.rs | 7 +- src/states/level/system_track.rs | 2 +- 9 files changed, 153 insertions(+), 53 deletions(-) create mode 100644 src/states/level/constants.rs diff --git a/src/states/level/components.rs b/src/states/level/components.rs index 2050aca..47b5660 100644 --- a/src/states/level/components.rs +++ b/src/states/level/components.rs @@ -39,6 +39,7 @@ pub struct WaveState { pub ball_reached_end: bool, pub is_warming_up: bool, pub is_queue_locked: bool, //отдельно для большей явности + pub last_insert_index: Option, //для системы удаления серий шариков } impl Default for WaveState { @@ -48,6 +49,7 @@ impl Default for WaveState { ball_reached_end: false, is_warming_up: false, is_queue_locked: false, + last_insert_index: None, } } } @@ -65,4 +67,8 @@ pub enum Direction { Bottom, } +#[derive(Resource, Default, Debug)] +pub struct Score(pub u32); +#[derive(Component)] +pub struct ScoreTextMarker; diff --git a/src/states/level/components_ball.rs b/src/states/level/components_ball.rs index c5af0ed..77bc7ef 100644 --- a/src/states/level/components_ball.rs +++ b/src/states/level/components_ball.rs @@ -1,6 +1,13 @@ use bevy::prelude::*; use rand::seq::IndexedRandom; +#[derive(Component, Debug, Clone, Copy)] +pub struct Ball { + pub ball_type: BallType, + pub slot_index: usize, + pub slot_progress: f32, +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] pub enum BallType { #[default] @@ -28,9 +35,3 @@ impl BallType { } } -#[derive(Component, Debug)] -pub struct Ball { - pub ball_type: BallType, - pub slot_index: usize, - pub slot_progress: f32, -} \ No newline at end of file diff --git a/src/states/level/constants.rs b/src/states/level/constants.rs new file mode 100644 index 0000000..b108631 --- /dev/null +++ b/src/states/level/constants.rs @@ -0,0 +1,14 @@ +use crate::FACTOR; + +pub const SHIFT: f32 = 1.0; // коэффициент пересчета для слотов +pub const SLOT_SIZE: f32 = FACTOR as f32 / SHIFT; + +pub const BALL_MOVEMENT_SPEED: f32 = 60.0; // пикселей в секунду +pub const INITIAL_BALLS_COUNT: usize = 10; +pub const WARMUP_BALL_MOVEMENT_MULTIPLIER: f32 = 5.0; // во сколько раз больше BALL_MOVEMENT_SPEED + +// Z-индексы элементов +pub const CANNON_Z_INDEX: f32 = 10.0; +pub const PROJECTILE_Z_INDEX: f32 = 11.0; +pub const CURRENT_SHOT_Z_INDEX: f32 = 12.0; +pub const NEXT_SHOT_Z_INDEX: f32 = 12.0; \ No newline at end of file diff --git a/src/states/level/mod.rs b/src/states/level/mod.rs index 671aa49..e9b663c 100644 --- a/src/states/level/mod.rs +++ b/src/states/level/mod.rs @@ -18,3 +18,9 @@ mod components_cannon; pub use components_cannon::*; mod components_ball; pub use components_ball::*; +mod ui; +pub use ui::*; +mod constants; +pub use constants::*; + + diff --git a/src/states/level/plugin.rs b/src/states/level/plugin.rs index 8afb31b..52e3f73 100644 --- a/src/states/level/plugin.rs +++ b/src/states/level/plugin.rs @@ -2,36 +2,40 @@ use crate::states::AppState::GameState; use crate::states::level::*; use bevy::prelude::*; - pub struct LevelPlugin; impl Plugin for LevelPlugin { fn build(&self, app: &mut App) { - app.add_systems(OnEnter(GameState), ( - setup_level, - initialize_queue, - setup_cannon).chain()) - .add_systems( - Update, + app.add_systems( + OnEnter(GameState), + (setup_level, initialize_queue, setup_cannon).chain(), + ) + .add_systems( + Update, + ( + // debug_draw_grid, + debug_draw_track, + warmup_queue_movement, //до спавна остальных шариков + spawn_new_ball, + check_wave_completion, + //cannon systems + cycle_cannon_type, + update_cannon_preview, + spawn_projectile_from_cannon, + //сохранять порядок верхних трех + rotate_cannon, + move_projectiles, + //сохраняем порядок трех ( - // debug_draw_grid, - debug_draw_track, - warmup_queue_movement, //до спавна остальных шариков - spawn_new_ball, - move_queue_along_track, - check_wave_completion, - //cannon systems - cycle_cannon_type, - update_cannon_preview, - spawn_projectile_from_cannon, - //сохранять порядок верхних трех - rotate_cannon, - move_projectiles, detect_projectile_hit, + check_and_remove_matches, + move_queue_along_track, ) - .run_if(in_state(GameState)), + .chain(), ) - .add_systems(OnExit(GameState), cleanup_main_menu); + .run_if(in_state(GameState)), + ) + .add_systems(OnExit(GameState), cleanup_main_menu); } } @@ -40,4 +44,4 @@ fn cleanup_main_menu(mut commands: Commands, query: Query, @@ -64,7 +58,7 @@ pub fn move_queue_along_track( for (mut ball, mut transform) in balls.iter_mut() { // Увеличиваем прогресс - ball.slot_progress += SPEED * time.delta_secs() / SLOT_SIZE; + ball.slot_progress += BALL_MOVEMENT_SPEED * time.delta_secs() / SLOT_SIZE; // Если шарик достиг конца текущего сегмента if ball.slot_progress >= 1.0 { @@ -92,8 +86,7 @@ pub fn move_queue_along_track( } } -// FIXME перенести позже в более удобное место/объект левела -const START_COUNT: usize = 10; + pub fn initialize_queue( mut commands: Commands, @@ -102,16 +95,16 @@ pub fn initialize_queue( mut wave: ResMut, ) { // заполняем слоты от -START_COUNT до -1 - let base_idx = track.spawn_index.saturating_sub(START_COUNT); + let base_idx = track.spawn_index.saturating_sub(INITIAL_BALLS_COUNT); - for i in 0..START_COUNT { + for i in 0..INITIAL_BALLS_COUNT { let slot_idx = base_idx + i; // пропускаем индекс 0 (спавн за экраном) let Some(&pos) = track.points.get(slot_idx) else { continue; }; println!("Slot {}: {:?}", slot_idx, pos); //debug - let target_idx = slot_idx + START_COUNT; + let target_idx = slot_idx + INITIAL_BALLS_COUNT; let ball_type = BallType::random(); let image: Handle = asset_server.load(ball_type.asset_path()); @@ -136,8 +129,7 @@ pub fn initialize_queue( wave.spawning_allowed = false; } -// FIXME перенести позже в более удобное место/объект левела -const WARMUP_MULTIPLIER: f32 = 5.0; + pub fn warmup_queue_movement( track: Res, mut wave: ResMut, @@ -149,7 +141,7 @@ pub fn warmup_queue_movement( ) { if !wave.is_warming_up { return; } - let speed = SPEED * WARMUP_MULTIPLIER; + let speed = BALL_MOVEMENT_SPEED * WARMUP_BALL_MOVEMENT_MULTIPLIER; let spawn_slot = track.spawn_index.saturating_sub(1); @@ -209,3 +201,82 @@ pub fn check_wave_completion( } } +pub fn check_and_remove_matches( + mut commands: Commands, + mut wave: ResMut, + mut balls: ParamSet<( + Query<(Entity, &Ball)>, + Query<(Entity, &mut Ball)> + )>, + mut score: ResMut, +) { + let Some(insert_idx) = wave.last_insert_index.take() else { return; }; + + let mut balls_sorted: Vec<(Entity, Ball)> = balls.p0() + .iter() + .map(|(e, b)| (e, *b)) + .collect(); + balls_sorted.sort_by_key(|(_, b)| b.slot_index); + + if balls_sorted.is_empty() { return; } + + let Some(pos) = balls_sorted.iter().position(|(_, b)| b.slot_index == insert_idx) else { return; }; + let inserted_ball = balls_sorted[pos].1; + + // Ищем группу того же типа + let mut group_start = pos; + while group_start > 0 && balls_sorted[group_start - 1].1.ball_type == inserted_ball.ball_type { + group_start -= 1; + } + + let mut group_end = pos + 1; + while group_end < balls_sorted.len() && balls_sorted[group_end].1.ball_type == inserted_ball.ball_type { + group_end += 1; + } + + let group_size = group_end - group_start; + if group_size < 3 { return; } + + // Запоминаем диапазон до удаления + let removed_min_idx = balls_sorted[group_start].1.slot_index; + let removed_max_idx = balls_sorted[group_end - 1].1.slot_index; + let removed_count = group_size; + + // Проверяем соседей + let has_left_neighbors = balls_sorted.iter().any(|(_, b)| b.slot_index < removed_min_idx); + let has_right_neighbors = balls_sorted.iter().any(|(_, b)| b.slot_index > removed_max_idx); + + // --- УДАЛЕНИЕ --- + let to_remove: Vec = balls_sorted[group_start..group_end] + .iter() + .map(|(e, _)| *e) + .collect(); + + for entity in to_remove.iter() { + commands.entity(*entity).despawn(); + } + + let gained = removed_count * 100; + score.0 += gained as u32; + println!("Match at idx {}! +{} points | total: {}", insert_idx, gained, score.0); + + // --- СХЛОПЫВАНИЕ ОЧЕРЕДИ --- + + if has_left_neighbors && has_right_neighbors { + // 🔸 Удаление ВНУТРИ: сдвигаем все шарики СПРАВА влево + println!("Closing gap ({}..{}), shifting right balls left by {}", + removed_min_idx, removed_max_idx, removed_count); + + // Сдвигаем индексы всех шариков с index > removed_max_idx + for (entity, mut ball) in balls.p1().iter_mut() { + if ball.slot_index > removed_max_idx { + ball.slot_index -= removed_count; + ball.slot_progress = 0.0; // "Примагничиваем" к новому слоту + } + } + return; + } + + // 🔸 Удаление в НАЧАЛЕ или КОНЦЕ: индексы не меняем + // (опционально: можно сбросить progress у правых соседей для чёткости) +} \ No newline at end of file diff --git a/src/states/level/system_cannon.rs b/src/states/level/system_cannon.rs index b63ed66..b8ad981 100644 --- a/src/states/level/system_cannon.rs +++ b/src/states/level/system_cannon.rs @@ -2,11 +2,6 @@ use bevy::prelude::*; use crate::states::level::*; use bevy::window::PrimaryWindow; -const CANNON_Z_INDEX: f32 = 10.0; -const PROJECTILE_Z_INDEX: f32 = 11.0; -const CURRENT_SHOT_Z_INDEX: f32 = 12.0; -const NEXT_SHOT_Z_INDEX: f32 = 12.0; - pub fn setup_cannon(mut commands: Commands, asset_server: Res) { let texture: Handle = asset_server.load("cannon/cannon.png"); @@ -191,6 +186,8 @@ pub fn detect_projectile_hit( slot_progress: target_progress, } }).remove::(); + // запоминаем индекс слота, куда попали + wave.last_insert_index = Some(insert_idx); } // Разблокировка (структурные изменения завершены) diff --git a/src/states/level/system_track.rs b/src/states/level/system_track.rs index 68a4014..16dd292 100644 --- a/src/states/level/system_track.rs +++ b/src/states/level/system_track.rs @@ -29,7 +29,7 @@ pub fn setup_track_with_buffer() -> TrackPath { TrackPath { points: full_points, - spawn_index: BUFFER_SLOTS, + spawn_index: BUFFER_SLOTS-1, buffer_size: BUFFER_SLOTS, } }