Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c68e1fb55 | ||
|
|
62aa598324 | ||
|
|
a58b5eeec7 | ||
|
|
615872f29c |
+9
-5
@@ -5,7 +5,7 @@ use bevy_ggrs::GgrsFrameTiming;
|
||||
|
||||
use crate::{
|
||||
debug_tools::DebugSettings,
|
||||
fighter::{manifest::FighterManifest, motion::sample_presentation_translation},
|
||||
fighter::{manifest::FighterManifest, motion::{sample_presentation_translation_with_snap, FighterPresentationSnap}},
|
||||
stage::manifest::StageCameraProfile,
|
||||
};
|
||||
|
||||
@@ -313,6 +313,7 @@ fn update_match_camera(
|
||||
&crate::fighter::motion::FighterPreviousTranslation,
|
||||
&crate::fighter::Fighter,
|
||||
&crate::fighter::FighterFacingDirection,
|
||||
&FighterPresentationSnap,
|
||||
&mut FighterCameraExtents,
|
||||
),
|
||||
With<crate::player::Player>,
|
||||
@@ -329,16 +330,17 @@ fn update_match_camera(
|
||||
let subjects = fighters
|
||||
.iter_mut()
|
||||
.map(
|
||||
|(translation, previous_translation, fighter, facing, mut extents)| {
|
||||
|(translation, previous_translation, fighter, facing, presentation_snap, mut extents)| {
|
||||
let manifest = manifests
|
||||
.get(&fighter.manifest)
|
||||
.expect("Fighter manifest should be valid");
|
||||
fighter_subject_bounds(
|
||||
sample_presentation_translation(
|
||||
sample_presentation_translation_with_snap(
|
||||
*previous_translation,
|
||||
*translation,
|
||||
timing.overstep_fraction(),
|
||||
debug_settings.motion_sampling,
|
||||
*presentation_snap,
|
||||
),
|
||||
manifest.camera,
|
||||
*facing,
|
||||
@@ -465,6 +467,7 @@ fn debug_draw_camera(
|
||||
&crate::fighter::motion::FighterPreviousTranslation,
|
||||
&crate::fighter::Fighter,
|
||||
&crate::fighter::FighterFacingDirection,
|
||||
&FighterPresentationSnap,
|
||||
&FighterCameraExtents,
|
||||
),
|
||||
With<crate::player::Player>,
|
||||
@@ -486,15 +489,16 @@ fn debug_draw_camera(
|
||||
Color::srgb(1.0, 0.5, 0.0),
|
||||
);
|
||||
draw_rectangle(&mut gizmos, camera.frame, Color::srgb(1.0, 1.0, 0.0));
|
||||
for (translation, previous_translation, fighter, _facing, extents) in &fighters {
|
||||
for (translation, previous_translation, fighter, _facing, presentation_snap, extents) in &fighters {
|
||||
let manifest = manifests
|
||||
.get(&fighter.manifest)
|
||||
.expect("Fighter manifest should be valid");
|
||||
let center = sample_presentation_translation(
|
||||
let center = sample_presentation_translation_with_snap(
|
||||
*previous_translation,
|
||||
*translation,
|
||||
timing.overstep_fraction(),
|
||||
debug_settings.motion_sampling,
|
||||
*presentation_snap,
|
||||
) + Vec2::Y * manifest.camera.vertical_origin_offset.to_num::<f32>();
|
||||
draw_rectangle(
|
||||
&mut gizmos,
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::fighter::ecb::FighterPreviousECB;
|
||||
use crate::fighter::{FighterECB, FighterPreviousTranslation, FighterTranslation};
|
||||
use crate::math::segment::FGSegment2d;
|
||||
use crate::math::vec::FGVec2;
|
||||
use crate::stage::line::{StageCollision, StageLineID};
|
||||
use crate::stage::line::{StageCollision, StageLineID, StagePolyType};
|
||||
/*
|
||||
pub fn collide_fighter_with_scene(
|
||||
fighters: Query<(&FighterECB, &mut FighterVelocity, &mut FighterTranslation)>,
|
||||
@@ -45,6 +45,7 @@ pub fn air_collide_with_stage(
|
||||
ecb: &FighterECB,
|
||||
stage_collision: &StageCollision,
|
||||
ignored_polys: &[usize],
|
||||
ignore_soft_platforms: bool,
|
||||
) -> Option<AirCollisionWithStageResult> {
|
||||
let ray_segment = FGSegment2d::new(
|
||||
prev_translation.0 + prev_ecb.get_bottom_point(),
|
||||
@@ -55,7 +56,10 @@ pub fn air_collide_with_stage(
|
||||
.stage_polys
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(poly_idx, _)| !ignored_polys.contains(poly_idx))
|
||||
.filter(|(poly_idx, poly)| {
|
||||
!ignored_polys.contains(poly_idx)
|
||||
&& !(ignore_soft_platforms && matches!(poly.poly_type, StagePolyType::Platform))
|
||||
})
|
||||
.filter_map(|(poly_idx, poly)| {
|
||||
poly.intersect_ray(ray_segment).map(|intersect_result| {
|
||||
(
|
||||
@@ -95,6 +99,18 @@ mod tests {
|
||||
)
|
||||
}
|
||||
|
||||
fn solid_floor() -> StagePoly {
|
||||
StagePoly::build(
|
||||
StagePolyType::Closed,
|
||||
&[
|
||||
(FGVec2::lit("-2", "0"), StagePolyLineSegmentType::Floor),
|
||||
(FGVec2::lit("2", "0"), StagePolyLineSegmentType::Floor),
|
||||
(FGVec2::lit("2", "-2"), StagePolyLineSegmentType::Wall),
|
||||
(FGVec2::lit("-2", "-2"), StagePolyLineSegmentType::Wall),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn airborne_collision_skips_ignored_polygons() {
|
||||
let stage_collision = StageCollision {
|
||||
@@ -122,10 +138,78 @@ mod tests {
|
||||
&ecb,
|
||||
&stage_collision,
|
||||
&[1],
|
||||
false,
|
||||
)
|
||||
.expect("the non-ignored platform should still be collidable");
|
||||
|
||||
assert_eq!(collision.line_id.polygon, 0);
|
||||
assert_eq!(collision.hit_position, FGVec2::lit("0", "0"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn airborne_collision_skips_soft_platforms_while_passing_down() {
|
||||
let stage_collision = StageCollision {
|
||||
stage_polys: vec![horizontal_platform("0")],
|
||||
};
|
||||
let previous_translation = FighterPreviousTranslation(FGVec2::lit("0", "2"));
|
||||
let translation = FighterTranslation(FGVec2::lit("0", "-1"));
|
||||
let previous_ecb = FighterPreviousECB {
|
||||
bottom: FGVec2::ZERO,
|
||||
top: FGVec2::ZERO,
|
||||
left: FGVec2::ZERO,
|
||||
right: FGVec2::ZERO,
|
||||
};
|
||||
let ecb = FighterECB {
|
||||
bottom: FGVec2::ZERO,
|
||||
top: FGVec2::ZERO,
|
||||
left: FGVec2::ZERO,
|
||||
right: FGVec2::ZERO,
|
||||
};
|
||||
|
||||
assert!(air_collide_with_stage(
|
||||
&previous_translation,
|
||||
&translation,
|
||||
&previous_ecb,
|
||||
&ecb,
|
||||
&stage_collision,
|
||||
&[],
|
||||
true,
|
||||
)
|
||||
.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn airborne_collision_still_hits_solid_floors_while_passing_down() {
|
||||
let stage_collision = StageCollision {
|
||||
stage_polys: vec![solid_floor()],
|
||||
};
|
||||
let previous_translation = FighterPreviousTranslation(FGVec2::lit("0", "2"));
|
||||
let translation = FighterTranslation(FGVec2::lit("0", "-1"));
|
||||
let previous_ecb = FighterPreviousECB {
|
||||
bottom: FGVec2::ZERO,
|
||||
top: FGVec2::ZERO,
|
||||
left: FGVec2::ZERO,
|
||||
right: FGVec2::ZERO,
|
||||
};
|
||||
let ecb = FighterECB {
|
||||
bottom: FGVec2::ZERO,
|
||||
top: FGVec2::ZERO,
|
||||
left: FGVec2::ZERO,
|
||||
right: FGVec2::ZERO,
|
||||
};
|
||||
|
||||
let collision = air_collide_with_stage(
|
||||
&previous_translation,
|
||||
&translation,
|
||||
&previous_ecb,
|
||||
&ecb,
|
||||
&stage_collision,
|
||||
&[],
|
||||
true,
|
||||
)
|
||||
.expect("solid floors remain collidable while passing down");
|
||||
|
||||
assert_eq!(collision.line_id.polygon, 0);
|
||||
assert_eq!(collision.hit_position, FGVec2::lit("0", "0"));
|
||||
}
|
||||
}
|
||||
|
||||
+9
-1
@@ -29,7 +29,8 @@ pub use attributes::FighterAttributes;
|
||||
pub use ecb::FighterECB;
|
||||
pub use manifest::FighterId;
|
||||
pub use motion::{
|
||||
FighterPreviousFacingDirection, FighterPreviousTranslation, FighterTranslation, FighterVelocity,
|
||||
FighterPresentationSnap, FighterPreviousFacingDirection, FighterPreviousTranslation,
|
||||
FighterTranslation, FighterVelocity,
|
||||
};
|
||||
pub use surface::FighterSurfaceState;
|
||||
|
||||
@@ -147,6 +148,10 @@ impl Plugin for FighterPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_plugins(FighterAttackPlugin)
|
||||
// Every state gets a chance to interrupt itself, in priority order.
|
||||
.add_systems(
|
||||
GgrsSchedule,
|
||||
motion::clear_fighter_presentation_snaps.in_set(GameplaySet::Input),
|
||||
)
|
||||
.add_systems(
|
||||
GgrsSchedule,
|
||||
(
|
||||
@@ -202,6 +207,8 @@ impl Plugin for FighterPlugin {
|
||||
.checksum_component_with_hash::<FighterTranslation>()
|
||||
.rollback_component_with_copy::<FighterPreviousTranslation>()
|
||||
.checksum_component_with_hash::<FighterPreviousTranslation>()
|
||||
.rollback_component_with_copy::<FighterPresentationSnap>()
|
||||
.checksum_component_with_hash::<FighterPresentationSnap>()
|
||||
.rollback_component_with_copy::<FighterPreviousFacingDirection>()
|
||||
.checksum_component_with_hash::<FighterPreviousFacingDirection>()
|
||||
.rollback_component_with_copy::<animation::FighterAnimationFrame>()
|
||||
@@ -304,6 +311,7 @@ pub fn spawn_fighter(
|
||||
FighterVelocity::default(),
|
||||
FighterTranslation(spawn_position),
|
||||
FighterPreviousTranslation(spawn_position),
|
||||
FighterPresentationSnap::default(),
|
||||
),
|
||||
visual_root,
|
||||
animations,
|
||||
|
||||
+42
-2
@@ -21,6 +21,11 @@ pub struct FighterVelocity(pub FGVec2);
|
||||
#[derive(Component, Deref, DerefMut, Default, Clone, Copy, Hash)]
|
||||
pub struct FighterPreviousTranslation(pub FGVec2);
|
||||
|
||||
/// Marks a simulation frame whose position changed discontinuously for
|
||||
/// presentation purposes (for example, a landing correction).
|
||||
#[derive(Component, Default, Clone, Copy, Hash, Debug, PartialEq, Eq)]
|
||||
pub struct FighterPresentationSnap(pub bool);
|
||||
|
||||
#[derive(Component, Deref, DerefMut, Default, Clone, Copy, Hash)]
|
||||
#[require(FighterPreviousTranslation)]
|
||||
pub struct FighterTranslation(pub FGVec2);
|
||||
@@ -52,10 +57,31 @@ pub fn sample_presentation_translation(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sample_presentation_translation_with_snap(
|
||||
previous: FighterPreviousTranslation,
|
||||
current: FighterTranslation,
|
||||
overstep_fraction: f32,
|
||||
mode: PresentationMotionMode,
|
||||
snapped: FighterPresentationSnap,
|
||||
) -> Vec2 {
|
||||
if snapped.0 {
|
||||
Vec2::new(current.x.to_num(), current.y.to_num())
|
||||
} else {
|
||||
sample_presentation_translation(previous, current, overstep_fraction, mode)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear_fighter_presentation_snaps(mut fighters: Query<&mut FighterPresentationSnap>) {
|
||||
for mut snap in &mut fighters {
|
||||
snap.0 = false;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_fighter_translation_to_visuals(
|
||||
query: Query<(
|
||||
&FighterTranslation,
|
||||
&FighterPreviousTranslation,
|
||||
&FighterPresentationSnap,
|
||||
&FighterHitstopShake,
|
||||
&mut Transform,
|
||||
)>,
|
||||
@@ -64,12 +90,13 @@ pub fn apply_fighter_translation_to_visuals(
|
||||
game_settings: Res<GameSettings>,
|
||||
) {
|
||||
let overstep_fraction = timing.overstep_fraction();
|
||||
for (translation, previous_translation, hitstop_shake, mut transform) in query {
|
||||
let translation = sample_presentation_translation(
|
||||
for (translation, previous_translation, presentation_snap, hitstop_shake, mut transform) in query {
|
||||
let translation = sample_presentation_translation_with_snap(
|
||||
*previous_translation,
|
||||
*translation,
|
||||
overstep_fraction,
|
||||
debug_settings.motion_sampling,
|
||||
*presentation_snap,
|
||||
);
|
||||
transform.translation = Vec3::new(
|
||||
translation.x,
|
||||
@@ -169,6 +196,19 @@ mod tests {
|
||||
assert_eq!(position, Vec2::new(7.0, 14.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn presentation_translation_uses_current_sample_after_snap() {
|
||||
let position = sample_presentation_translation_with_snap(
|
||||
FighterPreviousTranslation(FGVec2::new(FGi32::lit("2"), FGi32::lit("4"))),
|
||||
FighterTranslation(FGVec2::new(FGi32::lit("6"), FGi32::lit("12"))),
|
||||
0.25,
|
||||
PresentationMotionMode::Extrapolation,
|
||||
FighterPresentationSnap(true),
|
||||
);
|
||||
|
||||
assert_eq!(position, Vec2::new(6.0, 12.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn snapshot_fighter_motion_copies_current_world_state() {
|
||||
let mut world = World::new();
|
||||
|
||||
@@ -123,6 +123,7 @@ pub fn collision_interrupt(
|
||||
ctx.ecb.left -= diff;
|
||||
ctx.ecb.right -= diff;
|
||||
ctx.translation.0 = res.hit_position;
|
||||
ctx.presentation_snap.0 = true;
|
||||
ctx.surface.land(res.line_id);
|
||||
Some(FighterState::Land(LandingState::create(landing_type)))
|
||||
}
|
||||
|
||||
@@ -248,6 +248,7 @@ mod tests {
|
||||
use crate::fighter::ecb::FighterPreviousECB;
|
||||
use crate::fighter::{FighterECB, FighterSurfaceState};
|
||||
use crate::stage::line::StagePolyType;
|
||||
use crate::input::FighterInput;
|
||||
|
||||
fn two_segment_floor() -> StagePoly {
|
||||
StagePoly::build(
|
||||
@@ -375,15 +376,19 @@ mod tests {
|
||||
right: FGVec2::lit("0.5", "0.0"),
|
||||
};
|
||||
let previous_ecb = FighterPreviousECB::from(ecb);
|
||||
let mut presentation_snap = crate::fighter::FighterPresentationSnap::default();
|
||||
let mut context = CollisionContext {
|
||||
translation: &mut translation,
|
||||
prev_translation: &previous_translation,
|
||||
input: &FighterInput::default(),
|
||||
velocity: &mut velocity,
|
||||
surface: &mut surface,
|
||||
ecb: &mut ecb,
|
||||
prev_ecb: &previous_ecb,
|
||||
stage_collision: &stage_collision,
|
||||
leave_ground_ecb_lock_duration: 10,
|
||||
presentation_snap: &mut presentation_snap,
|
||||
downward_stick_threshold: FGi32::ONE,
|
||||
};
|
||||
|
||||
assert!(matches!(
|
||||
@@ -426,15 +431,19 @@ mod tests {
|
||||
right: FGVec2::lit("0.5", "0.0"),
|
||||
};
|
||||
let previous_ecb = FighterPreviousECB::from(ecb);
|
||||
let mut presentation_snap = crate::fighter::FighterPresentationSnap::default();
|
||||
let mut context = CollisionContext {
|
||||
translation: &mut translation,
|
||||
prev_translation: &previous_translation,
|
||||
input: &FighterInput::default(),
|
||||
velocity: &mut velocity,
|
||||
surface: &mut surface,
|
||||
ecb: &mut ecb,
|
||||
prev_ecb: &previous_ecb,
|
||||
stage_collision: &stage_collision,
|
||||
leave_ground_ecb_lock_duration: 10,
|
||||
presentation_snap: &mut presentation_snap,
|
||||
downward_stick_threshold: FGi32::ONE,
|
||||
};
|
||||
|
||||
assert!(matches!(
|
||||
|
||||
@@ -20,6 +20,7 @@ use crate::fighter::{
|
||||
Fighter, FighterECB, FighterFacingDirection, FighterHitboxes, FighterHitstop,
|
||||
FighterHitstopShake, FighterHitstopShakeKind, FighterHitstopShakeRole,
|
||||
FighterPreviousTranslation, FighterSurfaceState, FighterTranslation, FighterVelocity,
|
||||
FighterPresentationSnap,
|
||||
};
|
||||
use crate::fighter::surface::EcbLockPolicy;
|
||||
use crate::game_settings::GameSettings;
|
||||
@@ -193,12 +194,15 @@ impl UpdateContext<'_> {
|
||||
pub struct CollisionContext<'a> {
|
||||
pub translation: &'a mut FighterTranslation,
|
||||
pub prev_translation: &'a FighterPreviousTranslation,
|
||||
pub input: &'a FighterInput,
|
||||
pub velocity: &'a mut FighterVelocity,
|
||||
pub surface: &'a mut FighterSurfaceState,
|
||||
pub ecb: &'a mut FighterECB,
|
||||
pub prev_ecb: &'a FighterPreviousECB,
|
||||
pub stage_collision: &'a StageCollision,
|
||||
pub leave_ground_ecb_lock_duration: u32,
|
||||
pub presentation_snap: &'a mut FighterPresentationSnap,
|
||||
pub downward_stick_threshold: FGi32,
|
||||
}
|
||||
|
||||
impl CollisionContext<'_> {
|
||||
@@ -206,6 +210,8 @@ impl CollisionContext<'_> {
|
||||
&self,
|
||||
) -> Option<crate::fighter::collision::AirCollisionWithStageResult> {
|
||||
let ignored_polys = self.surface.ignored_polygon_ids();
|
||||
let ignore_soft_platforms = self.input.get_last_frame().movement.y
|
||||
<= -self.downward_stick_threshold;
|
||||
crate::fighter::collision::air_collide_with_stage(
|
||||
self.prev_translation,
|
||||
self.translation,
|
||||
@@ -213,6 +219,7 @@ impl CollisionContext<'_> {
|
||||
self.ecb,
|
||||
self.stage_collision,
|
||||
&ignored_polys,
|
||||
ignore_soft_platforms,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -239,6 +246,7 @@ fighter_states! {
|
||||
pub struct FighterFrameQuery {
|
||||
pub translation: &'static mut FighterTranslation,
|
||||
pub prev_translation: &'static mut FighterPreviousTranslation,
|
||||
pub presentation_snap: &'static mut FighterPresentationSnap,
|
||||
pub input: &'static mut FighterInput,
|
||||
pub hitboxes: &'static mut FighterHitboxes,
|
||||
pub velocity: &'static mut FighterVelocity,
|
||||
@@ -349,12 +357,15 @@ impl<'w, 's> FighterFrameQueryItem<'w, 's> {
|
||||
CollisionContext {
|
||||
translation: &mut self.translation,
|
||||
prev_translation: &self.prev_translation,
|
||||
presentation_snap: &mut self.presentation_snap,
|
||||
input: &self.input,
|
||||
velocity: &mut self.velocity,
|
||||
surface: &mut self.surface,
|
||||
ecb: &mut self.ecb,
|
||||
prev_ecb: &self.prev_ecb,
|
||||
stage_collision,
|
||||
leave_ground_ecb_lock_duration: game_settings.fighter_common.leave_ground_ecb_frame_lock_duration
|
||||
leave_ground_ecb_lock_duration: game_settings.fighter_common.leave_ground_ecb_frame_lock_duration,
|
||||
downward_stick_threshold: game_settings.input_common.run_stick_threshold,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-3
@@ -116,13 +116,12 @@ impl Plugin for FighterInputPlugin {
|
||||
binding: GamepadBinding::Button(GamepadButton::North),
|
||||
action: InputMapAction::Jump,
|
||||
},
|
||||
// shield/airdodge -- both shoulder buttons, Melee-style
|
||||
GamepadInputMapElement {
|
||||
binding: GamepadBinding::Button(GamepadButton::LeftTrigger),
|
||||
binding: GamepadBinding::Button(GamepadButton::LeftTrigger2),
|
||||
action: InputMapAction::Shield,
|
||||
},
|
||||
GamepadInputMapElement {
|
||||
binding: GamepadBinding::Button(GamepadButton::RightTrigger),
|
||||
binding: GamepadBinding::Button(GamepadButton::RightTrigger2),
|
||||
action: InputMapAction::Shield,
|
||||
},
|
||||
// attack
|
||||
|
||||
Reference in New Issue
Block a user