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
+75 -11
View File
@@ -8,20 +8,26 @@ use bevy_egui::{EguiContexts, egui};
use crate::debug_tools::{DebugSettings, FighterDebugRowKind};
use crate::fighter::animation::FighterAnimationFrame;
use crate::fighter::attack::FighterSolvedHurtboxes;
use crate::fighter::attack::{
FighterKnockbackVelocity, FighterLastAttackDebug, FighterPendingHitReaction,
FighterSolvedHurtboxes,
};
use crate::fighter::baked_animation::{BakedFighterAnimations, FighterBoneMatrices};
use crate::fighter::ecb::FighterPreviousECB;
use crate::fighter::errors::FighterErrors;
use crate::fighter::hurtbox::FixedAffineCapsule;
use crate::fighter::state::StateNameDebug;
use crate::fighter::manifest::FighterManifest;
use crate::fighter::state::{FighterState, air};
use crate::fighter::visual::FighterAnimations;
use crate::fighter::{
FighterECB, FighterFacingDirection, FighterHitboxes, FighterPreviousTranslation,
Fighter, FighterECB, FighterFacingDirection, FighterHitboxes, FighterPreviousTranslation,
FighterTranslation, FighterVelocity,
};
use crate::game_settings::GameSettings;
use crate::input::FighterInput;
use crate::math::int::FGi32;
use crate::math::vec::FGVec2;
use crate::player::Player;
use crate::scripting::FighterAttackScript;
pub fn debug_draw_ecb(
ecbs: Query<(
@@ -51,7 +57,7 @@ pub fn fighter_debug(
&FighterVelocity,
&FighterInput,
&FighterAnimationFrame,
&StateNameDebug,
&FighterState,
&FighterAnimations,
&FighterTranslation,
&FighterFacingDirection,
@@ -62,7 +68,7 @@ pub fn fighter_debug(
let mut fighters = query.iter().collect::<Vec<_>>();
fighters.sort_unstable_by_key(|(player, ..)| player.handle);
for (player, velocity, input, animation_frame, state_name, animations, translation, facing) in
for (player, velocity, input, animation_frame, state, animations, translation, facing) in
fighters
{
let Some(rows) = settings.player_rows(player.handle) else {
@@ -85,7 +91,7 @@ pub fn fighter_debug(
if let Some(frame_count) = frame_count {
ui.label(format!(
"State: {:?} ({:?} {}/{})",
state_name.0,
state.name(),
animation_frame.kind,
animation_frame.frame,
frame_count
@@ -93,7 +99,8 @@ pub fn fighter_debug(
} else {
ui.label(format!(
"State: {:?} ({:?})",
state_name.0, animation_frame.kind
state.name(),
animation_frame.kind
));
}
}
@@ -334,12 +341,11 @@ pub fn attack_debug(
&FighterFacingDirection,
&FighterAnimationFrame,
)>,
_attack_scripts: Res<Assets<FighterAttackScript>>,
mut gizmos: Gizmos,
) {
for (hitboxes, _matrices, _translation, _facing_direction, _frame) in query {
if let Some(_script) = &hitboxes.attack_script {
for solved_hitbox in hitboxes.active_hitboxes_solved.iter() {
if let Some(active_attack) = &hitboxes.0 {
for solved_hitbox in active_attack.solved_hitboxes.iter() {
let start = solved_hitbox.capsule.start.to_vec3();
let end = solved_hitbox.capsule.end.to_vec3();
let center =
@@ -369,3 +375,61 @@ pub fn attack_debug(
}
}
}
pub fn attack_debug_gather(
query: Query<(
Entity,
&FighterPendingHitReaction,
&FighterTranslation,
&Fighter,
)>,
mut commands: Commands,
manifests: Res<Assets<FighterManifest>>,
game_settings: Res<GameSettings>,
) -> Result {
for (entity, reaction, translation, fighter) in query {
let Some(ref hit) = reaction.0 else {
commands.entity(entity).remove::<FighterLastAttackDebug>();
continue;
};
let manifest = manifests
.get(&fighter.manifest)
.ok_or(FighterErrors::FighterManifestNotFound)?;
let mut knockback_vel = FighterKnockbackVelocity(hit.launch_velocity);
let mut fighter_vel = FighterVelocity(FGVec2::ZERO);
let mut translation = Vec3::new(translation.x.to_num(), translation.y.to_num(), 0.0);
let mut points = vec![];
points.push(translation);
points.reserve(hit.hitstun_frames as usize);
for _ in 1..hit.hitstun_frames {
air::integrate_gravity(&mut fighter_vel, &manifest.attributes);
let vel = knockback_vel.length();
let new_vel = vel
.saturating_sub(game_settings.fighter_common.knockback_decay_per_frame)
.max(FGi32::ZERO);
knockback_vel = FighterKnockbackVelocity(knockback_vel.0.normalize_or_zero() * new_vel);
translation += Vec3::new(knockback_vel.x.to_num(), knockback_vel.y.to_num(), 0.0);
translation += Vec3::new(fighter_vel.x.to_num(), fighter_vel.y.to_num(), 0.0);
points.push(translation);
}
commands.entity(entity).insert(FighterLastAttackDebug {
trajectory_points: points,
});
}
Ok(())
}
pub fn attack_launch_debug_draw(query: Query<&FighterLastAttackDebug>, mut gizmos: Gizmos) {
for debug_attack in query {
gizmos.linestrip(
debug_attack.trajectory_points.iter().copied(),
bevy::color::palettes::css::GREEN,
);
}
}