From 8b9e300277c2472ebaad88aa92380e3523fbe778 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=80lex=20Rom=C3=A0n?= Date: Sun, 30 Aug 2026 18:33:40 +0200 Subject: [PATCH] Add controller-driven debug menu --- src/camera/mod.rs | 19 -- src/debug_tools.rs | 512 +++++++++++++++++++++++++++++ src/fighter/attack.rs | 2 +- src/fighter/debug.rs | 91 +++-- src/fighter/mod.rs | 21 +- src/fighter/motion.rs | 1 - src/fighter/state/ground_attack.rs | 2 - src/fighter/state/mod.rs | 1 - src/game_settings.rs | 9 + src/input/debug.rs | 27 -- src/input/mod.rs | 3 - src/lib.rs | 2 + src/netcode/mod.rs | 5 +- src/stage/mod.rs | 4 +- 14 files changed, 619 insertions(+), 80 deletions(-) create mode 100644 src/debug_tools.rs delete mode 100644 src/input/debug.rs diff --git a/src/camera/mod.rs b/src/camera/mod.rs index 7e455b8..26c9dbf 100644 --- a/src/camera/mod.rs +++ b/src/camera/mod.rs @@ -785,23 +785,4 @@ mod tests { assert!((sixty_hz_lerp_alpha(0.09, 1.0 / 60.0) - 0.09).abs() < f32::EPSILON); assert!(sixty_hz_lerp_alpha(0.09, 1.0 / 30.0) > 0.09); } - - #[test] - fn battlefield_profile_matches_the_source_stage_data() { - let manifest: crate::stage::manifest::StageManifest = - ron::from_str(include_str!("../../assets/stages/test.stage.ron")).unwrap(); - let profile = manifest.camera; - - assert_eq!((profile.left, profile.right), (-200.0, 200.0)); - assert_eq!((profile.bottom, profile.top), (-59.0, 170.0)); - assert_eq!(profile.origin, Vec2::new(0.0, 44.0)); - assert_eq!(profile.vertical_fov_degrees, 30.0); - assert_eq!((profile.min_depth, profile.max_depth), (83.0, 1000.0)); - assert_eq!(profile.subject_scale, 1.5); - assert_eq!(profile.fighter_forward_extent_scale, 1.5); - assert_eq!(profile.tracking_smoothness, 1.8); - assert_eq!(profile.vertical_pan_degrees, -10.0); - assert_eq!(profile.horizontal_pan_degrees_per_unit, 0.1); - assert_eq!(profile.vertical_pan_degrees_per_unit, 0.1); - } } diff --git a/src/debug_tools.rs b/src/debug_tools.rs new file mode 100644 index 0000000..b42d8a6 --- /dev/null +++ b/src/debug_tools.rs @@ -0,0 +1,512 @@ +//! Local developer overlays and their controller-driven palette. + +use std::collections::HashMap; + +use bevy::prelude::*; +use bevy_egui::{EguiContexts, EguiPrimaryContextPass, egui}; + +use crate::player::Player; + +pub const PLAYER_DEBUG_ROW_COUNT: usize = 6; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum FighterDebugRowKind { + None, + CurrentState, + Velocity, + Input, + Position, + Facing, +} + +impl FighterDebugRowKind { + const ALL: [Self; 6] = [ + Self::None, + Self::CurrentState, + Self::Velocity, + Self::Input, + Self::Position, + Self::Facing, + ]; + + pub const fn label(self) -> &'static str { + match self { + Self::None => "None", + Self::CurrentState => "Current state", + Self::Velocity => "Velocity", + Self::Input => "Input", + Self::Position => "Position", + Self::Facing => "Facing", + } + } + + fn cycle(self, direction: i8) -> Self { + let index = Self::ALL + .iter() + .position(|kind| *kind == self) + .expect("all row kinds must be listed") as i8; + let index = (index + direction).rem_euclid(Self::ALL.len() as i8) as usize; + Self::ALL[index] + } +} + +#[derive(Clone, Copy)] +enum DebugFlag { + Network, + Ecb, + StageCollision, + AnimationBones, + AttackHitboxes, + DrawThroughGeometry, +} + +#[derive(Clone, Copy)] +enum DebugMenuEntry { + Toggle { + label: &'static str, + flag: DebugFlag, + }, + Submenu { + label: &'static str, + entries: &'static [DebugMenuEntry], + }, + PlayerSlots { + label: &'static str, + }, +} + +const FIGHTER_MENU: &[DebugMenuEntry] = &[ + DebugMenuEntry::PlayerSlots { + label: "Player windows", + }, + DebugMenuEntry::Toggle { + label: "ECB", + flag: DebugFlag::Ecb, + }, + DebugMenuEntry::Toggle { + label: "Animation bones", + flag: DebugFlag::AnimationBones, + }, + DebugMenuEntry::Toggle { + label: "Attack hitboxes", + flag: DebugFlag::AttackHitboxes, + }, +]; + +const WORLD_MENU: &[DebugMenuEntry] = &[DebugMenuEntry::Toggle { + label: "Stage collision", + flag: DebugFlag::StageCollision, +}]; + +const DIAGNOSTICS_MENU: &[DebugMenuEntry] = &[DebugMenuEntry::Toggle { + label: "Network stats", + flag: DebugFlag::Network, +}]; + +const RENDERING_MENU: &[DebugMenuEntry] = &[DebugMenuEntry::Toggle { + label: "Draw through geometry", + flag: DebugFlag::DrawThroughGeometry, +}]; + +const ROOT_MENU: &[DebugMenuEntry] = &[ + DebugMenuEntry::Submenu { + label: "Fighter", + entries: FIGHTER_MENU, + }, + DebugMenuEntry::Submenu { + label: "World", + entries: WORLD_MENU, + }, + DebugMenuEntry::Submenu { + label: "Diagnostics", + entries: DIAGNOSTICS_MENU, + }, + DebugMenuEntry::Submenu { + label: "Rendering", + entries: RENDERING_MENU, + }, +]; + +#[derive(Clone, Copy)] +enum DebugMenuPage { + Static(&'static [DebugMenuEntry]), + PlayerList, + PlayerRows(usize), +} + +#[derive(Resource)] +pub struct DebugSettings { + pub menu_open: bool, + controller: Option, + pages: Vec, + selections: Vec, + pub player_rows: HashMap, + pub network: bool, + pub ecb: bool, + pub stage_collision: bool, + pub animation_bones: bool, + pub attack_hitboxes: bool, + pub draw_through_geometry: bool, +} + +impl Default for DebugSettings { + fn default() -> Self { + Self { + menu_open: false, + controller: None, + pages: vec![DebugMenuPage::Static(ROOT_MENU)], + selections: vec![0], + player_rows: HashMap::new(), + network: false, + ecb: false, + stage_collision: false, + animation_bones: false, + attack_hitboxes: false, + draw_through_geometry: false, + } + } +} + +impl DebugSettings { + pub fn player_rows( + &self, + handle: usize, + ) -> Option<&[FighterDebugRowKind; PLAYER_DEBUG_ROW_COUNT]> { + self.player_rows.get(&handle) + } + + fn reset_navigation(&mut self) { + self.pages.truncate(1); + self.selections.truncate(1); + self.selections[0] = 0; + } + + fn page(&self) -> DebugMenuPage { + *self + .pages + .last() + .expect("debug menu always has a root page") + } + + fn selected(&self) -> usize { + *self + .selections + .last() + .expect("debug menu always has a root selection") + } + + fn selected_mut(&mut self) -> &mut usize { + self.selections + .last_mut() + .expect("debug menu always has a root selection") + } + + fn push_page(&mut self, page: DebugMenuPage) { + self.pages.push(page); + self.selections.push(0); + } + + fn pop_page(&mut self) -> bool { + if self.pages.len() == 1 { + return false; + } + self.pages.pop(); + self.selections.pop(); + true + } + + fn flag(&self, flag: DebugFlag) -> bool { + match flag { + DebugFlag::Network => self.network, + DebugFlag::Ecb => self.ecb, + DebugFlag::StageCollision => self.stage_collision, + DebugFlag::AnimationBones => self.animation_bones, + DebugFlag::AttackHitboxes => self.attack_hitboxes, + DebugFlag::DrawThroughGeometry => self.draw_through_geometry, + } + } + + fn toggle_flag(&mut self, flag: DebugFlag) { + match flag { + DebugFlag::Network => self.network = !self.network, + DebugFlag::Ecb => self.ecb = !self.ecb, + DebugFlag::StageCollision => self.stage_collision = !self.stage_collision, + DebugFlag::AnimationBones => self.animation_bones = !self.animation_bones, + DebugFlag::AttackHitboxes => self.attack_hitboxes = !self.attack_hitboxes, + DebugFlag::DrawThroughGeometry => { + self.draw_through_geometry = !self.draw_through_geometry + } + } + } + + fn rows_mut(&mut self, handle: usize) -> &mut [FighterDebugRowKind; PLAYER_DEBUG_ROW_COUNT] { + self.player_rows + .entry(handle) + .or_insert([FighterDebugRowKind::None; PLAYER_DEBUG_ROW_COUNT]) + } +} + +pub struct DebugToolsPlugin; + +impl Plugin for DebugToolsPlugin { + fn build(&self, app: &mut App) { + app.init_resource::() + .add_systems( + Update, + (handle_palette_input, sync_gizmo_depth_bias).chain(), + ) + .add_systems(EguiPrimaryContextPass, draw_palette); + } +} + +fn player_handles(players: &Query<&Player>) -> Vec { + let mut handles = players + .iter() + .map(|player| player.handle) + .collect::>(); + handles.sort_unstable(); + handles.dedup(); + handles +} + +fn page_len(page: DebugMenuPage, player_handles: &[usize]) -> usize { + match page { + DebugMenuPage::Static(entries) => entries.len(), + DebugMenuPage::PlayerList => player_handles.len(), + DebugMenuPage::PlayerRows(_) => PLAYER_DEBUG_ROW_COUNT, + } +} + +fn handle_palette_input( + keyboard: Res>, + gamepads: Query<(Entity, &Gamepad)>, + players: Query<&Player>, + mut settings: ResMut, +) { + if keyboard.just_pressed(KeyCode::KeyT) { + settings.toggle_flag(DebugFlag::DrawThroughGeometry); + } + + if !settings.menu_open { + for (entity, gamepad) in &gamepads { + if gamepad.just_pressed(GamepadButton::Start) && gamepad.pressed(GamepadButton::DPadUp) + { + settings.menu_open = true; + settings.controller = Some(entity); + settings.reset_navigation(); + return; + } + } + return; + } + + let Some(controller) = settings.controller else { + settings.menu_open = false; + return; + }; + let Ok((_, gamepad)) = gamepads.get(controller) else { + settings.menu_open = false; + settings.controller = None; + return; + }; + let player_handles = player_handles(&players); + let page = settings.page(); + let page_len = page_len(page, &player_handles); + + if page_len != 0 && settings.selected() >= page_len { + *settings.selected_mut() = 0; + } + + if gamepad.just_pressed(GamepadButton::DPadUp) && page_len != 0 { + let selected = settings.selected_mut(); + *selected = selected.checked_sub(1).unwrap_or(page_len - 1); + } + if gamepad.just_pressed(GamepadButton::DPadDown) && page_len != 0 { + let selected = settings.selected_mut(); + *selected = (*selected + 1) % page_len; + } + if matches!(page, DebugMenuPage::PlayerRows(_)) { + let direction = if gamepad.just_pressed(GamepadButton::DPadLeft) { + Some(-1) + } else if gamepad.just_pressed(GamepadButton::DPadRight) { + Some(1) + } else { + None + }; + if let Some(direction) = direction { + let DebugMenuPage::PlayerRows(handle) = page else { + unreachable!("player row navigation must have a player row page") + }; + let selected = settings.selected(); + let rows = settings.rows_mut(handle); + rows[selected] = rows[selected].cycle(direction); + } + } + if gamepad.just_pressed(GamepadButton::South) { + match page { + DebugMenuPage::Static(entries) if page_len != 0 => match entries[settings.selected()] { + DebugMenuEntry::Toggle { flag, .. } => settings.toggle_flag(flag), + DebugMenuEntry::Submenu { entries, .. } => { + settings.push_page(DebugMenuPage::Static(entries)); + } + DebugMenuEntry::PlayerSlots { .. } => settings.push_page(DebugMenuPage::PlayerList), + }, + DebugMenuPage::PlayerList if page_len != 0 => { + let handle = player_handles[settings.selected()]; + settings.rows_mut(handle); + settings.push_page(DebugMenuPage::PlayerRows(handle)); + } + _ => {} + } + } + if gamepad.just_pressed(GamepadButton::West) && !settings.pop_page() { + settings.menu_open = false; + settings.controller = None; + } +} + +fn sync_gizmo_depth_bias(settings: Res, mut configs: ResMut) { + if !settings.is_changed() { + return; + } + let depth_bias = if settings.draw_through_geometry { + -1.0 + } else { + 0.0 + }; + for (_, config, _) in configs.iter_mut() { + config.depth_bias = depth_bias; + } +} + +fn draw_palette( + mut contexts: EguiContexts, + settings: Res, + players: Query<&Player>, +) -> Result { + if !settings.menu_open { + return Ok(()); + } + + let player_handles = player_handles(&players); + let page = settings.page(); + let selected = settings.selected(); + egui::Window::new("Debug") + .anchor(egui::Align2::RIGHT_TOP, egui::vec2(-12.0, 12.0)) + .resizable(false) + .show(contexts.ctx_mut()?, |ui| { + ui.style_mut().override_font_id = Some(egui::FontId::proportional(20.0)); + match page { + DebugMenuPage::Static(entries) => { + for (index, entry) in entries.iter().enumerate() { + let marker = if index == selected { ">" } else { " " }; + match entry { + DebugMenuEntry::Toggle { label, flag } => { + let state = if settings.flag(*flag) { "on" } else { "off" }; + ui.label(format!("{marker} {label}: {state}")); + } + DebugMenuEntry::Submenu { label, .. } + | DebugMenuEntry::PlayerSlots { label } => { + ui.label(format!("{marker} {label} >")); + } + } + } + } + DebugMenuPage::PlayerList => { + for (index, handle) in player_handles.iter().enumerate() { + let marker = if index == selected { ">" } else { " " }; + ui.label(format!("{marker} Player {} >", handle + 1)); + } + if player_handles.is_empty() { + ui.label("No players in match"); + } + } + DebugMenuPage::PlayerRows(handle) => { + let rows = settings + .player_rows(handle) + .copied() + .unwrap_or([FighterDebugRowKind::None; PLAYER_DEBUG_ROW_COUNT]); + ui.label(format!("Player {}", handle + 1)); + for (index, row) in rows.iter().enumerate() { + let marker = if index == selected { ">" } else { " " }; + ui.label(format!("{marker} Row {}: {}", index + 1, row.label())); + } + } + } + ui.separator(); + let controls = if matches!(page, DebugMenuPage::PlayerRows(_)) { + "D-pad: select/change B: back" + } else { + "D-pad: select A: enter/toggle B: back" + }; + ui.label(controls); + }); + Ok(()) +} + +pub fn fighter_info_enabled(settings: Res) -> bool { + settings + .player_rows + .values() + .any(|rows| rows.iter().any(|row| *row != FighterDebugRowKind::None)) +} + +pub fn network_enabled(settings: Res) -> bool { + settings.network +} + +pub fn ecb_enabled(settings: Res) -> bool { + settings.ecb +} + +pub fn stage_collision_enabled(settings: Res) -> bool { + settings.stage_collision +} + +pub fn animation_bones_enabled(settings: Res) -> bool { + settings.animation_bones +} + +pub fn attack_hitboxes_enabled(settings: Res) -> bool { + settings.attack_hitboxes +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn player_rows_default_to_none_and_wrap() { + let mut settings = DebugSettings::default(); + let rows = settings.rows_mut(2); + + assert_eq!(*rows, [FighterDebugRowKind::None; PLAYER_DEBUG_ROW_COUNT]); + rows[0] = rows[0].cycle(-1); + assert_eq!(rows[0], FighterDebugRowKind::Facing); + rows[0] = rows[0].cycle(1); + assert_eq!(rows[0], FighterDebugRowKind::None); + } + + #[test] + fn nested_navigation_preserves_parent_selection() { + let mut settings = DebugSettings::default(); + *settings.selected_mut() = 2; + settings.push_page(DebugMenuPage::Static(DIAGNOSTICS_MENU)); + + assert!(settings.pop_page()); + assert_eq!(settings.selected(), 2); + assert!(!settings.pop_page()); + } + + #[test] + fn player_row_configuration_is_slot_specific() { + let mut settings = DebugSettings::default(); + settings.rows_mut(0)[0] = FighterDebugRowKind::CurrentState; + + assert_eq!( + settings.player_rows(0).unwrap()[0], + FighterDebugRowKind::CurrentState + ); + assert!(settings.player_rows(1).is_none()); + } +} diff --git a/src/fighter/attack.rs b/src/fighter/attack.rs index 7dd3000..cccae17 100644 --- a/src/fighter/attack.rs +++ b/src/fighter/attack.rs @@ -93,7 +93,7 @@ pub struct AttackHitbox { } pub fn update_active_hitbox_list(script: &FighterAttackScript, list: &mut Vec, frame: u32) { - *list = script.hitboxes.iter().enumerate().filter(|(i, hb)| { + *list = script.hitboxes.iter().enumerate().filter(|(_, hb)| { hb.start_frame <= frame && hb.end_frame > frame }).map(|(i, _)| i).collect() } diff --git a/src/fighter/debug.rs b/src/fighter/debug.rs index 23be78d..cec6956 100644 --- a/src/fighter/debug.rs +++ b/src/fighter/debug.rs @@ -14,7 +14,9 @@ use crate::fighter::{ }; use crate::input::FighterInput; use crate::math::vec3::FGVec3; +use crate::player::Player; use crate::scripting::FighterAttackScript; +use crate::debug_tools::{DebugSettings, FighterDebugRowKind}; pub fn debug_draw_ecb( ecbs: Query<( @@ -39,13 +41,77 @@ pub fn debug_draw_ecb( } pub fn fighter_debug( mut contexts: EguiContexts, - query: Query<(&FighterVelocity, &FighterInput, &StateNameDebug)>, + query: Query<( + &Player, + &FighterVelocity, + &FighterInput, + &FighterAnimationFrame, + &StateNameDebug, + &FighterAnimations, + &FighterTranslation, + &FighterFacingDirection, + )>, + anims: Res>, + settings: Res, ) -> Result { - for (i, (vel, input, name)) in query.iter().enumerate() { - egui::Window::new(format!("Player {}", i)).show(contexts.ctx_mut()?, |ui| { - ui.label(format!("State: {}", name.0)); - ui.label(format!("{:?}", vel)); - ui.label(format!("{:?}", input.get_last_frame())); + let mut fighters = query.iter().collect::>(); + fighters.sort_unstable_by_key(|(player, ..)| player.handle); + + for (player, velocity, input, animation_frame, state_name, animations, translation, facing) in fighters { + let Some(rows) = settings.player_rows(player.handle) else { + continue; + }; + if rows.iter().all(|row| *row == FighterDebugRowKind::None) { + continue; + } + + egui::Window::new(format!("Player {}", player.handle + 1)).show(contexts.ctx_mut()?, |ui| { + for row in rows { + match row { + FighterDebugRowKind::None => {} + FighterDebugRowKind::CurrentState => { + let frame_count = anims + .get(&animations.baked) + .and_then(|animation| animation.frame_count(animation_frame.kind)); + if let Some(frame_count) = frame_count { + ui.label(format!( + "State: {:?} ({:?} {}/{})", + state_name.0, animation_frame.kind, animation_frame.frame, frame_count + )); + } else { + ui.label(format!("State: {:?} ({:?})", state_name.0, animation_frame.kind)); + } + } + FighterDebugRowKind::Velocity => { + ui.label(format!("Velocity: ({:.2}, {:.2})", velocity.0.x, velocity.0.y)); + } + FighterDebugRowKind::Input => { + const STICK_SIZE: f32 = 50.0; + const STICK_SIZE_FRACTION: f32 = 0.75; + let last_frame = input.get_last_frame(); + let movement = egui::Vec2::new(last_frame.movement.x.to_num(), -last_frame.movement.y.to_num::()); + + ui.horizontal(|ui| { + let (_, rect) = ui.allocate_space(egui::Vec2::new(STICK_SIZE*(1.0 + STICK_SIZE_FRACTION), STICK_SIZE*(1.0 + STICK_SIZE_FRACTION))); + let painter = ui.painter(); + let button_bg = egui::Rgba::WHITE * egui::Rgba::from_white_alpha(0.25); + painter.circle_filled(rect.center(), STICK_SIZE * 0.5, button_bg); + painter.circle_filled( + rect.center() + STICK_SIZE * 0.5 * movement, + STICK_SIZE * STICK_SIZE_FRACTION * 0.5, + button_bg, + ); + ui.label(format!("X: {:.2}\nY:{:.2}", last_frame.movement.x, last_frame.movement.y)); + }); + } + FighterDebugRowKind::Position => { + ui.label(format!("Position: {:?}", translation.0)); + }, + FighterDebugRowKind::Facing => { + ui.label(format!("Facing: {:?}", facing)); + } + } + } }); } Ok(()) @@ -110,16 +176,3 @@ pub fn attack_debug( } } } - -pub fn update_config( - mut config_store: ResMut, - keyboard: Res>, - _real_time: Res>, - _virtual_time: ResMut>, -) { - if keyboard.just_pressed(KeyCode::KeyT) { - for (_, config, _) in config_store.iter_mut() { - config.depth_bias = if config.depth_bias == 0. { -1. } else { 0. }; - } - } -} diff --git a/src/fighter/mod.rs b/src/fighter/mod.rs index 3761254..74929cb 100644 --- a/src/fighter/mod.rs +++ b/src/fighter/mod.rs @@ -147,11 +147,22 @@ impl Plugin for FighterPlugin { .rollback_component_with_copy::() .rollback_resource_with_reflect::() // Debug views. - .add_systems(FixedPostUpdate, debug::debug_draw_ecb) - .add_systems(EguiPrimaryContextPass, debug::fighter_debug) - .add_systems(FixedPostUpdate, debug::animation_debug) - .add_systems(FixedPostUpdate, debug::attack_debug) - .add_systems(EguiPrimaryContextPass, debug::update_config) + .add_systems( + FixedPostUpdate, + debug::debug_draw_ecb.run_if(crate::debug_tools::ecb_enabled), + ) + .add_systems( + EguiPrimaryContextPass, + debug::fighter_debug.run_if(crate::debug_tools::fighter_info_enabled), + ) + .add_systems( + FixedPostUpdate, + debug::animation_debug.run_if(crate::debug_tools::animation_bones_enabled), + ) + .add_systems( + FixedPostUpdate, + debug::attack_debug.run_if(crate::debug_tools::attack_hitboxes_enabled), + ) .rollback_component_with_clone::() .rollback_component_with_clone::() .checksum_component_with_hash::() diff --git a/src/fighter/motion.rs b/src/fighter/motion.rs index b4764d9..cc48047 100644 --- a/src/fighter/motion.rs +++ b/src/fighter/motion.rs @@ -2,7 +2,6 @@ //! this module is what turns that into a position. use bevy::prelude::*; -use fixed::types::I16F16; use crate::{fighter::{FighterFacingDirection, baked_animation::FixedMat4}, math::{int::FGi32, vec::FGVec2, vec3::FGVec3}}; diff --git a/src/fighter/state/ground_attack.rs b/src/fighter/state/ground_attack.rs index d06075b..e250420 100644 --- a/src/fighter/state/ground_attack.rs +++ b/src/fighter/state/ground_attack.rs @@ -1,7 +1,5 @@ use crate::fighter::{attack::{AttackKind, update_active_hitbox_list}, state::{FighterState, FighterStateImpl, ground::GroundedStateCommon, wait::WaitState}}; -use bevy::prelude::*; - #[derive(Clone, Debug, Hash)] pub struct GroundAttackState { pub attack_kind: AttackKind, diff --git a/src/fighter/state/mod.rs b/src/fighter/state/mod.rs index fba26c1..31a9bef 100644 --- a/src/fighter/state/mod.rs +++ b/src/fighter/state/mod.rs @@ -10,7 +10,6 @@ use crate::fighter::attack::FighterAttackScriptAssets; use crate::fighter::baked_animation::{BakedFighterAnimations, FixedMat4}; use crate::fighter::ecb::FighterPreviousECB; use crate::fighter::manifest::FighterManifest; -use crate::fighter::state::ground_attack::GroundAttackState; use crate::fighter::visual::FighterAnimations; use crate::fighter::{ Fighter, FighterECB, FighterFacingDirection, FighterHitboxes, FighterPreviousTranslation, FighterTranslation, FighterVelocity, diff --git a/src/game_settings.rs b/src/game_settings.rs index 5850f4b..f7bd025 100644 --- a/src/game_settings.rs +++ b/src/game_settings.rs @@ -47,3 +47,12 @@ impl FromWorld for GameSettings { .clone() } } + +#[derive(Reflect, Resource, Default)] +#[reflect(Resource)] +pub struct DebugSettings { + pub show_attack_hitboxes: bool, + pub show_stage_lines: bool, + pub show_ecb: bool, + pub debug_gizmos_draw_in_front: bool +} \ No newline at end of file diff --git a/src/input/debug.rs b/src/input/debug.rs deleted file mode 100644 index b5d3140..0000000 --- a/src/input/debug.rs +++ /dev/null @@ -1,27 +0,0 @@ -use bevy_egui::prelude::*; - -use crate::{fighter::{FighterHitboxes, animation::FighterAnimationFrame, visual::FighterAnimations}, input::FighterInput}; -use bevy::prelude::*; - -pub fn input_debug(mut contexts: EguiContexts, query: Query<&FighterInput>) -> Result { - for (i, input) in query.iter().enumerate() { - const STICK_SIZE: f32 = 50.0; - const STICK_SIZE_FRACTION: f32 = 0.75; - let last_frame = input.get_last_frame(); - let movement = egui::Vec2::new(last_frame.movement.x.to_num(), -last_frame.movement.y.to_num::()); - egui::Window::new(format!("Input {}", i)).show(contexts.ctx_mut()?, |ui| { - let (_, rect) = ui.allocate_space(egui::Vec2::new(STICK_SIZE*(1.0 + STICK_SIZE_FRACTION), STICK_SIZE*(1.0 + STICK_SIZE_FRACTION))); - let painter = ui.painter(); - let button_bg = egui::Rgba::WHITE * egui::Rgba::from_white_alpha(0.25); - painter.circle_filled(rect.center(), STICK_SIZE * 0.5, button_bg); - painter.circle_filled( - rect.center() + STICK_SIZE * 0.5 * movement, - STICK_SIZE * STICK_SIZE_FRACTION * 0.5, - button_bg, - ); - ui.label(format!("X: {:.2}\nY:{:.2}", last_frame.movement.x, last_frame.movement.y)); - }); - } - - Ok(()) -} diff --git a/src/input/mod.rs b/src/input/mod.rs index e9cc003..b51f647 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -3,7 +3,6 @@ use bevy::platform::collections::HashMap; use bevy::prelude::*; -use bevy_egui::EguiPrimaryContextPass; use bevy_ggrs::prelude::*; use bevy_ggrs::{LocalInputs, LocalPlayers}; @@ -14,7 +13,6 @@ pub mod gamecube; pub mod gamepad; pub mod keyboard; pub mod map; -pub mod debug; pub use buffer::{FighterCommands, FighterInput}; pub use frame::FighterInputFrame; @@ -106,7 +104,6 @@ impl Plugin for FighterInputPlugin { }) .add_plugins(gamecube::GamecubeAdapterPlugin) .add_systems(ReadInputs, read_local_inputs) - .add_systems(EguiPrimaryContextPass, debug::input_debug) .add_systems( GgrsSchedule, buffer::postprocess_input.in_set(GameplaySet::Input), diff --git a/src/lib.rs b/src/lib.rs index 4d2f484..a1a2fe7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,6 +33,7 @@ use crate::{ mod args; pub mod camera; +pub mod debug_tools; pub mod fighter; pub mod game_settings; pub mod input; @@ -79,6 +80,7 @@ impl Plugin for GamePlugin { netcode::FighterNetcodePlugin::default(), GameplaySchedulePlugin, input::FighterInputPlugin, + debug_tools::DebugToolsPlugin, fighter::FighterPlugin, fighter::baked_animation::BakedAnimationPlugin, scripting::importer::FighterScriptImportPlugin, diff --git a/src/netcode/mod.rs b/src/netcode/mod.rs index 5265bb6..3a84a94 100644 --- a/src/netcode/mod.rs +++ b/src/netcode/mod.rs @@ -23,7 +23,10 @@ impl Plugin for FighterNetcodePlugin { // Gameplay components are registered by the plugin that owns them. .rollback_component_with_clone::() .rollback_component_with_clone::() - .add_systems(EguiPrimaryContextPass, debug::network_debug) + .add_systems( + EguiPrimaryContextPass, + debug::network_debug.run_if(crate::debug_tools::network_enabled), + ) .add_systems(Update, session::print_events_system) .insert_resource(RollbackFrameRate(60)); } diff --git a/src/stage/mod.rs b/src/stage/mod.rs index de72bdc..e69ad8d 100644 --- a/src/stage/mod.rs +++ b/src/stage/mod.rs @@ -20,7 +20,9 @@ impl Plugin for StagePlugin { fn build(&self, app: &mut App) { app.add_systems( Update, - debug::debug_draw_scene.run_if(resource_exists::), + debug::debug_draw_scene + .run_if(resource_exists::) + .run_if(crate::debug_tools::stage_collision_enabled), ); } }