Implement hitstun and knockback.

This commit is contained in:
2026-09-13 23:01:25 +02:00
parent 811578af2f
commit 4570860d64
26 changed files with 840 additions and 246 deletions
+55 -32
View File
@@ -13,6 +13,7 @@ pub mod baked_animation;
pub mod collision;
pub mod debug;
pub mod ecb;
pub mod errors;
pub mod hurtbox;
pub mod manifest;
pub mod motion;
@@ -32,22 +33,17 @@ pub use motion::{
};
use crate::{
fighter::{
AppState, fighter::{
attack::{
FighterAttackPlugin, FighterAttackScriptAssets, FighterSolvedHitboxes,
FighterSolvedHurtboxes,
AttackKind, FighterAttackPlugin, FighterAttackScriptAssets, FighterDamage,
FighterKnockbackVelocity, FighterPendingHitReaction, FighterReceivedAttacks,
FighterSolvedHurtboxes, FighterStaleMoveQueue,
},
baked_animation::{FighterBoneIndexMap, FighterBoneMatrices},
manifest::FighterManifest,
state::{FighterState, fall::FallState},
visual::FighterAnimations,
},
input::FighterInput,
math::{int::FGi32, vec::FGVec2},
player::Player,
schedule::GameplaySet,
scripting::FighterAttackScript,
stage::line::StageCollision,
}, input::FighterInput, math::{int::FGi32, vec::FGVec2}, player::Player, schedule::GameplaySet, scripting::FighterAttackScript, stage::line::StageCollision,
};
use state::state_interrupt_system;
@@ -62,6 +58,11 @@ pub enum FighterFacingDirection {
Right,
}
#[derive(Component, Default, Copy, Clone, Hash)]
pub struct FighterHitstop {
pub remaining_frames: u32,
}
impl FighterFacingDirection {
fn reverse(&self) -> Self {
match self {
@@ -107,6 +108,14 @@ impl FighterFacingDirection {
pub struct FighterVisual;
#[derive(Component)]
#[require(
FighterReceivedAttacks,
FighterStaleMoveQueue,
FighterDamage,
FighterPendingHitReaction,
FighterHitstop,
FighterKnockbackVelocity
)]
pub struct Fighter {
pub manifest: Handle<FighterManifest>,
}
@@ -130,7 +139,9 @@ impl Plugin for FighterPlugin {
)
.add_systems(
GgrsSchedule,
(state::state_update_system,).in_set(GameplaySet::StateUpdate),
(state::state_update_system, decay_hitstop)
.chain()
.in_set(GameplaySet::StateUpdate),
)
/*.add_systems(
GgrsSchedule,
@@ -156,6 +167,8 @@ impl Plugin for FighterPlugin {
// is a desync waiting to happen.
.rollback_component_with_copy::<FighterECB>()
.checksum_component_with_hash::<FighterECB>()
.rollback_component_with_clone::<FighterStaleMoveQueue>()
.checksum_component_with_hash::<FighterStaleMoveQueue>()
.rollback_component_with_copy::<ecb::FighterPreviousECB>()
.checksum_component_with_hash::<ecb::FighterPreviousECB>()
.rollback_component_with_copy::<FighterVelocity>()
@@ -171,6 +184,16 @@ impl Plugin for FighterPlugin {
.rollback_component_with_copy::<Grounded>()
.checksum_component_with_hash::<Grounded>()
.rollback_component_with_copy::<FighterFacingDirection>()
.rollback_component_with_copy::<FighterDamage>()
.checksum_component_with_hash::<FighterDamage>()
.rollback_component_with_clone::<FighterPendingHitReaction>()
.checksum_component_with_hash::<FighterPendingHitReaction>()
.rollback_component_with_copy::<FighterHitstop>()
.checksum_component_with_hash::<FighterHitstop>()
.rollback_component_with_copy::<FighterKnockbackVelocity>()
.checksum_component_with_hash::<FighterKnockbackVelocity>()
.rollback_component_with_clone::<FighterReceivedAttacks>()
.checksum_component_with_hash::<FighterReceivedAttacks>()
.rollback_resource_with_reflect::<StageCollision>()
// Debug views.
.add_systems(
@@ -187,7 +210,13 @@ impl Plugin for FighterPlugin {
)
.add_systems(
FixedPostUpdate,
debug::attack_debug.run_if(crate::debug_tools::attack_hitboxes_enabled),
(
debug::attack_debug_gather,
(debug::attack_debug, debug::attack_launch_debug_draw)
.after(debug::attack_debug_gather),
)
.chain()
.run_if(crate::debug_tools::attack_hitboxes_enabled).run_if(in_state(AppState::InMatch)),
)
.add_systems(
FixedPostUpdate,
@@ -204,23 +233,17 @@ impl Plugin for FighterPlugin {
.register_type::<FighterFacingDirection>();
}
}
#[derive(Component, Clone, Hash)]
pub struct FighterHitboxes {
pub attack_script: Option<Handle<FighterAttackScript>>,
#[derive(Clone, Debug, Hash)]
pub struct FighterActiveAttack {
pub kind: AttackKind,
pub script: Handle<FighterAttackScript>,
pub active_hitboxes: Vec<usize>,
pub active_hitboxes_solved: Vec<attack::FighterSolvedHitbox>,
pub solved_hitboxes: Vec<attack::FighterSolvedHitbox>,
pub hit_fighters: Vec<Entity>,
}
impl FighterHitboxes {
pub fn clear(&mut self) {
self.attack_script = None;
self.active_hitboxes.clear();
self.active_hitboxes_solved.clear();
self.hit_fighters.clear();
}
}
#[derive(Component, Clone, Debug, Default, Hash)]
pub struct FighterHitboxes(pub Option<FighterActiveAttack>);
pub fn spawn_fighter(
commands: &mut Commands,
@@ -247,12 +270,7 @@ pub fn spawn_fighter(
vertical_half: FGi32::lit("5.0"),
horizontal_half: FGi32::lit("2.5"),
},
FighterHitboxes {
attack_script: None,
active_hitboxes: vec![],
active_hitboxes_solved: vec![],
hit_fighters: vec![],
},
FighterHitboxes::default(),
),
(
FighterVelocity::default(),
@@ -268,7 +286,6 @@ pub fn spawn_fighter(
baked_animation::FighterBoneMatrices::default(),
FighterBoneIndexMap::default(),
FighterSolvedHurtboxes::default(),
FighterSolvedHitboxes::default(),
FighterVisual,
FighterInput::default(),
crate::camera::FighterCameraExtents::default(),
@@ -276,3 +293,9 @@ pub fn spawn_fighter(
))
.id()
}
pub fn decay_hitstop(query: Query<&mut FighterHitstop>) {
for mut hitstop in query {
hitstop.remaining_frames = hitstop.remaining_frames.saturating_sub(1);
}
}