Add animation baking system
This commit is contained in:
@@ -7,7 +7,11 @@ pub fn integrate_gravity(velocity: &mut FighterVelocity, attributes: &FighterAtt
|
||||
velocity.y = velocity.y.max(-attributes.terminal_velocity);
|
||||
}
|
||||
|
||||
pub fn apply_air_drift(velocity: &mut FighterVelocity, attribs: &FighterAttributes, input: &FighterInput) {
|
||||
pub fn apply_air_drift(
|
||||
velocity: &mut FighterVelocity,
|
||||
attribs: &FighterAttributes,
|
||||
input: &FighterInput,
|
||||
) {
|
||||
if input.get_last_frame().movement.x.is_zero() {
|
||||
if velocity.x.abs() < attribs.air_friction {
|
||||
velocity.x = FGi32::ZERO;
|
||||
@@ -16,9 +20,12 @@ pub fn apply_air_drift(velocity: &mut FighterVelocity, attribs: &FighterAttribut
|
||||
velocity.x += (-velocity_sign) * attribs.air_friction;
|
||||
}
|
||||
} else {
|
||||
let target_velocity = input.get_last_frame().movement.x.signum() * attribs.max_air_horizontal_velocity;
|
||||
let acceleration = input.get_last_frame().movement.x.signum() * attribs.air_acceleration_base;
|
||||
let mut acceleration = acceleration + input.get_last_frame().movement.x * attribs.air_acceleration_stick;
|
||||
let target_velocity =
|
||||
input.get_last_frame().movement.x.signum() * attribs.max_air_horizontal_velocity;
|
||||
let acceleration =
|
||||
input.get_last_frame().movement.x.signum() * attribs.air_acceleration_base;
|
||||
let mut acceleration =
|
||||
acceleration + input.get_last_frame().movement.x * attribs.air_acceleration_stick;
|
||||
let velocity_x = velocity.x;
|
||||
if !(velocity_x * acceleration < FGi32::ZERO) {
|
||||
if acceleration > FGi32::ZERO {
|
||||
@@ -46,13 +53,11 @@ pub fn apply_air_drift(velocity: &mut FighterVelocity, attribs: &FighterAttribut
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
|
||||
@@ -14,7 +14,9 @@ use crate::{
|
||||
wait::WaitState,
|
||||
walk,
|
||||
},
|
||||
}, input, math::vec::FGVec2,
|
||||
},
|
||||
input,
|
||||
math::vec::FGVec2,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
@@ -26,8 +28,8 @@ pub struct AirDodgeState {
|
||||
impl FighterStateImpl for AirDodgeState {
|
||||
const NAME: &'static str = "AirDodge";
|
||||
|
||||
fn check_interrupt(&self, state_context: &FighterStateContext) -> Option<FighterState> {
|
||||
if self.duration_counter >= state_context.fighter_attribs.air_dodge_duration {
|
||||
fn check_interrupt(&self, state_context: &mut FighterStateContext) -> Option<FighterState> {
|
||||
if self.duration_counter >= state_context.fighter_manifest.attributes.air_dodge_duration {
|
||||
Some(FighterState::Fall(FallState))
|
||||
} else {
|
||||
None
|
||||
@@ -37,7 +39,7 @@ impl FighterStateImpl for AirDodgeState {
|
||||
fn update(&mut self, state_context: &mut FighterStateContext) {
|
||||
self.duration_counter += 1;
|
||||
|
||||
state_context.velocity.0 *= state_context.fighter_attribs.air_dodge_decay;
|
||||
state_context.velocity.0 *= state_context.fighter_manifest.attributes.air_dodge_decay;
|
||||
|
||||
air::apply_air_motion(
|
||||
state_context.velocity,
|
||||
@@ -66,13 +68,10 @@ impl FighterStateImpl for AirDodgeState {
|
||||
}
|
||||
|
||||
fn on_enter(&mut self, state_context: &mut FighterStateContext) {
|
||||
state_context.animation_transitions.play(
|
||||
state_context.animation_player,
|
||||
state_context.animations.clips[&AnimKind::AirDodge],
|
||||
Duration::ZERO,
|
||||
);
|
||||
state_context.play_animation(AnimKind::AirDodge, false);
|
||||
|
||||
state_context.velocity.0 = self.direction * state_context.fighter_attribs.air_dodge_velocity;
|
||||
state_context.velocity.0 =
|
||||
self.direction * state_context.fighter_manifest.attributes.air_dodge_velocity;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+29
-21
@@ -4,11 +4,11 @@ use std::time::Duration;
|
||||
use bevy::prelude::*;
|
||||
|
||||
use super::{FighterStateContext, FighterStateImpl, ground, run, wait, walk};
|
||||
use crate::fighter::FighterFacingDirection;
|
||||
use crate::fighter::animation::AnimKind;
|
||||
use crate::fighter::state::FighterState;
|
||||
use crate::fighter::state::fall::FallState;
|
||||
use crate::fighter::state::ground::{GroundedMotionResult, GroundedStateCommon};
|
||||
use crate::fighter::FighterFacingDirection;
|
||||
use crate::input::FighterCommands::{SmashMoveLeft, SmashMoveRight};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -37,9 +37,9 @@ impl DashState {
|
||||
|
||||
impl FighterStateImpl for DashState {
|
||||
const NAME: &'static str = "Dash";
|
||||
fn check_interrupt(&self, ctx: &FighterStateContext) -> Option<FighterState> {
|
||||
fn check_interrupt(&self, ctx: &mut FighterStateContext) -> Option<FighterState> {
|
||||
let ground = &self.ground_common;
|
||||
let attributes = ctx.fighter_attribs;
|
||||
let attributes = ctx.fighter_manifest.attributes;
|
||||
|
||||
// A reverse dash can interrupt before IASA
|
||||
if let Some(direction) =
|
||||
@@ -70,42 +70,48 @@ impl FighterStateImpl for DashState {
|
||||
|
||||
fn on_enter(&mut self, state_context: &mut FighterStateContext) {
|
||||
*state_context.facing_direction = self.direction;
|
||||
state_context.velocity.x =
|
||||
state_context.fighter_attribs.dash_initial_velocity * self.direction.to_sign();
|
||||
state_context.velocity.x = state_context
|
||||
.fighter_manifest
|
||||
.attributes
|
||||
.dash_initial_velocity
|
||||
* self.direction.to_sign();
|
||||
|
||||
state_context.animation_transitions.play(
|
||||
&mut state_context.animation_player,
|
||||
state_context.animations.clips[&AnimKind::Dash],
|
||||
Duration::ZERO,
|
||||
);
|
||||
state_context.play_animation(AnimKind::Dash, false);
|
||||
|
||||
match state_context.facing_direction {
|
||||
FighterFacingDirection::Left => {
|
||||
state_context.input.clear_command(crate::input::FighterCommands::SmashMoveLeft);
|
||||
},
|
||||
state_context
|
||||
.input
|
||||
.clear_command(crate::input::FighterCommands::SmashMoveLeft);
|
||||
}
|
||||
FighterFacingDirection::Right => {
|
||||
state_context.input.clear_command(crate::input::FighterCommands::SmashMoveRight);
|
||||
},
|
||||
state_context
|
||||
.input
|
||||
.clear_command(crate::input::FighterCommands::SmashMoveRight);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fn update(&mut self, state_context: &mut FighterStateContext) {
|
||||
self.frames_in_dash += 1;
|
||||
|
||||
if self.frames_in_dash < state_context.fighter_attribs.dash_acceleration_duration {
|
||||
|
||||
}
|
||||
if self.frames_in_dash
|
||||
< state_context
|
||||
.fighter_manifest
|
||||
.attributes
|
||||
.dash_acceleration_duration
|
||||
{}
|
||||
|
||||
let last_frame = state_context.input.get_last_frame();
|
||||
let (accel, target_vel) = state_context
|
||||
.fighter_attribs
|
||||
.fighter_manifest
|
||||
.attributes
|
||||
.get_accel_and_target_dashrun(&last_frame, self.direction.to_sign());
|
||||
let accel = ground::compute_ground_accel(
|
||||
accel,
|
||||
target_vel,
|
||||
state_context.velocity.x,
|
||||
state_context.fighter_attribs,
|
||||
&state_context.fighter_manifest.attributes,
|
||||
state_context.game_settings,
|
||||
);
|
||||
state_context.velocity.x += accel;
|
||||
@@ -131,7 +137,9 @@ impl FighterStateImpl for DashState {
|
||||
}
|
||||
}
|
||||
|
||||
fn check_smash_input_with_dir(state_context: &FighterStateContext) -> Option<FighterFacingDirection> {
|
||||
fn check_smash_input_with_dir(
|
||||
state_context: &FighterStateContext,
|
||||
) -> Option<FighterFacingDirection> {
|
||||
if state_context.input.has_command(SmashMoveLeft) {
|
||||
return Some(FighterFacingDirection::Left);
|
||||
} else if state_context.input.has_command(SmashMoveRight) {
|
||||
|
||||
@@ -10,23 +10,22 @@ pub struct FallState;
|
||||
|
||||
impl FighterStateImpl for FallState {
|
||||
const NAME: &'static str = "Fall";
|
||||
fn check_interrupt(&self, state_context: &FighterStateContext) -> Option<FighterState> {
|
||||
fn check_interrupt(&self, state_context: &mut FighterStateContext) -> Option<FighterState> {
|
||||
jump::check_input_double_jump(state_context)
|
||||
}
|
||||
|
||||
fn on_enter(&mut self, state_context: &mut super::FighterStateContext) {
|
||||
state_context.animation_transitions.play(
|
||||
&mut state_context.animation_player,
|
||||
state_context.animations.clips[&AnimKind::Fall],
|
||||
std::time::Duration::ZERO,
|
||||
).repeat();
|
||||
state_context.play_animation(AnimKind::Fall, true);
|
||||
}
|
||||
|
||||
fn update(&mut self, state_context: &mut super::FighterStateContext) {
|
||||
air::integrate_gravity(state_context.velocity, state_context.fighter_attribs);
|
||||
air::integrate_gravity(
|
||||
state_context.velocity,
|
||||
&state_context.fighter_manifest.attributes,
|
||||
);
|
||||
air::apply_air_drift(
|
||||
state_context.velocity,
|
||||
state_context.fighter_attribs,
|
||||
&state_context.fighter_manifest.attributes,
|
||||
state_context.input,
|
||||
);
|
||||
air::apply_air_motion(
|
||||
|
||||
@@ -2,16 +2,14 @@
|
||||
//! 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::state::{FighterState, FighterStateContext};
|
||||
use crate::fighter::state::{FighterState, FighterStateContext, dash, turn, wait, walk};
|
||||
use crate::fighter::{
|
||||
FighterAttributes, FighterPreviousTranslation, FighterTranslation, FighterVelocity,
|
||||
};
|
||||
use crate::game_settings::GameSettings;
|
||||
use crate::math::int::FGi32;
|
||||
use crate::math::vec::FGVec2;
|
||||
use crate::stage::line::{
|
||||
StageLineID, StagePolyLineSegmentType,
|
||||
};
|
||||
use crate::stage::line::{StageLineID, StagePolyLineSegmentType};
|
||||
|
||||
use bevy::prelude::*;
|
||||
use fixed::traits::Fixed;
|
||||
@@ -149,3 +147,20 @@ pub fn grounded_movement_common_interrupts(
|
||||
) -> Option<FighterState> {
|
||||
super::jump::check_input(state_context, ground_common)
|
||||
}
|
||||
|
||||
pub fn grounded_movement_standstill_common_interrupts(
|
||||
state_context: &FighterStateContext,
|
||||
ground_common: &GroundedStateCommon,
|
||||
) -> Option<FighterState> {
|
||||
if let Some(state) = dash::check_input(state_context, &ground_common) {
|
||||
Some(state)
|
||||
} else if let Some(state) = turn::check_input(state_context, &ground_common) {
|
||||
Some(state)
|
||||
} else if let Some(state) = walk::check_input(state_context, &ground_common) {
|
||||
Some(state)
|
||||
} else if let Some(state) = wait::check_input(state_context, &ground_common) {
|
||||
Some(state)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
+67
-51
@@ -47,10 +47,11 @@ impl JumpSquatState {
|
||||
impl FighterStateImpl for JumpSquatState {
|
||||
const NAME: &'static str = "JumpSquat";
|
||||
|
||||
fn check_interrupt(&self, state_context: &FighterStateContext) -> Option<FighterState> {
|
||||
fn check_interrupt(&self, state_context: &mut FighterStateContext) -> Option<FighterState> {
|
||||
// Special handling for wavedash from jump squat
|
||||
if state_context.input.has_command(FighterCommands::Shield) {
|
||||
// If aiming sideways and in the deadzone, do a perfect wavedash
|
||||
// TODO: Improve this so we don't have to go through the airborne state first
|
||||
let mut wavedash_dir = state_context
|
||||
.input
|
||||
.get_last_frame()
|
||||
@@ -71,7 +72,9 @@ impl FighterStateImpl for JumpSquatState {
|
||||
duration_counter: 0,
|
||||
direction: wavedash_dir.normalize_or_zero(),
|
||||
}))
|
||||
} else if self.duration_counter >= state_context.fighter_attribs.jumpsquat_duration {
|
||||
} else if self.duration_counter
|
||||
>= state_context.fighter_manifest.attributes.jumpsquat_duration
|
||||
{
|
||||
info!("Jump {}", state_context.input.get_last_frame().jump);
|
||||
let jump_type = if state_context.input.get_last_frame().jump {
|
||||
JumpType::LongJump
|
||||
@@ -87,16 +90,17 @@ impl FighterStateImpl for JumpSquatState {
|
||||
fn update(&mut self, state_context: &mut FighterStateContext) {
|
||||
self.duration_counter += 1;
|
||||
|
||||
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
|
||||
.fighter_common
|
||||
.ground_friction_over_walk_speed_multiplier
|
||||
} else {
|
||||
state_context.fighter_attribs.ground_friction
|
||||
};
|
||||
let friction = if state_context.velocity.x.abs()
|
||||
> state_context.fighter_manifest.attributes.max_walk_vel
|
||||
{
|
||||
state_context.fighter_manifest.attributes.ground_friction
|
||||
* state_context
|
||||
.game_settings
|
||||
.fighter_common
|
||||
.ground_friction_over_walk_speed_multiplier
|
||||
} else {
|
||||
state_context.fighter_manifest.attributes.ground_friction
|
||||
};
|
||||
|
||||
let ground_velocity = state_context.velocity.x;
|
||||
|
||||
@@ -123,18 +127,14 @@ impl FighterStateImpl for JumpSquatState {
|
||||
}
|
||||
|
||||
fn on_enter(&mut self, state_context: &mut FighterStateContext) {
|
||||
state_context.animation_transitions.play(
|
||||
state_context.animation_player,
|
||||
state_context.animations.clips[&AnimKind::JumpSquat],
|
||||
Duration::ZERO,
|
||||
);
|
||||
state_context.play_animation(AnimKind::JumpSquat, false);
|
||||
state_context.input.clear_command(FighterCommands::Jump);
|
||||
}
|
||||
}
|
||||
|
||||
impl FighterStateImpl for JumpState {
|
||||
const NAME: &'static str = "Jump";
|
||||
fn check_interrupt(&self, state_context: &FighterStateContext) -> Option<FighterState> {
|
||||
fn check_interrupt(&self, state_context: &mut FighterStateContext) -> Option<FighterState> {
|
||||
if state_context.input.has_command(FighterCommands::Jump) {
|
||||
Some(FighterState::Jump(JumpState {
|
||||
jump_type: JumpType::DoubleJump,
|
||||
@@ -145,10 +145,13 @@ impl FighterStateImpl for JumpState {
|
||||
}
|
||||
|
||||
fn update(&mut self, state_context: &mut FighterStateContext) {
|
||||
air::integrate_gravity(state_context.velocity, state_context.fighter_attribs);
|
||||
air::integrate_gravity(
|
||||
state_context.velocity,
|
||||
&state_context.fighter_manifest.attributes,
|
||||
);
|
||||
air::apply_air_drift(
|
||||
state_context.velocity,
|
||||
state_context.fighter_attribs,
|
||||
&state_context.fighter_manifest.attributes,
|
||||
state_context.input,
|
||||
);
|
||||
air::apply_air_motion(
|
||||
@@ -168,51 +171,64 @@ impl FighterStateImpl for JumpState {
|
||||
if movement_stick_x.is_zero()
|
||||
|| movement_stick_x.signum() == state_context.facing_direction.to_sign()
|
||||
{
|
||||
state_context.animation_transitions.play(
|
||||
state_context.animation_player,
|
||||
state_context.animations.clips[&AnimKind::JumpForward],
|
||||
Duration::ZERO,
|
||||
);
|
||||
state_context.play_animation(AnimKind::JumpForward, false);
|
||||
} else {
|
||||
state_context.animation_transitions.play(
|
||||
state_context.animation_player,
|
||||
state_context.animations.clips[&AnimKind::JumpBack],
|
||||
Duration::ZERO,
|
||||
);
|
||||
state_context.play_animation(AnimKind::JumpBack, false);
|
||||
}
|
||||
}
|
||||
JumpType::DoubleJump => {
|
||||
state_context.animation_transitions.play(
|
||||
state_context.animation_player,
|
||||
state_context.animations.clips[&AnimKind::DoubleJump],
|
||||
Duration::ZERO,
|
||||
);
|
||||
state_context.play_animation(AnimKind::DoubleJump, false);
|
||||
}
|
||||
}
|
||||
|
||||
let vertical_vel = match self.jump_type {
|
||||
JumpType::ShortHop => state_context.fighter_attribs.short_hop_vertical_velocity,
|
||||
JumpType::LongJump => state_context.fighter_attribs.full_jump_vertical_velocity,
|
||||
JumpType::ShortHop => {
|
||||
state_context
|
||||
.fighter_manifest
|
||||
.attributes
|
||||
.short_hop_vertical_velocity
|
||||
}
|
||||
JumpType::LongJump => {
|
||||
state_context
|
||||
.fighter_manifest
|
||||
.attributes
|
||||
.full_jump_vertical_velocity
|
||||
}
|
||||
JumpType::DoubleJump => {
|
||||
state_context.fighter_attribs.full_jump_vertical_velocity
|
||||
* state_context.fighter_attribs.air_jump_multiplier
|
||||
state_context
|
||||
.fighter_manifest
|
||||
.attributes
|
||||
.full_jump_vertical_velocity
|
||||
* state_context
|
||||
.fighter_manifest
|
||||
.attributes
|
||||
.air_jump_multiplier
|
||||
}
|
||||
};
|
||||
|
||||
match self.jump_type {
|
||||
JumpType::ShortHop | JumpType::LongJump => {
|
||||
state_context.velocity.x +=
|
||||
state_context.fighter_attribs.jump_horizontal_velocity * movement_stick_x;
|
||||
state_context.velocity.x += state_context
|
||||
.fighter_manifest
|
||||
.attributes
|
||||
.jump_horizontal_velocity
|
||||
* movement_stick_x;
|
||||
}
|
||||
JumpType::DoubleJump => {
|
||||
state_context.velocity.x =
|
||||
state_context.fighter_attribs.air_jump_horizontal_velocity * movement_stick_x;
|
||||
state_context.velocity.x = state_context
|
||||
.fighter_manifest
|
||||
.attributes
|
||||
.air_jump_horizontal_velocity
|
||||
* movement_stick_x;
|
||||
}
|
||||
};
|
||||
|
||||
state_context.velocity.y = vertical_vel;
|
||||
state_context.velocity.x +=
|
||||
state_context.fighter_attribs.jump_horizontal_velocity * movement_stick_x;
|
||||
state_context.velocity.x += state_context
|
||||
.fighter_manifest
|
||||
.attributes
|
||||
.jump_horizontal_velocity
|
||||
* movement_stick_x;
|
||||
}
|
||||
|
||||
fn check_collision_interrupt(
|
||||
@@ -245,13 +261,13 @@ pub fn check_input(
|
||||
.then(|| FighterState::JumpSquat(JumpSquatState::create(ground_common.clone())))
|
||||
}
|
||||
|
||||
pub fn check_input_double_jump(
|
||||
state_context: &FighterStateContext
|
||||
) -> Option<FighterState> {
|
||||
pub fn check_input_double_jump(state_context: &FighterStateContext) -> Option<FighterState> {
|
||||
state_context
|
||||
.input
|
||||
.has_command(FighterCommands::Jump)
|
||||
.then(|| FighterState::Jump(JumpState{
|
||||
jump_type: JumpType::DoubleJump
|
||||
}))
|
||||
.then(|| {
|
||||
FighterState::Jump(JumpState {
|
||||
jump_type: JumpType::DoubleJump,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
+27
-29
@@ -3,15 +3,18 @@ use std::time::Duration;
|
||||
use bevy::prelude::*;
|
||||
|
||||
use crate::fighter::{
|
||||
animation::AnimKind,
|
||||
state::{
|
||||
FighterState, FighterStateContext, FighterStateImpl, dash,
|
||||
fall::FallState,
|
||||
ground::{self, GroundedMotionResult, GroundedStateCommon},
|
||||
wait::WaitState,
|
||||
walk,
|
||||
animation::AnimKind,
|
||||
state::{
|
||||
FighterState, FighterStateContext, FighterStateImpl, dash,
|
||||
fall::FallState,
|
||||
ground::{
|
||||
self, GroundedMotionResult, GroundedStateCommon,
|
||||
grounded_movement_standstill_common_interrupts,
|
||||
},
|
||||
};
|
||||
wait::WaitState,
|
||||
walk,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
pub struct LandingState {
|
||||
@@ -31,12 +34,10 @@ impl LandingState {
|
||||
impl FighterStateImpl for LandingState {
|
||||
const NAME: &'static str = "Landing";
|
||||
|
||||
fn check_interrupt(&self, state_context: &FighterStateContext) -> Option<FighterState> {
|
||||
if self.duration_counter >= state_context.fighter_attribs.landing_duration {
|
||||
Some(FighterState::Wait(WaitState {
|
||||
grounded_common: self.grounded_common.clone(),
|
||||
}))
|
||||
} else if self.duration_counter >= state_context.fighter_attribs.landing_iasa {
|
||||
fn check_interrupt(&self, state_context: &mut FighterStateContext) -> Option<FighterState> {
|
||||
if state_context.is_current_animation_finished() {
|
||||
grounded_movement_standstill_common_interrupts(state_context, &self.grounded_common)
|
||||
} else if self.duration_counter >= state_context.fighter_manifest.attributes.landing_iasa {
|
||||
dash::check_input(state_context, &self.grounded_common).or_else(|| {
|
||||
ground::grounded_movement_common_interrupts(state_context, &self.grounded_common)
|
||||
.or_else(|| walk::check_input(state_context, &self.grounded_common))
|
||||
@@ -49,16 +50,17 @@ impl FighterStateImpl for LandingState {
|
||||
fn update(&mut self, state_context: &mut FighterStateContext) {
|
||||
self.duration_counter += 1;
|
||||
|
||||
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
|
||||
.fighter_common
|
||||
.ground_friction_over_walk_speed_multiplier
|
||||
} else {
|
||||
state_context.fighter_attribs.ground_friction
|
||||
};
|
||||
let friction = if state_context.velocity.x.abs()
|
||||
> state_context.fighter_manifest.attributes.max_walk_vel
|
||||
{
|
||||
state_context.fighter_manifest.attributes.ground_friction
|
||||
* state_context
|
||||
.game_settings
|
||||
.fighter_common
|
||||
.ground_friction_over_walk_speed_multiplier
|
||||
} else {
|
||||
state_context.fighter_manifest.attributes.ground_friction
|
||||
};
|
||||
|
||||
let ground_velocity = state_context.velocity.x;
|
||||
|
||||
@@ -85,10 +87,6 @@ impl FighterStateImpl for LandingState {
|
||||
}
|
||||
|
||||
fn on_enter(&mut self, state_context: &mut FighterStateContext) {
|
||||
state_context.animation_transitions.play(
|
||||
state_context.animation_player,
|
||||
state_context.animations.clips[&AnimKind::Landing],
|
||||
Duration::ZERO,
|
||||
);
|
||||
state_context.play_animation(AnimKind::Landing, false);
|
||||
}
|
||||
}
|
||||
|
||||
+79
-11
@@ -5,26 +5,30 @@
|
||||
use bevy::ecs::query::QueryData;
|
||||
use bevy::prelude::*;
|
||||
|
||||
use crate::fighter::animation::FighterAnimationPlayerLink;
|
||||
use crate::fighter::animation::{AnimKind, FighterAnimationFrame, FighterAnimationPlayerLink};
|
||||
use crate::fighter::baked_animation::{BakedFighterAnimations, FixedMat4};
|
||||
use crate::fighter::ecb::FighterPreviousECB;
|
||||
use crate::fighter::manifest::FighterManifest;
|
||||
use crate::fighter::visual::FighterAnimations;
|
||||
use crate::fighter::{
|
||||
FighterAttributes, FighterECB, FighterFacingDirection, FighterPreviousTranslation, FighterTranslation, FighterVelocity,
|
||||
Fighter, FighterAttributes, FighterECB, FighterFacingDirection, FighterPreviousTranslation,
|
||||
FighterTranslation, FighterVelocity,
|
||||
};
|
||||
use crate::game_settings::GameSettings;
|
||||
use crate::input::FighterInput;
|
||||
use crate::stage::line::StageCollision;
|
||||
|
||||
pub mod air;
|
||||
pub mod air_dodge;
|
||||
pub mod dash;
|
||||
pub mod fall;
|
||||
pub mod ground;
|
||||
pub mod jump;
|
||||
pub mod land;
|
||||
pub mod run;
|
||||
pub mod turn;
|
||||
pub mod wait;
|
||||
pub mod walk;
|
||||
pub mod land;
|
||||
pub mod air_dodge;
|
||||
|
||||
macro_rules! fighter_states {
|
||||
($($variant:ident => $ty:ty),* $(,)?) => {
|
||||
@@ -35,7 +39,7 @@ macro_rules! fighter_states {
|
||||
pub fn name(&self) -> &'static str {
|
||||
match self { $(Self::$variant(_) => <$ty>::NAME),* }
|
||||
}
|
||||
pub fn check_interrupt(&self, ctx: &FighterStateContext) -> Option<FighterState> {
|
||||
pub fn check_interrupt(&self, ctx: &mut FighterStateContext) -> Option<FighterState> {
|
||||
match self { $(Self::$variant(s) => s.check_interrupt(ctx)),* }
|
||||
}
|
||||
|
||||
@@ -59,7 +63,7 @@ macro_rules! fighter_states {
|
||||
|
||||
pub trait FighterStateImpl: Sized {
|
||||
const NAME: &'static str;
|
||||
fn check_interrupt(&self, state_context: &FighterStateContext) -> Option<FighterState>;
|
||||
fn check_interrupt(&self, state_context: &mut FighterStateContext) -> Option<FighterState>;
|
||||
|
||||
fn on_enter(&mut self, _state_context: &mut FighterStateContext) {}
|
||||
fn update(&mut self, _state_context: &mut FighterStateContext);
|
||||
@@ -79,13 +83,49 @@ pub struct FighterStateContext<'a> {
|
||||
pub velocity: &'a mut FighterVelocity,
|
||||
pub ecb: &'a mut FighterECB,
|
||||
pub prev_ecb: &'a mut FighterPreviousECB,
|
||||
pub fighter_attribs: &'a FighterAttributes,
|
||||
pub facing_direction: &'a mut FighterFacingDirection,
|
||||
pub game_settings: &'a GameSettings,
|
||||
pub stage_collision: &'a StageCollision,
|
||||
pub animations: &'a FighterAnimations,
|
||||
pub animation_player: &'a mut AnimationPlayer,
|
||||
pub animation_transitions: &'a mut AnimationTransitions,
|
||||
pub animation_frame: &'a mut FighterAnimationFrame,
|
||||
pub baked_animations: &'a Assets<BakedFighterAnimations>,
|
||||
pub fighter_manifest: &'a FighterManifest,
|
||||
}
|
||||
|
||||
impl FighterStateContext<'_> {
|
||||
pub fn play_animation(&mut self, kind: AnimKind, repeat: bool) {
|
||||
let active = self.animation_transitions.play(
|
||||
self.animation_player,
|
||||
self.animations.clips[&kind],
|
||||
std::time::Duration::ZERO,
|
||||
);
|
||||
if repeat {
|
||||
active.repeat();
|
||||
}
|
||||
self.animation_frame.reset(kind, repeat);
|
||||
}
|
||||
|
||||
pub fn get_current_animation_frame(&self) -> u32 {
|
||||
self.animation_frame.frame
|
||||
}
|
||||
|
||||
pub fn is_current_animation_finished(&self) -> bool {
|
||||
let anims_data = self
|
||||
.baked_animations
|
||||
.get(&self.animations.baked)
|
||||
.expect("Animation should exist");
|
||||
anims_data
|
||||
.frame_count(self.animation_frame.kind)
|
||||
.expect("Current animation should have baked frames")
|
||||
< self.animation_frame.frame
|
||||
}
|
||||
|
||||
pub fn sample_bone(&self, bone: &str) -> Option<FixedMat4> {
|
||||
self.animations
|
||||
.sample_bone(self.baked_animations, *self.animation_frame, bone)
|
||||
}
|
||||
}
|
||||
|
||||
fighter_states! {
|
||||
@@ -97,7 +137,8 @@ fighter_states! {
|
||||
JumpSquat => jump::JumpSquatState,
|
||||
Jump => jump::JumpState,
|
||||
Land => land::LandingState,
|
||||
AirDodge => air_dodge::AirDodgeState
|
||||
AirDodge => air_dodge::AirDodgeState,
|
||||
Turn => turn::TurnState
|
||||
}
|
||||
|
||||
#[derive(QueryData)]
|
||||
@@ -110,7 +151,8 @@ pub struct FighterFrameQuery {
|
||||
pub ecb: &'static mut FighterECB,
|
||||
pub prev_ecb: &'static mut FighterPreviousECB,
|
||||
pub facing_direction: &'static mut FighterFacingDirection,
|
||||
pub attributes: &'static FighterAttributes,
|
||||
pub animation_frame: &'static mut FighterAnimationFrame,
|
||||
pub fighter: &'static Fighter,
|
||||
}
|
||||
|
||||
impl<'w, 's> FighterFrameQueryItem<'w, 's> {
|
||||
@@ -121,6 +163,8 @@ impl<'w, 's> FighterFrameQueryItem<'w, 's> {
|
||||
animations: &'a FighterAnimations,
|
||||
animation_player: &'a mut AnimationPlayer,
|
||||
animation_transitions: &'a mut AnimationTransitions,
|
||||
baked_animations: &'a Assets<BakedFighterAnimations>,
|
||||
manifest: &'a FighterManifest,
|
||||
) -> FighterStateContext<'a> {
|
||||
FighterStateContext {
|
||||
translation: &mut self.translation,
|
||||
@@ -130,12 +174,14 @@ impl<'w, 's> FighterFrameQueryItem<'w, 's> {
|
||||
ecb: &mut self.ecb,
|
||||
prev_ecb: &mut self.prev_ecb,
|
||||
facing_direction: &mut self.facing_direction,
|
||||
fighter_attribs: self.attributes,
|
||||
game_settings,
|
||||
stage_collision,
|
||||
animations,
|
||||
animation_player,
|
||||
animation_transitions,
|
||||
animation_frame: &mut self.animation_frame,
|
||||
baked_animations,
|
||||
fighter_manifest: manifest,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -148,6 +194,7 @@ pub struct StatefulFighterQuery {
|
||||
pub frame: FighterFrameQuery,
|
||||
pub animations: &'static FighterAnimations,
|
||||
pub animation_player_link: &'static FighterAnimationPlayerLink,
|
||||
pub fighter: &'static Fighter,
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
@@ -164,6 +211,8 @@ pub fn state_update_system(
|
||||
mut animation_players: Query<(&mut AnimationPlayer, &mut AnimationTransitions)>,
|
||||
stage_collision: Res<StageCollision>,
|
||||
game_settings: Res<GameSettings>,
|
||||
baked_animations: Res<Assets<BakedFighterAnimations>>,
|
||||
manifests: Res<Assets<FighterManifest>>,
|
||||
) {
|
||||
for fighter in &mut query {
|
||||
let StatefulFighterQueryItem {
|
||||
@@ -178,12 +227,17 @@ pub fn state_update_system(
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let mut update_ctx = frame.context(
|
||||
&game_settings,
|
||||
&stage_collision,
|
||||
animations,
|
||||
&mut animation_player,
|
||||
&mut animation_transitions,
|
||||
&baked_animations,
|
||||
&manifests
|
||||
.get(&(fighter.fighter.manifest))
|
||||
.expect("Fighter manifest should be valid"),
|
||||
);
|
||||
state.update(&mut update_ctx);
|
||||
}
|
||||
@@ -194,6 +248,8 @@ pub fn state_interrupt_system(
|
||||
mut animation_players: Query<(&mut AnimationPlayer, &mut AnimationTransitions)>,
|
||||
stage_collision: Res<StageCollision>,
|
||||
game_settings: Res<GameSettings>,
|
||||
baked_animations: Res<Assets<BakedFighterAnimations>>,
|
||||
fighter_manifests: Res<Assets<FighterManifest>>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
for fighter in &mut query {
|
||||
@@ -203,6 +259,7 @@ pub fn state_interrupt_system(
|
||||
mut frame,
|
||||
animations,
|
||||
animation_player_link,
|
||||
fighter,
|
||||
} = fighter;
|
||||
let Ok((mut animation_player, mut animation_transitions)) =
|
||||
animation_players.get_mut(animation_player_link.player())
|
||||
@@ -215,8 +272,12 @@ pub fn state_interrupt_system(
|
||||
animations,
|
||||
&mut animation_player,
|
||||
&mut animation_transitions,
|
||||
&baked_animations,
|
||||
fighter_manifests
|
||||
.get(&fighter.manifest)
|
||||
.expect("Fighter manifest should be valid"),
|
||||
);
|
||||
if let Some(new_state) = state.check_interrupt(&state_context) {
|
||||
if let Some(new_state) = state.check_interrupt(&mut state_context) {
|
||||
let mut new_state = new_state;
|
||||
let old_state_name = state.name();
|
||||
let new_state_name = new_state.name();
|
||||
@@ -234,6 +295,8 @@ pub fn state_collision_interrupt_system(
|
||||
mut animation_players: Query<(&mut AnimationPlayer, &mut AnimationTransitions)>,
|
||||
stage_collision: Res<StageCollision>,
|
||||
game_settings: Res<GameSettings>,
|
||||
baked_animations: Res<Assets<BakedFighterAnimations>>,
|
||||
fighter_manifests: Res<Assets<FighterManifest>>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
for fighter in &mut query {
|
||||
@@ -243,6 +306,7 @@ pub fn state_collision_interrupt_system(
|
||||
mut frame,
|
||||
animations,
|
||||
animation_player_link,
|
||||
fighter,
|
||||
} = fighter;
|
||||
let Ok((mut animation_player, mut animation_transitions)) =
|
||||
animation_players.get_mut(animation_player_link.player())
|
||||
@@ -255,6 +319,10 @@ pub fn state_collision_interrupt_system(
|
||||
animations,
|
||||
&mut animation_player,
|
||||
&mut animation_transitions,
|
||||
&baked_animations,
|
||||
fighter_manifests
|
||||
.get(&fighter.manifest)
|
||||
.expect("Fighter manifest should be valid"),
|
||||
);
|
||||
if let Some(new_state) = state.check_collision_interrupt(&mut state_context) {
|
||||
let mut new_state = new_state;
|
||||
|
||||
@@ -16,8 +16,10 @@ pub struct RunState {
|
||||
|
||||
impl FighterStateImpl for RunState {
|
||||
const NAME: &'static str = "Run";
|
||||
fn check_interrupt(&self, state_context: &FighterStateContext) -> Option<FighterState> {
|
||||
if let Some(state) = ground::grounded_movement_common_interrupts(state_context, &self.grounded_common) {
|
||||
fn check_interrupt(&self, state_context: &mut FighterStateContext) -> Option<FighterState> {
|
||||
if let Some(state) =
|
||||
ground::grounded_movement_common_interrupts(state_context, &self.grounded_common)
|
||||
{
|
||||
return Some(state);
|
||||
}
|
||||
|
||||
@@ -33,26 +35,20 @@ impl FighterStateImpl for RunState {
|
||||
}
|
||||
|
||||
fn on_enter(&mut self, state_context: &mut super::FighterStateContext) {
|
||||
state_context
|
||||
.animation_transitions
|
||||
.play(
|
||||
&mut state_context.animation_player,
|
||||
state_context.animations.clips[&AnimKind::Run],
|
||||
Duration::ZERO,
|
||||
)
|
||||
.repeat();
|
||||
state_context.play_animation(AnimKind::Run, true);
|
||||
}
|
||||
|
||||
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
|
||||
.fighter_manifest
|
||||
.attributes
|
||||
.get_accel_and_target_dashrun(&last_frame, FGi32::ONE);
|
||||
let accel = ground::compute_ground_accel(
|
||||
accel,
|
||||
target_vel,
|
||||
state_context.velocity.x,
|
||||
state_context.fighter_attribs,
|
||||
&state_context.fighter_manifest.attributes,
|
||||
state_context.game_settings,
|
||||
);
|
||||
state_context.velocity.x += accel;
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
use super::{FighterStateContext, FighterStateImpl};
|
||||
use crate::fighter::animation::AnimKind;
|
||||
use crate::fighter::collision;
|
||||
use crate::fighter::state::fall::FallState;
|
||||
use crate::fighter::state::ground::{self, GroundedMotionResult, GroundedStateCommon};
|
||||
use crate::fighter::state::land::LandingState;
|
||||
use crate::fighter::state::{FighterState, air, dash, jump, wait, walk};
|
||||
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
pub struct TurnState {
|
||||
grounded_common: GroundedStateCommon,
|
||||
}
|
||||
|
||||
impl FighterStateImpl for TurnState {
|
||||
const NAME: &'static str = "Turn";
|
||||
fn check_interrupt(&self, state_context: &mut FighterStateContext) -> Option<FighterState> {
|
||||
// State chain that results in the turn completing
|
||||
if let Some(state) = jump::check_input(state_context, &self.grounded_common) {
|
||||
Some(state)
|
||||
} else if state_context.is_current_animation_finished() {
|
||||
if let Some(state) = dash::check_input(state_context, &self.grounded_common) {
|
||||
Some(state)
|
||||
} else if let Some(state) = walk::check_input(state_context, &self.grounded_common) {
|
||||
Some(state)
|
||||
} else if let Some(state) = wait::check_input(state_context, &self.grounded_common) {
|
||||
Some(state)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn on_enter(&mut self, state_context: &mut super::FighterStateContext) {
|
||||
state_context.play_animation(AnimKind::Turn, false);
|
||||
*state_context.facing_direction = state_context.facing_direction.reverse();
|
||||
}
|
||||
|
||||
fn update(&mut self, state_context: &mut super::FighterStateContext) {
|
||||
let friction = if state_context.velocity.x.abs()
|
||||
> state_context.fighter_manifest.attributes.max_walk_vel
|
||||
{
|
||||
state_context.fighter_manifest.attributes.ground_friction
|
||||
* state_context
|
||||
.game_settings
|
||||
.fighter_common
|
||||
.ground_friction_over_walk_speed_multiplier
|
||||
} else {
|
||||
state_context.fighter_manifest.attributes.ground_friction
|
||||
};
|
||||
|
||||
let ground_velocity = state_context.velocity.x;
|
||||
|
||||
state_context.velocity.x += ground::apply_grounded_friction(friction, ground_velocity);
|
||||
|
||||
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,
|
||||
grounded_common: &GroundedStateCommon,
|
||||
) -> Option<FighterState> {
|
||||
let input_frame = state_context.input.get_last_frame();
|
||||
|
||||
if !input_frame.movement.x.is_zero()
|
||||
&& input_frame.movement.x.signum() != state_context.facing_direction.to_sign()
|
||||
{
|
||||
return Some(FighterState::Turn(TurnState {
|
||||
grounded_common: grounded_common.clone(),
|
||||
}));
|
||||
}
|
||||
None
|
||||
}
|
||||
+31
-24
@@ -8,7 +8,7 @@ use crate::fighter::state::fall::FallState;
|
||||
use crate::fighter::state::ground::{
|
||||
GroundedMotionResult, GroundedStateCommon, grounded_movement_common_interrupts,
|
||||
};
|
||||
use crate::fighter::state::{FighterState, dash};
|
||||
use crate::fighter::state::{FighterState, dash, turn};
|
||||
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
pub struct WaitState {
|
||||
@@ -17,35 +17,42 @@ pub struct WaitState {
|
||||
|
||||
impl FighterStateImpl for WaitState {
|
||||
const NAME: &'static str = "Wait";
|
||||
fn check_interrupt(&self, state_context: &FighterStateContext) -> Option<FighterState> {
|
||||
dash::check_input(state_context, &self.grounded_common).or_else(|| {
|
||||
grounded_movement_common_interrupts(state_context, &self.grounded_common)
|
||||
.or_else(|| walk::check_input(state_context, &self.grounded_common))
|
||||
})
|
||||
fn check_interrupt(&self, state_context: &mut FighterStateContext) -> Option<FighterState> {
|
||||
if let Some(state) = ground::grounded_movement_standstill_common_interrupts(
|
||||
state_context,
|
||||
&self.grounded_common,
|
||||
) {
|
||||
// Prevent transitioning into myself
|
||||
if let FighterState::Wait(_) = state {
|
||||
None
|
||||
} else {
|
||||
Some(state)
|
||||
}
|
||||
} else if let Some(state) =
|
||||
ground::grounded_movement_common_interrupts(state_context, &self.grounded_common)
|
||||
{
|
||||
Some(state)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn on_enter(&mut self, state_context: &mut super::FighterStateContext) {
|
||||
state_context
|
||||
.animation_transitions
|
||||
.play(
|
||||
&mut state_context.animation_player,
|
||||
state_context.animations.clips[&AnimKind::Wait],
|
||||
Duration::ZERO,
|
||||
)
|
||||
.repeat();
|
||||
state_context.play_animation(AnimKind::Wait, true);
|
||||
}
|
||||
|
||||
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
|
||||
.fighter_common
|
||||
.ground_friction_over_walk_speed_multiplier
|
||||
} else {
|
||||
state_context.fighter_attribs.ground_friction
|
||||
};
|
||||
let friction = if state_context.velocity.x.abs()
|
||||
> state_context.fighter_manifest.attributes.max_walk_vel
|
||||
{
|
||||
state_context.fighter_manifest.attributes.ground_friction
|
||||
* state_context
|
||||
.game_settings
|
||||
.fighter_common
|
||||
.ground_friction_over_walk_speed_multiplier
|
||||
} else {
|
||||
state_context.fighter_manifest.attributes.ground_friction
|
||||
};
|
||||
|
||||
let ground_velocity = state_context.velocity.x;
|
||||
|
||||
|
||||
+10
-14
@@ -4,9 +4,9 @@ use bevy::prelude::*;
|
||||
|
||||
use super::{FighterStateContext, FighterStateImpl, dash, ground, wait};
|
||||
use crate::fighter::animation::AnimKind;
|
||||
use crate::fighter::state::FighterState;
|
||||
use crate::fighter::state::fall::FallState;
|
||||
use crate::fighter::state::ground::{GroundedMotionResult, GroundedStateCommon};
|
||||
use crate::fighter::state::FighterState;
|
||||
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
pub struct WalkState {
|
||||
@@ -15,8 +15,10 @@ pub struct WalkState {
|
||||
|
||||
impl FighterStateImpl for WalkState {
|
||||
const NAME: &'static str = "Walk";
|
||||
fn check_interrupt(&self, state_context: &FighterStateContext) -> Option<FighterState> {
|
||||
if let Some(state) = ground::grounded_movement_common_interrupts(state_context, &self.grounded_common) {
|
||||
fn check_interrupt(&self, state_context: &mut FighterStateContext) -> Option<FighterState> {
|
||||
if let Some(state) =
|
||||
ground::grounded_movement_common_interrupts(state_context, &self.grounded_common)
|
||||
{
|
||||
return Some(state);
|
||||
}
|
||||
wait::check_input(state_context, &self.grounded_common)
|
||||
@@ -24,27 +26,21 @@ impl FighterStateImpl for WalkState {
|
||||
}
|
||||
|
||||
fn on_enter(&mut self, state_context: &mut FighterStateContext) {
|
||||
state_context
|
||||
.animation_transitions
|
||||
.play(
|
||||
&mut state_context.animation_player,
|
||||
state_context.animations.clips[&AnimKind::Walk],
|
||||
Duration::ZERO,
|
||||
)
|
||||
.repeat();
|
||||
state_context.play_animation(AnimKind::Walk, true);
|
||||
}
|
||||
|
||||
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
|
||||
.fighter_manifest
|
||||
.attributes
|
||||
.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.fighter_manifest.attributes,
|
||||
&state_context.game_settings,
|
||||
);
|
||||
|
||||
@@ -77,7 +73,7 @@ pub fn check_input(
|
||||
) -> 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 {
|
||||
if !input_frame.movement.x.is_zero() {
|
||||
return Some(FighterState::Walk(WalkState {
|
||||
grounded_common: ground_state_common.clone(),
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user