Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
46c2af8114 | ||
|
|
b56b7b0421 | ||
|
|
84e731fbc8 | ||
|
|
65727ed04f |
@@ -84,6 +84,9 @@ pub enum AnimKind {
|
||||
AirDodge,
|
||||
Fall,
|
||||
Turn,
|
||||
CrouchEnter,
|
||||
CrouchIdle,
|
||||
CrouchExit,
|
||||
|
||||
FlinchGround,
|
||||
FlinchAir,
|
||||
|
||||
@@ -605,7 +605,6 @@ impl Plugin for FighterAttackPlugin {
|
||||
intersect_attacks_with_hurtboxes,
|
||||
)
|
||||
.chain()
|
||||
.after(crate::fighter::baked_animation::update_fighter_bone_matrices)
|
||||
.in_set(GameplaySet::Attacks),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -219,6 +219,7 @@ mod tests {
|
||||
second.translate(FGVec3::lit("0", "3", "0"));
|
||||
let mut matrices = FighterBoneMatrices::default();
|
||||
matrices.set_pose(
|
||||
AnimKind::Wait,
|
||||
&["root".into(), "hand".into()],
|
||||
&[None, Some(0)],
|
||||
&[first, second],
|
||||
@@ -239,6 +240,7 @@ mod tests {
|
||||
hand.translate(FGVec3::lit("0", "3", "0"));
|
||||
let mut matrices = FighterBoneMatrices::default();
|
||||
matrices.set_pose(
|
||||
AnimKind::Wait,
|
||||
&["root".into(), "hand".into()],
|
||||
&[None, Some(0)],
|
||||
&[root, hand],
|
||||
@@ -266,9 +268,9 @@ mod tests {
|
||||
let parents = [None, Some(0)];
|
||||
let pose = [FixedMat4::IDENTITY, FixedMat4::IDENTITY];
|
||||
|
||||
matrices.set_pose(&names, &parents, &pose, FGi32::ONE);
|
||||
matrices.set_pose(AnimKind::Wait, &names, &parents, &pose, FGi32::ONE);
|
||||
let capacities = matrices.buffer_capacities();
|
||||
matrices.set_pose(&names, &parents, &pose, FGi32::ONE);
|
||||
matrices.set_pose(AnimKind::Wait, &names, &parents, &pose, FGi32::ONE);
|
||||
|
||||
assert_eq!(capacities, matrices.buffer_capacities());
|
||||
}
|
||||
@@ -281,6 +283,7 @@ mod tests {
|
||||
hand.translate(FGVec3::lit("0", "2", "0"));
|
||||
let mut matrices = FighterBoneMatrices::default();
|
||||
matrices.set_pose(
|
||||
AnimKind::Wait,
|
||||
&["root".into(), "hand".into()],
|
||||
&[None, Some(0)],
|
||||
&[root, hand],
|
||||
|
||||
@@ -71,6 +71,7 @@ pub struct FighterBoneMatrices {
|
||||
local_matrices: Vec<FixedMat4>,
|
||||
previous_local_matrices: Vec<FixedMat4>,
|
||||
model_matrices: Vec<Option<FixedMat4>>,
|
||||
presentation_kind: Option<AnimKind>,
|
||||
}
|
||||
|
||||
#[derive(Component, Clone, Debug, Default)]
|
||||
@@ -138,6 +139,7 @@ impl FighterBoneMatrices {
|
||||
}
|
||||
pub(super) fn set_pose(
|
||||
&mut self,
|
||||
kind: AnimKind,
|
||||
bone_names: &[String],
|
||||
parents: &[Option<usize>],
|
||||
local: &[FixedMat4],
|
||||
@@ -166,6 +168,7 @@ impl FighterBoneMatrices {
|
||||
self.previous_local_matrices.reserve(local.len());
|
||||
self.model_matrices.reserve(local.len());
|
||||
std::mem::swap(&mut self.matrices, &mut self.previous_frame_matrices);
|
||||
let animation_changed = self.presentation_kind != Some(kind);
|
||||
std::mem::swap(&mut self.local_matrices, &mut self.previous_local_matrices);
|
||||
self.local_matrices.clear();
|
||||
self.local_matrices.extend_from_slice(local);
|
||||
@@ -189,6 +192,14 @@ impl FighterBoneMatrices {
|
||||
model_scale.mul(matrix.expect("baked bone hierarchy should be valid"))
|
||||
}),
|
||||
);
|
||||
// A state transition is a discontinuity, not an animation frame to
|
||||
// interpolate from the previous clip. Keep the presentation pose at
|
||||
// the new clip's first/current frame immediately.
|
||||
if animation_changed {
|
||||
self.previous_local_matrices
|
||||
.clone_from(&self.local_matrices);
|
||||
}
|
||||
self.presentation_kind = Some(kind);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,6 +229,7 @@ pub fn update_fighter_bone_matrices(
|
||||
.get(&fighter.manifest)
|
||||
.expect("fighter manifest should be loaded before the match starts");
|
||||
matrices.set_pose(
|
||||
frame.kind,
|
||||
&baked.bone_names,
|
||||
&baked.parents,
|
||||
pose,
|
||||
|
||||
+15
-1
@@ -27,6 +27,7 @@ use crate::game_settings::GameSettings;
|
||||
use crate::input::FighterInput;
|
||||
use crate::math::int::FGi32;
|
||||
use crate::math::vec::FGVec2;
|
||||
use crate::math::vec3::FGVec3;
|
||||
use crate::player::Player;
|
||||
|
||||
pub fn debug_draw_ecb(
|
||||
@@ -39,6 +40,12 @@ pub fn debug_draw_ecb(
|
||||
mut gizmos: Gizmos,
|
||||
) {
|
||||
for (ecb, trf, ecb_prev, trf_prev) in ecbs {
|
||||
gizmos.cross(
|
||||
FGVec3::new(trf.0.x, trf.0.y, FGi32::ZERO).to_vec3(),
|
||||
2.0,
|
||||
bevy::color::palettes::css::BLUE,
|
||||
);
|
||||
|
||||
let transformed =
|
||||
ecb.to_3d_lineloop(Transform::from_xyz(trf.x.to_num(), trf.y.to_num(), 0.0));
|
||||
gizmos.lineloop(transformed, YELLOW);
|
||||
@@ -153,7 +160,14 @@ pub fn fighter_debug(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn animation_debug(query: Query<(&FighterTranslation, &FighterFacingDirection, &FighterBoneMatrices)>, mut gizmos: Gizmos) {
|
||||
pub fn animation_debug(
|
||||
query: Query<(
|
||||
&FighterTranslation,
|
||||
&FighterFacingDirection,
|
||||
&FighterBoneMatrices,
|
||||
)>,
|
||||
mut gizmos: Gizmos,
|
||||
) {
|
||||
for (global_transform, facing_dir, matrices) in query {
|
||||
let global_transform = global_transform.get_3d_transform(facing_dir);
|
||||
let marker_transform = global_transform.to_mat4();
|
||||
|
||||
+2
-7
@@ -80,13 +80,10 @@ pub fn update_ecb(
|
||||
&FighterTranslation,
|
||||
&FighterFacingDirection,
|
||||
)>,
|
||||
mut gizmos: Gizmos
|
||||
) {
|
||||
for (mut ecb, surface_state, bone_indices, bone_matrices, translation, facing_dir) in query {
|
||||
let trf = translation.get_3d_transform(facing_dir);
|
||||
|
||||
gizmos.cross(trf.get_translation().to_vec3(), 2.0, bevy::color::palettes::css::BLUE);
|
||||
|
||||
let transformed_ecb_bones = bone_indices.indices.iter().filter_map(|idx| {
|
||||
let Some((_, bone_matrix)) = bone_matrices.get_by_idx(*idx) else {
|
||||
return None;
|
||||
@@ -114,10 +111,8 @@ pub fn update_ecb(
|
||||
0
|
||||
};
|
||||
|
||||
dbg!(ecb_lock_frames_remaining);
|
||||
|
||||
ecb.top = top;
|
||||
|
||||
|
||||
if ecb_lock_frames_remaining == 0 {
|
||||
ecb.bottom = bottom;
|
||||
}
|
||||
@@ -134,7 +129,7 @@ pub fn update_ecb(
|
||||
pub fn decrement_ecb_lockout(surface_states: Query<&mut FighterSurfaceState>) {
|
||||
for mut state in surface_states {
|
||||
state.tick_ecb_lock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
+7
-2
@@ -171,10 +171,15 @@ impl Plugin for FighterPlugin {
|
||||
)*/
|
||||
.add_systems(
|
||||
GgrsSchedule,
|
||||
(ecb::update_ecb, state::state_collision_interrupt_system, ecb::decrement_ecb_lockout).chain().in_set(GameplaySet::Collision),
|
||||
(
|
||||
(ecb::update_ecb, ecb::decrement_ecb_lockout, state::state_collision_interrupt_system).chain(),
|
||||
baked_animation::update_fighter_bone_matrices,
|
||||
)
|
||||
.chain()
|
||||
.in_set(GameplaySet::Collision),
|
||||
)
|
||||
.add_systems(
|
||||
Update,
|
||||
PostUpdate,
|
||||
(
|
||||
motion::apply_fighter_translation_to_visuals,
|
||||
animation::apply_animation,
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
use super::{
|
||||
CollisionContext, EnterContext, FighterStateImpl, InterruptContext, UpdateContext, ground,
|
||||
};
|
||||
use crate::fighter::animation::AnimKind;
|
||||
use crate::fighter::state::{FighterState, dash, ground_attack, turn, walk};
|
||||
use crate::fighter::state::ground::{grounded_movement_common_interrupts, grounded_movement_standstill_common_interrupts};
|
||||
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
pub enum CrouchSubState {
|
||||
Entering,
|
||||
Idle,
|
||||
Exiting
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
pub struct CrouchState {
|
||||
sub_state: CrouchSubState
|
||||
}
|
||||
|
||||
pub fn crouch_idle_interrupts(
|
||||
state_context: &InterruptContext,
|
||||
) -> Option<FighterState> {
|
||||
if let Some(state) = ground_attack::check_interrupt(state_context) {
|
||||
Some(state)
|
||||
} else if let Some(state) = dash::check_input(state_context) {
|
||||
Some(state)
|
||||
} else if let Some(state) = turn::check_input(state_context) {
|
||||
Some(state)
|
||||
} else if let Some(state) = walk::check_input(state_context) {
|
||||
Some(state)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn crouch_entering_interrupts(
|
||||
state_context: &InterruptContext,
|
||||
) -> Option<FighterState> {
|
||||
if let Some(state) = ground_attack::check_interrupt(state_context) {
|
||||
Some(state)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl FighterStateImpl for CrouchState {
|
||||
const NAME: &'static str = "Crouch";
|
||||
fn check_interrupt(&self, state_context: &mut InterruptContext) -> Option<FighterState> {
|
||||
if let Some(state) = grounded_movement_common_interrupts(state_context) {
|
||||
return Some(state);
|
||||
}
|
||||
|
||||
match self.sub_state {
|
||||
CrouchSubState::Entering => {
|
||||
// While entering the crouch animation we do not allow movement inputs, but we do allow attacks and jumps
|
||||
if state_context.is_current_animation_finished() {
|
||||
return Some(FighterState::Crouch(
|
||||
CrouchState {
|
||||
sub_state: CrouchSubState::Idle,
|
||||
}
|
||||
));
|
||||
} else {
|
||||
return crouch_entering_interrupts(state_context);
|
||||
}
|
||||
},
|
||||
CrouchSubState::Idle => {
|
||||
if state_context.input.get_last_frame().movement.y > -state_context.game_settings.input_common.run_stick_threshold {
|
||||
return Some(FighterState::Crouch (
|
||||
CrouchState {
|
||||
sub_state: CrouchSubState::Exiting,
|
||||
}
|
||||
));
|
||||
} else {
|
||||
return crouch_idle_interrupts(state_context);
|
||||
}
|
||||
},
|
||||
CrouchSubState::Exiting => {
|
||||
if state_context.is_current_animation_finished() {
|
||||
return grounded_movement_standstill_common_interrupts(state_context);
|
||||
} else {
|
||||
return crouch_idle_interrupts(state_context);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn on_enter(&mut self, state_context: &mut EnterContext) {
|
||||
match self.sub_state {
|
||||
CrouchSubState::Entering => {
|
||||
state_context.play_animation(AnimKind::CrouchEnter, false);
|
||||
},
|
||||
CrouchSubState::Idle => {
|
||||
state_context.play_animation(AnimKind::CrouchIdle, true);
|
||||
},
|
||||
CrouchSubState::Exiting => {
|
||||
state_context.play_animation(AnimKind::CrouchExit, false);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, state_context: &mut UpdateContext) {
|
||||
ground::update_friction_motion(state_context);
|
||||
}
|
||||
|
||||
fn check_collision_interrupt(
|
||||
&mut self,
|
||||
state_context: &mut CollisionContext,
|
||||
) -> Option<FighterState> {
|
||||
ground::collision_interrupt(state_context, false)
|
||||
}
|
||||
|
||||
fn is_visually_grounded<'a>(&'a self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_input(state_context: &InterruptContext) -> Option<FighterState> {
|
||||
let input_frame = state_context.input.get_last_frame();
|
||||
|
||||
if input_frame.movement.y <= -state_context.game_settings.input_common.run_stick_threshold {
|
||||
return Some(FighterState::Crouch(CrouchState {
|
||||
sub_state: CrouchSubState::Entering
|
||||
}));
|
||||
}
|
||||
None
|
||||
}
|
||||
@@ -3,8 +3,7 @@
|
||||
//! bit-for-bit deterministic for rollback.
|
||||
|
||||
use crate::fighter::state::{
|
||||
CollisionContext, FighterState, InterruptContext, air, dash, fall, ground_attack, turn, wait,
|
||||
walk,
|
||||
CollisionContext, FighterState, InterruptContext, air, crouch, dash, fall, ground_attack, turn, wait, walk,
|
||||
};
|
||||
use crate::fighter::{
|
||||
FighterAttributes, FighterPreviousTranslation, FighterTranslation, FighterVelocity,
|
||||
@@ -461,6 +460,8 @@ pub fn grounded_movement_standstill_common_interrupts(
|
||||
Some(state)
|
||||
} else if let Some(state) = turn::check_input(state_context) {
|
||||
Some(state)
|
||||
} else if let Some(state) = crouch::check_input(state_context) {
|
||||
Some(state)
|
||||
} else if let Some(state) = walk::check_input(state_context) {
|
||||
Some(state)
|
||||
} else if let Some(state) = wait::check_input(state_context) {
|
||||
|
||||
@@ -41,6 +41,7 @@ pub mod run;
|
||||
pub mod turn;
|
||||
pub mod wait;
|
||||
pub mod walk;
|
||||
pub mod crouch;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
|
||||
pub enum SurfacePolicy {
|
||||
@@ -211,6 +212,7 @@ impl CollisionContext<'_> {
|
||||
fighter_states! {
|
||||
Wait => wait::WaitState,
|
||||
Walk => walk::WalkState,
|
||||
Crouch => crouch::CrouchState,
|
||||
Dash => dash::DashState,
|
||||
Run => run::RunState,
|
||||
Fall => fall::FallState,
|
||||
|
||||
+3
-3
@@ -19,10 +19,10 @@ pub enum GameplaySet {
|
||||
StateUpdate,
|
||||
/// Integrate velocities into positions.
|
||||
Physics,
|
||||
/// Solve collision boxes
|
||||
Animation,
|
||||
/// Resolve the integrated positions against the stage.
|
||||
Collision,
|
||||
/// Solve collision boxes
|
||||
Animation,
|
||||
/// Resolve attacks
|
||||
Attacks,
|
||||
}
|
||||
@@ -40,8 +40,8 @@ impl Plugin for GameplaySchedulePlugin {
|
||||
GameplaySet::Interrupt,
|
||||
GameplaySet::StateUpdate,
|
||||
GameplaySet::Physics,
|
||||
GameplaySet::Collision,
|
||||
GameplaySet::Animation,
|
||||
GameplaySet::Collision,
|
||||
GameplaySet::Attacks,
|
||||
)
|
||||
.chain(),
|
||||
|
||||
Reference in New Issue
Block a user