From f6b5765b835c633ca49de9087e3e98555543d931 Mon Sep 17 00:00:00 2001 From: nquidox Date: Tue, 21 Apr 2026 00:26:36 +0300 Subject: [PATCH 1/2] finsert fn refactor + dead code removal --- src/states/linear/systems_projectile.rs | 106 +++++++----------------- 1 file changed, 29 insertions(+), 77 deletions(-) diff --git a/src/states/linear/systems_projectile.rs b/src/states/linear/systems_projectile.rs index dc51d2e..60a22a0 100644 --- a/src/states/linear/systems_projectile.rs +++ b/src/states/linear/systems_projectile.rs @@ -1,6 +1,6 @@ use crate::states::linear::*; use crate::{FACTOR, HEIGHT, WIDTH}; -use bevy::asset::AssetServer; +use bevy::asset::{AssetServer, ErasedAssetLoader}; use bevy::math::Vec2; use bevy::prelude::*; use std::f32::consts::{FRAC_PI_2, PI}; @@ -46,32 +46,7 @@ pub fn calculate_projectile_hits( // сортируем от ближайшего t к дальнему intersections.sort_by(|a, b| a.t.partial_cmp(&b.t).unwrap()); - - // // если есть точки пересечения - // if let Some(closest_hit) = intersections.first() { - // // собираем шарики - // let mut occupied: Vec = balls_on_track.iter().map(|b| b.track_progress).collect(); - // // сортируем прогресс для дальнейших вычислений - // occupied.sort_by(|a, b| a.partial_cmp(b).unwrap()); - // - // //считаем прогресс для точки попадания через мировые координаты и индекс сегмента на треке - // let hit_progress = calculate_progress(closest_hit, &track); - // - // let radius_norm = BALL_RADIUS / track.total_length; - // if is_occupied(hit_progress, &occupied, radius_norm) { - // proj.hit_result = Some(ProjectileHit { - // hit_progress, - // hit_position: closest_hit.world_pos, - // segment_index: closest_hit.segment_index, - // }); - // println!("Попадание в трек: {}", hit_progress); - // transform.translation = closest_hit.world_pos.extend(transform.translation.z); - // // убираем маркер поиска, добавляем маркер готовности ко вставке для другой системы - // commands - // .entity(entity) - // .remove::() - // .insert(InsertMarker); - // } + let mut occupied: Vec = balls_on_track.iter().map(|b| b.track_progress).collect(); occupied.sort_by(|a, b| a.partial_cmp(b).unwrap()); let radius_norm = BALL_RADIUS / track.total_length; @@ -95,9 +70,6 @@ pub fn calculate_projectile_hits( }); // записываем координаты точки попадания, до которой летит шарик - // вероятен момент, когда шарик летит, а в точке пересечения появится шарики на треке - // если вылезет такое, пофиксить через проверку всех точек пересечения, а не только - // конкретной, которая тут записана proj.target_position = Some(target_inter.world_pos); // Переключаем маркер: больше не ищем, готовы к вставке @@ -310,75 +282,55 @@ fn is_occupied(target: f32, occupied: &[f32], radius_norm: f32) -> bool { pub fn insert_projectile_into_track( mut commands: Commands, mut projectiles: Query<(Entity, &RoundBallProjectile), With>, - mut balls_on_track: Query<(Entity, &mut RoundBall)>, + mut balls_on_track: Query<(Entity, &mut RoundBall, &Transform)>, track: Res, asset_server: Res, ) { for (proj_entity, proj) in projectiles.iter_mut() { - let Some(hit) = proj.hit_result else { - continue; - }; + // let Some(hit) = proj.hit_result else { continue }; // артефактный код, пока просто закоментил + let proj_pos = proj.previous_position; - let Some(target_pos) = proj.target_position else { continue }; - let distance = proj.previous_position.distance(target_pos); + // ищем любой шарик, до которого снаряд долетел визуально + let mut target_found = None; + for (ball_entity, ball, ball_tf) in balls_on_track.iter() { + let ball_pos = ball_tf.translation.truncate(); + let distance = proj_pos.distance(ball_pos); - // расстояние, при котором встраивается шарик - const ARRIVAL_DIST: f32 = 5.0; - - if distance > ARRIVAL_DIST { - continue; + if distance < BALL_COLLISION_THRESHOLD { + target_found = Some((ball_entity, ball.track_progress, ball_pos)); + break; // нашли первую коллизию - дальше не ищем + } } - - // ищем шарик, в который попали (цель) - // собираем данные, чтобы не конфликтовать с мутабельным доступом ниже - let mut balls_data: Vec<_> = balls_on_track - .iter() - .map(|(entity, ball)| (entity, ball.track_progress)) - .collect(); - - // сортируем по близости к точке удара - balls_data.sort_by(|a, b| { - let diff_a = (a.1 - hit.hit_progress).abs(); - let diff_b = (b.1 - hit.hit_progress).abs(); - diff_a.partial_cmp(&diff_b).unwrap() - }); - - let Some((target_entity, original_target_progress)) = balls_data.first() else { - continue; - }; - // копируем значение - let original_progress = *original_target_progress; - - // размер сдвига посчитан в треке - let shift = track.min_spawn_gap; - // ставим на место шарика-цели, а сам шарик-цель сдвигаем вперед по треку - for (entity, mut ball) in balls_on_track.iter_mut() { - if entity == *target_entity { - ball.track_progress = (original_progress + shift).clamp(0.0, 1.0); + // target_entity - артефактный код, пока просто закоментил + let Some((target_entity, original_progress, target_ball_pos)) = target_found else { + continue; // ни с одним шариком не столкнулся - летит дальше + }; + + // entity - артефактный код, пока просто закоментил + // сдвигаем только те шарики, которые находятся после точки вставки + for (entity, mut ball, _) in balls_on_track.iter_mut() { + if ball.track_progress >= original_progress { + ball.track_progress = (ball.track_progress + track.min_spawn_gap).clamp(0.0, 1.0); } } - for (entity, mut ball) in balls_on_track.iter_mut() { - ball.track_progress = (ball.track_progress + shift).clamp(0.0, 1.0); - } - - // спавним новый шарик на месте цели + // спавним новый шарик ровно в позицию целевого commands.spawn(( Sprite { image: asset_server.load(proj.ball_type.asset_path()), custom_size: Some(Vec2::splat(ROUND_BALL_SIZE)), ..default() }, - Transform::from_translation(hit.hit_position.extend(ROUND_BALL_Z)), + Transform::from_translation(target_ball_pos.extend(ROUND_BALL_Z)), RoundBall { ball_type: proj.ball_type, - track_progress: original_progress, + track_progress: original_progress, // занимает место шарика, в который попали }, LinearStateMarker, + HitMarker, // маркер для системы уничтожения шариков )); - // деспавн шарика-снаряда commands.entity(proj_entity).despawn(); } -} +} \ No newline at end of file From 745ed5cc54f59a6715ca2a9c88d3ac46f6ac072d Mon Sep 17 00:00:00 2001 From: nquidox Date: Tue, 21 Apr 2026 00:28:52 +0300 Subject: [PATCH 2/2] count matching balls system --- src/states/linear/components.rs | 8 ++- src/states/linear/plugin.rs | 1 + src/states/linear/system_ball.rs | 91 ++++++++++++++++++++++++++++++-- 3 files changed, 95 insertions(+), 5 deletions(-) diff --git a/src/states/linear/components.rs b/src/states/linear/components.rs index 06548c8..2d78beb 100644 --- a/src/states/linear/components.rs +++ b/src/states/linear/components.rs @@ -18,4 +18,10 @@ pub enum DebugToggle{ Track, Grid, Laser, -} \ No newline at end of file +} + +#[derive(Component, Copy, Clone)] +pub struct HitMarker; + +#[derive(Component, Copy, Clone)] +pub struct ToRemoveMarker; \ No newline at end of file diff --git a/src/states/linear/plugin.rs b/src/states/linear/plugin.rs index 5c0ceac..5d39da1 100644 --- a/src/states/linear/plugin.rs +++ b/src/states/linear/plugin.rs @@ -47,6 +47,7 @@ impl Plugin for LinearPlayPlugin { calculate_projectile_hits, linear_move_projectiles, insert_projectile_into_track, + count_matching_balls, ).in_set(LinearUpdateSet::Track), ) .add_systems(OnExit(LinearPlayState), cleanup); diff --git a/src/states/linear/system_ball.rs b/src/states/linear/system_ball.rs index be907ed..24866bf 100644 --- a/src/states/linear/system_ball.rs +++ b/src/states/linear/system_ball.rs @@ -48,20 +48,20 @@ pub fn move_round_balls( for (mut transform, mut ball) in &mut balls { // сразу обновляем прогресс ball.track_progress += track.speed_norm * time.delta_secs(); - + // потом позиционируем визуал for seg in &track.segments { if ball.track_progress >= seg.t_start && ball.track_progress < seg.t_end { // вычисляем локальное нормализованое положение на треке let t_local = (ball.track_progress - seg.t_start) / (seg.t_end - seg.t_start); - + let mut pos = Vec2::ZERO; - match seg.segment_type{ + match seg.segment_type { SegementType::Line {} => { pos = seg.start_pos + seg.direction * seg.length * t_local; // transform.translation = pos.extend(ROUND_BALL_Z); } - + SegementType::Turn {} => { let angle = seg.start_angle + FRAC_PI_2 * seg.sweep_sign * t_local; pos = seg.center + Vec2::from_angle(angle) * seg.radius; @@ -72,3 +72,86 @@ pub fn move_round_balls( } } } + +pub fn count_matching_balls( + mut commands: Commands, + start_point: Query<(Entity, &RoundBall), With>, + balls: Query<(Entity, &RoundBall)>, +) { + // если хит маркеров нет, сразу выходим + if start_point.is_empty() { + return; + } + + println!("Проверка на группу начата"); + + //собираем все шарики на треке + let mut balls_sorted: Vec<(Entity, RoundBall)> = balls.iter().map(|(e, b)| (e, *b)).collect(); + + if balls_sorted.is_empty() { + return; + } + + // сортируем по прогрессу + balls_sorted.sort_by(|a, b| a.1.track_progress.total_cmp(&b.1.track_progress)); + + // принимаем шарик снаряд как точку отсчета + for (start_entity, start_round) in start_point.iter() { + // сразу снимаем маркер попадания с бывшего шарика-снаряда, который был встроен в трек + commands.entity(start_entity).remove::(); + + // берем прогресс и тип шарика в точке попадания + let start_progress = start_round.track_progress; + let hit_ball_type = start_round.ball_type; + + // будем итерировать по индексам + let mut hit_index = 0; + for (index, ball) in balls_sorted.iter().enumerate() { + if ball.1.track_progress == start_progress { + hit_index = index; + } + } + + let mut left_threshold = hit_index; + let mut right_threshold = hit_index; + + // ищем крайнего соседа слева + for li in (0..=hit_index).rev() { + // + if balls_sorted[li].1.ball_type == hit_ball_type { + // записываем индекс как порог + left_threshold = li; + continue; + } else { + // иначе прерываем + break; + } + } + + // то же самое для правого + for ri in hit_index..=balls_sorted.len() { + if balls_sorted[ri].1.ball_type == hit_ball_type { + right_threshold = ri; + continue; + } else { + break; + } + } + + // считаем размер группы + let group_size = right_threshold - left_threshold + 1; + if group_size < 3 { + continue; // не return, так как может быть несколько хит маркеров + } + + println!("Группа из 3+ шариков! Размер: {}", group_size); + + // маркируем группу по порогам на удаление + for entity in balls_sorted[left_threshold..=right_threshold] + .iter() + .map(|(e, _)| *e) + { + commands.entity(entity).insert(ToRemoveMarker); + } + } +}