Make dashback more lenient

This commit is contained in:
2026-08-29 11:36:13 +02:00
parent dc8e4e93a5
commit 07834dcb08
10 changed files with 159 additions and 59 deletions
+26 -6
View File
@@ -3,7 +3,11 @@ use std::time::Duration;
use bevy::prelude::*; use bevy::prelude::*;
use crate::{ use crate::{
fighter::{FighterECB, FighterFacingDirection, FighterTranslation, FighterVisual, visual::FighterAnimations}, player::Player, fighter::{
FighterECB, FighterFacingDirection, FighterTranslation, FighterVisual,
visual::FighterAnimations,
},
player::Player,
}; };
#[derive(Component)] #[derive(Component)]
@@ -26,7 +30,11 @@ pub struct FighterAnimationFrame {
impl FighterAnimationFrame { impl FighterAnimationFrame {
pub const fn new(kind: AnimKind, repeat: bool) -> Self { pub const fn new(kind: AnimKind, repeat: bool) -> Self {
Self { kind, frame: 0, repeat } Self {
kind,
frame: 0,
repeat,
}
} }
pub fn reset(&mut self, kind: AnimKind, repeat: bool) { pub fn reset(&mut self, kind: AnimKind, repeat: bool) {
@@ -78,16 +86,28 @@ pub fn setup_fighter_animation_player(
} }
} }
pub fn apply_animation(children: Query<(&mut Transform, &FighterFacingDirection, &FighterAnimationFrame), With<FighterVisual>>) { pub fn apply_animation(
children: Query<
(
&mut Transform,
&FighterFacingDirection,
&FighterAnimationFrame,
),
With<FighterVisual>,
>,
) {
for (mut trf, facing_direction, frame) in children { for (mut trf, facing_direction, frame) in children {
// Melee model scale is in decimeters // Melee model scale is in decimeters
trf.rotation = Quat::IDENTITY; trf.rotation = Quat::IDENTITY;
if let AnimKind::Turn = frame.kind { if let AnimKind::Turn = frame.kind {
trf.rotate_local_y(std::f32::consts::PI * -0.5 * facing_direction.to_sign().to_num::<f32>()); trf.rotate_local_y(
std::f32::consts::PI * -0.5 * facing_direction.to_sign().to_num::<f32>(),
);
} else { } else {
trf.rotate_local_y(std::f32::consts::PI * 0.5 * facing_direction.to_sign().to_num::<f32>()); trf.rotate_local_y(
std::f32::consts::PI * 0.5 * facing_direction.to_sign().to_num::<f32>(),
);
} }
} }
} }
+1 -1
View File
@@ -40,7 +40,7 @@ pub struct FighterAttributes {
pub air_dodge_duration: u32, pub air_dodge_duration: u32,
pub air_dodge_velocity: FGi32, pub air_dodge_velocity: FGi32,
pub air_dodge_decay: FGi32 pub air_dodge_decay: FGi32,
} }
impl FighterAttributes { impl FighterAttributes {
+13 -9
View File
@@ -10,7 +10,10 @@ use crate::fighter::ecb::FighterPreviousECB;
use crate::fighter::manifest::{FighterManifest, FighterManifestRegistry}; use crate::fighter::manifest::{FighterManifest, FighterManifestRegistry};
use crate::fighter::state::StateNameDebug; use crate::fighter::state::StateNameDebug;
use crate::fighter::visual::FighterAnimations; use crate::fighter::visual::FighterAnimations;
use crate::fighter::{Fighter, FighterECB, FighterFacingDirection, FighterPreviousTranslation, FighterTranslation, FighterVelocity}; use crate::fighter::{
Fighter, FighterECB, FighterFacingDirection, FighterPreviousTranslation, FighterTranslation,
FighterVelocity,
};
use crate::input::FighterInput; use crate::input::FighterInput;
use crate::match_loading::MatchPlayer; use crate::match_loading::MatchPlayer;
@@ -45,26 +48,27 @@ pub fn fighter_debug(
ui.label(format!("{:?}", vel)); ui.label(format!("{:?}", vel));
ui.label(format!("{:?}", input.get_last_frame())); ui.label(format!("{:?}", input.get_last_frame()));
}); });
} }
Ok(()) Ok(())
} }
pub fn animation_debug( pub fn animation_debug(
query: Query<( query: Query<(&GlobalTransform, &FighterAnimationFrame, &FighterAnimations)>,
&GlobalTransform,
&FighterAnimationFrame,
&FighterAnimations,
)>,
baked_anims: Res<Assets<BakedFighterAnimations>>, baked_anims: Res<Assets<BakedFighterAnimations>>,
mut gizmos: Gizmos, mut gizmos: Gizmos,
) { ) {
for (global_transform, frame, anims) in query { for (global_transform, frame, anims) in query {
let baked: &BakedFighterAnimations = baked_anims.get(&anims.baked).expect("Baked anims should be in"); let baked: &BakedFighterAnimations = baked_anims
.get(&anims.baked)
.expect("Baked anims should be in");
for bone in &baked.bone_names { for bone in &baked.bone_names {
let out = anims.sample_bone(&baked_anims, *frame, bone); let out = anims.sample_bone(&baked_anims, *frame, bone);
if let Some(out) = out { if let Some(out) = out {
let pos = global_transform.transform_point(Vec3::new(out.cols[3][0].to_num(), out.cols[3][1].to_num(), out.cols[3][2].to_num())); let pos = global_transform.transform_point(Vec3::new(
out.cols[3][0].to_num(),
out.cols[3][1].to_num(),
out.cols[3][2].to_num(),
));
gizmos.cross(pos, 0.5, bevy::color::palettes::css::RED); gizmos.cross(pos, 0.5, bevy::color::palettes::css::RED);
} }
} }
+30 -6
View File
@@ -36,10 +36,22 @@ pub fn snapshot_fighter_ecb(fighters: Query<(&FighterECB, &mut FighterPreviousEC
impl FighterECB { impl FighterECB {
pub fn to_3d_lineloop(&self, trf: Transform) -> [Vec3; 4] { pub fn to_3d_lineloop(&self, trf: Transform) -> [Vec3; 4] {
let rel_up = trf.transform_point(Vec3::new(0.0, (self.vertical_half + self.vertical_half).to_num(), 0.0)); let rel_up = trf.transform_point(Vec3::new(
0.0,
(self.vertical_half + self.vertical_half).to_num(),
0.0,
));
let rel_down = trf.transform_point(Vec3::new(0.0, 0.0, 0.0)); let rel_down = trf.transform_point(Vec3::new(0.0, 0.0, 0.0));
let rel_left = trf.transform_point(Vec3::new((-self.horizontal_half).to_num(), self.vertical_half.to_num(), 0.0)); let rel_left = trf.transform_point(Vec3::new(
let rel_right = trf.transform_point(Vec3::new(self.horizontal_half.to_num(), self.vertical_half.to_num(), 0.0)); (-self.horizontal_half).to_num(),
self.vertical_half.to_num(),
0.0,
));
let rel_right = trf.transform_point(Vec3::new(
self.horizontal_half.to_num(),
self.vertical_half.to_num(),
0.0,
));
[rel_up, rel_right, rel_down, rel_left] [rel_up, rel_right, rel_down, rel_left]
} }
@@ -50,10 +62,22 @@ impl FighterECB {
impl FighterPreviousECB { impl FighterPreviousECB {
pub fn to_3d_lineloop(&self, trf: Transform) -> [Vec3; 4] { pub fn to_3d_lineloop(&self, trf: Transform) -> [Vec3; 4] {
let rel_up = trf.transform_point(Vec3::new(0.0, (self.vertical_half + self.vertical_half).to_num(), 0.0)); let rel_up = trf.transform_point(Vec3::new(
0.0,
(self.vertical_half + self.vertical_half).to_num(),
0.0,
));
let rel_down = trf.transform_point(Vec3::new(0.0, 0.0, 0.0)); let rel_down = trf.transform_point(Vec3::new(0.0, 0.0, 0.0));
let rel_left = trf.transform_point(Vec3::new((-self.horizontal_half).to_num(), self.vertical_half.to_num(), 0.0)); let rel_left = trf.transform_point(Vec3::new(
let rel_right = trf.transform_point(Vec3::new(self.horizontal_half.to_num(), self.vertical_half.to_num(), 0.0)); (-self.horizontal_half).to_num(),
self.vertical_half.to_num(),
0.0,
));
let rel_right = trf.transform_point(Vec3::new(
self.horizontal_half.to_num(),
self.vertical_half.to_num(),
0.0,
));
[rel_up, rel_right, rel_down, rel_left] [rel_up, rel_right, rel_down, rel_left]
} }
+14 -5
View File
@@ -7,8 +7,8 @@ use bevy_ggrs::prelude::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
pub mod animation; pub mod animation;
pub mod baked_animation;
pub mod attributes; pub mod attributes;
pub mod baked_animation;
pub mod collision; pub mod collision;
pub mod debug; pub mod debug;
pub mod ecb; pub mod ecb;
@@ -28,8 +28,16 @@ pub use motion::{FighterPreviousTranslation, FighterTranslation, FighterVelocity
use crate::{ use crate::{
fighter::{ fighter::{
animation::animation_init, manifest::FighterManifest, state::{FighterState, fall::FallState}, visual::FighterAnimations, animation::animation_init,
}, input::FighterInput, math::{int::FGi32, vec::FGVec2}, player::Player, schedule::GameplaySet, stage::line::StageCollision, manifest::FighterManifest,
state::{FighterState, fall::FallState},
visual::FighterAnimations,
},
input::FighterInput,
math::{int::FGi32, vec::FGVec2},
player::Player,
schedule::GameplaySet,
stage::line::StageCollision,
}; };
use state::state_interrupt_system; use state::state_interrupt_system;
@@ -91,7 +99,7 @@ pub struct FighterVisual;
#[derive(Component)] #[derive(Component)]
pub struct Fighter { pub struct Fighter {
id: FighterId, id: FighterId,
pub manifest: Handle<FighterManifest> pub manifest: Handle<FighterManifest>,
} }
impl Plugin for FighterPlugin { impl Plugin for FighterPlugin {
@@ -174,7 +182,7 @@ pub fn spawn_fighter(
}, },
Fighter { Fighter {
id: manifest.1.id, id: manifest.1.id,
manifest: manifest.0 manifest: manifest.0,
}, },
FighterFacingDirection::Right, FighterFacingDirection::Right,
FighterECB { FighterECB {
@@ -189,5 +197,6 @@ pub fn spawn_fighter(
animation::FighterAnimationFrame::new(animation::AnimKind::Wait, true), animation::FighterAnimationFrame::new(animation::AnimKind::Wait, true),
FighterVisual, FighterVisual,
FighterInput::default(), FighterInput::default(),
crate::camera::FighterCameraExtents::default(),
)); ));
} }
+6 -2
View File
@@ -4,7 +4,7 @@ use crate::fighter::collision;
use crate::fighter::state::fall::FallState; use crate::fighter::state::fall::FallState;
use crate::fighter::state::ground::{self, GroundedMotionResult, GroundedStateCommon}; use crate::fighter::state::ground::{self, GroundedMotionResult, GroundedStateCommon};
use crate::fighter::state::land::LandingState; use crate::fighter::state::land::LandingState;
use crate::fighter::state::{FighterState, air, dash, jump, wait, walk}; use crate::fighter::state::{air, dash, jump, wait, walk, FighterState};
#[derive(Debug, Clone, Hash)] #[derive(Debug, Clone, Hash)]
pub struct TurnState { pub struct TurnState {
@@ -80,9 +80,13 @@ pub fn check_input(
grounded_common: &GroundedStateCommon, grounded_common: &GroundedStateCommon,
) -> Option<FighterState> { ) -> Option<FighterState> {
let input_frame = state_context.input.get_last_frame(); let input_frame = state_context.input.get_last_frame();
let smash_input_frame_threshold = state_context
.game_settings
.input_common
.smash_input_frame_threshold;
if !input_frame.movement.x.is_zero() if !input_frame.movement.x.is_zero()
&& input_frame.movement.x.signum() != state_context.facing_direction.to_sign() && input_frame.movement.x.signum() != state_context.facing_direction.to_sign()
&& state_context.input.get_frames_in_smash_move_deadzone() > smash_input_frame_threshold
{ {
return Some(FighterState::Turn(TurnState { return Some(FighterState::Turn(TurnState {
grounded_common: grounded_common.clone(), grounded_common: grounded_common.clone(),
+6 -8
View File
@@ -18,20 +18,18 @@ pub struct WaitState {
impl FighterStateImpl for WaitState { impl FighterStateImpl for WaitState {
const NAME: &'static str = "Wait"; const NAME: &'static str = "Wait";
fn check_interrupt(&self, state_context: &mut FighterStateContext) -> Option<FighterState> { fn check_interrupt(&self, state_context: &mut FighterStateContext) -> Option<FighterState> {
if let Some(state) = ground::grounded_movement_standstill_common_interrupts( if let Some(state) = ground::grounded_movement_common_interrupts(state_context, &self.grounded_common) {
Some(state)
} else if let Some(state) = ground::grounded_movement_standstill_common_interrupts(
state_context, state_context,
&self.grounded_common, &self.grounded_common)
) { {
// Prevent transitioning into myself // Don't transition into yourself
if let FighterState::Wait(_) = state { if let FighterState::Wait(_) = state {
None None
} else { } else {
Some(state) Some(state)
} }
} else if let Some(state) =
ground::grounded_movement_common_interrupts(state_context, &self.grounded_common)
{
Some(state)
} else { } else {
None None
} }
+5 -1
View File
@@ -6,6 +6,7 @@ use super::{FighterStateContext, FighterStateImpl, dash, ground, wait};
use crate::fighter::animation::AnimKind; use crate::fighter::animation::AnimKind;
use crate::fighter::state::FighterState; use crate::fighter::state::FighterState;
use crate::fighter::state::fall::FallState; use crate::fighter::state::fall::FallState;
use crate::fighter::state::turn;
use crate::fighter::state::ground::{GroundedMotionResult, GroundedStateCommon}; use crate::fighter::state::ground::{GroundedMotionResult, GroundedStateCommon};
#[derive(Debug, Clone, Hash)] #[derive(Debug, Clone, Hash)]
@@ -16,7 +17,10 @@ pub struct WalkState {
impl FighterStateImpl for WalkState { impl FighterStateImpl for WalkState {
const NAME: &'static str = "Walk"; const NAME: &'static str = "Walk";
fn check_interrupt(&self, state_context: &mut FighterStateContext) -> Option<FighterState> { fn check_interrupt(&self, state_context: &mut FighterStateContext) -> Option<FighterState> {
if let Some(state) = if let Some(state) = turn::check_input(state_context, &self.grounded_common) {
return Some(state)
}
else if let Some(state) =
ground::grounded_movement_common_interrupts(state_context, &self.grounded_common) ground::grounded_movement_common_interrupts(state_context, &self.grounded_common)
{ {
return Some(state); return Some(state);
+5 -1
View File
@@ -94,6 +94,10 @@ impl FighterInput {
pub fn reduce_command_lifetime(&mut self, command: FighterCommands) { pub fn reduce_command_lifetime(&mut self, command: FighterCommands) {
self.command_lifetimes[command] = self.command_lifetimes[command].saturating_sub(1); self.command_lifetimes[command] = self.command_lifetimes[command].saturating_sub(1);
} }
pub fn get_frames_in_smash_move_deadzone(&self) -> u8 {
self.frames_in_smash_move_deadzone
}
} }
/// Turns this frame's rolled-back inputs into per-fighter [`FighterInput`] /// Turns this frame's rolled-back inputs into per-fighter [`FighterInput`]
@@ -124,7 +128,7 @@ pub fn postprocess_input(
if prev_x_abs < reset_deadzone if prev_x_abs < reset_deadzone
|| input.prev_frame.movement.x.signum() != input.current_frame.movement.x.signum() || input.prev_frame.movement.x.signum() != input.current_frame.movement.x.signum()
{ {
input.frames_in_smash_move_deadzone = 0; input.frames_in_smash_move_deadzone = 1;
} else { } else {
input.frames_in_smash_move_deadzone = input.frames_in_smash_move_deadzone.saturating_add(1); input.frames_in_smash_move_deadzone = input.frames_in_smash_move_deadzone.saturating_add(1);
} }
+52 -19
View File
@@ -2,37 +2,62 @@ use std::collections::HashMap;
use bevy_asset_loader::asset_collection::AssetCollection; use bevy_asset_loader::asset_collection::AssetCollection;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use strum_macros::{EnumIter};
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use strum_macros::EnumIter;
use crate::{ use crate::{
AppState, game_settings::GameSettings, math::{int::FGi32, vec::FGVec2}, stage::{StagePoly, line::{StageCollision, StagePolyLineSegmentType, StagePolyType}}, AppState,
game_settings::GameSettings,
math::{int::FGi32, vec::FGVec2},
stage::{
StagePoly,
line::{StageCollision, StagePolyLineSegmentType, StagePolyType},
},
}; };
use bevy::prelude::*; use bevy::prelude::*;
/// Per-stage presentation tuning for the standard match camera. /// Per-stage presentation tuning for the standard match camera.
/// ///
/// Values use gameplay world units on the XY plane /// Spatial values use gameplay world units on the XY plane. Angle values use
/// degrees
#[derive(Component, Serialize, Deserialize, Clone, Reflect)] #[derive(Component, Serialize, Deserialize, Clone, Reflect)]
#[reflect(opaque)] #[reflect(opaque)]
pub struct StageCameraProfile { pub struct StageCameraProfile {
/// World-space left edge of the camera range.
pub left: f32, pub left: f32,
/// World-space right edge of the camera range.
pub right: f32, pub right: f32,
/// World-space bottom edge of the camera range.
pub bottom: f32, pub bottom: f32,
/// World-space top edge of the camera range.
pub top: f32, pub top: f32,
/// Neutral focus point used for empty framing and dynamic pan calculations.
pub origin: Vec2, pub origin: Vec2,
/// Vertical perspective field of view for the standard match camera.
pub vertical_fov_degrees: f32, pub vertical_fov_degrees: f32,
/// Nearest allowed positive Z distance from the gameplay plane.
pub min_depth: f32, pub min_depth: f32,
/// Farthest allowed positive Z distance from the gameplay plane.
pub max_depth: f32, pub max_depth: f32,
/// Multiplies fighter camera extents after the player-count scale.
pub subject_scale: f32, pub subject_scale: f32,
/// Multiplies the facing-forward horizontal fighter camera extent.
pub fighter_forward_extent_scale: f32,
/// Multiplies the standard camera's interest and eye follow speeds.
pub tracking_smoothness: f32, pub tracking_smoothness: f32,
pub vertical_tilt_degrees: f32, /// Constant vertical pitch added after dynamic vertical pan is clamped.
pub horizontal_pan_coefficient: f32, pub vertical_pan_degrees: f32,
pub vertical_pan_coefficient: f32, /// Dynamic horizontal pan in degrees for every world unit from 0,0
pub horizontal_pan_degrees_per_unit: f32,
/// Dynamic vertical pan in degrees for every world unit from the vertical reference line
pub vertical_pan_degrees_per_unit: f32,
/// Symmetric limit on dynamic horizontal pan, before any other adjustment.
pub max_horizontal_pan_degrees: f32, pub max_horizontal_pan_degrees: f32,
/// Maximum upward dynamic vertical pan, before constant pitch is added.
pub max_upward_pan_degrees: f32, pub max_upward_pan_degrees: f32,
/// Magnitude of the maximum downward dynamic vertical pan, before constant
/// pitch is added.
pub max_downward_pan_degrees: f32, pub max_downward_pan_degrees: f32,
pub max_downward_expansion: f32,
} }
#[derive(Reflect, Serialize, Deserialize, Copy, Clone, Eq, PartialEq, Hash, Debug, EnumIter)] #[derive(Reflect, Serialize, Deserialize, Copy, Clone, Eq, PartialEq, Hash, Debug, EnumIter)]
@@ -101,7 +126,6 @@ fn fail(next_state: &mut NextState<AppState>, message: &str) {
next_state.set(AppState::CommonAssetLoadFailed); next_state.set(AppState::CommonAssetLoadFailed);
} }
pub fn prepare_stage_manifests( pub fn prepare_stage_manifests(
mut commands: Commands, mut commands: Commands,
handles: Res<StageManifestAssets>, handles: Res<StageManifestAssets>,
@@ -154,13 +178,13 @@ pub fn prepare_stage_manifests(
impl StageManifest { impl StageManifest {
pub fn to_collision(&self) -> StageCollision { pub fn to_collision(&self) -> StageCollision {
let stage_polys: Vec<StagePoly> = self.polygons.iter().map(|def| { let stage_polys: Vec<StagePoly> = self
StagePoly::build(def.poly_type, &def.lines) .polygons
}).collect(); .iter()
.map(|def| StagePoly::build(def.poly_type, &def.lines))
.collect();
StageCollision { StageCollision { stage_polys }
stage_polys
}
} }
} }
@@ -175,24 +199,33 @@ impl StageCameraProfile {
&& self.min_depth.is_finite() && self.min_depth.is_finite()
&& self.max_depth.is_finite() && self.max_depth.is_finite()
&& self.subject_scale.is_finite() && self.subject_scale.is_finite()
&& self.fighter_forward_extent_scale.is_finite()
&& self.tracking_smoothness.is_finite() && self.tracking_smoothness.is_finite()
&& self.vertical_tilt_degrees.is_finite() && self.vertical_pan_degrees.is_finite()
&& self.horizontal_pan_coefficient.is_finite() && self.horizontal_pan_degrees_per_unit.is_finite()
&& self.vertical_pan_coefficient.is_finite() && self.vertical_pan_degrees_per_unit.is_finite()
&& self.max_horizontal_pan_degrees.is_finite() && self.max_horizontal_pan_degrees.is_finite()
&& self.max_upward_pan_degrees.is_finite() && self.max_upward_pan_degrees.is_finite()
&& self.max_downward_pan_degrees.is_finite() && self.max_downward_pan_degrees.is_finite()
&& self.max_downward_expansion.is_finite()
&& self.left < self.right && self.left < self.right
&& self.bottom < self.top && self.bottom < self.top
&& (self.left..=self.right).contains(&self.origin.x)
&& (self.bottom..=self.top).contains(&self.origin.y)
&& (0.0..180.0).contains(&self.vertical_fov_degrees) && (0.0..180.0).contains(&self.vertical_fov_degrees)
&& self.min_depth > 0.0 && self.min_depth > 0.0
&& self.min_depth <= self.max_depth && self.min_depth <= self.max_depth
&& self.subject_scale > 0.0 && self.subject_scale > 0.0
&& self.fighter_forward_extent_scale > 0.0
&& self.tracking_smoothness >= 0.0 && self.tracking_smoothness >= 0.0
&& self.max_horizontal_pan_degrees >= 0.0 && self.max_horizontal_pan_degrees >= 0.0
&& self.max_upward_pan_degrees >= 0.0 && self.max_upward_pan_degrees >= 0.0
&& self.max_downward_pan_degrees >= 0.0 && self.max_downward_pan_degrees >= 0.0
&& self.max_downward_expansion >= 0.0 && self.vertical_fov_degrees * 0.5 + self.max_horizontal_pan_degrees < 90.0
&& self.vertical_fov_degrees * 0.5
+ self.vertical_pan_degrees.abs()
+ self
.max_upward_pan_degrees
.max(self.max_downward_pan_degrees)
< 90.0
} }
} }