From 7b334b33c020a60b2444e9a6c2111bc3d2bf99fe Mon Sep 17 00:00:00 2001 From: nquidox Date: Thu, 16 Apr 2026 22:34:54 +0300 Subject: [PATCH 1/7] constants moved --- src/states/linear/constants.rs | 15 ++++ src/states/linear/systems.rs | 4 - src/states/linear/systems_track.rs | 133 ++++++++++++++++++----------- 3 files changed, 96 insertions(+), 56 deletions(-) create mode 100644 src/states/linear/constants.rs diff --git a/src/states/linear/constants.rs b/src/states/linear/constants.rs new file mode 100644 index 0000000..a5689ba --- /dev/null +++ b/src/states/linear/constants.rs @@ -0,0 +1,15 @@ +use crate::FACTOR; +use bevy::color::Color; + +pub const CENTER_X: f32 = 0.0; +pub const CENTER_Y: f32 = 0.0; + +// pub const FACTOR_RADIUS: f32 = FACTOR as f32 / 2.0; + +pub const STEP: f32 = FACTOR as f32 / SCALE; +pub const SCALE: f32 = 2.0; + +//COLORS +pub const PINK: Color = Color::srgb_u8(250, 0, 155); +pub const BLUE: Color = Color::srgb_u8(0, 0, 255); +pub const GREEN: Color = Color::srgb_u8(0, 255, 0); diff --git a/src/states/linear/systems.rs b/src/states/linear/systems.rs index af68fe8..d48d23c 100644 --- a/src/states/linear/systems.rs +++ b/src/states/linear/systems.rs @@ -3,10 +3,6 @@ use bevy::prelude::*; use states::linear::*; use std::f32::consts::FRAC_PI_2; -const PINK: Color = Color::srgb_u8(250, 0, 155); -const BLUE: Color = Color::srgb_u8(0, 0, 255); -const GREEN: Color = Color::srgb_u8(0, 255, 0); - pub fn draw_track_gizmos(track: Res, mut gizmos: Gizmos) { let mut current_pos = track.start_point; let mut current_dir = track.start_direction.normalize(); diff --git a/src/states/linear/systems_track.rs b/src/states/linear/systems_track.rs index 60b0172..c4d7e75 100644 --- a/src/states/linear/systems_track.rs +++ b/src/states/linear/systems_track.rs @@ -1,47 +1,71 @@ -use std::f32::consts::FRAC_PI_2; +use crate::FACTOR; use crate::states::linear::*; -use crate::{FACTOR}; use bevy::prelude::*; use bevy::reflect::List; - -pub const CENTER_X: f32 = 0.0; -pub const CENTER_Y: f32 = 0.0; - -// pub const FACTOR_RADIUS: f32 = FACTOR as f32 / 2.0; - -pub const STEP: f32 = FACTOR as f32 / SCALE; -pub const SCALE: f32 = 2.0; - +use std::f32::consts::FRAC_PI_2; pub fn setup_linear_track() -> Track { println!("Построение трека начато"); - Track { - start_point: Vec2 { - x: CENTER_X - FACTOR as f32 * 7.0, - y: CENTER_Y - FACTOR as f32 * 4.0, + Track { + start_point: Vec2 { + x: CENTER_X - FACTOR as f32 * 7.0, + y: CENTER_Y - FACTOR as f32 * 4.0, + }, + start_direction: Vec2::X, + segments: vec![ + PathSegment::Line { length: STEP * 6.0 }, + PathSegment::Turn { + radius: STEP, + left: true, }, - start_direction: Vec2::X, - segments: vec![ - PathSegment::Line { length: STEP * 6.0 }, - PathSegment::Turn { radius: STEP, left: true }, - PathSegment::Line { length: STEP }, - PathSegment::Turn { radius: STEP, left: false }, - PathSegment::Line { length: STEP * 18.0 }, - PathSegment::Turn { radius: STEP, left: true }, - PathSegment::Line { length: STEP * 4.0 }, - PathSegment::Turn { radius: STEP, left: true }, - PathSegment::Turn { radius: STEP, left: false }, - PathSegment::Line { length: STEP * 4.0 }, - PathSegment::Turn { radius: STEP, left: true }, - PathSegment::Line { length: STEP * 20.0 }, - PathSegment::Turn { radius: STEP, left: true }, - PathSegment::Line { length: STEP * 5.0 }, - PathSegment::Turn { radius: STEP, left: true }, - PathSegment::Line { length: STEP * 10.0 }, - PathSegment::Turn { radius: STEP, left: false }, - PathSegment::Line { length: STEP * 2.0 }, - ], - } + PathSegment::Line { length: STEP }, + PathSegment::Turn { + radius: STEP, + left: false, + }, + PathSegment::Line { + length: STEP * 18.0, + }, + PathSegment::Turn { + radius: STEP, + left: true, + }, + PathSegment::Line { length: STEP * 4.0 }, + PathSegment::Turn { + radius: STEP, + left: true, + }, + PathSegment::Turn { + radius: STEP, + left: false, + }, + PathSegment::Line { length: STEP * 4.0 }, + PathSegment::Turn { + radius: STEP, + left: true, + }, + PathSegment::Line { + length: STEP * 20.0, + }, + PathSegment::Turn { + radius: STEP, + left: true, + }, + PathSegment::Line { length: STEP * 5.0 }, + PathSegment::Turn { + radius: STEP, + left: true, + }, + PathSegment::Line { + length: STEP * 10.0, + }, + PathSegment::Turn { + radius: STEP, + left: false, + }, + PathSegment::Line { length: STEP * 2.0 }, + ], + } } pub fn precalculate_track(track: &Track) -> PrecalculatedTrack { @@ -50,10 +74,10 @@ pub fn precalculate_track(track: &Track) -> PrecalculatedTrack { let mut current_pos = track.start_point; let mut current_dir = track.start_direction; - + // на первом проходе заполняем все, кроме t_start и t_end, // потому что мы не можем посчитать их без общей длины - for seg in track.segments.iter(){ + for seg in track.segments.iter() { match seg { PathSegment::Line { length } => { let calc_seg = PTSegment { @@ -73,12 +97,12 @@ pub fn precalculate_track(track: &Track) -> PrecalculatedTrack { current_pos = current_pos + length * current_dir; // направление не меняем, пропуск // накапливаем длину - cumulative_length.push(cumulative_length[cumulative_length.len() -1] + *length); + cumulative_length.push(cumulative_length[cumulative_length.len() - 1] + *length); } - PathSegment::Turn {radius, left } => { + PathSegment::Turn { radius, left } => { let arc = helper_arc_calculator(current_pos, current_dir, *left, *radius); - + let calc_seg = PTSegment { t_start: 0.0, t_end: 0.0, @@ -95,31 +119,32 @@ pub fn precalculate_track(track: &Track) -> PrecalculatedTrack { current_pos = arc.end_pos; current_dir = arc.normal; // накапливаем длину - cumulative_length.push(cumulative_length[cumulative_length.len() -1] + (FRAC_PI_2 * radius)); + cumulative_length + .push(cumulative_length[cumulative_length.len() - 1] + (FRAC_PI_2 * radius)); } } } - + let last_index = cumulative_length.len() - 1; let total_length = cumulative_length[last_index]; - + // итерируемся уже по прекалькулированному вектору // заполняем точки на треке в пересчете на общую длину println!("Precalculated track:"); for (i, seg) in pt.segments.iter_mut().enumerate() { seg.t_start = cumulative_length[i] / total_length; if i < last_index { - seg.t_end = cumulative_length[i+1] / total_length; + seg.t_end = cumulative_length[i + 1] / total_length; } else { seg.t_end = 1.0; } println!("{:?}", seg); } - + pt } -pub fn helper_arc_calculator(pos: Vec2, dir: Vec2, turn_to: bool, radius: f32) -> ArcCalculation{ +pub fn helper_arc_calculator(pos: Vec2, dir: Vec2, turn_to: bool, radius: f32) -> ArcCalculation { let (normal, sign): (Vec2, f32) = if turn_to { (Vec2::new(-dir.y, dir.x), -1.0) } else { @@ -128,14 +153,18 @@ pub fn helper_arc_calculator(pos: Vec2, dir: Vec2, turn_to: bool, radius: f32) - let center = pos + normal * radius; let start_vec = pos - center; - let initial_vec = if turn_to { -start_vec.perp() } else { -start_vec }; + let initial_vec = if turn_to { + -start_vec.perp() + } else { + -start_vec + }; let angle = initial_vec.to_angle(); - - ArcCalculation{ + + ArcCalculation { normal, center, start_angle: angle, sweep_sign: sign, end_pos: center + normal.perp() * radius * sign, } -} \ No newline at end of file +} From 5ba555b06201f38defad0b4d1f9182b4cf6c0c0c Mon Sep 17 00:00:00 2001 From: nquidox Date: Thu, 16 Apr 2026 23:05:31 +0300 Subject: [PATCH 2/7] spawn ball + run in set --- src/states/linear/components_ball.rs | 35 ++++++++++++++++++++++++++++ src/states/linear/constants.rs | 5 +++- src/states/linear/mod.rs | 9 +++++++ src/states/linear/plugin.rs | 25 +++++++++++++------- src/states/linear/system_ball.rs | 27 +++++++++++++++++++++ 5 files changed, 92 insertions(+), 9 deletions(-) create mode 100644 src/states/linear/components_ball.rs create mode 100644 src/states/linear/system_ball.rs diff --git a/src/states/linear/components_ball.rs b/src/states/linear/components_ball.rs new file mode 100644 index 0000000..634a81e --- /dev/null +++ b/src/states/linear/components_ball.rs @@ -0,0 +1,35 @@ +use bevy::prelude::Component; +use rand::prelude::IndexedRandom; + +#[derive(Component, Debug, Clone, Copy)] +pub struct RoundBall { + pub ball_type: RoundBallType, + pub track_progress: f32, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +pub enum RoundBallType { + #[default] + Red, + Green, + Blue, + Purple, +} + +impl RoundBallType { + pub const fn asset_path(&self) -> &'static str { + match self { + RoundBallType::Red => "sprites/round/red.png", + RoundBallType::Green => "sprites/round/green.png", + RoundBallType::Blue => "sprites/round/blue.png", + RoundBallType::Purple => "sprites/round/purple.png", + } + } + + pub fn random_from_4() -> Self { + [Self::Red, Self::Green, Self::Blue, Self::Purple] + .choose(&mut rand::rng()) + .copied() + .unwrap_or_default() + } +} \ No newline at end of file diff --git a/src/states/linear/constants.rs b/src/states/linear/constants.rs index a5689ba..248dac0 100644 --- a/src/states/linear/constants.rs +++ b/src/states/linear/constants.rs @@ -9,7 +9,10 @@ pub const CENTER_Y: f32 = 0.0; pub const STEP: f32 = FACTOR as f32 / SCALE; pub const SCALE: f32 = 2.0; -//COLORS +// Z-INDEXES +pub const ROUND_BALL_Z: f32 = 10.0; + +// COLORS pub const PINK: Color = Color::srgb_u8(250, 0, 155); pub const BLUE: Color = Color::srgb_u8(0, 0, 255); pub const GREEN: Color = Color::srgb_u8(0, 255, 0); diff --git a/src/states/linear/mod.rs b/src/states/linear/mod.rs index c9c5a5f..5a29cd3 100644 --- a/src/states/linear/mod.rs +++ b/src/states/linear/mod.rs @@ -11,3 +11,12 @@ pub use components_track::*; mod systems_track; pub use systems_track::*; +mod components_ball; +pub use components_ball::*; + +mod system_ball; +pub use system_ball::*; +mod constants; +pub use constants::*; + + diff --git a/src/states/linear/plugin.rs b/src/states/linear/plugin.rs index 09d1a1e..190b88d 100644 --- a/src/states/linear/plugin.rs +++ b/src/states/linear/plugin.rs @@ -1,23 +1,31 @@ -use bevy::prelude::*; use crate::states::AppState::LinearState; -use crate::states::level::GameOver; use crate::states::linear::*; +use bevy::prelude::*; pub struct LinearPlugin; +// создаем енум, чтоб не указывать run_if(in_state) для каждой системы +#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)] +pub enum LinearUpdateSet { + Track, +} + impl Plugin for LinearPlugin { fn build(&self, app: &mut App) { - app - .add_systems(OnEnter (LinearState), setup) - .add_systems(Update, (draw_track_gizmos).run_if(in_state(LinearState))) - .add_systems(OnExit (LinearState), cleanup); + app.add_systems(OnEnter(LinearState), setup) + .configure_sets(Update, LinearUpdateSet::Track.run_if(in_state(LinearState))) + .add_systems( + Update, + (draw_track_gizmos, spawn_round_ball).in_set(LinearUpdateSet::Track), + ) + .add_systems(OnExit(LinearState), cleanup); } } fn setup(mut commands: Commands) { let build_track = setup_linear_track(); let precalculated_track = precalculate_track(&build_track); - + commands.insert_resource(build_track); commands.insert_resource(precalculated_track); } @@ -30,4 +38,5 @@ fn cleanup(mut commands: Commands, query: Query> // зачищаем ресурсы по типу commands.remove_resource::(); -} \ No newline at end of file + commands.remove_resource::(); +} diff --git a/src/states/linear/system_ball.rs b/src/states/linear/system_ball.rs new file mode 100644 index 0000000..597ab82 --- /dev/null +++ b/src/states/linear/system_ball.rs @@ -0,0 +1,27 @@ +use bevy::asset::ErasedAssetLoader; +use bevy::prelude::*; +use crate::states::linear::*; + + +pub fn spawn_round_ball( + track: Res, + mut commands: Commands, + asset_server: Res, +) { + let ball_type = RoundBallType::random_from_4(); + let image: Handle = asset_server.load(ball_type.asset_path()); + + commands.spawn(( + Sprite { + image, + custom_size: Some(Vec2::splat(STEP)), + ..default() + }, + Transform::from_xyz(0.0, 0.0, ROUND_BALL_Z), + RoundBall { + ball_type, + track_progress: 0.0, + }, + LinearStateMarker + )); +} From a1180ea7691fe449465ea6e0112d68301b7c2c86 Mon Sep 17 00:00:00 2001 From: nquidox Date: Fri, 17 Apr 2026 16:24:53 +0300 Subject: [PATCH 3/7] additional track fields + simplified arc helper --- src/states/linear/components_track.rs | 6 ++++- src/states/linear/systems_track.rs | 37 +++++++++++++++++---------- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/src/states/linear/components_track.rs b/src/states/linear/components_track.rs index a9d286d..58c0a75 100644 --- a/src/states/linear/components_track.rs +++ b/src/states/linear/components_track.rs @@ -27,7 +27,10 @@ pub enum PathSegment { #[derive(Resource)] pub struct PrecalculatedTrack{ - pub segments: Vec + pub segments: Vec, + pub min_spawn_gap: f32, //нормализован + pub total_length: f32, + pub speed_norm: f32, } #[derive(Debug)] @@ -41,6 +44,7 @@ pub struct PTSegment { pub radius: f32, pub start_angle: f32, pub sweep_sign: f32, + pub length: f32, } #[derive(Debug, Clone, Copy)] diff --git a/src/states/linear/systems_track.rs b/src/states/linear/systems_track.rs index c4d7e75..0bedbe9 100644 --- a/src/states/linear/systems_track.rs +++ b/src/states/linear/systems_track.rs @@ -1,7 +1,6 @@ use crate::FACTOR; use crate::states::linear::*; use bevy::prelude::*; -use bevy::reflect::List; use std::f32::consts::FRAC_PI_2; pub fn setup_linear_track() -> Track { @@ -69,7 +68,12 @@ pub fn setup_linear_track() -> Track { } pub fn precalculate_track(track: &Track) -> PrecalculatedTrack { - let mut pt = PrecalculatedTrack { segments: vec![] }; + let mut pt = PrecalculatedTrack { + segments: vec![], + min_spawn_gap: 0.0, + total_length: 0.0, + speed_norm: 0.0, + }; let mut cumulative_length: Vec = vec![0.0]; let mut current_pos = track.start_point; @@ -90,6 +94,7 @@ pub fn precalculate_track(track: &Track) -> PrecalculatedTrack { radius: 0.0, start_angle: 0.0, sweep_sign: 0.0, + length: *length, }; pt.segments.push(calc_seg); @@ -102,6 +107,7 @@ pub fn precalculate_track(track: &Track) -> PrecalculatedTrack { PathSegment::Turn { radius, left } => { let arc = helper_arc_calculator(current_pos, current_dir, *left, *radius); + let arc_len = FRAC_PI_2 * radius; let calc_seg = PTSegment { t_start: 0.0, @@ -113,14 +119,14 @@ pub fn precalculate_track(track: &Track) -> PrecalculatedTrack { radius: *radius, start_angle: arc.start_angle, sweep_sign: arc.sweep_sign, + length: arc_len, }; pt.segments.push(calc_seg); // сдвигаем позицию для следующего сегмента current_pos = arc.end_pos; current_dir = arc.normal; // накапливаем длину - cumulative_length - .push(cumulative_length[cumulative_length.len() - 1] + (FRAC_PI_2 * radius)); + cumulative_length.push(cumulative_length[cumulative_length.len() - 1] + arc_len); } } } @@ -141,30 +147,35 @@ pub fn precalculate_track(track: &Track) -> PrecalculatedTrack { println!("{:?}", seg); } + // считаем нормализованный зазор на треке, когда будет доступен спавн шарика + // pt.min_spawn_gap = STEP / total_length; + pt.min_spawn_gap = STEP / total_length; + pt.total_length = total_length; + pt.speed_norm = STEP / total_length; // нормализованное смещение для скорости в пикселях + + println!( + "Нормализованное значение размера шарика: {}, уе: {}, общая длина: {}", + pt.min_spawn_gap, STEP, total_length + ); pt } pub fn helper_arc_calculator(pos: Vec2, dir: Vec2, turn_to: bool, radius: f32) -> ArcCalculation { let (normal, sign): (Vec2, f32) = if turn_to { - (Vec2::new(-dir.y, dir.x), -1.0) + (Vec2::new(-dir.y, dir.x), 1.0) } else { - (Vec2::new(dir.y, -dir.x), 1.0) + (Vec2::new(dir.y, -dir.x), -1.0) }; let center = pos + normal * radius; let start_vec = pos - center; - let initial_vec = if turn_to { - -start_vec.perp() - } else { - -start_vec - }; - let angle = initial_vec.to_angle(); + let angle = start_vec.to_angle(); ArcCalculation { normal, center, start_angle: angle, sweep_sign: sign, - end_pos: center + normal.perp() * radius * sign, + end_pos: center - normal.perp() * radius * sign, } } From 0c7ceb5d8d2ef002ad82b39d07d37493de2be4a5 Mon Sep 17 00:00:00 2001 From: nquidox Date: Fri, 17 Apr 2026 16:25:26 +0300 Subject: [PATCH 4/7] draw arc by lines instead of arc_2d --- src/states/linear/systems.rs | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/src/states/linear/systems.rs b/src/states/linear/systems.rs index d48d23c..af95c8a 100644 --- a/src/states/linear/systems.rs +++ b/src/states/linear/systems.rs @@ -22,13 +22,28 @@ pub fn draw_track_gizmos(track: Res, mut gizmos: Gizmos) { PathSegment::Turn { radius, left } => { // вычисляем нормаль, центр, угол поворота арки и знак let arc = helper_arc_calculator(current_pos, current_dir, *left, *radius); + + // арки никак не хотели вставать на свои места + // gizmos.arc_2d( + // Isometry2d::new(arc.center, Rot2::degrees(current_angle)), + // FRAC_PI_2 * arc.sweep_sign, + // *radius, + // GREEN, + // ); + + // поэтому отрисовка поворота через линии + let steps = 16; + let mut prev_point = current_pos; - gizmos.arc_2d( - Isometry2d::new(arc.center, Rot2::radians(arc.start_angle)), - FRAC_PI_2, - *radius, - GREEN, - ); + for i in 1..=steps { + let t = i as f32 / steps as f32; + let angle = arc.start_angle + arc.sweep_sign * FRAC_PI_2 * t; + let point = arc.center + Vec2::from_angle(angle) * *radius; + + gizmos.line_2d(prev_point, point, GREEN); + prev_point = point; + } + current_pos = arc.end_pos; current_dir = arc.normal; } @@ -36,4 +51,4 @@ pub fn draw_track_gizmos(track: Res, mut gizmos: Gizmos) { } // рисуем финиш gizmos.circle_2d(current_pos, 5.0, Color::WHITE); -} +} \ No newline at end of file From 65069551158d0d937201732a7145636f89ec6a2c Mon Sep 17 00:00:00 2001 From: nquidox Date: Fri, 17 Apr 2026 16:26:43 +0300 Subject: [PATCH 5/7] move round balls system + extended spawn system --- src/states/linear/system_ball.rs | 83 +++++++++++++++++++++++++------- 1 file changed, 65 insertions(+), 18 deletions(-) diff --git a/src/states/linear/system_ball.rs b/src/states/linear/system_ball.rs index 597ab82..b74b174 100644 --- a/src/states/linear/system_ball.rs +++ b/src/states/linear/system_ball.rs @@ -1,27 +1,74 @@ -use bevy::asset::ErasedAssetLoader; -use bevy::prelude::*; +use std::f32::consts::FRAC_PI_2; use crate::states::linear::*; - +use bevy::prelude::*; pub fn spawn_round_ball( track: Res, mut commands: Commands, asset_server: Res, + balls: Query<&RoundBall>, ) { - let ball_type = RoundBallType::random_from_4(); - let image: Handle = asset_server.load(ball_type.asset_path()); - - commands.spawn(( - Sprite { - image, - custom_size: Some(Vec2::splat(STEP)), - ..default() - }, - Transform::from_xyz(0.0, 0.0, ROUND_BALL_Z), - RoundBall { - ball_type, - track_progress: 0.0, - }, - LinearStateMarker + // метод all выдаст истину, если все элементы выполняют условие + let spawn_allowed = balls + .iter() + .all(|b| b.track_progress >= track.min_spawn_gap); + + let min_progress = balls + .iter() + .map(|b| b.track_progress) + .reduce(f32::min) + .unwrap_or(1.0); + + if min_progress >= track.min_spawn_gap { + let ball_type = RoundBallType::random_from_4(); + let image: Handle = asset_server.load(ball_type.asset_path()); + + commands.spawn(( + Sprite { + image, + custom_size: Some(Vec2::splat(STEP)), + ..default() + }, + Transform::from_translation((track.segments[0].start_pos).extend(ROUND_BALL_Z)), + RoundBall { + ball_type, + track_progress: 0.0, + }, + LinearStateMarker, )); + } +} + +pub fn move_round_balls( + track: Res, + mut balls: Query<(&mut Transform, &mut RoundBall)>, + time: Res