Initial work on rollback and refactor
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
use crate::input::FighterInputFrame;
|
||||
|
||||
/// Per-fighter tuning values. These are character data, not global rules —
|
||||
/// anything that applies to every fighter belongs in
|
||||
/// [`crate::game_settings::FighterSettingsCommon`] instead.
|
||||
#[derive(Component, Clone, Copy)]
|
||||
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,
|
||||
}
|
||||
|
||||
impl FighterAttributes {
|
||||
pub fn get_accel_and_target_dashrun(&self, input: &FighterInputFrame) -> (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;
|
||||
|
||||
(accel, target_vel)
|
||||
}
|
||||
|
||||
pub fn get_accel_and_target_walk(&self, input: &FighterInputFrame) -> (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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
//! Resolving an integrated fighter position against the stage.
|
||||
|
||||
use bevy::{color::palettes::css::HOT_PINK, prelude::*};
|
||||
|
||||
use crate::fighter::{FighterECB, FighterTranslation, FighterVelocity};
|
||||
use crate::stage::StageLine;
|
||||
|
||||
pub fn collide_fighter_with_scene(
|
||||
fighters: Query<(&FighterECB, &mut FighterVelocity, &mut FighterTranslation)>,
|
||||
stage_planes: Query<&StageLine>,
|
||||
mut gizmos: Gizmos,
|
||||
) {
|
||||
for (ecb, mut velocity, mut translation) in fighters {
|
||||
for plane in stage_planes {
|
||||
let ray_from = translation.0;
|
||||
let ray_to = translation.0 + Vec2::new(0.0, -ecb.vertical_half);
|
||||
|
||||
let intersection_result = plane.intersect_ray(Segment2d::new(ray_from, ray_to));
|
||||
|
||||
if let Some(result) = intersection_result {
|
||||
gizmos.sphere(
|
||||
Isometry3d::from(Vec3::new(result.position.x, result.position.y, 0.0)),
|
||||
0.1f32,
|
||||
HOT_PINK,
|
||||
);
|
||||
velocity.0 = plane.project(velocity.0);
|
||||
translation.0 = result.position + Vec2::new(0.0, ecb.vertical_half);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
#[derive(Component)]
|
||||
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,
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
//! Fighter debug views: gizmos and the egui inspector window.
|
||||
|
||||
use bevy::{color::palettes::css::YELLOW, prelude::*};
|
||||
use bevy_egui::{EguiContexts, egui};
|
||||
|
||||
use crate::fighter::state::StateNameDebug;
|
||||
use crate::fighter::{FighterECB, FighterTranslation, FighterVelocity};
|
||||
use crate::input::FighterInput;
|
||||
|
||||
pub fn debug_draw_ecb(ecbs: Query<(&FighterECB, &FighterTranslation)>, mut gizmos: Gizmos) {
|
||||
for (ecb, trf) in ecbs {
|
||||
let transformed = ecb.to_3d_lineloop(Transform::from_xyz(trf.x, trf.y, 0.0));
|
||||
gizmos.lineloop(transformed, YELLOW);
|
||||
}
|
||||
}
|
||||
|
||||
pub 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(())
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
/// Environment collision box: the diamond used to resolve a fighter against
|
||||
/// the stage. Purely geometry — the collision response lives in
|
||||
/// [`super::collision`].
|
||||
#[derive(Component, Debug, Clone, Copy)]
|
||||
#[require(Transform)]
|
||||
pub struct FighterECB {
|
||||
pub vertical_half: f32,
|
||||
pub horizontal_half: f32,
|
||||
}
|
||||
|
||||
impl FighterECB {
|
||||
pub fn to_3d_lineloop(&self, trf: Transform) -> [Vec3; 4] {
|
||||
let rel_up = trf.transform_point(Vec3::new(0.0, self.vertical_half, 0.0));
|
||||
let rel_down = trf.transform_point(Vec3::new(0.0, -self.vertical_half, 0.0));
|
||||
let rel_left = trf.transform_point(Vec3::new(-self.horizontal_half, 0.0, 0.0));
|
||||
let rel_right = trf.transform_point(Vec3::new(self.horizontal_half, 0.0, 0.0));
|
||||
[rel_up, rel_right, rel_down, rel_left]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
//! Everything fighter-shaped: the components that describe a fighter, the
|
||||
//! state machine that drives it, and the plugin that schedules it all.
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_egui::EguiPrimaryContextPass;
|
||||
use bevy_ggrs::prelude::*;
|
||||
|
||||
pub mod attributes;
|
||||
pub mod collision;
|
||||
pub mod debug;
|
||||
pub mod ecb;
|
||||
pub mod motion;
|
||||
pub mod state;
|
||||
|
||||
// The fighter components are re-exported so the rest of the crate can say
|
||||
// `fighter::FighterVelocity` without caring which file it lives in. Named
|
||||
// explicitly rather than globbed, so moving a type between submodules can't
|
||||
// silently change what this module exports.
|
||||
pub use attributes::FighterAttributes;
|
||||
pub use ecb::FighterECB;
|
||||
pub use motion::{FighterPreviousTranslation, FighterTranslation, FighterVelocity, Grounded};
|
||||
|
||||
use crate::schedule::GameplaySet;
|
||||
use state::state_interrupt_system;
|
||||
|
||||
/// Owns fighter simulation, the rollback registrations for fighter
|
||||
/// components, and the fighter debug overlays.
|
||||
#[derive(Default)]
|
||||
pub struct FighterPlugin;
|
||||
|
||||
impl Plugin for FighterPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app
|
||||
// Every state gets a chance to interrupt itself, in priority order.
|
||||
.add_systems(
|
||||
GgrsSchedule,
|
||||
(
|
||||
state_interrupt_system::<state::wait::WaitState>,
|
||||
state_interrupt_system::<state::walk::WalkState>,
|
||||
state_interrupt_system::<state::dash::DashState>,
|
||||
state_interrupt_system::<state::run::RunState>,
|
||||
)
|
||||
.chain()
|
||||
.in_set(GameplaySet::Interrupt),
|
||||
)
|
||||
.add_systems(
|
||||
GgrsSchedule,
|
||||
(
|
||||
state::wait::wait_update,
|
||||
state::walk::walk_update,
|
||||
state::dash::dash_update,
|
||||
state::run::run_update,
|
||||
)
|
||||
.chain()
|
||||
.in_set(GameplaySet::StateUpdate),
|
||||
)
|
||||
.add_systems(
|
||||
GgrsSchedule,
|
||||
(motion::integrate_gravity, motion::apply_motion)
|
||||
.chain()
|
||||
.in_set(GameplaySet::Physics),
|
||||
)
|
||||
.add_systems(
|
||||
GgrsSchedule,
|
||||
collision::collide_fighter_with_scene.in_set(GameplaySet::Collision),
|
||||
)
|
||||
.add_observer(state::dash::on_insert_dash)
|
||||
// Rollback registration lives next to the systems that write these
|
||||
// components; a component simulated here but missing from this list
|
||||
// is a desync waiting to happen.
|
||||
.rollback_component_with_copy::<FighterECB>()
|
||||
.rollback_component_with_copy::<FighterVelocity>()
|
||||
.rollback_component_with_copy::<FighterTranslation>()
|
||||
.rollback_component_with_copy::<FighterPreviousTranslation>()
|
||||
.rollback_component_with_copy::<FighterAttributes>()
|
||||
.rollback_component_with_copy::<Grounded>()
|
||||
.rollback_component_with_copy::<state::wait::WaitState>()
|
||||
.rollback_component_with_copy::<state::walk::WalkState>()
|
||||
.rollback_component_with_copy::<state::dash::DashState>()
|
||||
.rollback_component_with_copy::<state::run::RunState>()
|
||||
.checksum_component::<state::dash::DashState>(state::dash::hash_dash_state)
|
||||
// Debug views.
|
||||
.add_systems(FixedPostUpdate, debug::debug_draw_ecb)
|
||||
.add_systems(EguiPrimaryContextPass, debug::fighter_debug);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
//! Where a fighter is and how it moves. States write [`FighterVelocity`];
|
||||
//! this module is what turns that into a position.
|
||||
|
||||
use bevy::prelude::*;
|
||||
|
||||
#[derive(Component, Clone, Copy)]
|
||||
pub struct Grounded;
|
||||
|
||||
#[derive(Component, Deref, DerefMut, Default, Debug, Clone, Copy)]
|
||||
pub struct FighterVelocity(pub Vec2);
|
||||
|
||||
#[derive(Component, Deref, DerefMut, Default, Clone, Copy)]
|
||||
pub struct FighterPreviousTranslation(pub Vec2);
|
||||
|
||||
#[derive(Component, Deref, DerefMut, Default, Clone, Copy)]
|
||||
#[require(FighterPreviousTranslation)]
|
||||
pub struct FighterTranslation(pub Vec2);
|
||||
|
||||
pub fn integrate_gravity(_fighters: Query<&mut FighterVelocity>, _time: Res<Time<Fixed>>) {}
|
||||
|
||||
pub fn apply_motion(
|
||||
fighters: Query<(
|
||||
&FighterVelocity,
|
||||
&mut FighterTranslation,
|
||||
&mut FighterPreviousTranslation,
|
||||
)>,
|
||||
_time: Res<Time<Fixed>>,
|
||||
) {
|
||||
for (velocity, mut translation, mut prev_translation) in fighters {
|
||||
prev_translation.0 = translation.0;
|
||||
translation.0 += velocity.0;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fighter_movement() {}
|
||||
@@ -1,48 +0,0 @@
|
||||
use bevy::prelude::*;
|
||||
use bevy_egui::{EguiContexts, EguiPrimaryContextPass, egui};
|
||||
use crate::fighter::state::StateNameDebug;
|
||||
use crate::fighter::{FighterVelocity, states};
|
||||
use crate::fighter::{
|
||||
apply_motion, collide_fighter_with_scene, integrate_gravity, state::state_interrupt_system,
|
||||
};
|
||||
use crate::input::player::FighterInput;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FighterPlugin;
|
||||
|
||||
impl Plugin for FighterPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_systems(FixedPostUpdate, super::debug_draw_ecb)
|
||||
.add_systems(
|
||||
FixedUpdate,
|
||||
(
|
||||
state_interrupt_system::<states::wait::WaitState>,
|
||||
state_interrupt_system::<states::walk::WalkState>,
|
||||
state_interrupt_system::<states::dash::DashState>,
|
||||
state_interrupt_system::<states::run::RunState>,
|
||||
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_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(())
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
use bevy::prelude::*;
|
||||
|
||||
use super::{FighterState, FighterStateContext, FighterStateTransition, ground, run, wait, walk};
|
||||
use crate::fighter::{FighterAttributes, FighterVelocity};
|
||||
use crate::game_settings::GameSettings;
|
||||
use crate::input::{
|
||||
FighterCommands::{SmashMoveLeft, SmashMoveRight},
|
||||
FighterInput,
|
||||
};
|
||||
|
||||
pub struct DashStateCreator {}
|
||||
|
||||
#[derive(Component, Debug, Clone, Copy)]
|
||||
pub struct DashState {
|
||||
frames_in_dash: u32,
|
||||
direction: f32,
|
||||
}
|
||||
|
||||
pub fn hash_dash_state(dash_state: &DashState) -> u64 {
|
||||
let mut hasher = bevy_ggrs::checksum_hasher();
|
||||
dash_state.direction.to_bits().hash(&mut hasher);
|
||||
dash_state.frames_in_dash.hash(&mut hasher);
|
||||
hasher.finish()
|
||||
}
|
||||
|
||||
impl DashState {
|
||||
pub fn new(direction: f32) -> Self {
|
||||
Self {
|
||||
frames_in_dash: 0,
|
||||
direction,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FighterState for DashState {
|
||||
fn check_interrupt(
|
||||
&self,
|
||||
state_context: &FighterStateContext,
|
||||
) -> Option<FighterStateTransition> {
|
||||
if self.frames_in_dash > state_context.fighter_attribs.dash_duration {
|
||||
// Dash finish shenanigans
|
||||
run::check_input(state_context)
|
||||
.or_else(|| walk::check_input(state_context))
|
||||
.or_else(|| wait::check_input(state_context))
|
||||
} else {
|
||||
check_smash_input_with_dir(state_context)
|
||||
.filter(|dir| *dir != self.direction)
|
||||
.and_then(|dir| Some(FighterStateTransition::Dash(dir)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start(state_context: &mut FighterStateContext) {
|
||||
state_context.input.clear_buffer();
|
||||
}
|
||||
|
||||
fn check_smash_input_with_dir(state_context: &FighterStateContext) -> Option<f32> {
|
||||
if state_context.input.has_command(SmashMoveLeft) {
|
||||
return Some(-1.0);
|
||||
} else if state_context.input.has_command(SmashMoveRight) {
|
||||
return Some(1.0);
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_input(state_context: &FighterStateContext) -> Option<FighterStateTransition> {
|
||||
check_smash_input_with_dir(state_context).map(|dir| FighterStateTransition::Dash(dir))
|
||||
}
|
||||
|
||||
pub fn dash_update(
|
||||
dash_query: Query<(
|
||||
&mut DashState,
|
||||
&FighterAttributes,
|
||||
&FighterInput,
|
||||
&mut FighterVelocity,
|
||||
)>,
|
||||
game_settings: Res<GameSettings>,
|
||||
) {
|
||||
for (mut dash, attributes, input, mut velocity) in dash_query {
|
||||
dash.frames_in_dash += 1;
|
||||
|
||||
let last_frame = input.get_last_frame();
|
||||
let (accel, target_vel) = attributes.get_accel_and_target_dashrun(&last_frame);
|
||||
let accel = ground::compute_ground_accel(
|
||||
accel,
|
||||
target_vel,
|
||||
velocity.x,
|
||||
attributes,
|
||||
game_settings.as_ref(),
|
||||
);
|
||||
velocity.x += accel;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn on_insert_dash(
|
||||
add: On<Insert, DashState>,
|
||||
query: Query<
|
||||
(
|
||||
Entity,
|
||||
&mut FighterInput,
|
||||
&FighterAttributes,
|
||||
&mut FighterVelocity,
|
||||
),
|
||||
With<DashState>,
|
||||
>,
|
||||
) {
|
||||
for (entity, mut input, attribs, mut velocity) in query {
|
||||
if entity == add.entity {
|
||||
velocity.x = attribs.dash_initial_velocity * input.get_last_frame().movement.x.signum();
|
||||
input.clear_buffer();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
use bevy::log::info;
|
||||
//! Ground movement math shared by every grounded state. Deliberately free of
|
||||
//! ECS types so it stays cheap to reason about and to unit-test — it has to be
|
||||
//! bit-for-bit deterministic for rollback.
|
||||
|
||||
use crate::{fighter::FighterAttributes, game_settings::GameSettings, input::player::InputFrame};
|
||||
use crate::fighter::FighterAttributes;
|
||||
use crate::game_settings::GameSettings;
|
||||
|
||||
pub fn apply_grounded_friction(friction: f32, ground_vel: f32) -> f32 {
|
||||
if friction.abs() > ground_vel.abs() {
|
||||
@@ -10,11 +13,15 @@ pub fn apply_grounded_friction(friction: f32, ground_vel: f32) -> f32 {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn move_accelerate() {
|
||||
pub fn move_accelerate() {}
|
||||
|
||||
}
|
||||
|
||||
pub fn compute_ground_accel(accel: f32, target_vel: f32, gr_vel: f32, attribs: &FighterAttributes, game_settings: &GameSettings) -> f32 {
|
||||
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 {
|
||||
@@ -46,24 +53,3 @@ pub fn compute_ground_accel(accel: f32, target_vel: f32, gr_vel: f32, attribs: &
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,28 @@
|
||||
use bevy::{prelude::*};
|
||||
//! The fighter state machine: the [`FighterState`] trait every state
|
||||
//! implements, the context they inspect, and the generic system that runs the
|
||||
//! interrupt check. One state per submodule.
|
||||
|
||||
use crate::{fighter::{FighterAttributes, states}, game_settings::GameSettings, input::player::FighterInput};
|
||||
use bevy::prelude::*;
|
||||
|
||||
use crate::fighter::FighterAttributes;
|
||||
use crate::game_settings::GameSettings;
|
||||
use crate::input::FighterInput;
|
||||
|
||||
pub mod dash;
|
||||
pub mod ground;
|
||||
pub mod run;
|
||||
pub mod wait;
|
||||
pub mod walk;
|
||||
|
||||
pub enum FighterStateTransition {
|
||||
Wait,
|
||||
Walk,
|
||||
Dash,
|
||||
Dash(f32),
|
||||
Run,
|
||||
}
|
||||
|
||||
pub trait FighterState : Component + Sized {
|
||||
fn remove(&self,
|
||||
state_context: &FighterStateContext,
|
||||
commands: &mut Commands) {
|
||||
pub trait FighterState: Component + Sized {
|
||||
fn remove(&self, state_context: &FighterStateContext, commands: &mut Commands) {
|
||||
commands.entity(state_context.entity).remove::<Self>();
|
||||
}
|
||||
fn check_interrupt(
|
||||
@@ -24,17 +34,21 @@ pub trait FighterState : Component + Sized {
|
||||
pub struct FighterStateContext<'a> {
|
||||
pub input: &'a mut FighterInput,
|
||||
pub fighter_attribs: &'a FighterAttributes,
|
||||
pub game_settings: &'a Res<'a, GameSettings>,
|
||||
pub game_settings: &'a GameSettings,
|
||||
pub entity: Entity,
|
||||
}
|
||||
|
||||
fn create_state(transition: FighterStateTransition, state_context: FighterStateContext, commands: &mut Commands) {
|
||||
fn create_state(
|
||||
transition: FighterStateTransition,
|
||||
state_context: FighterStateContext,
|
||||
commands: &mut Commands,
|
||||
) {
|
||||
let mut ent_cmd = commands.entity(state_context.entity);
|
||||
match transition {
|
||||
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),
|
||||
FighterStateTransition::Wait => ent_cmd.insert(wait::WaitState),
|
||||
FighterStateTransition::Walk => ent_cmd.insert(walk::WalkState),
|
||||
FighterStateTransition::Dash(dir) => ent_cmd.insert(dash::DashState::new(dir)),
|
||||
FighterStateTransition::Run => ent_cmd.insert(run::RunState),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -42,7 +56,7 @@ fn get_state_debug_name(transition: &FighterStateTransition) -> &'static str {
|
||||
match transition {
|
||||
FighterStateTransition::Wait => "wait",
|
||||
FighterStateTransition::Walk => "walk",
|
||||
FighterStateTransition::Dash => "dash",
|
||||
FighterStateTransition::Dash(_) => "dash",
|
||||
FighterStateTransition::Run => "run",
|
||||
}
|
||||
}
|
||||
@@ -62,7 +76,6 @@ pub fn state_interrupt_system<T: FighterState + Component + std::fmt::Debug>(
|
||||
mut commands: Commands,
|
||||
) {
|
||||
for (ent, state, mut input, attribs) in query {
|
||||
info!("{:?}", state);
|
||||
let state_context = FighterStateContext {
|
||||
input: &mut input,
|
||||
fighter_attribs: &attribs,
|
||||
@@ -71,7 +84,10 @@ pub fn state_interrupt_system<T: FighterState + Component + std::fmt::Debug>(
|
||||
};
|
||||
if let Some(new_state) = state.check_interrupt(&state_context) {
|
||||
commands.entity(ent).remove::<T>();
|
||||
commands.entity(ent).insert(StateNameDebug(get_state_debug_name(&new_state)));
|
||||
info!("State {:?} -> {}", state, get_state_debug_name(&new_state));
|
||||
commands
|
||||
.entity(ent)
|
||||
.insert(StateNameDebug(get_state_debug_name(&new_state)));
|
||||
create_state(new_state, state_context, &mut commands);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
use super::{FighterState, FighterStateContext, FighterStateTransition, ground, wait, walk};
|
||||
use crate::fighter::{FighterAttributes, FighterVelocity};
|
||||
use crate::game_settings::GameSettings;
|
||||
use crate::input::FighterInput;
|
||||
|
||||
#[derive(Component, Debug, Clone, Copy)]
|
||||
pub struct RunState;
|
||||
|
||||
impl FighterState for RunState {
|
||||
fn check_interrupt(
|
||||
&self,
|
||||
state_context: &FighterStateContext,
|
||||
) -> Option<FighterStateTransition> {
|
||||
if let Some(_) = check_input(state_context) {
|
||||
None
|
||||
} else {
|
||||
walk::check_input(state_context).or_else(|| wait::check_input(state_context))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_input(state_context: &FighterStateContext) -> Option<FighterStateTransition> {
|
||||
let 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
|
||||
}
|
||||
|
||||
pub fn run_update(
|
||||
fighters: Query<(&mut FighterVelocity, &FighterAttributes, &FighterInput), With<RunState>>,
|
||||
game_settings: Res<GameSettings>,
|
||||
) {
|
||||
for (mut velocity, attributes, input) in fighters {
|
||||
let last_frame = input.get_last_frame();
|
||||
let (accel, target_vel) = attributes.get_accel_and_target_dashrun(&last_frame);
|
||||
let accel = ground::compute_ground_accel(
|
||||
accel,
|
||||
target_vel,
|
||||
velocity.x,
|
||||
attributes,
|
||||
game_settings.as_ref(),
|
||||
);
|
||||
velocity.x += accel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
use super::{FighterState, FighterStateContext, FighterStateTransition, ground, walk};
|
||||
use crate::fighter::{FighterAttributes, FighterVelocity};
|
||||
use crate::game_settings::GameSettings;
|
||||
|
||||
#[derive(Component, Debug, Clone, Copy)]
|
||||
pub struct WaitState;
|
||||
|
||||
impl FighterState for WaitState {
|
||||
fn check_interrupt(
|
||||
&self,
|
||||
state_context: &FighterStateContext,
|
||||
) -> Option<FighterStateTransition> {
|
||||
walk::check_input(state_context)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_input(state_context: &FighterStateContext) -> Option<FighterStateTransition> {
|
||||
let input_frame = state_context.input.get_last_frame();
|
||||
|
||||
if input_frame.movement.x.abs() < state_context.game_settings.input_common.stick_deadzone {
|
||||
return Some(FighterStateTransition::Wait);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn wait_update(
|
||||
query: Query<(&FighterAttributes, &mut FighterVelocity), With<WaitState>>,
|
||||
game_settings: Res<GameSettings>,
|
||||
) {
|
||||
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 += ground::apply_grounded_friction(friction, velocity.x);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
use super::{FighterState, FighterStateContext, FighterStateTransition, dash, ground, wait};
|
||||
use crate::fighter::{FighterAttributes, FighterVelocity};
|
||||
use crate::game_settings::GameSettings;
|
||||
use crate::input::FighterInput;
|
||||
|
||||
#[derive(Component, Debug, Clone, Copy)]
|
||||
pub struct WalkState;
|
||||
|
||||
impl FighterState for WalkState {
|
||||
fn check_interrupt(
|
||||
&self,
|
||||
state_context: &FighterStateContext,
|
||||
) -> Option<FighterStateTransition> {
|
||||
wait::check_input(state_context).or_else(|| dash::check_input(state_context))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_input(state_context: &FighterStateContext) -> Option<FighterStateTransition> {
|
||||
let input_frame = state_context.input.get_last_frame();
|
||||
|
||||
if input_frame.movement.x.abs() >= state_context.game_settings.input_common.stick_deadzone {
|
||||
return Some(FighterStateTransition::Walk);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn walk_update(
|
||||
fighters: Query<(&mut FighterVelocity, &FighterAttributes, &FighterInput), With<WalkState>>,
|
||||
game_settings: Res<GameSettings>,
|
||||
) {
|
||||
for (mut velocity, attributes, input) in fighters {
|
||||
let last_frame = input.get_last_frame();
|
||||
let (accel, target_vel) = attributes.get_accel_and_target_walk(&last_frame);
|
||||
|
||||
let accel =
|
||||
ground::compute_ground_accel(accel, target_vel, velocity.x, attributes, &game_settings);
|
||||
|
||||
velocity.x += accel;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
pub mod wait;
|
||||
pub mod walk;
|
||||
pub mod dash;
|
||||
pub mod run;
|
||||
pub mod grounded;
|
||||
@@ -1,116 +0,0 @@
|
||||
use crate::{
|
||||
fighter::{
|
||||
FighterAttributes, FighterVelocity,
|
||||
state::{FighterState, FighterStateContext, FighterStateTransition},
|
||||
states::grounded,
|
||||
},
|
||||
game_settings::GameSettings,
|
||||
input::player::FighterInput,
|
||||
};
|
||||
use bevy::prelude::*;
|
||||
|
||||
pub struct DashStateCreator {}
|
||||
|
||||
#[derive(Component, Debug)]
|
||||
pub struct DashState {
|
||||
frames_in_dash: u32,
|
||||
}
|
||||
|
||||
impl Default for DashState {
|
||||
fn default() -> Self {
|
||||
Self { frames_in_dash: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl FighterState for DashState {
|
||||
fn check_interrupt(
|
||||
&self,
|
||||
state_context: &FighterStateContext,
|
||||
) -> Option<FighterStateTransition> {
|
||||
if self.frames_in_dash > state_context.fighter_attribs.dash_duration {
|
||||
// Dash finish shenanigans
|
||||
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)
|
||||
.filter(|dir| *dir != last_frame.movement.x.signum())
|
||||
.and_then(|_| super::dash::check_input(state_context))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start(state_context: &mut FighterStateContext) {
|
||||
state_context.input.clear_buffer();
|
||||
}
|
||||
|
||||
fn check_smash_input_with_dir(state_context: &FighterStateContext) -> Option<f32> {
|
||||
state_context.input.has_smash_x_movement(
|
||||
state_context
|
||||
.game_settings
|
||||
.input_common
|
||||
.smash_input_reset_axis_threshold,
|
||||
state_context
|
||||
.game_settings
|
||||
.input_common
|
||||
.smash_input_axis_threshold,
|
||||
state_context
|
||||
.game_settings
|
||||
.input_common
|
||||
.smash_input_frame_threshold,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn dash_update(
|
||||
dash_query: Query<(
|
||||
&mut DashState,
|
||||
&FighterAttributes,
|
||||
&FighterInput,
|
||||
&mut FighterVelocity,
|
||||
)>,
|
||||
game_settings: Res<GameSettings>,
|
||||
) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_input(state_context: &FighterStateContext) -> Option<FighterStateTransition> {
|
||||
check_smash_input_with_dir(state_context).map(|_| FighterStateTransition::Dash)
|
||||
}
|
||||
|
||||
pub fn on_insert_dash(
|
||||
add: On<Insert, DashState>,
|
||||
query: Query<
|
||||
(
|
||||
Entity,
|
||||
&mut FighterInput,
|
||||
&FighterAttributes,
|
||||
&mut FighterVelocity,
|
||||
),
|
||||
With<DashState>,
|
||||
>,
|
||||
) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
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<FighterStateTransition> {
|
||||
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<crate::fighter::state::FighterStateTransition> {
|
||||
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<RunState>>,
|
||||
game_settings: Res<GameSettings>,
|
||||
) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
use crate::{fighter::{state::{FighterState, FighterStateContext, FighterStateTransition}, states::{grounded, walk}}, game_settings::GameSettings};
|
||||
use crate::fighter::*;
|
||||
|
||||
#[derive(Component, Debug)]
|
||||
pub struct WaitState;
|
||||
|
||||
impl FighterState for WaitState {
|
||||
fn check_interrupt(
|
||||
&self,
|
||||
state_context: &FighterStateContext,
|
||||
) -> Option<FighterStateTransition> {
|
||||
walk::check_input(state_context)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_input(state_context: &FighterStateContext) -> Option<FighterStateTransition> {
|
||||
if let Some(input_frame) = state_context.input.get_last_frame() {
|
||||
if input_frame.movement.x.abs() < state_context.game_settings.input_common.stick_deadzone {
|
||||
return Some(FighterStateTransition::Wait);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn wait_update(query: Query<(&FighterAttributes, &mut FighterVelocity), With<WaitState>>, game_settings: Res<GameSettings>) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
use crate::{
|
||||
fighter::{
|
||||
FighterAttributes, FighterVelocity, state::{FighterState, FighterStateContext, FighterStateTransition}, states::{dash, grounded, wait},
|
||||
}, game_settings::GameSettings, input::player::FighterInput,
|
||||
};
|
||||
use bevy::prelude::*;
|
||||
|
||||
#[derive(Component, Debug)]
|
||||
pub struct WalkState;
|
||||
|
||||
impl FighterState for WalkState {
|
||||
fn check_interrupt(
|
||||
&self,
|
||||
state_context: &FighterStateContext,
|
||||
) -> Option<FighterStateTransition> {
|
||||
wait::check_input(state_context).or_else(|| dash::check_input(state_context))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn walk_update(
|
||||
fighters: Query<(
|
||||
&mut FighterVelocity,
|
||||
&FighterAttributes,
|
||||
&FighterInput,
|
||||
), With<WalkState>>,
|
||||
game_settings: Res<GameSettings>,
|
||||
) {
|
||||
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_walk(last_frame);
|
||||
|
||||
let accel = grounded::compute_ground_accel(accel, target_vel, velocity.x, attributes, &game_settings);
|
||||
|
||||
velocity.x += accel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_input(state_context: &FighterStateContext) -> Option<FighterStateTransition> {
|
||||
if let Some(input_frame) = state_context.input.get_last_frame() {
|
||||
if input_frame.movement.x.abs() >= state_context.game_settings.input_common.stick_deadzone {
|
||||
return Some(FighterStateTransition::Walk);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
Reference in New Issue
Block a user