diff --git a/src/states/level/components.rs b/src/states/level/components.rs index 2050aca..04c1874 100644 --- a/src/states/level/components.rs +++ b/src/states/level/components.rs @@ -19,6 +19,39 @@ pub struct TrackPath { pub buffer_size: usize, } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +pub enum BallType { + #[default] + First, + Second, + Third, + Forth, +} + +impl BallType { + pub const fn asset_path(&self) -> &'static str { + match self { + BallType::First => "balls/1.png", + BallType::Second => "balls/2.png", + BallType::Third => "balls/3.png", + BallType::Forth => "balls/4.png", + } + } + + pub fn random() -> Self { + [Self::First, Self::Second, Self::Third, Self::Forth] + .choose(&mut rand::rng()) + .copied() + .unwrap_or_default() + } +} + +#[derive(Component, Debug)] +pub struct Ball { + pub ball_type: BallType, + pub slot_index: usize, + pub slot_progress: f32, +} #[derive(Resource)] pub struct SpawnTimer { @@ -38,7 +71,6 @@ 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 { @@ -47,7 +79,6 @@ impl Default for WaveState { spawning_allowed: false, ball_reached_end: false, is_warming_up: false, - is_queue_locked: false, } } } @@ -57,12 +88,12 @@ pub struct WarmupTarget { pub target_index: usize, } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Direction { - Left, - Right, - Top, - Bottom, -} - +#[derive(Component)] +pub struct Cannon; +#[derive(Component)] +pub struct BallProjectile { + pub velocity: Vec2, + pub previous_position: Vec2, + pub ball_type: BallType, +} \ No newline at end of file diff --git a/src/states/level/components_ball.rs b/src/states/level/components_ball.rs deleted file mode 100644 index c5af0ed..0000000 --- a/src/states/level/components_ball.rs +++ /dev/null @@ -1,36 +0,0 @@ -use bevy::prelude::*; -use rand::seq::IndexedRandom; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] -pub enum BallType { - #[default] - First, - Second, - Third, - Forth, -} - -impl BallType { - pub const fn asset_path(&self) -> &'static str { - match self { - BallType::First => "balls/1.png", - BallType::Second => "balls/2.png", - BallType::Third => "balls/3.png", - BallType::Forth => "balls/4.png", - } - } - - pub fn random() -> Self { - [Self::First, Self::Second, Self::Third, Self::Forth] - .choose(&mut rand::rng()) - .copied() - .unwrap_or_default() - } -} - -#[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/components_cannon.rs b/src/states/level/components_cannon.rs deleted file mode 100644 index fc40ee8..0000000 --- a/src/states/level/components_cannon.rs +++ /dev/null @@ -1,54 +0,0 @@ -use bevy::math::Vec2; -use bevy::prelude::{Component, Resource}; -use crate::states::level::*; - -#[derive(Component)] -pub struct Cannon; - -#[derive(Resource, Debug, Clone)] -pub struct CannonState { - pub current_type: BallType, - pub next_type: BallType, -} - -impl Default for CannonState { - fn default() -> Self { - Self { - current_type: BallType::random(), - next_type: BallType::random(), - } - } -} - -impl CannonState { - pub fn fire(&mut self) { - self.current_type = self.next_type; - self.next_type = BallType::random(); - } - - 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; - - } -} - -#[derive(Component)] -pub struct CurrentPreviewMarker; - -#[derive(Component)] -pub struct NextPreviewMarker; - -#[derive(Component)] -pub struct BallProjectile { - pub velocity: Vec2, - pub previous_position: Vec2, - pub ball_type: BallType, -} \ No newline at end of file diff --git a/src/states/level/mod.rs b/src/states/level/mod.rs index 671aa49..eb0e473 100644 --- a/src/states/level/mod.rs +++ b/src/states/level/mod.rs @@ -1,20 +1,8 @@ pub mod plugin; -mod components; -pub use components::*; -mod system; -pub use system::*; -mod state; -pub use state::*; -mod system_ball; -pub use system_ball::*; -mod system_cannon; -pub use system_cannon::*; -mod system_track; -pub use system_track::*; -pub mod system_grid; -pub use system_grid::*; - -mod components_cannon; -pub use components_cannon::*; -mod components_ball; -pub use components_ball::*; +pub mod components; +pub mod system; +pub mod state; +pub mod system_ball; +pub mod system_cannon; +pub mod system_track; +pub mod system_grid; \ No newline at end of file diff --git a/src/states/level/plugin.rs b/src/states/level/plugin.rs index 8afb31b..5ef7031 100644 --- a/src/states/level/plugin.rs +++ b/src/states/level/plugin.rs @@ -1,7 +1,10 @@ use crate::states::AppState::GameState; -use crate::states::level::*; +use crate::states::level::system_ball::*; +use crate::states::level::system::*; use bevy::prelude::*; - +use crate::states::level::system_cannon::*; +use crate::states::level::components::LevelMarker; +use crate::states::level::system_track::{debug_draw_track}; pub struct LevelPlugin; @@ -20,12 +23,8 @@ impl Plugin for LevelPlugin { spawn_new_ball, move_queue_along_track, check_wave_completion, - //cannon systems - cycle_cannon_type, - update_cannon_preview, - spawn_projectile_from_cannon, - //сохранять порядок верхних трех rotate_cannon, + spawn_projectile_from_cannon, move_projectiles, detect_projectile_hit, ) diff --git a/src/states/level/system.rs b/src/states/level/system.rs index 104f95b..74dd8e7 100644 --- a/src/states/level/system.rs +++ b/src/states/level/system.rs @@ -1,8 +1,10 @@ -use crate::states::level::*; + +use crate::states::level::components::*; use crate::{FACTOR, HEIGHT, WIDTH}; use bevy::prelude::*; +use crate::states::level::system_track::setup_track_with_buffer; -pub const SHIFT: f32 = 1.0; +pub const SHIFT: f32 = 2.0; pub const SLOT_SIZE: f32 = FACTOR as f32 / SHIFT; pub fn setup_timer(mut commands: Commands){ @@ -11,12 +13,6 @@ pub fn setup_timer(mut commands: Commands){ pub fn setup_level(mut commands: Commands){ commands.insert_resource(setup_track_with_buffer()); - println!("Track is set up"); - commands.insert_resource(WaveState::default()); - println!("WaveState is set up"); - - commands.insert_resource(build_cannon_state()); - println!("CannonState is set up"); } diff --git a/src/states/level/system_ball.rs b/src/states/level/system_ball.rs index 0392caa..2f5cac7 100644 --- a/src/states/level/system_ball.rs +++ b/src/states/level/system_ball.rs @@ -1,5 +1,6 @@ +use crate::states::level::components::{Ball, BallType, LevelMarker, TrackPath, WarmupTarget, WaveState}; +use crate::states::level::system::SLOT_SIZE; use bevy::prelude::*; -use crate::states::level::*; pub fn spawn_new_ball( mut commands: Commands, @@ -10,7 +11,7 @@ pub fn spawn_new_ball( balls: Query<&Ball>, wave: Res, ) { - if !wave.spawning_allowed || wave.is_queue_locked { + if !wave.spawning_allowed { timer.reset(); return; } @@ -208,4 +209,3 @@ pub fn check_wave_completion( } } } - diff --git a/src/states/level/system_cannon.rs b/src/states/level/system_cannon.rs index b63ed66..e2cbb94 100644 --- a/src/states/level/system_cannon.rs +++ b/src/states/level/system_cannon.rs @@ -1,23 +1,21 @@ use bevy::prelude::*; -use crate::states::level::*; use bevy::window::PrimaryWindow; +use crate::states::level::components::{Ball, BallProjectile, BallType, Cannon, LevelMarker, TrackPath}; +use crate::states::level::system::SLOT_SIZE; -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) { +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, CANNON_Z_INDEX), + Transform::from_xyz(579.0-(1280.0/2.0), (768.0/2.0)-150.0, 1.0), Sprite::from_image(texture), Visibility::Visible, )); - println!("Cannon visuals is set up"); } pub fn rotate_cannon( @@ -25,23 +23,13 @@ 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); @@ -57,23 +45,18 @@ pub fn spawn_projectile_from_cannon( cannon_query: Query<&Transform, With>, mouse_input: Res>, asset_server: Res, - mut cannon_state: ResMut ) { - 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 = cannon_state.current_type; + 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(PROJECTILE_Z_INDEX); + let spawn_pos = spawn_pos_2d.extend(10.0); // z=10, чтобы было поверх трека commands.spawn(( LevelMarker, @@ -90,7 +73,6 @@ pub fn spawn_projectile_from_cannon( ball_type, }, )); - cannon_state.fire(); } pub fn move_projectiles( @@ -100,9 +82,7 @@ pub fn move_projectiles( ) { for (entity, mut proj, mut transform) in projectiles.iter_mut() { // Обновляем позицию - let delta = proj.velocity * time.delta_secs(); - let new_pos = proj.previous_position + delta; - + let new_pos = proj.previous_position + proj.velocity * time.delta_secs(); transform.translation = new_pos.extend(transform.translation.z); proj.previous_position = new_pos; @@ -113,151 +93,64 @@ 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<(Entity, &mut Ball)>, + mut balls: Query<&mut Ball>, // <-- нужен мутабельный доступ для сдвига ) { - // пока идёт структурное изменение, спавн и другие мутации ждут - if wave.is_queue_locked { - return; - } - for (proj_entity, proj) in projectiles.iter_mut() { - let mut min_dist_any = f32::MAX; - let mut nearest_idx_any = 0; + let mut nearest_idx = 0; + let mut min_dist = f32::MAX; for (idx, &slot_pos) in track.points.iter().enumerate() { let dist = proj.previous_position.distance(slot_pos); - if dist < min_dist_any { - min_dist_any = dist; - nearest_idx_any = idx; + if dist < min_dist { + min_dist = dist; + nearest_idx = idx; } } + + 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) { - // если снаряд далеко от трека - промах - 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 { //TODO check correction - nearest_idx_any - 1 - } else { - nearest_idx_any - }; - } 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; + 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 { - to_despawn.push(entity); + 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::(); } - - // Разблокировка (структурные изменения завершены) - wave.is_queue_locked = false; -} - -pub fn cycle_cannon_type( - mut cannon_state: ResMut, - mouse_input: Res>, -) { - if mouse_input.just_pressed(MouseButton::Right) { - cannon_state.cycle_next(); - } -} - -pub fn update_cannon_preview( - mut commands: Commands, - cannon_state: Res, - asset_server: Res, - cannon_query: Query>, - muzzle_query: Query>, - next_query: Query>, -) { - let Ok(cannon_entity) = cannon_query.single() else { return }; - - let muzzle_image = asset_server.load(cannon_state.current_type.asset_path()); - - if let Ok(muzzle_entity) = muzzle_query.single() { - // усли шарик у дула есть, то обновляем спрайт - commands.entity(muzzle_entity).insert(Sprite::from_image(muzzle_image)); - } else { - //иначе создаем - let preview_entity = commands.spawn(( - CurrentPreviewMarker, - Sprite{ - image: muzzle_image, - custom_size: Some(Vec2::splat(SLOT_SIZE)), - ..default() - }, - Transform::from_xyz(0.0, 100.0, CURRENT_SHOT_Z_INDEX), - Visibility::Visible, - )).id(); - commands.entity(cannon_entity).add_child(preview_entity); - } - - let next_image = asset_server.load(cannon_state.next_type.asset_path()); - - if let Ok(next_entity) = next_query.single() { - commands.entity(next_entity).insert(Sprite::from_image(next_image)); - } else { - let preview_entity = commands.spawn(( - NextPreviewMarker, - Sprite{ - image: next_image, - custom_size: Some(Vec2::splat(SLOT_SIZE)), - ..default() - }, - Transform::from_xyz(0.0, -50.0, NEXT_SHOT_Z_INDEX), - Visibility::Visible, - )).id(); - commands.entity(cannon_entity).add_child(preview_entity); - } -} - -pub fn build_cannon_state() -> CannonState { - CannonState { - current_type: BallType::random(), - next_type: BallType::random(), - } -} +} \ No newline at end of file diff --git a/src/states/level/system_grid.rs b/src/states/level/system_grid.rs index 511c7c5..efe105c 100644 --- a/src/states/level/system_grid.rs +++ b/src/states/level/system_grid.rs @@ -1,7 +1,8 @@ use bevy::color::Color; use bevy::math::{Vec2, Vec3}; -use bevy::prelude::*; -use crate::states::level::*; +use bevy::prelude::{default, Commands, Gizmos, Text2d, TextColor, TextFont, Transform}; +use crate::states::level::components::DebugSlotNumber; +use crate::states::level::system::{SHIFT, SLOT_SIZE}; use crate::{HEIGHT, WIDTH}; const GRID_COLS: f32 = WIDTH as f32 * SHIFT; diff --git a/src/states/level/system_track.rs b/src/states/level/system_track.rs index 68a4014..889a409 100644 --- a/src/states/level/system_track.rs +++ b/src/states/level/system_track.rs @@ -1,7 +1,8 @@ use bevy::color::Color; use bevy::math::Vec2; -use bevy::prelude::*; -use crate::states::level::*; +use bevy::prelude::{Gizmos, Res}; +use crate::states::level::components::TrackPath; +use crate::states::level::system::SLOT_SIZE; const BUFFER_SLOTS: usize = 20; @@ -34,38 +35,8 @@ pub fn setup_track_with_buffer() -> TrackPath { } } -// for debug fn make_track() -> Vec { - make_track_80() -} - -fn make_track_80() -> Vec { - // let track = generate_track_line(Vec2::new(-600.0, -80.0), Vec2::new(600.0, -80.0), Direction::Right); - - let track: Vec = vec![ - Vec2::new(-600.0, -80.0), - Vec2::new(-520.0, -80.0), - Vec2::new(-440.0, -80.0), - Vec2::new(-360.0, -80.0), - Vec2::new(-280.0, -80.0), - Vec2::new(-200.0, -80.0), - Vec2::new(-120.0, -80.0), - Vec2::new(-40.0, -80.0), - Vec2::new(40.0, -80.0), - Vec2::new(120.0, -80.0), - Vec2::new(200.0, -80.0), - Vec2::new(280.0, -80.0), - Vec2::new(360.0, -80.0), - Vec2::new(440.0, -80.0), - Vec2::new(520.0, -80.0), - Vec2::new(600.0, -80.0), - ]; - - track -} - -fn make_track_40() -> Vec { - let mut track = vec![ + vec![ //спавн точка за видимой областью // Vec2::new(-660.0, -140.0), Vec2::new(-620.0, -140.0), @@ -101,8 +72,7 @@ fn make_track_40() -> Vec { Vec2::new(300.0, 140.0), Vec2::new(340.0, 140.0), Vec2::new(380.0, 140.0), - ]; - track + ] } @@ -116,30 +86,17 @@ pub fn debug_draw_track(mut gizmos: Gizmos, track: Res) { } } -fn generate_track_line(start: Vec2, end: Vec2, direction: Direction) -> Vec { - //генерируем только точки между началом и концом - let mut result = vec![end]; - - let step = match direction { - Direction::Left => Vec2::new(-SLOT_SIZE, 0.0), - Direction::Right => Vec2::new( SLOT_SIZE, 0.0), - Direction::Top => Vec2::new(0.0, SLOT_SIZE), - Direction::Bottom => Vec2::new(0.0, -SLOT_SIZE), - }; - - let steps = match direction { - Direction::Left | Direction::Right => { - ((end.x - start.x).abs() / SLOT_SIZE) as usize - } - Direction::Top | Direction::Bottom => { - ((end.y - start.y).abs() / SLOT_SIZE) as usize - } - }; - - for i in 0..=steps { - let pos = start + step * i as f32; - result.push(pos); - } - - result -} \ No newline at end of file +// fn generate_track(mut commands: Commands) { +// let half_w = crate::states::level::system::GRID_COLS as f32 * SLOT_SIZE / 2.0; +// let half_h = crate::states::level::system::GRID_ROWS as f32 * SLOT_SIZE / 2.0; +// +// let mut points = Vec::with_capacity(crate::states::level::system::GRID_COLS as usize); +// +// for col in (0..crate::states::level::system::GRID_COLS as u32).rev() { +// let x = -half_w + col as f32 * SLOT_SIZE + SLOT_SIZE / 2.0; +// let y = 0.0; +// points.push(Vec2::new(x, y)); +// } +// +// commands.insert_resource(TrackPath { points, spawn_index: 0, buffer_size: 0 }); +// } \ No newline at end of file