diff --git a/.gitignore b/.gitignore index b9b0926..f35fcc8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ /target /assets .idea -.directory -/tiled/ +.directory \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 2066e59..7851732 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,9 +15,9 @@ use crate::states::main_menu::state::MainMenuState; use crate::states::settings_menu::state::SettingsMenuState; use crate::states::linear::plugin::LinearPlayPlugin; -const FACTOR: u32 = 40; -const WIDTH: u32 = 32; -const HEIGHT: u32 = 18; +const FACTOR: u32 = 80; +const WIDTH: u32 = 16; +const HEIGHT: u32 = 9; fn main() { diff --git a/src/states/linear/components.rs b/src/states/linear/components.rs index 06548c8..6c0a133 100644 --- a/src/states/linear/components.rs +++ b/src/states/linear/components.rs @@ -4,18 +4,4 @@ use bevy::prelude::*; pub struct LinearStateMarker; #[derive(Component, Copy, Clone)] -pub struct LinearRestartMarker; - -#[derive(Resource)] -pub struct LinearDebugConfig { - pub toggle_track: bool, - pub toggle_grid: bool, - pub toggle_laser: bool, -} - -#[derive(Component, Copy, Clone)] -pub enum DebugToggle{ - Track, - Grid, - Laser, -} \ No newline at end of file +pub struct LinearRestartMarker; \ No newline at end of file diff --git a/src/states/linear/components_cannon.rs b/src/states/linear/components_cannon.rs index 89d3f11..bc631a4 100644 --- a/src/states/linear/components_cannon.rs +++ b/src/states/linear/components_cannon.rs @@ -34,16 +34,8 @@ impl LinearCannonState { #[derive(Component)] pub struct RoundBallProjectile { pub velocity: Vec2, - pub direction: Vec2, pub previous_position: Vec2, pub ball_type: RoundBallType, - pub hit_result: Option, -} - -pub struct ProjectileHit { - pub hit_progress: f32, - pub hit_position: Vec2, - pub segment_index: usize, } @@ -51,13 +43,4 @@ pub struct ProjectileHit { pub struct ShotMarker; #[derive(Component)] -pub struct SwapMarker; - -#[derive(Component)] -pub struct IntersectMarker; - -pub struct Intersection { - pub t: f32, - pub world_pos: Vec2, - pub segment_index: usize, -} \ No newline at end of file +pub struct SwapMarker; \ No newline at end of file diff --git a/src/states/linear/constants.rs b/src/states/linear/constants.rs index 3099699..ae03297 100644 --- a/src/states/linear/constants.rs +++ b/src/states/linear/constants.rs @@ -6,13 +6,8 @@ pub const CENTER_Y: f32 = 0.0; // pub const FACTOR_RADIUS: f32 = FACTOR as f32 / 2.0; -// основная размерность и масштаб -pub const SCALE: f32 = 1.0; pub const STEP: f32 = FACTOR as f32 / SCALE; - -// зависимые размеры -pub const ROUND_BALL_SIZE: f32 = STEP; -pub const BALL_RADIUS: f32 = ROUND_BALL_SIZE / 2.0; +pub const SCALE: f32 = 2.0; // Z-INDEXES pub const ROUND_BALL_Z: f32 = 10.0; diff --git a/src/states/linear/mod.rs b/src/states/linear/mod.rs index bd0d3da..1bb90fd 100644 --- a/src/states/linear/mod.rs +++ b/src/states/linear/mod.rs @@ -3,8 +3,8 @@ pub mod plugin; mod components; pub use components::*; -mod systems_debug; -pub use systems_debug::*; +mod systems; +pub use systems::*; mod components_track; pub use components_track::*; diff --git a/src/states/linear/plugin.rs b/src/states/linear/plugin.rs index b02d144..3b82147 100644 --- a/src/states/linear/plugin.rs +++ b/src/states/linear/plugin.rs @@ -24,12 +24,9 @@ impl Plugin for LinearPlayPlugin { .add_systems( Update, ( - toggle_debug_systems, - draw_track_gizmos, + draw_track_gizmos, draw_grid, - // draw_cannon_laser, //несколько систем с соблюдением порядка - calculate_projectile_hits, linear_move_projectiles, cycle_cannon_balls, update_linear_cannon_preview, @@ -51,13 +48,6 @@ fn setup(mut commands: Commands) { commands.insert_resource(build_track); commands.insert_resource(precalculated_track); commands.insert_resource(build_linear_cannon_state()); - - let debug_config = LinearDebugConfig{ - toggle_track: true, - toggle_grid: true, - toggle_laser: false, - }; - commands.insert_resource(debug_config); } fn cleanup(mut commands: Commands, query: Query>) { @@ -70,7 +60,6 @@ fn cleanup(mut commands: Commands, query: Query> commands.remove_resource::(); //зачистить сразу после калькуляции commands.remove_resource::(); commands.remove_resource::(); - commands.remove_resource::(); } fn linear_restart(mut next_state: ResMut>) { diff --git a/src/states/linear/system_ball.rs b/src/states/linear/system_ball.rs index be907ed..b74b174 100644 --- a/src/states/linear/system_ball.rs +++ b/src/states/linear/system_ball.rs @@ -26,7 +26,7 @@ pub fn spawn_round_ball( commands.spawn(( Sprite { image, - custom_size: Some(Vec2::splat(ROUND_BALL_SIZE)), + custom_size: Some(Vec2::splat(STEP)), ..default() }, Transform::from_translation((track.segments[0].start_pos).extend(ROUND_BALL_Z)), diff --git a/src/states/linear/systems_debug.rs b/src/states/linear/systems.rs similarity index 59% rename from src/states/linear/systems_debug.rs rename to src/states/linear/systems.rs index bac5622..16d63b9 100644 --- a/src/states/linear/systems_debug.rs +++ b/src/states/linear/systems.rs @@ -1,13 +1,9 @@ -use crate::{FACTOR, HEIGHT, WIDTH, states}; +use crate::{HEIGHT, WIDTH, states, FACTOR}; use bevy::prelude::*; use states::linear::*; use std::f32::consts::FRAC_PI_2; -pub fn draw_track_gizmos(track: Res, mut gizmos: Gizmos, config: ResMut) { - if !config.toggle_track { - return; - }; - +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(); @@ -57,51 +53,23 @@ pub fn draw_track_gizmos(track: Res, mut gizmos: Gizmos, config: ResMut) { - if !config.toggle_grid { - return; - }; - const FF: f32 = FACTOR as f32; - - const HALF_X: f32 = WIDTH as f32 / 2.0; - const HALF_Y: f32 = HEIGHT as f32 / 2.0; - const STEP_X: f32 = HALF_X * FF; - const STEP_Y: f32 = HALF_Y * FF; +pub fn draw_grid(mut gizmos: Gizmos) { + let half_x = WIDTH as f32 / 2.0 * FACTOR as f32; + let half_y = HEIGHT as f32 / 2.0 * FACTOR as f32; for i in 0..WIDTH { gizmos.line_2d( - Vec2::new((i as f32 - HALF_X) * FF, STEP_Y), - Vec2::new((i as f32 - HALF_X) * FF, -STEP_Y), + Vec2::new((i as f32 - 8.0) * FACTOR as f32, half_y), + Vec2::new((i as f32 - 8.0) * FACTOR as f32, -half_y), DARK_GREEN, ); } - + for i in 0..HEIGHT { gizmos.line_2d( - Vec2::new(-STEP_X, (i as f32 - HALF_Y) * FF), - Vec2::new(STEP_X, (i as f32 - HALF_Y) * FF), + Vec2::new(-half_x, (i as f32 - 4.0) * FACTOR as f32), + Vec2::new(half_x, (i as f32 - 4.0) * FACTOR as f32), DARK_GREEN, ) } } - -// pub fn draw_cannon_laser(mut gizmos: Gizmos, config: ResMut){ -// if !config.toggle_laser { -// return; -// }; -// } - -pub fn toggle_debug_systems( - buttons: Query<(&Interaction, &DebugToggle), Changed>, - mut config: ResMut, -) { - for (interaction, toggle_type) in &buttons { - if matches!(interaction, Interaction::Pressed) { - match toggle_type { - DebugToggle::Track => config.toggle_track = !config.toggle_track, - DebugToggle::Grid => config.toggle_grid = !config.toggle_grid, - DebugToggle::Laser => config.toggle_laser = !config.toggle_laser, - } - } - } -} diff --git a/src/states/linear/systems_cannon.rs b/src/states/linear/systems_cannon.rs index 16865a5..023aa28 100644 --- a/src/states/linear/systems_cannon.rs +++ b/src/states/linear/systems_cannon.rs @@ -1,8 +1,10 @@ use crate::states::linear::*; -use crate::{FACTOR, HEIGHT, WIDTH}; use bevy::prelude::*; use bevy::window::PrimaryWindow; -use std::f32::consts::{FRAC_PI_2, PI}; +use std::f32::consts::FRAC_PI_2; +use bevy::asset::ErasedAssetLoader; +use crate::{FACTOR, HEIGHT, WIDTH}; +use crate::states::level::{BallProjectile, BallType, Cannon, CannonState, CurrentPreviewMarker, NextPreviewMarker, CURRENT_SHOT_Z_INDEX, NEXT_SHOT_Z_INDEX, SLOT_SIZE}; pub fn spawn_linear_cannon(mut commands: Commands, asset_server: Res) { let image: Handle = asset_server.load("sprites/cannon/cannon.png"); @@ -23,8 +25,6 @@ pub fn rotate_linear_cannon( mut query: Query<&mut Transform, With>, window_query: Query<&Window, With>, camera_query: Query<(&Camera, &GlobalTransform)>, - config: ResMut, //FIXME debug - mut gizmos: Gizmos, ) { let Ok(mut cannon_tf) = query.single_mut() else { return; @@ -51,13 +51,6 @@ pub fn rotate_linear_cannon( let angle = direction.to_angle() - FRAC_PI_2; cannon_tf.rotation = Quat::from_rotation_z(angle); - - if !config.toggle_laser { - return; - } else { - const OUT_OF_SCREEN: f32 = (WIDTH * FACTOR) as f32 * 1.5; - gizmos.line_2d(cannon_pos, direction * OUT_OF_SCREEN, PINK); - }; } pub fn spawn_projectile_from_cannon( @@ -65,7 +58,7 @@ pub fn spawn_projectile_from_cannon( cannon_query: Query<&Transform, With>, mouse_input: Res>, asset_server: Res, - mut cannon_state: ResMut, + mut cannon_state: ResMut ) { if !mouse_input.just_pressed(MouseButton::Left) { return; @@ -86,18 +79,15 @@ pub fn spawn_projectile_from_cannon( commands.spawn(( Sprite { image, - custom_size: Some(Vec2::splat(ROUND_BALL_SIZE)), + custom_size: Some(Vec2::splat(STEP)), ..default() }, Transform::from_translation(spawn_pos), RoundBallProjectile { velocity: direction * 800.0, - direction, // задаем отдельным полем, чтоб сэкономить на вычислениях в рейкастинге previous_position: spawn_pos_2d, ball_type, - hit_result: None, }, - IntersectMarker, // маркер для фильтрации, что шарик-снаряд может иметь пересечения с треком LinearStateMarker, )); cannon_state.fire(); @@ -118,32 +108,30 @@ pub fn update_linear_cannon_preview( shot_query: Query>, swap_query: Query>, ) { - let Ok(cannon_entity) = cannon_query.single() else { - return; - }; - + let Ok(cannon_entity) = cannon_query.single() else { return }; + let shot_image: Handle = asset_server.load(cannon_state.shot.asset_path()); if let Ok(shot_entity) = shot_query.single() { // усли шарик у дула есть, то обновляем спрайт - commands.entity(shot_entity).insert(Sprite { - image: shot_image, - custom_size: Some(Vec2::splat(ROUND_BALL_SIZE)), - ..default() - }); + commands.entity(shot_entity).insert( + Sprite{ + image: shot_image, + custom_size: Some(Vec2::splat(STEP)), + ..default() + } + ); } else { //иначе создаем - let preview_entity = commands - .spawn(( - Sprite { - image: shot_image, - custom_size: Some(Vec2::splat(ROUND_BALL_SIZE)), - ..default() - }, - Transform::from_xyz(0.0, STEP * 2.2, LINEAR_CANNON_BALL_Z), - ShotMarker, - )) - .id(); + let preview_entity = commands.spawn(( + Sprite{ + image: shot_image, + custom_size: Some(Vec2::splat(STEP)), + ..default() + }, + Transform::from_xyz(0.0, STEP * 2.2, LINEAR_CANNON_BALL_Z), + ShotMarker + )).id(); commands.entity(cannon_entity).add_child(preview_entity); } @@ -151,23 +139,21 @@ pub fn update_linear_cannon_preview( let swap_image = asset_server.load(cannon_state.swap.asset_path()); if let Ok(swap_entity) = swap_query.single() { - commands.entity(swap_entity).insert(Sprite { + commands.entity(swap_entity).insert(Sprite{ image: swap_image, - custom_size: Some(Vec2::splat(ROUND_BALL_SIZE)), + custom_size: Some(Vec2::splat(STEP)), ..default() }); } else { - let preview_entity = commands - .spawn(( - Sprite { - image: swap_image, - custom_size: Some(Vec2::splat(ROUND_BALL_SIZE)), - ..default() - }, - Transform::from_xyz(0.0, -STEP * 1.5, LINEAR_CANNON_BALL_Z), - SwapMarker, - )) - .id(); + let preview_entity = commands.spawn(( + Sprite{ + image: swap_image, + custom_size: Some(Vec2::splat(STEP)), + ..default() + }, + Transform::from_xyz(0.0, -STEP * 1.5, LINEAR_CANNON_BALL_Z), + SwapMarker, + )).id(); commands.entity(cannon_entity).add_child(preview_entity); } } @@ -194,255 +180,10 @@ pub fn linear_move_projectiles( proj.previous_position = new_pos; // удаляем снаряды, улетевшие за экран - if new_pos.x.abs() > 2.0 * (WIDTH * FACTOR) as f32 - || new_pos.y.abs() > 2.0 * (HEIGHT * FACTOR) as f32 - { + if new_pos.x.abs() > 1.5 * (WIDTH * FACTOR) as f32 || new_pos.y.abs() > 1.5 * (HEIGHT * FACTOR) as f32 { commands.entity(entity).despawn(); } } } -pub fn calculate_projectile_hits( - mut commands: Commands, - mut projectiles: Query< - (Entity, &mut RoundBallProjectile, &mut Transform), - With, - >, - track: Res, - balls_on_track: Query<&RoundBall>, -) { - for (entity, mut proj, mut transform) in projectiles.iter_mut() { - // если уже посчитано место попадания, пропускаем - if proj.hit_result.is_some() { - continue; - } - - // ищем все точки пересечений с треком - let mut intersections = raycast_track(proj.previous_position, proj.direction, &track); - - // сортируем от ближайшего 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::(); - } - } else { - // Пересечений нет -> убираем маркер, снаряд летит дальше - commands.entity(entity).remove::(); - } - } -} - -fn raycast_track(origin: Vec2, direction: Vec2, track: &PrecalculatedTrack) -> Vec { - let mut intersections = vec![]; - - for (i, seg) in track.segments.iter().enumerate() { - match seg.segment_type { - SegementType::Line => { - // ищем пересечение - if let Some(t) = - intersect_line(origin, direction, seg.start_pos, seg.direction, seg.length) - { - let pos = origin + direction * t; - intersections.push(Intersection { - t, - world_pos: pos, - segment_index: i, - }); - } - } - - SegementType::Turn => { - let hits = intersect_arc( - origin, - direction, - seg.center, - seg.radius, - seg.start_angle, - seg.sweep_sign, - ); - - for t in hits { - let pos = origin + direction * t; - intersections.push(Intersection { - t, - world_pos: pos, - segment_index: i, - }); - } - } - } - } - - intersections -} - -fn intersect_line( - origin: Vec2, - direction: Vec2, - seg_start: Vec2, - seg_dir: Vec2, - seg_length: f32, -) -> Option { - // вектор самого отрезка трека - let seg_vec = seg_dir * seg_length; - - // знаменатель: векторное произведение направления луча и вектора отрезка - // x1*y2 - x2*y1 - let denom = direction.x * seg_vec.y - direction.y * seg_vec.x; - - // если знаменатель близок к нулю - луч параллелен отрезку, пересечения нет - if denom.abs() < 1e-6 { - return None; - } - - let w = seg_start - origin; - - // вычисляем параметры t и u - // t - расстояние по лучу - // u - позиция на отрезке (0.0 = начало, 1.0 = конец) - let t = (w.x * seg_vec.y - w.y * seg_vec.x) / denom; - let u = (w.x * direction.y - w.y * direction.x) / denom; - - // проверяем условия попадания - // t > 0 - точка пересечения находится перед пушкой (а не за спиной) - // u >= 0 && u <= 1 - точка пересечения находится внутри отрезка (не на бесконечной линии) - if t > 0.0 && u >= 0.0 && u <= 1.0 { - return Some(t); - } - - None -} - -fn intersect_arc( - origin: Vec2, - direction: Vec2, - center: Vec2, - radius: f32, - start_angle: f32, - sweep_sign: f32, -) -> Vec { - // возвращает список дистанций t до точек пересечения с дугой. - // может вернуть 0, 1 или 2 точки (вход и выход), если луч проходит сквозь дугу. - - let mut hits = Vec::new(); - - // считаем пересечения луча и полной окружности через квадратное уравнение - - let l = origin - center; - let b = 2.0 * l.dot(direction); // поскольку направление нормализовано, a = 1 - let c = l.dot(l) - radius * radius; - - let discriminant = b * b - 4.0 * c; - - // если дискриминант меньше нуля, луч пролетает мимо окружности - if discriminant < 0.0 { - return hits; - } - - let sqrt_discriminant = discriminant.sqrt(); - - // находим корни - let roots = vec![ - (-b - sqrt_discriminant) / 2.0, - (-b + sqrt_discriminant) / 2.0, - ]; - - for t in roots { - // ищем только положительные точки, потому что они будут перед пушкой - if t > 0.0 { - let hit_point = origin + direction * t; - - // попали ли в сектор дуги - let hit_angle = (hit_point - center).to_angle(); - - // находим разницу между углом попадания и стартовым углом - let mut angle_diff = hit_angle - start_angle; - - // нормализуем разницу в диапазон [-PI, PI] - // это нужно, чтобы корректно обрабатывать переход через плюс/минус пи - while angle_diff > PI { - angle_diff -= 2.0 * PI; - } - while angle_diff < -PI { - angle_diff += 2.0 * PI; - } - - // проверяем, лежит ли разница в пределах 90 градусов (пи пополам) с учетом направления - let is_valid_angle = if sweep_sign > 0.0 { - // против часовой - angle_diff >= 0.0 && angle_diff <= FRAC_PI_2 - } else { - // по часовой - angle_diff >= -FRAC_PI_2 && angle_diff <= 0.0 - }; - - if is_valid_angle { - hits.push(t); - } - } - } - - hits -} - -fn calculate_progress(inter: &Intersection, track: &PrecalculatedTrack) -> f32 { - // берем только нужный сегмент трека, где есть пересечение - let track_seg = &track.segments[inter.segment_index]; - - // по типу сегмента переводим точку из мировых координат Vec2 в прогресс трека 0.0 ... 1.0 - match track_seg.segment_type { - SegementType::Line => { - let diff = inter.world_pos - track_seg.start_pos; - let local_t = (diff.x * track_seg.direction.x + diff.y * track_seg.direction.y) - / track_seg.length; - // возвращаем прогресс - track_seg.t_start + (track_seg.t_end - track_seg.t_start) * local_t - } - - SegementType::Turn => { - let hit_angle = (inter.world_pos - track_seg.center).to_angle(); - let mut angle_diff = hit_angle - track_seg.start_angle; - - // критичный момент - важно нормализовать разницу в диапазон от -пи до +пи, чтоб он не улетел - while angle_diff > PI { - angle_diff -= 2.0 * PI - } - while angle_diff < -PI { - angle_diff += 2.0 * PI - } - - // учитываем направление обхода - let local_t = angle_diff / (track_seg.sweep_sign * FRAC_PI_2); - - // возвращаем прогресс - track_seg.t_start + (track_seg.t_end - track_seg.t_start) * local_t - } - } -} - -fn is_occupied(target: f32, occupied: &[f32], radius_norm: f32) -> bool { - let min = target - radius_norm; - let max = target + radius_norm; - - occupied.iter().any(|&p| p >= min && p <= max) -} +// pub fn detect_track_hit(){} //TODO \ No newline at end of file diff --git a/src/states/linear/systems_track.rs b/src/states/linear/systems_track.rs index bf2a264..0bedbe9 100644 --- a/src/states/linear/systems_track.rs +++ b/src/states/linear/systems_track.rs @@ -7,8 +7,8 @@ pub fn setup_linear_track() -> Track { println!("Построение трека начато"); Track { start_point: Vec2 { - x: CENTER_X - STEP * 7.0 * 2.0, - y: CENTER_Y - STEP * 4.0 * 2.0, + x: CENTER_X - FACTOR as f32 * 7.0, + y: CENTER_Y - FACTOR as f32 * 4.0, }, start_direction: Vec2::X, segments: vec![ diff --git a/src/states/linear/ui.rs b/src/states/linear/ui.rs index 9ceb97c..18e4c93 100644 --- a/src/states/linear/ui.rs +++ b/src/states/linear/ui.rs @@ -1,12 +1,11 @@ use crate::states::AppState; use crate::states::AppState::LinearPlayState; use crate::states::linear::plugin::LinearUpdateSet; -use crate::states::linear::{DebugToggle, LinearRestartMarker, LinearStateMarker}; +use crate::states::linear::{LinearRestartMarker, LinearStateMarker}; use crate::ui::button_click::ButtonClickMessage; use crate::ui::click::handle_click_system; -use crate::ui::{ButtonStyle, spawn_button, spawn_background}; +use crate::ui::{ButtonStyle, spawn_button}; use bevy::prelude::*; -use crate::{FACTOR, HEIGHT, WIDTH}; pub struct LinearUIPlugin; @@ -45,63 +44,25 @@ fn setup_ui(mut commands: Commands, asset_server: Res) { )) .id(); - let button_style = ButtonStyle { + let restart_button_style = ButtonStyle { font: asset_server.load("fonts/QR Ames Beta.otf"), - font_size: 16.0, + font_size: 24.0, text_color: Color::WHITE, normal_bg: Color::linear_rgba(0.15, 0.15, 0.15, 1.0), hovered_bg: Color::linear_rgb(0.25, 0.25, 0.25), pressed_bg: Color::linear_rgb(0.3, 0.3, 0.3), - width: Val::Px(100.0), - height: Val::Px(25.0), + width: Val::Px(200.0), + height: Val::Px(50.0), margin: UiRect::all(Val::Px(10.0)), }; - - let bg_image: Handle = asset_server.load("sprites/bg/linear_bg.png"); - commands.spawn(( - Sprite{ - image: bg_image, - custom_size: Some(Vec2::new((WIDTH*FACTOR) as f32, (HEIGHT*FACTOR) as f32)), - ..default() - }, - Transform::from_xyz(0.0, 0.0, -10.0), - LinearStateMarker, - )); - // spawn_background(&mut commands, bg_image, LinearStateMarker); spawn_button( &mut commands, root, "Restart", - &button_style, + &restart_button_style, LinearRestartMarker, ); - - spawn_button( - &mut commands, - root, - "Track", - &button_style, - DebugToggle::Track, - ); - - spawn_button( - &mut commands, - root, - "Grid", - &button_style, - DebugToggle::Grid, - ); - - spawn_button( - &mut commands, - root, - "Laser", - &button_style, - DebugToggle::Laser, - ); - - } pub fn restart_game_button_system(