Add physics interpolation and fix lingering ground attack hitboxes
This commit is contained in:
@@ -4,7 +4,7 @@ use bevy::prelude::*;
|
||||
|
||||
use crate::{
|
||||
fighter::{
|
||||
FighterECB, FighterFacingDirection, FighterTranslation, FighterVisual,
|
||||
FighterFacingDirection, FighterVisual,
|
||||
visual::FighterAnimations,
|
||||
},
|
||||
player::Player,
|
||||
@@ -54,15 +54,6 @@ pub fn advance_fighter_animation_frames(mut fighters: Query<&mut FighterAnimatio
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_fighter_translation_to_visuals(
|
||||
query: Query<(&FighterTranslation, &FighterECB, &mut Transform)>,
|
||||
) {
|
||||
for (translation, ecb, mut transform) in query {
|
||||
let translation = translation.0 + ecb.get_bottom_point();
|
||||
transform.translation = Vec3::new(translation.x.to_num(), translation.y.to_num(), 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setup_fighter_animation_player(
|
||||
mut commands: Commands,
|
||||
animated: Query<Entity, Added<AnimationPlayer>>,
|
||||
@@ -153,4 +144,5 @@ pub enum AnimKind {
|
||||
// Attacks
|
||||
|
||||
AttackJab1,
|
||||
AttackUpTilt,
|
||||
}
|
||||
|
||||
@@ -64,7 +64,8 @@ impl AttackAngle {
|
||||
#[derive(Serialize, Deserialize, Reflect, PartialEq, Eq, Hash, Clone, Debug)]
|
||||
pub enum AttackKind {
|
||||
NAir,
|
||||
Jab
|
||||
Jab,
|
||||
UpTilt,
|
||||
}
|
||||
|
||||
impl AttackKind {
|
||||
@@ -72,6 +73,7 @@ impl AttackKind {
|
||||
match self {
|
||||
AttackKind::NAir => AnimKind::AttackJab1,
|
||||
AttackKind::Jab => AnimKind::AttackJab1,
|
||||
AttackKind::UpTilt => AnimKind::AttackUpTilt,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+15
-8
@@ -125,13 +125,10 @@ impl Plugin for FighterPlugin {
|
||||
state::state_collision_interrupt_system.in_set(GameplaySet::Collision),
|
||||
)
|
||||
.add_systems(
|
||||
GgrsSchedule,
|
||||
(
|
||||
animation::apply_fighter_translation_to_visuals,
|
||||
animation::apply_animation,
|
||||
)
|
||||
Update,
|
||||
(motion::apply_fighter_translation_to_visuals, animation::apply_animation)
|
||||
.chain()
|
||||
.in_set(GameplaySet::Animation),
|
||||
.run_if(in_state(crate::AppState::InMatch)),
|
||||
)
|
||||
// Rollback registration lives next to the systems that write these
|
||||
// components; a component simulated here but missing from this list
|
||||
@@ -177,6 +174,13 @@ pub struct FighterHitboxes {
|
||||
pub active_hitboxes: Vec<usize>
|
||||
}
|
||||
|
||||
impl FighterHitboxes {
|
||||
pub fn clear(&mut self) {
|
||||
self.attack_script = None;
|
||||
self.active_hitboxes.clear();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn spawn_fighter(
|
||||
commands: &mut Commands,
|
||||
player_handle: usize,
|
||||
@@ -203,8 +207,11 @@ pub fn spawn_fighter(
|
||||
attack_script: None,
|
||||
active_hitboxes: vec![]
|
||||
}),
|
||||
FighterVelocity::default(),
|
||||
FighterTranslation(spawn_position),
|
||||
(
|
||||
FighterVelocity::default(),
|
||||
FighterTranslation(spawn_position),
|
||||
FighterPreviousTranslation(spawn_position),
|
||||
),
|
||||
visual_root,
|
||||
animations,
|
||||
attack_scripts,
|
||||
|
||||
+78
-1
@@ -2,8 +2,13 @@
|
||||
//! this module is what turns that into a position.
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_ggrs::GgrsFrameTiming;
|
||||
|
||||
use crate::{fighter::{FighterFacingDirection, baked_animation::FixedMat4}, math::{int::FGi32, vec::FGVec2, vec3::FGVec3}};
|
||||
use crate::debug_tools::PresentationMotionMode;
|
||||
use crate::{
|
||||
fighter::{FighterECB, FighterFacingDirection, baked_animation::FixedMat4},
|
||||
math::{int::FGi32, vec::FGVec2, vec3::FGVec3},
|
||||
};
|
||||
|
||||
#[derive(Component, Clone, Copy)]
|
||||
pub struct Grounded;
|
||||
@@ -29,6 +34,49 @@ impl FighterTranslation {
|
||||
}
|
||||
}
|
||||
|
||||
/// Samples deterministic fighter positions for presentation without changing
|
||||
/// gameplay state or rollback timing.
|
||||
pub fn sample_presentation_translation(
|
||||
previous: FighterPreviousTranslation,
|
||||
current: FighterTranslation,
|
||||
overstep_fraction: f32,
|
||||
mode: PresentationMotionMode,
|
||||
) -> Vec2 {
|
||||
let previous = Vec2::new(previous.x.to_num(), previous.y.to_num());
|
||||
let current = Vec2::new(current.x.to_num(), current.y.to_num());
|
||||
match mode {
|
||||
PresentationMotionMode::Interpolation => previous.lerp(current, overstep_fraction),
|
||||
PresentationMotionMode::Extrapolation => current + (current - previous) * overstep_fraction,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_fighter_translation_to_visuals(
|
||||
query: Query<(
|
||||
&FighterTranslation,
|
||||
&FighterPreviousTranslation,
|
||||
&FighterECB,
|
||||
&mut Transform,
|
||||
)>,
|
||||
timing: Res<GgrsFrameTiming>,
|
||||
debug_settings: Res<crate::debug_tools::DebugSettings>,
|
||||
) {
|
||||
let overstep_fraction = timing.overstep_fraction();
|
||||
for (translation, previous_translation, ecb, mut transform) in query {
|
||||
let translation = sample_presentation_translation(
|
||||
*previous_translation,
|
||||
*translation,
|
||||
overstep_fraction,
|
||||
debug_settings.motion_sampling,
|
||||
);
|
||||
let bottom = ecb.get_bottom_point();
|
||||
transform.translation = Vec3::new(
|
||||
translation.x + bottom.x.to_num::<f32>(),
|
||||
translation.y + bottom.y.to_num::<f32>(),
|
||||
0.0,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_air_motion(
|
||||
fighters: Query<(
|
||||
&FighterVelocity,
|
||||
@@ -51,3 +99,32 @@ pub fn copy_fighter_transform_to_visuals(fighters: Query<(&FighterTranslation, &
|
||||
}
|
||||
|
||||
pub fn fighter_movement() {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn presentation_translation_interpolates_between_fixed_samples() {
|
||||
let position = sample_presentation_translation(
|
||||
FighterPreviousTranslation(FGVec2::new(FGi32::lit("2"), FGi32::lit("4"))),
|
||||
FighterTranslation(FGVec2::new(FGi32::lit("6"), FGi32::lit("12"))),
|
||||
0.25,
|
||||
PresentationMotionMode::Interpolation,
|
||||
);
|
||||
|
||||
assert_eq!(position, Vec2::new(3.0, 6.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn presentation_translation_extrapolates_from_current_sample() {
|
||||
let position = sample_presentation_translation(
|
||||
FighterPreviousTranslation(FGVec2::new(FGi32::lit("2"), FGi32::lit("4"))),
|
||||
FighterTranslation(FGVec2::new(FGi32::lit("6"), FGi32::lit("12"))),
|
||||
0.25,
|
||||
PresentationMotionMode::Extrapolation,
|
||||
);
|
||||
|
||||
assert_eq!(position, Vec2::new(7.0, 14.0));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,17 @@ impl FighterStateImpl for GroundAttackState {
|
||||
state_context.hitboxes.attack_script = Some(state_context.attack_script_assets.scripts[&self.attack_kind].clone());
|
||||
state_context.hitboxes.active_hitboxes.clear();
|
||||
}
|
||||
|
||||
fn on_exit(&mut self, state_context: &mut super::FighterStateContext) {
|
||||
state_context.hitboxes.clear();
|
||||
}
|
||||
|
||||
fn check_collision_interrupt(
|
||||
&mut self,
|
||||
_state_context: &mut super::FighterStateContext,
|
||||
) -> Option<FighterState> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_interrupt(state_context: &super::FighterStateContext, grounded_common: &GroundedStateCommon) -> Option<FighterState> {
|
||||
|
||||
@@ -52,6 +52,10 @@ macro_rules! fighter_states {
|
||||
match self { $(Self::$variant(s) => s.on_enter(ctx)),* }
|
||||
}
|
||||
|
||||
pub fn on_exit(&mut self, ctx: &mut FighterStateContext) {
|
||||
match self { $(Self::$variant(s) => s.on_exit(ctx)),* }
|
||||
}
|
||||
|
||||
pub fn check_collision_interrupt(&mut self, ctx: &mut FighterStateContext) -> Option<FighterState> {
|
||||
match self { $(Self::$variant(s) => s.check_collision_interrupt(ctx)),* }
|
||||
}
|
||||
@@ -68,6 +72,7 @@ pub trait FighterStateImpl: Sized {
|
||||
fn check_interrupt(&self, state_context: &mut FighterStateContext) -> Option<FighterState>;
|
||||
|
||||
fn on_enter(&mut self, _state_context: &mut FighterStateContext) {}
|
||||
fn on_exit(&mut self, _state_context: &mut FighterStateContext) {}
|
||||
fn update(&mut self, _state_context: &mut FighterStateContext);
|
||||
|
||||
fn check_collision_interrupt(
|
||||
@@ -272,7 +277,7 @@ pub fn state_interrupt_system(
|
||||
for fighter in &mut query {
|
||||
let StatefulFighterQueryItem {
|
||||
entity,
|
||||
state,
|
||||
mut state,
|
||||
mut frame,
|
||||
animations,
|
||||
animation_player_link,
|
||||
@@ -301,6 +306,7 @@ pub fn state_interrupt_system(
|
||||
let mut new_state = new_state;
|
||||
let old_state_name = state.name();
|
||||
let new_state_name = new_state.name();
|
||||
state.on_exit(&mut state_context);
|
||||
new_state.on_enter(&mut state_context);
|
||||
commands
|
||||
.entity(entity)
|
||||
@@ -352,6 +358,7 @@ pub fn state_collision_interrupt_system(
|
||||
let mut new_state = new_state;
|
||||
let old_state_name = state.name();
|
||||
let new_state_name = new_state.name();
|
||||
state.on_exit(&mut state_context);
|
||||
new_state.on_enter(&mut state_context);
|
||||
commands
|
||||
.entity(entity)
|
||||
|
||||
Reference in New Issue
Block a user