Fix fighter visuals after landing snap (#67)
## Summary - Mark landing position corrections as presentation discontinuities. - Render and frame fighters from the current position for the snap frame. - Keep previous translation history unchanged for gameplay collision and attack logic. Reviewed-on: #67 Co-authored-by: Álex Román Núñez <eirexe@eirteam.moe>
This commit was merged in pull request #67.
This commit is contained in:
+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,
|
||||
|
||||
+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)))
|
||||
}
|
||||
|
||||
@@ -376,6 +376,7 @@ 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,
|
||||
@@ -386,6 +387,7 @@ mod tests {
|
||||
prev_ecb: &previous_ecb,
|
||||
stage_collision: &stage_collision,
|
||||
leave_ground_ecb_lock_duration: 10,
|
||||
presentation_snap: &mut presentation_snap,
|
||||
downward_stick_threshold: FGi32::ONE,
|
||||
};
|
||||
|
||||
@@ -429,6 +431,7 @@ 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,
|
||||
@@ -439,6 +442,7 @@ mod tests {
|
||||
prev_ecb: &previous_ecb,
|
||||
stage_collision: &stage_collision,
|
||||
leave_ground_ecb_lock_duration: 10,
|
||||
presentation_snap: &mut presentation_snap,
|
||||
downward_stick_threshold: FGi32::ONE,
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -200,6 +201,7 @@ pub struct CollisionContext<'a> {
|
||||
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,
|
||||
}
|
||||
|
||||
@@ -244,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,
|
||||
@@ -354,6 +357,7 @@ 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,
|
||||
|
||||
Reference in New Issue
Block a user