Initial work on proper character movement
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
use crate::fighter::{*};
|
||||
|
||||
pub fn integrate_gravity(velocity: &mut FighterVelocity, attributes: &FighterAttributes) {
|
||||
velocity.y -= attributes.gravity;
|
||||
velocity.y = velocity.y.max(-attributes.terminal_velocity);
|
||||
}
|
||||
|
||||
pub fn apply_air_motion(
|
||||
velocity: &FighterVelocity,
|
||||
translation: &mut FighterTranslation,
|
||||
prev_translation: &mut FighterPreviousTranslation,
|
||||
) {
|
||||
// Vertical velocity should be 0 on the ground
|
||||
prev_translation.0 = translation.0;
|
||||
translation.0 += velocity.0;
|
||||
}
|
||||
+65
-67
@@ -2,58 +2,100 @@ use std::hash::{Hash, Hasher};
|
||||
|
||||
use bevy::prelude::*;
|
||||
|
||||
use super::{FighterState, FighterStateContext, FighterStateTransition, ground, run, wait, walk};
|
||||
use crate::fighter::{FighterAttributes, FighterVelocity};
|
||||
use super::{FighterStateImpl, FighterStateContext, FighterStateTransition, ground, run, wait, walk};
|
||||
use crate::fighter::state::ground::GroundedStateCommon;
|
||||
use crate::fighter::state::wait::WaitState;
|
||||
use crate::fighter::state::{FighterState};
|
||||
use crate::fighter::{FighterAttributes, FighterVelocity, collision};
|
||||
use crate::game_settings::GameSettings;
|
||||
use crate::input::{
|
||||
FighterCommands::{SmashMoveLeft, SmashMoveRight},
|
||||
FighterInput,
|
||||
};
|
||||
|
||||
pub struct DashStateCreator {}
|
||||
|
||||
#[derive(Component, Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DashState {
|
||||
frames_in_dash: u32,
|
||||
direction: f32,
|
||||
ground_common: GroundedStateCommon
|
||||
}
|
||||
|
||||
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 std::hash::Hash for DashState {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.direction.to_bits().hash(state);
|
||||
self.frames_in_dash.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl DashState {
|
||||
pub fn new(direction: f32) -> Self {
|
||||
pub fn new(direction: f32, ground_common: GroundedStateCommon) -> Self {
|
||||
Self {
|
||||
frames_in_dash: 0,
|
||||
direction,
|
||||
ground_common
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FighterState for DashState {
|
||||
impl FighterStateImpl for DashState {
|
||||
const NAME: &'static str = "Dash";
|
||||
fn check_interrupt(
|
||||
&self,
|
||||
state_context: &FighterStateContext,
|
||||
) -> Option<FighterStateTransition> {
|
||||
) -> Option<FighterState> {
|
||||
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))
|
||||
run::check_input(state_context, &self.ground_common)
|
||||
.or_else(|| walk::check_input(state_context, &self.ground_common))
|
||||
.or_else(|| wait::check_input(state_context, &self.ground_common))
|
||||
} else {
|
||||
check_smash_input_with_dir(state_context)
|
||||
.filter(|dir| *dir != self.direction)
|
||||
.and_then(|dir| Some(FighterStateTransition::Dash(dir)))
|
||||
.and_then(|dir| Some(FighterState::Dash(DashState::new(dir, self.ground_common.clone()))))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn on_enter(&mut self, state_context: &mut FighterStateContext) {
|
||||
state_context.input.clear_buffer();
|
||||
state_context.velocity.x = state_context.fighter_attribs.dash_initial_velocity * self.direction;
|
||||
}
|
||||
|
||||
fn update(&mut self, state_context: &mut FighterStateContext) {
|
||||
self.frames_in_dash += 1;
|
||||
|
||||
pub fn start(state_context: &mut FighterStateContext) {
|
||||
state_context.input.clear_buffer();
|
||||
let last_frame = state_context.input.get_last_frame();
|
||||
let (accel, target_vel) = state_context.fighter_attribs.get_accel_and_target_dashrun(&last_frame);
|
||||
let accel = ground::compute_ground_accel(
|
||||
accel,
|
||||
target_vel,
|
||||
state_context.velocity.x,
|
||||
state_context.fighter_attribs,
|
||||
state_context.game_settings,
|
||||
);
|
||||
state_context.velocity.x += accel;
|
||||
|
||||
ground::apply_grounded_motion(
|
||||
state_context.velocity,
|
||||
state_context.translation,
|
||||
state_context.prev_translation,
|
||||
);
|
||||
}
|
||||
|
||||
fn check_collision_interrupt(
|
||||
&mut self,
|
||||
state_context: &mut FighterStateContext,
|
||||
) -> Option<FighterState> {
|
||||
if let Some(res) = collision::air_collide_with_stage(state_context) {
|
||||
// Adjust translation
|
||||
state_context.translation.0 = res.hit_position - state_context.ecb.get_bottom_point();
|
||||
let grounded_common = GroundedStateCommon {
|
||||
current_line_id: res.line_id
|
||||
};
|
||||
Some(FighterState::Wait(WaitState { grounded_common } ))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_smash_input_with_dir(state_context: &FighterStateContext) -> Option<f32> {
|
||||
@@ -66,51 +108,7 @@ fn check_smash_input_with_dir(state_context: &FighterStateContext) -> Option<f32
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
pub fn check_input(state_context: &FighterStateContext, ground_common: &GroundedStateCommon) -> Option<FighterState> {
|
||||
check_smash_input_with_dir(state_context).map(|dir| FighterState::Dash(DashState::new(dir, ground_common.clone())));
|
||||
None
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
use super::{FighterStateImpl, FighterStateContext, FighterStateTransition, ground, walk};
|
||||
use crate::fighter::state::{FighterState, air};
|
||||
use crate::fighter::state::ground::GroundedStateCommon;
|
||||
use crate::fighter::state::wait::WaitState;
|
||||
use crate::fighter::{FighterAttributes, FighterVelocity, collision, motion};
|
||||
use crate::game_settings::GameSettings;
|
||||
use crate::stage::line::StageCollision;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Hash)]
|
||||
pub struct FallState;
|
||||
|
||||
impl FighterStateImpl for FallState {
|
||||
const NAME: &'static str = "Fall";
|
||||
fn check_interrupt(
|
||||
&self,
|
||||
state_context: &FighterStateContext,
|
||||
) -> Option<FighterState> {
|
||||
None
|
||||
}
|
||||
|
||||
fn on_enter(&mut self, _state_context: &mut super::FighterStateContext) {}
|
||||
|
||||
fn update(&mut self, state_context: &mut super::FighterStateContext) {
|
||||
air::integrate_gravity(state_context.velocity, state_context.fighter_attribs);
|
||||
air::apply_air_motion(state_context.velocity, state_context.translation, state_context.prev_translation);
|
||||
}
|
||||
|
||||
fn check_collision_interrupt(
|
||||
&mut self,
|
||||
state_context: &mut FighterStateContext,
|
||||
) -> Option<FighterState> {
|
||||
if let Some(res) = collision::air_collide_with_stage(state_context) {
|
||||
// Adjust translation
|
||||
state_context.translation.0 = res.hit_position - state_context.ecb.get_bottom_point();
|
||||
let grounded_common = GroundedStateCommon {
|
||||
current_line_id: res.line_id
|
||||
};
|
||||
Some(FighterState::Wait(WaitState { grounded_common } ))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_input(state_context: &FighterStateContext) -> Option<FighterState> {
|
||||
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(FighterState::Fall(FallState));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
@@ -2,8 +2,20 @@
|
||||
//! 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;
|
||||
use crate::fighter::state::FighterStateContext;
|
||||
use crate::fighter::{
|
||||
FighterAttributes, FighterECB, FighterPreviousTranslation, FighterTranslation, FighterVelocity,
|
||||
};
|
||||
use crate::game_settings::GameSettings;
|
||||
use crate::stage::StagePoly;
|
||||
use crate::stage::line::{StageCollision, StageLineID, StagePolyLineSegment, StagePolyLineSegmentType};
|
||||
|
||||
use bevy::prelude::*;
|
||||
|
||||
#[derive(Reflect, Hash, Clone, Debug)]
|
||||
pub struct GroundedStateCommon {
|
||||
pub current_line_id: StageLineID,
|
||||
}
|
||||
|
||||
pub fn apply_grounded_friction(friction: f32, ground_vel: f32) -> f32 {
|
||||
if friction.abs() > ground_vel.abs() {
|
||||
@@ -53,3 +65,79 @@ pub fn compute_ground_accel(
|
||||
accel
|
||||
}
|
||||
}
|
||||
|
||||
pub enum GroundedMotionResult {
|
||||
InGround,
|
||||
InAir,
|
||||
}
|
||||
|
||||
pub fn apply_grounded_motion(
|
||||
velocity: &FighterVelocity,
|
||||
translation: &mut FighterTranslation,
|
||||
prev_translation: &mut FighterPreviousTranslation,
|
||||
) {
|
||||
// Vertical velocity should be 0 on the ground
|
||||
prev_translation.0 = translation.0;
|
||||
translation.0 += Vec2::new(velocity.0.x ,0.0);
|
||||
}
|
||||
|
||||
pub fn collide_with_stage_grounded(
|
||||
state_context: &mut FighterStateContext,
|
||||
ground_common: &mut GroundedStateCommon,
|
||||
can_walk_off: bool
|
||||
) -> GroundedMotionResult {
|
||||
let translation = &mut state_context.translation;
|
||||
let ecb = &state_context.ecb;
|
||||
let stage_collision = &state_context.stage_collision;
|
||||
|
||||
let stage_poly = stage_collision
|
||||
.stage_polys
|
||||
.get(ground_common.current_line_id.polygon);
|
||||
|
||||
if let Some(stage_poly) = stage_poly {
|
||||
let ecb_bottom_pos = translation.0 + ecb.get_bottom_point();
|
||||
let left_segment_idx = ground_common
|
||||
.current_line_id
|
||||
.segment
|
||||
.checked_sub(1)
|
||||
.unwrap_or(0);
|
||||
let right_segment_idx =
|
||||
(ground_common.current_line_id.segment + 1) % stage_poly.segments.len();
|
||||
|
||||
// First, we check the current segment, if that fails, we check the surrounding ones
|
||||
let grounded_ecb_bottom_pos = [
|
||||
ground_common.current_line_id.segment.clone(),
|
||||
left_segment_idx,
|
||||
right_segment_idx,
|
||||
]
|
||||
.iter()
|
||||
.find_map(|segment_idx| {
|
||||
let segment = stage_poly.get_segment(*segment_idx)?;
|
||||
if segment.segment_type != StagePolyLineSegmentType::Floor {
|
||||
return None;
|
||||
}
|
||||
|
||||
let line_dir = segment.segment.direction();
|
||||
let projected_point = segment.segment.point1() + (ecb_bottom_pos - segment.segment.point1()).project_onto(line_dir.as_vec2());
|
||||
let closest_point = segment.segment.closest_point(ecb_bottom_pos);
|
||||
let distance = closest_point.distance(projected_point);
|
||||
|
||||
if distance < 0.01 {
|
||||
return Some((projected_point, *segment_idx, segment.normal));
|
||||
}
|
||||
|
||||
None
|
||||
});
|
||||
|
||||
if let Some((bp, new_segment_idx, normal)) = grounded_ecb_bottom_pos {
|
||||
translation.0 = bp - ecb.get_bottom_point();
|
||||
state_context.velocity.0 = state_context.velocity.0 - state_context.velocity.0.project_onto_normalized(normal);
|
||||
ground_common.current_line_id.segment = new_segment_idx;
|
||||
GroundedMotionResult::InGround
|
||||
} else {
|
||||
GroundedMotionResult::InAir
|
||||
}
|
||||
} else {
|
||||
GroundedMotionResult::InAir
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
use crate::fighter::state::{FighterState, FighterStateImpl};
|
||||
|
||||
pub struct JumpState {
|
||||
pub counter: u32
|
||||
}
|
||||
|
||||
pub struct JumpSquatState {
|
||||
pub counter: u32
|
||||
}
|
||||
|
||||
|
||||
impl FighterStateImpl for JumpSquatState {
|
||||
const NAME: &'static str = "JumpSquat";
|
||||
fn check_interrupt(
|
||||
&self,
|
||||
state_context: &super::FighterStateContext,
|
||||
) -> Option<FighterState> {
|
||||
None
|
||||
}
|
||||
|
||||
fn update(&mut self, _state_context: &mut super::FighterStateContext) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl FighterStateImpl for JumpState {
|
||||
const NAME: &'static str = "Jump";
|
||||
fn check_interrupt(
|
||||
&self,
|
||||
state_context: &super::FighterStateContext,
|
||||
) -> Option<FighterState> {
|
||||
None
|
||||
}
|
||||
|
||||
fn update(&mut self, _state_context: &mut super::FighterStateContext) {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
+150
-44
@@ -2,65 +2,105 @@
|
||||
//! implements, the context they inspect, and the generic system that runs the
|
||||
//! interrupt check. One state per submodule.
|
||||
|
||||
use bevy::ecs::component::Mutable;
|
||||
use bevy::ecs::system::ScheduleSystem;
|
||||
use bevy::prelude::*;
|
||||
use bevy_ggrs::{GgrsSchedule, RollbackApp};
|
||||
|
||||
use crate::fighter::FighterAttributes;
|
||||
use crate::fighter::ecb::FighterPreviousECB;
|
||||
use crate::fighter::{FighterAttributes, FighterECB, FighterPreviousTranslation, FighterTranslation, FighterVelocity};
|
||||
use crate::game_settings::GameSettings;
|
||||
use crate::input::FighterInput;
|
||||
use crate::schedule::GameplaySet;
|
||||
use crate::stage::StagePoly;
|
||||
use crate::stage::line::StageCollision;
|
||||
|
||||
pub mod dash;
|
||||
pub mod ground;
|
||||
pub mod run;
|
||||
pub mod wait;
|
||||
pub mod walk;
|
||||
pub mod fall;
|
||||
pub mod air;
|
||||
pub mod jump;
|
||||
|
||||
pub enum FighterStateTransition {
|
||||
Wait,
|
||||
Walk,
|
||||
Dash(f32),
|
||||
Run,
|
||||
Fall,
|
||||
}
|
||||
|
||||
pub trait FighterState: Component + Sized {
|
||||
fn remove(&self, state_context: &FighterStateContext, commands: &mut Commands) {
|
||||
commands.entity(state_context.entity).remove::<Self>();
|
||||
}
|
||||
fn check_interrupt(
|
||||
&self,
|
||||
state_context: &FighterStateContext,
|
||||
) -> Option<FighterStateTransition>;
|
||||
}
|
||||
macro_rules! fighter_states {
|
||||
($($variant:ident => $ty:ty),* $(,)?) => {
|
||||
#[derive(Component, Clone, Debug, Hash)]
|
||||
pub enum FighterState { $($variant($ty)),* }
|
||||
|
||||
pub struct FighterStateContext<'a> {
|
||||
pub input: &'a mut FighterInput,
|
||||
pub fighter_attribs: &'a FighterAttributes,
|
||||
pub game_settings: &'a GameSettings,
|
||||
pub entity: Entity,
|
||||
}
|
||||
impl FighterState {
|
||||
pub fn name(&self) -> &'static str {
|
||||
match self { $(Self::$variant(_) => <$ty>::NAME),* }
|
||||
}
|
||||
pub fn check_interrupt(&self, ctx: &FighterStateContext) -> Option<FighterState> {
|
||||
match self { $(Self::$variant(s) => s.check_interrupt(ctx)),* }
|
||||
}
|
||||
pub fn update(&mut self, ctx: &mut FighterStateContext) {
|
||||
match self { $(Self::$variant(s) => s.update(ctx)),* }
|
||||
}
|
||||
pub fn on_enter(&mut self, ctx: &mut FighterStateContext) {
|
||||
match self { $(Self::$variant(s) => s.on_enter(ctx)),* }
|
||||
}
|
||||
|
||||
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(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),
|
||||
pub fn check_collision_interrupt(&mut self, ctx: &mut FighterStateContext) -> Option<FighterState> {
|
||||
match self { $(Self::$variant(s) => s.check_collision_interrupt(ctx)),* }
|
||||
}
|
||||
}
|
||||
|
||||
$(impl From<$ty> for FighterState {
|
||||
fn from(s: $ty) -> Self { Self::$variant(s) }
|
||||
})*
|
||||
};
|
||||
}
|
||||
|
||||
fn get_state_debug_name(transition: &FighterStateTransition) -> &'static str {
|
||||
match transition {
|
||||
FighterStateTransition::Wait => "wait",
|
||||
FighterStateTransition::Walk => "walk",
|
||||
FighterStateTransition::Dash(_) => "dash",
|
||||
FighterStateTransition::Run => "run",
|
||||
pub trait FighterStateImpl: Sized {
|
||||
const NAME: &'static str;
|
||||
fn check_interrupt(
|
||||
&self,
|
||||
state_context: &FighterStateContext,
|
||||
) -> Option<FighterState>;
|
||||
|
||||
fn on_enter(&mut self, _state_context: &mut FighterStateContext) {}
|
||||
fn update(&mut self, _state_context: &mut FighterStateContext);
|
||||
|
||||
fn check_collision_interrupt(
|
||||
&mut self,
|
||||
_state_context: &mut FighterStateContext,
|
||||
) -> Option<FighterState> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FighterStateContext<'a> {
|
||||
pub translation: &'a mut FighterTranslation,
|
||||
pub prev_translation: &'a mut FighterPreviousTranslation,
|
||||
pub input: &'a mut FighterInput,
|
||||
pub velocity: &'a mut FighterVelocity,
|
||||
pub ecb: &'a mut FighterECB,
|
||||
pub prev_ecb: &'a mut FighterPreviousECB,
|
||||
pub fighter_attribs: &'a FighterAttributes,
|
||||
pub game_settings: &'a GameSettings,
|
||||
pub entity: Entity,
|
||||
pub stage_collision: &'a StageCollision,
|
||||
}
|
||||
|
||||
fighter_states! {
|
||||
Wait => wait::WaitState,
|
||||
Walk => walk::WalkState,
|
||||
Dash => dash::DashState,
|
||||
Run => run::RunState,
|
||||
Fall => fall::FallState
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct StateNameDebug(pub &'static str);
|
||||
|
||||
@@ -70,25 +110,91 @@ impl std::fmt::Display for StateNameDebug {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn state_interrupt_system<T: FighterState + Component + std::fmt::Debug>(
|
||||
query: Query<(Entity, &T, &mut FighterInput, &FighterAttributes)>,
|
||||
pub fn state_update_system(
|
||||
query: Query<(Entity, &mut FighterState, &mut FighterTranslation, &mut FighterPreviousTranslation, &mut FighterInput, &mut FighterVelocity, &mut FighterECB, &mut FighterPreviousECB, &FighterAttributes)>,
|
||||
stage_collision: Res<StageCollision>,
|
||||
game_settings: Res<GameSettings>,
|
||||
mut commands: Commands,
|
||||
mut gizmos: Gizmos,
|
||||
) {
|
||||
for (ent, state, mut input, attribs) in query {
|
||||
let state_context = FighterStateContext {
|
||||
for (ent, mut state, mut translation, mut prev_translation, mut input, mut velocity, mut ecb, mut prev_ecb, attribs) in query {
|
||||
let mut update_ctx = FighterStateContext {
|
||||
translation: &mut translation,
|
||||
prev_translation: &mut prev_translation,
|
||||
input: &mut input,
|
||||
velocity: &mut velocity,
|
||||
ecb: &mut ecb,
|
||||
prev_ecb: &mut prev_ecb,
|
||||
fighter_attribs: &attribs,
|
||||
game_settings: &game_settings,
|
||||
entity: ent,
|
||||
stage_collision: &stage_collision,
|
||||
};
|
||||
state.update(&mut update_ctx);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn state_interrupt_system (
|
||||
query: Query<(Entity, &FighterState, &mut FighterTranslation, &mut FighterPreviousTranslation, &mut FighterInput, &mut FighterVelocity, &mut FighterECB, &mut FighterPreviousECB, &FighterAttributes)>,
|
||||
stage_collision: Res<StageCollision>,
|
||||
game_settings: Res<GameSettings>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
for (ent, state, mut translation, mut prev_translation, mut input, mut velocity, mut ecb, mut prev_ecb, attribs) in query {
|
||||
let mut state_context = FighterStateContext {
|
||||
translation: &mut translation,
|
||||
prev_translation: &mut prev_translation,
|
||||
input: &mut input,
|
||||
velocity: &mut velocity,
|
||||
ecb: &mut ecb,
|
||||
prev_ecb: &mut prev_ecb,
|
||||
fighter_attribs: &attribs,
|
||||
game_settings: &game_settings,
|
||||
entity: ent,
|
||||
stage_collision: &stage_collision
|
||||
};
|
||||
if let Some(new_state) = state.check_interrupt(&state_context) {
|
||||
commands.entity(ent).remove::<T>();
|
||||
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);
|
||||
let mut new_state = new_state;
|
||||
let old_state_name = state.name();
|
||||
let new_state_name = new_state.name();
|
||||
new_state.on_enter(&mut state_context);
|
||||
commands.entity(ent).insert((
|
||||
new_state,
|
||||
StateNameDebug(new_state_name)
|
||||
));
|
||||
info!("State {} -> {}", old_state_name, new_state_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn state_collision_interrupt_system(
|
||||
query: Query<(Entity, &mut FighterState, &mut FighterTranslation, &mut FighterPreviousTranslation, &mut FighterInput, &mut FighterVelocity, &mut FighterECB, &mut FighterPreviousECB, &FighterAttributes)>,
|
||||
stage_collision: Res<StageCollision>,
|
||||
game_settings: Res<GameSettings>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
for (ent, mut state, mut translation, mut prev_translation, mut input, mut velocity, mut ecb, mut prev_ecb, attribs) in query {
|
||||
let mut state_context = FighterStateContext {
|
||||
translation: &mut translation,
|
||||
prev_translation: &mut prev_translation,
|
||||
input: &mut input,
|
||||
velocity: &mut velocity,
|
||||
ecb: &mut ecb,
|
||||
prev_ecb: &mut prev_ecb,
|
||||
fighter_attribs: &attribs,
|
||||
game_settings: &game_settings,
|
||||
entity: ent,
|
||||
stage_collision: &stage_collision
|
||||
};
|
||||
if let Some(new_state) = state.check_collision_interrupt(&mut state_context) {
|
||||
let mut new_state = new_state;
|
||||
let old_state_name = state.name();
|
||||
let new_state_name = new_state.name();
|
||||
new_state.on_enter(&mut state_context);
|
||||
commands.entity(ent).insert((
|
||||
new_state,
|
||||
StateNameDebug(new_state_name)
|
||||
));
|
||||
info!("State {} -> {}", old_state_name, new_state_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
+53
-28
@@ -1,49 +1,74 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
use super::{FighterState, FighterStateContext, FighterStateTransition, ground, wait, walk};
|
||||
use super::{FighterStateImpl, FighterStateContext, FighterStateTransition, ground, wait, walk};
|
||||
use crate::fighter::state::FighterState;
|
||||
use crate::fighter::state::fall::FallState;
|
||||
use crate::fighter::state::ground::{GroundedMotionResult, GroundedStateCommon};
|
||||
use crate::fighter::{FighterAttributes, FighterVelocity};
|
||||
use crate::game_settings::GameSettings;
|
||||
use crate::input::FighterInput;
|
||||
|
||||
#[derive(Component, Debug, Clone, Copy)]
|
||||
pub struct RunState;
|
||||
#[derive(Component, Debug, Clone, Hash)]
|
||||
pub struct RunState {
|
||||
pub grounded_common: GroundedStateCommon
|
||||
}
|
||||
|
||||
impl FighterState for RunState {
|
||||
impl FighterStateImpl for RunState {
|
||||
const NAME: &'static str = "Run";
|
||||
fn check_interrupt(
|
||||
&self,
|
||||
state_context: &FighterStateContext,
|
||||
) -> Option<FighterStateTransition> {
|
||||
if let Some(_) = check_input(state_context) {
|
||||
) -> Option<FighterState> {
|
||||
let input_frame = state_context.input.get_last_frame();
|
||||
if input_frame.movement.x.abs() >= state_context.game_settings.input_common.run_stick_threshold {
|
||||
None
|
||||
} else {
|
||||
walk::check_input(state_context).or_else(|| wait::check_input(state_context))
|
||||
walk::check_input(state_context, &self.grounded_common).or_else(|| wait::check_input(state_context, &self.grounded_common))
|
||||
}
|
||||
}
|
||||
|
||||
fn on_enter(&mut self, _state_context: &mut super::FighterStateContext) {}
|
||||
|
||||
fn update(&mut self, state_context: &mut super::FighterStateContext) {
|
||||
let last_frame = state_context.input.get_last_frame();
|
||||
let (accel, target_vel) = state_context.fighter_attribs.get_accel_and_target_dashrun(&last_frame);
|
||||
let accel = ground::compute_ground_accel(
|
||||
accel,
|
||||
target_vel,
|
||||
state_context.velocity.x,
|
||||
state_context.fighter_attribs,
|
||||
state_context.game_settings,
|
||||
);
|
||||
state_context.velocity.x += accel;
|
||||
|
||||
ground::apply_grounded_motion(
|
||||
state_context.velocity,
|
||||
state_context.translation,
|
||||
state_context.prev_translation,
|
||||
);
|
||||
}
|
||||
|
||||
fn check_collision_interrupt(
|
||||
&mut self,
|
||||
state_context: &mut FighterStateContext,
|
||||
) -> Option<FighterState> {
|
||||
if let GroundedMotionResult::InAir = ground::collide_with_stage_grounded(
|
||||
state_context,
|
||||
&mut self.grounded_common,
|
||||
false
|
||||
) {
|
||||
Some(FighterState::Fall(FallState))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_input(state_context: &FighterStateContext) -> Option<FighterStateTransition> {
|
||||
pub fn check_input(state_context: &FighterStateContext, ground_state_common: &GroundedStateCommon) -> Option<FighterState> {
|
||||
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);
|
||||
return Some(FighterState::Run(RunState { grounded_common: ground_state_common.clone() }));
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+50
-24
@@ -1,43 +1,69 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
use super::{FighterState, FighterStateContext, FighterStateTransition, ground, walk};
|
||||
use super::{FighterStateImpl, FighterStateContext, FighterStateTransition, ground, walk};
|
||||
use crate::fighter::state::FighterState;
|
||||
use crate::fighter::state::fall::FallState;
|
||||
use crate::fighter::state::ground::{GroundedMotionResult, GroundedStateCommon};
|
||||
use crate::fighter::{FighterAttributes, FighterVelocity};
|
||||
use crate::game_settings::GameSettings;
|
||||
use crate::stage::line::StageLineID;
|
||||
|
||||
#[derive(Component, Debug, Clone, Copy)]
|
||||
pub struct WaitState;
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
pub struct WaitState {
|
||||
pub grounded_common: GroundedStateCommon
|
||||
}
|
||||
|
||||
impl FighterState for WaitState {
|
||||
impl FighterStateImpl for WaitState {
|
||||
const NAME: &'static str = "Wait";
|
||||
fn check_interrupt(
|
||||
&self,
|
||||
state_context: &FighterStateContext,
|
||||
) -> Option<FighterStateTransition> {
|
||||
walk::check_input(state_context)
|
||||
) -> Option<FighterState> {
|
||||
walk::check_input(state_context, &self.grounded_common)
|
||||
}
|
||||
|
||||
fn on_enter(&mut self, _state_context: &mut super::FighterStateContext) {}
|
||||
|
||||
fn update(&mut self, state_context: &mut super::FighterStateContext) {
|
||||
let friction = if state_context.velocity.x.abs() > state_context.fighter_attribs.max_walk_vel {
|
||||
state_context.fighter_attribs.ground_friction
|
||||
* state_context.game_settings
|
||||
.figher_common
|
||||
.ground_friction_over_walk_speed_multiplier
|
||||
} else {
|
||||
state_context.fighter_attribs.ground_friction
|
||||
};
|
||||
state_context.velocity.x += ground::apply_grounded_friction(friction, state_context.velocity.x);
|
||||
|
||||
ground::apply_grounded_motion(
|
||||
state_context.velocity,
|
||||
state_context.translation,
|
||||
state_context.prev_translation,
|
||||
);
|
||||
}
|
||||
|
||||
fn check_collision_interrupt(
|
||||
&mut self,
|
||||
state_context: &mut FighterStateContext,
|
||||
) -> Option<FighterState> {
|
||||
if let GroundedMotionResult::InAir = ground::collide_with_stage_grounded(
|
||||
state_context,
|
||||
&mut self.grounded_common,
|
||||
false
|
||||
) {
|
||||
Some(FighterState::Fall(FallState))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_input(state_context: &FighterStateContext) -> Option<FighterStateTransition> {
|
||||
pub fn check_input(state_context: &FighterStateContext, ground_state_common: &GroundedStateCommon) -> Option<FighterState> {
|
||||
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);
|
||||
return Some(FighterState::Wait(WaitState { grounded_common: ground_state_common.clone() }));
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
+47
-23
@@ -1,43 +1,67 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
use super::{FighterState, FighterStateContext, FighterStateTransition, dash, ground, wait};
|
||||
use super::{FighterStateImpl, FighterStateContext, FighterStateTransition, dash, ground, wait};
|
||||
use crate::fighter::state::fall::FallState;
|
||||
use crate::fighter::state::ground::{GroundedMotionResult, GroundedStateCommon};
|
||||
use crate::fighter::state::{self, FighterState};
|
||||
use crate::fighter::{FighterAttributes, FighterVelocity};
|
||||
use crate::game_settings::GameSettings;
|
||||
use crate::input::FighterInput;
|
||||
|
||||
#[derive(Component, Debug, Clone, Copy)]
|
||||
pub struct WalkState;
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
pub struct WalkState {
|
||||
pub grounded_common: GroundedStateCommon
|
||||
}
|
||||
|
||||
impl FighterState for WalkState {
|
||||
impl FighterStateImpl for WalkState {
|
||||
const NAME: &'static str = "Walk";
|
||||
fn check_interrupt(
|
||||
&self,
|
||||
state_context: &FighterStateContext,
|
||||
) -> Option<FighterStateTransition> {
|
||||
wait::check_input(state_context).or_else(|| dash::check_input(state_context))
|
||||
) -> Option<FighterState> {
|
||||
wait::check_input(state_context, &self.grounded_common).or_else(|| dash::check_input(state_context, &self.grounded_common))
|
||||
}
|
||||
|
||||
fn on_enter(&mut self, _state_context: &mut FighterStateContext) {}
|
||||
|
||||
fn update(&mut self, state_context: &mut FighterStateContext) {
|
||||
let last_frame = state_context.input.get_last_frame();
|
||||
let (accel, target_vel) = state_context.fighter_attribs.get_accel_and_target_walk(&last_frame);
|
||||
|
||||
let accel =
|
||||
ground::compute_ground_accel(accel, target_vel, state_context.velocity.x, state_context.fighter_attribs, &state_context.game_settings);
|
||||
|
||||
state_context.velocity.x += accel;
|
||||
|
||||
ground::apply_grounded_motion(
|
||||
state_context.velocity,
|
||||
state_context.translation,
|
||||
state_context.prev_translation,
|
||||
);
|
||||
}
|
||||
|
||||
fn check_collision_interrupt(
|
||||
&mut self,
|
||||
state_context: &mut FighterStateContext,
|
||||
) -> Option<FighterState> {
|
||||
if let GroundedMotionResult::InAir = ground::collide_with_stage_grounded(
|
||||
state_context,
|
||||
&mut self.grounded_common,
|
||||
false
|
||||
) {
|
||||
Some(FighterState::Fall(FallState))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_input(state_context: &FighterStateContext) -> Option<FighterStateTransition> {
|
||||
pub fn check_input(state_context: &FighterStateContext, ground_state_common: &GroundedStateCommon) -> Option<FighterState> {
|
||||
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);
|
||||
return Some(FighterState::Walk(WalkState { grounded_common: ground_state_common.clone() }));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user