From ad6c1e17aceba1709ab1499c4894302a9c993be1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=80lex=20Rom=C3=A0n?= Date: Fri, 7 Aug 2026 15:13:21 +0200 Subject: [PATCH] Refactor state system --- .cargo/config.toml | 2 +- src/{fighter => }/fighter.rs | 26 ++++++------- src/fighter/data.rs | 8 +++- src/fighter/mod.rs | 8 ---- src/fighter/plugin.rs | 25 ++++++++++-- src/fighter/state.rs | 25 +++++++++++- src/fighter/states.rs | 5 +++ src/fighter/states/dash.rs | 49 +++++++++++++++++++++--- src/fighter/states/grounded.rs | 69 ++++++++++++++++++++++++++++++++++ src/fighter/states/mod.rs | 3 -- src/fighter/states/run.rs | 44 ++++++++++++++++++++++ src/fighter/states/wait.rs | 15 +++++++- src/fighter/states/walk.rs | 56 ++------------------------- src/game_settings.rs | 5 ++- src/{input/mod.rs => input.rs} | 0 src/input/logic.rs | 2 +- src/input/player.rs | 14 +++++-- src/input/plugin.rs | 2 - src/lib.rs | 11 ++++-- src/stage/mod.rs | 2 +- 20 files changed, 268 insertions(+), 103 deletions(-) rename src/{fighter => }/fighter.rs (86%) delete mode 100644 src/fighter/mod.rs create mode 100644 src/fighter/states.rs create mode 100644 src/fighter/states/grounded.rs delete mode 100644 src/fighter/states/mod.rs create mode 100644 src/fighter/states/run.rs rename src/{input/mod.rs => input.rs} (100%) diff --git a/.cargo/config.toml b/.cargo/config.toml index a640d4b..8e4313f 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -7,7 +7,7 @@ rustflags = ["-C", "link-arg=-fuse-ld=/usr/bin/mold", "-Zshare-generics=y", ] codegen-backend = true [profile.dev] -codegen-backend = "cranelift" +codegen-backend = "llvm" [profile.dev.package."*"] codegen-backend = "llvm" \ No newline at end of file diff --git a/src/fighter/fighter.rs b/src/fighter.rs similarity index 86% rename from src/fighter/fighter.rs rename to src/fighter.rs index 4bd128f..7fe7c39 100644 --- a/src/fighter/fighter.rs +++ b/src/fighter.rs @@ -1,14 +1,16 @@ use bevy::{ - color::palettes::css::{GREEN, HOT_PINK, RED, YELLOW}, - ecs::relationship::Relationship, + color::palettes::css::{HOT_PINK, YELLOW}, prelude::*, }; -use crate::{ - game_settings::GameSettings, - input::player::{ControlledBy, FighterInput}, - stage::StagePlane, -}; +pub mod plugin; +pub mod data; +pub mod state; +pub mod states; +pub use plugin::*; +pub use data::*; + +use crate::stage::StagePlane; #[derive(Component)] pub struct Grounded; @@ -48,12 +50,9 @@ pub fn debug_draw_ecb(ecbs: Query<(&FighterECB, &FighterTranslation)>, mut gizmo } pub fn integrate_gravity( - fighters: Query<(&mut FighterVelocity)>, - time: Res>, + _fighters: Query<&mut FighterVelocity>, + _time: Res>, ) { - /*for (movement, mut velocity) in fighters { - //velocity.y -= movement.gravity; - }*/ } pub fn apply_motion( @@ -62,7 +61,7 @@ pub fn apply_motion( &mut FighterTranslation, &mut FighterPreviousTranslation, )>, - time: Res>, + _time: Res>, ) { for (velocity, mut translation, mut prev_translation) in fighters { prev_translation.0 = translation.0; @@ -98,3 +97,4 @@ pub fn collide_fighter_with_scene( } pub fn fighter_movement() {} + diff --git a/src/fighter/data.rs b/src/fighter/data.rs index 230caca..c7ce535 100644 --- a/src/fighter/data.rs +++ b/src/fighter/data.rs @@ -5,6 +5,12 @@ pub struct FighterAttributes { pub base_walk_accel: f32, pub stick_walk_accel: f32, pub max_walk_vel: f32, + + pub dash_duration: u32, + pub dash_initial_velocity: f32, + pub base_dash_accel: f32, + pub stick_dash_accel: f32, + pub max_dash_vel: f32, + pub ground_friction: f32, - pub dash_duration: u32 } \ No newline at end of file diff --git a/src/fighter/mod.rs b/src/fighter/mod.rs deleted file mode 100644 index 342d11a..0000000 --- a/src/fighter/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -pub mod plugin; -pub mod fighter; -pub mod data; -pub mod state; -pub mod states; -pub use fighter::*; -pub use plugin::*; -pub use data::*; \ No newline at end of file diff --git a/src/fighter/plugin.rs b/src/fighter/plugin.rs index 9e62455..2e2dc80 100644 --- a/src/fighter/plugin.rs +++ b/src/fighter/plugin.rs @@ -1,8 +1,11 @@ use bevy::prelude::*; -use crate::fighter::states; +use bevy_egui::{EguiContexts, EguiPrimaryContextPass, egui}; +use crate::fighter::state::StateNameDebug; +use crate::fighter::{FighterVelocity, states}; use crate::fighter::{ - FighterECB, FighterTranslation, FighterVelocity, apply_motion, collide_fighter_with_scene, integrate_gravity, state::state_interrupt_system, + apply_motion, collide_fighter_with_scene, integrate_gravity, state::state_interrupt_system, }; +use crate::input::player::FighterInput; #[derive(Default)] pub struct FighterPlugin; @@ -16,14 +19,30 @@ impl Plugin for FighterPlugin { state_interrupt_system::, state_interrupt_system::, state_interrupt_system::, + state_interrupt_system::, + states::wait::wait_update, states::walk::walk_update, states::dash::dash_update, + states::run::run_update, integrate_gravity, apply_motion, collide_fighter_with_scene, ) .chain(), ) - .add_observer(states::dash::on_insert_dash); + .add_observer(states::dash::on_insert_dash) + .add_systems(EguiPrimaryContextPass, fighter_debug); } } + +fn fighter_debug(mut contexts: EguiContexts, query: Query<(&FighterVelocity, &FighterInput, &StateNameDebug)>) -> Result { + for (vel, input, name) in query { + egui::Window::new("Hello").show(contexts.ctx_mut()?, |ui| { + ui.label(format!("State: {}", name.0)); + ui.label(format!("{:?}", vel)); + ui.label(format!("{:?}", input.get_last_frame())); + }); + } + + Ok(()) +} diff --git a/src/fighter/state.rs b/src/fighter/state.rs index 35fedb5..fd2e0d9 100644 --- a/src/fighter/state.rs +++ b/src/fighter/state.rs @@ -1,11 +1,12 @@ -use bevy::{ecs::relationship::RelationshipSourceCollection, prelude::*}; +use bevy::{prelude::*}; use crate::{fighter::{FighterAttributes, states}, game_settings::GameSettings, input::player::FighterInput}; pub enum FighterStateTransition { Wait, Walk, - Dash + Dash, + Run, } pub trait FighterState : Component + Sized { @@ -33,9 +34,28 @@ fn create_state(transition: FighterStateTransition, state_context: FighterStateC FighterStateTransition::Wait => ent_cmd.insert(states::wait::WaitState), FighterStateTransition::Walk => ent_cmd.insert(states::walk::WalkState), FighterStateTransition::Dash => ent_cmd.insert(states::dash::DashState::default()), + FighterStateTransition::Run => ent_cmd.insert(states::run::RunState), }; } +fn get_state_debug_name(transition: &FighterStateTransition) -> &'static str { + match transition { + FighterStateTransition::Wait => "wait", + FighterStateTransition::Walk => "walk", + FighterStateTransition::Dash => "dash", + FighterStateTransition::Run => "run", + } +} + +#[derive(Component)] +pub struct StateNameDebug(pub &'static str); + +impl std::fmt::Display for StateNameDebug { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + pub fn state_interrupt_system( query: Query<(Entity, &T, &mut FighterInput, &FighterAttributes)>, game_settings: Res, @@ -51,6 +71,7 @@ pub fn state_interrupt_system( }; if let Some(new_state) = state.check_interrupt(&state_context) { commands.entity(ent).remove::(); + commands.entity(ent).insert(StateNameDebug(get_state_debug_name(&new_state))); create_state(new_state, state_context, &mut commands); } } diff --git a/src/fighter/states.rs b/src/fighter/states.rs new file mode 100644 index 0000000..923c929 --- /dev/null +++ b/src/fighter/states.rs @@ -0,0 +1,5 @@ +pub mod wait; +pub mod walk; +pub mod dash; +pub mod run; +pub mod grounded; \ No newline at end of file diff --git a/src/fighter/states/dash.rs b/src/fighter/states/dash.rs index e783de2..b91980b 100644 --- a/src/fighter/states/dash.rs +++ b/src/fighter/states/dash.rs @@ -1,5 +1,10 @@ use crate::{ - fighter::state::{FighterState, FighterStateContext, FighterStateTransition}, + fighter::{ + FighterAttributes, FighterVelocity, + state::{FighterState, FighterStateContext, FighterStateTransition}, + states::grounded, + }, + game_settings::GameSettings, input::player::FighterInput, }; use bevy::prelude::*; @@ -24,7 +29,8 @@ impl FighterState for DashState { ) -> Option { if self.frames_in_dash > state_context.fighter_attribs.dash_duration { // Dash finish shenanigans - super::walk::check_input(state_context) + super::run::check_input(state_context) + .or_else(|| super::walk::check_input(state_context)) .or_else(|| super::wait::check_input(state_context)) } else if let Some(last_frame) = state_context.input.get_last_frame() { check_smash_input_with_dir(state_context) @@ -57,9 +63,29 @@ fn check_smash_input_with_dir(state_context: &FighterStateContext) -> Option) { - for mut dash in dash_query { +pub fn dash_update( + dash_query: Query<( + &mut DashState, + &FighterAttributes, + &FighterInput, + &mut FighterVelocity, + )>, + game_settings: Res, +) { + for (mut dash, attributes, input, mut velocity) in dash_query { dash.frames_in_dash += 1; + + if let Some(last_frame) = input.get_last_frame() { + let (accel, target_vel) = attributes.get_accel_and_target_dashrun(last_frame); + let accel = grounded::compute_ground_accel( + accel, + target_vel, + velocity.x, + attributes, + game_settings.as_ref(), + ); + velocity.x += accel; + } } } @@ -69,10 +95,21 @@ pub fn check_input(state_context: &FighterStateContext) -> Option, - query: Query<(Entity, &mut FighterInput), With>, + query: Query< + ( + Entity, + &mut FighterInput, + &FighterAttributes, + &mut FighterVelocity, + ), + With, + >, ) { - for (entity, mut input) in query { + for (entity, mut input, attribs, mut velocity) in query { if entity == add.entity { + if let Some(last_frame) = input.get_last_frame() { + velocity.x = attribs.dash_initial_velocity * last_frame.movement.x.signum(); + } input.clear_buffer(); } } diff --git a/src/fighter/states/grounded.rs b/src/fighter/states/grounded.rs new file mode 100644 index 0000000..f701698 --- /dev/null +++ b/src/fighter/states/grounded.rs @@ -0,0 +1,69 @@ +use bevy::log::info; + +use crate::{fighter::FighterAttributes, game_settings::GameSettings, input::player::InputFrame}; + +pub fn apply_grounded_friction(friction: f32, ground_vel: f32) -> f32 { + if friction.abs() > ground_vel.abs() { + -ground_vel + } else { + (-ground_vel.signum()) * friction.abs() + } +} + +pub fn move_accelerate() { + +} + +pub fn compute_ground_accel(accel: f32, target_vel: f32, gr_vel: f32, attribs: &FighterAttributes, game_settings: &GameSettings) -> f32 { + if target_vel == 0.0 { + apply_grounded_friction(attribs.ground_friction, gr_vel) + } else { + let mut accel = accel; + let ground_max_horizontal_velocity = + game_settings.figher_common.ground_max_horizontal_velocity; + if !(gr_vel * accel < 0.0) { + // accelerating, not reversing + if accel > 0.0 { + if gr_vel + accel > target_vel { + accel = -attribs.ground_friction; + if gr_vel + accel < target_vel { + accel = target_vel - gr_vel; + } + if gr_vel + accel > ground_max_horizontal_velocity { + accel = ground_max_horizontal_velocity - gr_vel; + } + } + } else if gr_vel + accel < target_vel { + accel = attribs.ground_friction; + if gr_vel + accel > target_vel { + accel = target_vel - gr_vel; + } + if gr_vel + accel < -ground_max_horizontal_velocity { + accel = -ground_max_horizontal_velocity - gr_vel; + } + } + } + accel + } +} + + +impl FighterAttributes { + pub fn get_accel_and_target_dashrun(&self, input: &InputFrame) -> (f32, f32) { + let accel = input.movement.x * self.stick_dash_accel; + let accel = accel + input.movement.x.signum() * self.base_dash_accel; + let target_vel = input.movement.x * self.max_dash_vel; + + info!("accel {:?} target_vel {:?}", accel, target_vel); + + (accel, target_vel) + } + + pub fn get_accel_and_target_walk(&self, input: &InputFrame) -> (f32, f32) { + let accel = input.movement.x * self.stick_walk_accel; + let accel = accel + input.movement.x.signum() * self.base_walk_accel; + let target_vel = input.movement.x * self.max_walk_vel; + + (accel, target_vel) + } +} diff --git a/src/fighter/states/mod.rs b/src/fighter/states/mod.rs deleted file mode 100644 index 7cd4510..0000000 --- a/src/fighter/states/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod wait; -pub mod walk; -pub mod dash; \ No newline at end of file diff --git a/src/fighter/states/run.rs b/src/fighter/states/run.rs new file mode 100644 index 0000000..02d65a4 --- /dev/null +++ b/src/fighter/states/run.rs @@ -0,0 +1,44 @@ +use crate::{ + fighter::{FighterAttributes, FighterVelocity, state::{FighterStateContext, FighterStateTransition}, states::grounded}, game_settings::GameSettings, input::player::FighterInput, state::FighterState, +}; +use bevy::prelude::*; + +#[derive(Component, Debug)] +pub struct RunState; + +pub fn check_input(state_context: &FighterStateContext) -> Option { + if let Some(input_frame) = state_context.input.get_last_frame() { + if input_frame.movement.x.abs() >= state_context.game_settings.input_common.run_stick_threshold { + return Some(FighterStateTransition::Run); + } + } + None +} + +impl FighterState for RunState { + fn check_interrupt( + &self, + state_context: &crate::fighter::state::FighterStateContext, + ) -> Option { + use super::*; + + if let Some(_) = check_input(state_context) { + None + } else { + walk::check_input(state_context).or_else(|| wait::check_input(state_context)) + } + } +} + +pub fn run_update( + fighters: Query<(&mut FighterVelocity, &FighterAttributes, &FighterInput), With>, + game_settings: Res, +) { + for ( mut velocity, attributes, input) in fighters { + if let Some(last_frame) = input.get_last_frame() { + let (accel, target_vel) = attributes.get_accel_and_target_dashrun(last_frame); + let accel = grounded::compute_ground_accel(accel, target_vel, velocity.x, attributes, game_settings.as_ref()); + velocity.x += accel; + } + } +} diff --git a/src/fighter/states/wait.rs b/src/fighter/states/wait.rs index eab9efd..bb5721b 100644 --- a/src/fighter/states/wait.rs +++ b/src/fighter/states/wait.rs @@ -1,5 +1,5 @@ -use bevy::prelude::*; -use crate::fighter::{state::{FighterState, FighterStateContext, FighterStateTransition}, states::walk}; +use crate::{fighter::{state::{FighterState, FighterStateContext, FighterStateTransition}, states::{grounded, walk}}, game_settings::GameSettings}; +use crate::fighter::*; #[derive(Component, Debug)] pub struct WaitState; @@ -20,4 +20,15 @@ pub fn check_input(state_context: &FighterStateContext) -> Option>, game_settings: Res) { + for (attribs, mut velocity) in query { + let friction = if velocity.x.abs() > attribs.max_walk_vel { + attribs.ground_friction * game_settings.figher_common.ground_friction_over_walk_speed_multiplier + } else { + attribs.ground_friction + }; + velocity.x += grounded::apply_grounded_friction(friction, velocity.x); + } } \ No newline at end of file diff --git a/src/fighter/states/walk.rs b/src/fighter/states/walk.rs index 16b7857..92d54d9 100644 --- a/src/fighter/states/walk.rs +++ b/src/fighter/states/walk.rs @@ -1,11 +1,7 @@ use crate::{ fighter::{ - FighterAttributes, FighterVelocity, - state::{FighterState, FighterStateContext, FighterStateTransition}, - states::{dash, wait}, - }, - game_settings::GameSettings, - input::player::FighterInput, + FighterAttributes, FighterVelocity, state::{FighterState, FighterStateContext, FighterStateTransition}, states::{dash, grounded, wait}, + }, game_settings::GameSettings, input::player::FighterInput, }; use bevy::prelude::*; @@ -31,53 +27,9 @@ pub fn walk_update( ) { for (mut velocity, attributes, input) in fighters { if let Some(last_frame) = input.get_last_frame() { - let accel_mul = 1.0; - let mut accel = last_frame.movement.x * attributes.stick_walk_accel * accel_mul; // stick-proportional - accel += (last_frame.movement.x.signum() * attributes.base_walk_accel) * accel_mul; // constant - let target_vel = last_frame.movement.x * attributes.max_walk_vel * accel_mul; + let (accel, target_vel) = attributes.get_accel_and_target_walk(last_frame); - if target_vel != 0.0 { - let mult = velocity.x / target_vel; - if mult > 0.0 && mult < 1.0 { - accel *= (1.0 - mult) * game_settings.figher_common.walk_speed_ease; // 0.5 — linear ease-out toward target - } - } - - // Friction mode - if target_vel == 0.0 { - let mut out_friction = velocity.x.signum() * (-attributes.ground_friction); - // apply friction - if attributes.ground_friction.abs() > velocity.x.abs() { - out_friction = -velocity.x; - } - - accel = out_friction; - } else { - let ground_max_horizontal_velocity = - game_settings.figher_common.ground_max_horizontal_velocity; - if !(velocity.x * accel < 0.0) { - // accelerating, not reversing - if accel > 0.0 { - if velocity.x + accel > target_vel { - accel = -attributes.ground_friction; - if velocity.x + accel < target_vel { - accel = target_vel - velocity.x; - } - if velocity.x + accel > ground_max_horizontal_velocity { - accel = ground_max_horizontal_velocity - velocity.x; - } - } - } else if velocity.x + accel < target_vel { - accel = attributes.ground_friction; - if velocity.x + accel > target_vel { - accel = target_vel - velocity.x; - } - if velocity.x + accel < -ground_max_horizontal_velocity { - accel = -ground_max_horizontal_velocity - velocity.x; - } - } - } - } + let accel = grounded::compute_ground_accel(accel, target_vel, velocity.x, attributes, &game_settings); velocity.x += accel; } diff --git a/src/game_settings.rs b/src/game_settings.rs index ec41286..855892f 100644 --- a/src/game_settings.rs +++ b/src/game_settings.rs @@ -3,13 +3,15 @@ use bevy::prelude::*; pub struct FighterSettingsCommon { pub walk_speed_ease: f32, pub ground_max_horizontal_velocity: f32, + pub ground_friction_over_walk_speed_multiplier: f32 } pub struct InputSettingsCommon { pub stick_deadzone: f32, pub trigger_deadzone: f32, pub smash_input_reset_axis_threshold: f32, pub smash_input_axis_threshold: f32, - pub smash_input_frame_threshold: u32 + pub run_stick_threshold: f32, + pub smash_input_frame_threshold: u32, } impl Default for InputSettingsCommon { @@ -19,6 +21,7 @@ impl Default for InputSettingsCommon { trigger_deadzone: 0.3f32, smash_input_reset_axis_threshold: 0.25, smash_input_axis_threshold: 0.8, + run_stick_threshold: 0.625, smash_input_frame_threshold: 2 } } diff --git a/src/input/mod.rs b/src/input.rs similarity index 100% rename from src/input/mod.rs rename to src/input.rs diff --git a/src/input/logic.rs b/src/input/logic.rs index 38cfe91..4778f95 100644 --- a/src/input/logic.rs +++ b/src/input/logic.rs @@ -5,7 +5,7 @@ use crate::input::player::InputFrame; use super::player::{FighterInput, BaseInputMap, InputActionState}; pub fn postprocess_input(input_state: Query<&mut FighterInput>) { - for mut player_input in input_state { + for _player_input in input_state { } } pub fn preprocess_keyboard_input(key: Res>, input_map: Res, mut input_state: Query<&mut FighterInput>) { diff --git a/src/input/player.rs b/src/input/player.rs index 8ce8832..e8cf40b 100644 --- a/src/input/player.rs +++ b/src/input/player.rs @@ -1,23 +1,28 @@ use std::collections::VecDeque; use bevy::prelude::*; -use arrayvec::ArrayVec; -#[derive(Default, Clone)] +#[derive(Default, Clone, Debug)] pub struct InputFrame { pub movement: Vec2, pub directional_attack: Vec2 } -#[derive(Component, Default)] +#[derive(Component)] pub struct FighterInput { input_buffer: VecDeque } +impl Default for FighterInput { + fn default() -> Self { + Self { input_buffer: vec!(InputFrame::default()).into() } + } +} + impl FighterInput { const INPUT_BUFFER_SIZE: usize = 5; pub fn get_last_frame(&self) -> Option<&InputFrame> { - self.input_buffer.back() + Some(self.input_buffer.back().unwrap()) } pub fn push_input_frame(&mut self, frame: InputFrame) { @@ -28,6 +33,7 @@ impl FighterInput { } pub fn clear_buffer(&mut self) { + info!("CLEAR"); while self.input_buffer.len() > 1 { self.input_buffer.pop_front(); } diff --git a/src/input/plugin.rs b/src/input/plugin.rs index 28c19eb..92fbc2d 100644 --- a/src/input/plugin.rs +++ b/src/input/plugin.rs @@ -1,6 +1,4 @@ use bevy::prelude::*; -use crate::fighter::*; -use crate::input::player::{ControlledBy, Controls, FighterInput}; use crate::input::{ logic::{postprocess_input, preprocess_keyboard_input}, player::{BaseInputMap, InputMapAction, KeyboardInputMapElement}, }; diff --git a/src/lib.rs b/src/lib.rs index e90d779..dbc4578 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,7 +38,7 @@ pub mod game_settings; pub struct GamePlugin; pub fn setup(mut commands: Commands) { - let fighter = commands + let _fighter = commands .spawn(( Name("Fighter".into()), FighterECB { @@ -52,7 +52,11 @@ pub fn setup(mut commands: Commands) { stick_walk_accel: 0.02, max_walk_vel: 0.16, ground_friction: 0.008, - dash_duration: 15 + dash_duration: 15, + base_dash_accel: 0.002, + stick_dash_accel: 0.01, + max_dash_vel: 0.22, + dash_initial_velocity: 0.19, }, WaitState {}, input::player::FighterInput::default() @@ -71,7 +75,8 @@ impl Plugin for GamePlugin { .insert_resource(GameSettings { figher_common: FighterSettingsCommon { walk_speed_ease: 0.5, - ground_max_horizontal_velocity: 0.3 + ground_max_horizontal_velocity: 0.3, + ground_friction_over_walk_speed_multiplier: 2.0 }, input_common: InputSettingsCommon::default(), }); diff --git a/src/stage/mod.rs b/src/stage/mod.rs index e03eb76..2d03806 100644 --- a/src/stage/mod.rs +++ b/src/stage/mod.rs @@ -1,4 +1,4 @@ -use bevy::{camera_controller::free_camera::FreeCamera, color::palettes::css::{GREEN, RED}, prelude::*}; +use bevy::{color::palettes::css::{GREEN, RED}, prelude::*}; #[derive(Component, Reflect, Clone, Default)] #[reflect(Component)]