diff --git a/src/states/level/components.rs b/src/states/level/components.rs index 733be10..2050aca 100644 --- a/src/states/level/components.rs +++ b/src/states/level/components.rs @@ -19,39 +19,6 @@ 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 { @@ -90,12 +57,12 @@ pub struct WarmupTarget { pub target_index: usize, } -#[derive(Component)] -pub struct Cannon; +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Direction { + Left, + Right, + Top, + Bottom, +} + -#[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 new file mode 100644 index 0000000..c5af0ed --- /dev/null +++ b/src/states/level/components_ball.rs @@ -0,0 +1,36 @@ +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 new file mode 100644 index 0000000..fc40ee8 --- /dev/null +++ b/src/states/level/components_cannon.rs @@ -0,0 +1,54 @@ +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 eb0e473..671aa49 100644 --- a/src/states/level/mod.rs +++ b/src/states/level/mod.rs @@ -1,8 +1,20 @@ pub mod plugin; -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 +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::*; diff --git a/src/states/level/plugin.rs b/src/states/level/plugin.rs index 5ef7031..8afb31b 100644 --- a/src/states/level/plugin.rs +++ b/src/states/level/plugin.rs @@ -1,10 +1,7 @@ use crate::states::AppState::GameState; -use crate::states::level::system_ball::*; -use crate::states::level::system::*; +use crate::states::level::*; 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; @@ -23,8 +20,12 @@ impl Plugin for LevelPlugin { spawn_new_ball, move_queue_along_track, check_wave_completion, - rotate_cannon, + //cannon systems + cycle_cannon_type, + update_cannon_preview, spawn_projectile_from_cannon, + //сохранять порядок верхних трех + rotate_cannon, move_projectiles, detect_projectile_hit, ) diff --git a/src/states/level/system.rs b/src/states/level/system.rs index 74dd8e7..104f95b 100644 --- a/src/states/level/system.rs +++ b/src/states/level/system.rs @@ -1,10 +1,8 @@ - -use crate::states::level::components::*; +use crate::states::level::*; use crate::{FACTOR, HEIGHT, WIDTH}; use bevy::prelude::*; -use crate::states::level::system_track::setup_track_with_buffer; -pub const SHIFT: f32 = 2.0; +pub const SHIFT: f32 = 1.0; pub const SLOT_SIZE: f32 = FACTOR as f32 / SHIFT; pub fn setup_timer(mut commands: Commands){ @@ -13,6 +11,12 @@ 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 c2908a3..0392caa 100644 --- a/src/states/level/system_ball.rs +++ b/src/states/level/system_ball.rs @@ -1,6 +1,5 @@ -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, diff --git a/src/states/level/system_cannon.rs b/src/states/level/system_cannon.rs index 40ed360..b63ed66 100644 --- a/src/states/level/system_cannon.rs +++ b/src/states/level/system_cannon.rs @@ -1,20 +1,23 @@ -use crate::states::level::components::{ - Ball, BallProjectile, BallType, Cannon, LevelMarker, TrackPath, WaveState, -}; -use crate::states::level::system::SLOT_SIZE; 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"); 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, CANNON_Z_INDEX), Sprite::from_image(texture), Visibility::Visible, )); + println!("Cannon visuals is set up"); } pub fn rotate_cannon( @@ -54,6 +57,7 @@ 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; @@ -63,13 +67,13 @@ pub fn spawn_projectile_from_cannon( return; }; - let ball_type = BallType::First; // TODO: рандом/цикл + let ball_type = cannon_state.current_type; let image = asset_server.load(ball_type.asset_path()); 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, чтобы было поверх трека + let spawn_pos = spawn_pos_2d.extend(PROJECTILE_Z_INDEX); commands.spawn(( LevelMarker, @@ -86,6 +90,7 @@ pub fn spawn_projectile_from_cannon( ball_type, }, )); + cannon_state.fire(); } pub fn move_projectiles( @@ -97,7 +102,7 @@ pub fn move_projectiles( // Обновляем позицию let delta = proj.velocity * time.delta_secs(); let new_pos = proj.previous_position + delta; - + transform.translation = new_pos.extend(transform.translation.z); proj.previous_position = new_pos; @@ -147,14 +152,14 @@ pub fn detect_projectile_hit( target_progress = target_ball.slot_progress; // Определяем индекс вставки - insert_idx = if target_progress <= 0.5 { - nearest_idx_any + insert_idx = if target_progress <= 0.5 { //TODO check correction + nearest_idx_any - 1 } else { - nearest_idx_any + 1 + nearest_idx_any }; } else { // Слот пуст — снаряд пролетает мимо - commands.entity(proj_entity).despawn(); + // commands.entity(proj_entity).despawn(); continue; } @@ -163,8 +168,8 @@ pub fn detect_projectile_hit( // сдвигаем очередь 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 { @@ -174,11 +179,11 @@ pub fn detect_projectile_hit( }; } } - + for entity in to_despawn { commands.entity(entity).despawn(); } - + commands.entity(proj_entity).insert({ Ball { ball_type: proj.ball_type, @@ -191,3 +196,68 @@ pub fn detect_projectile_hit( // Разблокировка (структурные изменения завершены) 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(), + } +} diff --git a/src/states/level/system_grid.rs b/src/states/level/system_grid.rs index efe105c..511c7c5 100644 --- a/src/states/level/system_grid.rs +++ b/src/states/level/system_grid.rs @@ -1,8 +1,7 @@ use bevy::color::Color; use bevy::math::{Vec2, Vec3}; -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 bevy::prelude::*; +use crate::states::level::*; 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 889a409..68a4014 100644 --- a/src/states/level/system_track.rs +++ b/src/states/level/system_track.rs @@ -1,8 +1,7 @@ use bevy::color::Color; use bevy::math::Vec2; -use bevy::prelude::{Gizmos, Res}; -use crate::states::level::components::TrackPath; -use crate::states::level::system::SLOT_SIZE; +use bevy::prelude::*; +use crate::states::level::*; const BUFFER_SLOTS: usize = 20; @@ -35,8 +34,38 @@ pub fn setup_track_with_buffer() -> TrackPath { } } +// for debug fn make_track() -> Vec { - 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![ //спавн точка за видимой областью // Vec2::new(-660.0, -140.0), Vec2::new(-620.0, -140.0), @@ -72,7 +101,8 @@ fn make_track() -> Vec { Vec2::new(300.0, 140.0), Vec2::new(340.0, 140.0), Vec2::new(380.0, 140.0), - ] + ]; + track } @@ -86,17 +116,30 @@ pub fn debug_draw_track(mut gizmos: Gizmos, track: Res) { } } -// 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 +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