Add physics interpolation and fix lingering ground attack hitboxes

This commit is contained in:
2026-08-30 22:28:27 +02:00
parent 8b9e300277
commit 4a2ec33d60
9 changed files with 242 additions and 45 deletions
+43 -18
View File
@@ -1,8 +1,13 @@
//! Presentation-only state and tuning for the match camera. //! Presentation-only state and tuning for the match camera.
use bevy::prelude::*; use bevy::prelude::*;
use bevy_ggrs::GgrsFrameTiming;
use crate::{fighter::manifest::FighterManifest, stage::manifest::StageCameraProfile}; use crate::{
debug_tools::DebugSettings,
fighter::{manifest::FighterManifest, motion::sample_presentation_translation},
stage::manifest::StageCameraProfile,
};
const EMPTY_FRAME_HALF_SIZE: f32 = 40.0; const EMPTY_FRAME_HALF_SIZE: f32 = 40.0;
const SUBJECT_COUNT_SCALES: [f32; 4] = [1.5, 1.32, 1.16, 1.0]; const SUBJECT_COUNT_SCALES: [f32; 4] = [1.5, 1.32, 1.16, 1.0];
@@ -110,7 +115,10 @@ impl Plugin for MatchCameraPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_systems( app.add_systems(
Update, Update,
(update_match_camera, debug_draw_camera) (
update_match_camera,
debug_draw_camera.run_if(crate::debug_tools::camera_debug_enabled),
)
.chain() .chain()
.run_if(in_state(crate::AppState::InMatch)), .run_if(in_state(crate::AppState::InMatch)),
); );
@@ -302,6 +310,7 @@ fn update_match_camera(
mut fighters: Query< mut fighters: Query<
( (
&crate::fighter::motion::FighterTranslation, &crate::fighter::motion::FighterTranslation,
&crate::fighter::motion::FighterPreviousTranslation,
&crate::fighter::Fighter, &crate::fighter::Fighter,
&crate::fighter::FighterFacingDirection, &crate::fighter::FighterFacingDirection,
&mut FighterCameraExtents, &mut FighterCameraExtents,
@@ -311,25 +320,34 @@ fn update_match_camera(
camera_profiles: Query<&StageCameraProfile, With<MatchCamera>>, camera_profiles: Query<&StageCameraProfile, With<MatchCamera>>,
mut cameras: Query<(&mut Transform, &mut Projection, &mut MatchCamera), With<MatchCamera>>, mut cameras: Query<(&mut Transform, &mut Projection, &mut MatchCamera), With<MatchCamera>>,
manifests: Res<Assets<FighterManifest>>, manifests: Res<Assets<FighterManifest>>,
timing: Res<GgrsFrameTiming>,
debug_settings: Res<DebugSettings>,
) { ) {
let Ok(profile) = camera_profiles.single() else { let Ok(profile) = camera_profiles.single() else {
return; return;
}; };
let subjects = fighters let subjects = fighters
.iter_mut() .iter_mut()
.map(|(translation, fighter, facing, mut extents)| { .map(
let manifest = manifests |(translation, previous_translation, fighter, facing, mut extents)| {
.get(&fighter.manifest) let manifest = manifests
.expect("Fighter manifest should be valid"); .get(&fighter.manifest)
fighter_subject_bounds( .expect("Fighter manifest should be valid");
Vec2::new(translation.x.to_num(), translation.y.to_num()), fighter_subject_bounds(
manifest.camera, sample_presentation_translation(
*facing, *previous_translation,
profile, *translation,
&mut extents, timing.overstep_fraction(),
time.delta_secs(), debug_settings.motion_sampling,
) ),
}) manifest.camera,
*facing,
profile,
&mut extents,
time.delta_secs(),
)
},
)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
for (mut transform, mut projection, mut camera) in &mut cameras { for (mut transform, mut projection, mut camera) in &mut cameras {
@@ -444,6 +462,7 @@ fn debug_draw_camera(
fighters: Query< fighters: Query<
( (
&crate::fighter::motion::FighterTranslation, &crate::fighter::motion::FighterTranslation,
&crate::fighter::motion::FighterPreviousTranslation,
&crate::fighter::Fighter, &crate::fighter::Fighter,
&crate::fighter::FighterFacingDirection, &crate::fighter::FighterFacingDirection,
&FighterCameraExtents, &FighterCameraExtents,
@@ -452,6 +471,8 @@ fn debug_draw_camera(
>, >,
cameras: Query<(&MatchCamera, &StageCameraProfile)>, cameras: Query<(&MatchCamera, &StageCameraProfile)>,
manifests: Res<Assets<FighterManifest>>, manifests: Res<Assets<FighterManifest>>,
timing: Res<GgrsFrameTiming>,
debug_settings: Res<DebugSettings>,
) { ) {
for (camera, profile) in &cameras { for (camera, profile) in &cameras {
draw_rectangle( draw_rectangle(
@@ -465,12 +486,16 @@ fn debug_draw_camera(
Color::srgb(1.0, 0.5, 0.0), Color::srgb(1.0, 0.5, 0.0),
); );
draw_rectangle(&mut gizmos, camera.frame, Color::srgb(1.0, 1.0, 0.0)); draw_rectangle(&mut gizmos, camera.frame, Color::srgb(1.0, 1.0, 0.0));
for (translation, fighter, _facing, extents) in &fighters { for (translation, previous_translation, fighter, _facing, extents) in &fighters {
let manifest = manifests let manifest = manifests
.get(&fighter.manifest) .get(&fighter.manifest)
.expect("Fighter manifest should be valid"); .expect("Fighter manifest should be valid");
let center = Vec2::new(translation.x.to_num(), translation.y.to_num()) let center = sample_presentation_translation(
+ Vec2::Y * manifest.camera.vertical_origin_offset.to_num::<f32>(); *previous_translation,
*translation,
timing.overstep_fraction(),
debug_settings.motion_sampling,
) + Vec2::Y * manifest.camera.vertical_origin_offset.to_num::<f32>();
draw_rectangle( draw_rectangle(
&mut gizmos, &mut gizmos,
CameraFrameBounds { CameraFrameBounds {
+82 -5
View File
@@ -19,6 +19,29 @@ pub enum FighterDebugRowKind {
Facing, Facing,
} }
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum PresentationMotionMode {
#[default]
Interpolation,
Extrapolation,
}
impl PresentationMotionMode {
pub const fn label(self) -> &'static str {
match self {
Self::Interpolation => "Interpolation",
Self::Extrapolation => "Extrapolation",
}
}
fn cycle(&mut self) {
*self = match self {
Self::Interpolation => Self::Extrapolation,
Self::Extrapolation => Self::Interpolation,
};
}
}
impl FighterDebugRowKind { impl FighterDebugRowKind {
const ALL: [Self; 6] = [ const ALL: [Self; 6] = [
Self::None, Self::None,
@@ -57,6 +80,7 @@ enum DebugFlag {
StageCollision, StageCollision,
AnimationBones, AnimationBones,
AttackHitboxes, AttackHitboxes,
Camera,
DrawThroughGeometry, DrawThroughGeometry,
} }
@@ -73,6 +97,9 @@ enum DebugMenuEntry {
PlayerSlots { PlayerSlots {
label: &'static str, label: &'static str,
}, },
MotionSampling {
label: &'static str,
},
} }
const FIGHTER_MENU: &[DebugMenuEntry] = &[ const FIGHTER_MENU: &[DebugMenuEntry] = &[
@@ -87,6 +114,10 @@ const FIGHTER_MENU: &[DebugMenuEntry] = &[
label: "Animation bones", label: "Animation bones",
flag: DebugFlag::AnimationBones, flag: DebugFlag::AnimationBones,
}, },
DebugMenuEntry::Toggle {
label: "Camera",
flag: DebugFlag::Camera,
},
DebugMenuEntry::Toggle { DebugMenuEntry::Toggle {
label: "Attack hitboxes", label: "Attack hitboxes",
flag: DebugFlag::AttackHitboxes, flag: DebugFlag::AttackHitboxes,
@@ -103,10 +134,15 @@ const DIAGNOSTICS_MENU: &[DebugMenuEntry] = &[DebugMenuEntry::Toggle {
flag: DebugFlag::Network, flag: DebugFlag::Network,
}]; }];
const RENDERING_MENU: &[DebugMenuEntry] = &[DebugMenuEntry::Toggle { const RENDERING_MENU: &[DebugMenuEntry] = &[
label: "Draw through geometry", DebugMenuEntry::MotionSampling {
flag: DebugFlag::DrawThroughGeometry, label: "Motion sampling",
}]; },
DebugMenuEntry::Toggle {
label: "Draw through geometry",
flag: DebugFlag::DrawThroughGeometry,
},
];
const ROOT_MENU: &[DebugMenuEntry] = &[ const ROOT_MENU: &[DebugMenuEntry] = &[
DebugMenuEntry::Submenu { DebugMenuEntry::Submenu {
@@ -146,7 +182,9 @@ pub struct DebugSettings {
pub stage_collision: bool, pub stage_collision: bool,
pub animation_bones: bool, pub animation_bones: bool,
pub attack_hitboxes: bool, pub attack_hitboxes: bool,
pub camera: bool,
pub draw_through_geometry: bool, pub draw_through_geometry: bool,
pub motion_sampling: PresentationMotionMode,
} }
impl Default for DebugSettings { impl Default for DebugSettings {
@@ -162,7 +200,9 @@ impl Default for DebugSettings {
stage_collision: false, stage_collision: false,
animation_bones: false, animation_bones: false,
attack_hitboxes: false, attack_hitboxes: false,
camera: false,
draw_through_geometry: false, draw_through_geometry: false,
motion_sampling: PresentationMotionMode::default(),
} }
} }
} }
@@ -223,6 +263,7 @@ impl DebugSettings {
DebugFlag::AnimationBones => self.animation_bones, DebugFlag::AnimationBones => self.animation_bones,
DebugFlag::AttackHitboxes => self.attack_hitboxes, DebugFlag::AttackHitboxes => self.attack_hitboxes,
DebugFlag::DrawThroughGeometry => self.draw_through_geometry, DebugFlag::DrawThroughGeometry => self.draw_through_geometry,
DebugFlag::Camera => self.camera,
} }
} }
@@ -236,9 +277,14 @@ impl DebugSettings {
DebugFlag::DrawThroughGeometry => { DebugFlag::DrawThroughGeometry => {
self.draw_through_geometry = !self.draw_through_geometry self.draw_through_geometry = !self.draw_through_geometry
} }
DebugFlag::Camera => self.camera = !self.camera,
} }
} }
fn cycle_motion_sampling(&mut self) {
self.motion_sampling.cycle();
}
fn rows_mut(&mut self, handle: usize) -> &mut [FighterDebugRowKind; PLAYER_DEBUG_ROW_COUNT] { fn rows_mut(&mut self, handle: usize) -> &mut [FighterDebugRowKind; PLAYER_DEBUG_ROW_COUNT] {
self.player_rows self.player_rows
.entry(handle) .entry(handle)
@@ -342,6 +388,16 @@ fn handle_palette_input(
rows[selected] = rows[selected].cycle(direction); rows[selected] = rows[selected].cycle(direction);
} }
} }
if let DebugMenuPage::Static(entries) = page
&& matches!(
entries.get(settings.selected()),
Some(DebugMenuEntry::MotionSampling { .. })
)
&& (gamepad.just_pressed(GamepadButton::DPadLeft)
|| gamepad.just_pressed(GamepadButton::DPadRight))
{
settings.cycle_motion_sampling();
}
if gamepad.just_pressed(GamepadButton::South) { if gamepad.just_pressed(GamepadButton::South) {
match page { match page {
DebugMenuPage::Static(entries) if page_len != 0 => match entries[settings.selected()] { DebugMenuPage::Static(entries) if page_len != 0 => match entries[settings.selected()] {
@@ -350,6 +406,7 @@ fn handle_palette_input(
settings.push_page(DebugMenuPage::Static(entries)); settings.push_page(DebugMenuPage::Static(entries));
} }
DebugMenuEntry::PlayerSlots { .. } => settings.push_page(DebugMenuPage::PlayerList), DebugMenuEntry::PlayerSlots { .. } => settings.push_page(DebugMenuPage::PlayerList),
DebugMenuEntry::MotionSampling { .. } => settings.cycle_motion_sampling(),
}, },
DebugMenuPage::PlayerList if page_len != 0 => { DebugMenuPage::PlayerList if page_len != 0 => {
let handle = player_handles[settings.selected()]; let handle = player_handles[settings.selected()];
@@ -409,6 +466,12 @@ fn draw_palette(
| DebugMenuEntry::PlayerSlots { label } => { | DebugMenuEntry::PlayerSlots { label } => {
ui.label(format!("{marker} {label} >")); ui.label(format!("{marker} {label} >"));
} }
DebugMenuEntry::MotionSampling { label } => {
ui.label(format!(
"{marker} {label}: {}",
settings.motion_sampling.label()
));
}
} }
} }
} }
@@ -437,7 +500,7 @@ fn draw_palette(
let controls = if matches!(page, DebugMenuPage::PlayerRows(_)) { let controls = if matches!(page, DebugMenuPage::PlayerRows(_)) {
"D-pad: select/change B: back" "D-pad: select/change B: back"
} else { } else {
"D-pad: select A: enter/toggle B: back" "D-pad: select A: enter/toggle Left/right: change B: back"
}; };
ui.label(controls); ui.label(controls);
}); });
@@ -471,6 +534,10 @@ pub fn attack_hitboxes_enabled(settings: Res<DebugSettings>) -> bool {
settings.attack_hitboxes settings.attack_hitboxes
} }
pub fn camera_debug_enabled(settings: Res<DebugSettings>) -> bool {
settings.camera
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@@ -509,4 +576,14 @@ mod tests {
); );
assert!(settings.player_rows(1).is_none()); assert!(settings.player_rows(1).is_none());
} }
#[test]
fn presentation_motion_mode_cycles_between_choices() {
let mut mode = PresentationMotionMode::Interpolation;
mode.cycle();
assert_eq!(mode, PresentationMotionMode::Extrapolation);
mode.cycle();
assert_eq!(mode, PresentationMotionMode::Interpolation);
}
} }
+2 -10
View File
@@ -4,7 +4,7 @@ use bevy::prelude::*;
use crate::{ use crate::{
fighter::{ fighter::{
FighterECB, FighterFacingDirection, FighterTranslation, FighterVisual, FighterFacingDirection, FighterVisual,
visual::FighterAnimations, visual::FighterAnimations,
}, },
player::Player, player::Player,
@@ -54,15 +54,6 @@ pub fn advance_fighter_animation_frames(mut fighters: Query<&mut FighterAnimatio
} }
} }
pub fn apply_fighter_translation_to_visuals(
query: Query<(&FighterTranslation, &FighterECB, &mut Transform)>,
) {
for (translation, ecb, mut transform) in query {
let translation = translation.0 + ecb.get_bottom_point();
transform.translation = Vec3::new(translation.x.to_num(), translation.y.to_num(), 0.0);
}
}
pub fn setup_fighter_animation_player( pub fn setup_fighter_animation_player(
mut commands: Commands, mut commands: Commands,
animated: Query<Entity, Added<AnimationPlayer>>, animated: Query<Entity, Added<AnimationPlayer>>,
@@ -153,4 +144,5 @@ pub enum AnimKind {
// Attacks // Attacks
AttackJab1, AttackJab1,
AttackUpTilt,
} }
+3 -1
View File
@@ -64,7 +64,8 @@ impl AttackAngle {
#[derive(Serialize, Deserialize, Reflect, PartialEq, Eq, Hash, Clone, Debug)] #[derive(Serialize, Deserialize, Reflect, PartialEq, Eq, Hash, Clone, Debug)]
pub enum AttackKind { pub enum AttackKind {
NAir, NAir,
Jab Jab,
UpTilt,
} }
impl AttackKind { impl AttackKind {
@@ -72,6 +73,7 @@ impl AttackKind {
match self { match self {
AttackKind::NAir => AnimKind::AttackJab1, AttackKind::NAir => AnimKind::AttackJab1,
AttackKind::Jab => AnimKind::AttackJab1, AttackKind::Jab => AnimKind::AttackJab1,
AttackKind::UpTilt => AnimKind::AttackUpTilt,
} }
} }
} }
+15 -8
View File
@@ -125,13 +125,10 @@ impl Plugin for FighterPlugin {
state::state_collision_interrupt_system.in_set(GameplaySet::Collision), state::state_collision_interrupt_system.in_set(GameplaySet::Collision),
) )
.add_systems( .add_systems(
GgrsSchedule, Update,
( (motion::apply_fighter_translation_to_visuals, animation::apply_animation)
animation::apply_fighter_translation_to_visuals,
animation::apply_animation,
)
.chain() .chain()
.in_set(GameplaySet::Animation), .run_if(in_state(crate::AppState::InMatch)),
) )
// Rollback registration lives next to the systems that write these // Rollback registration lives next to the systems that write these
// components; a component simulated here but missing from this list // components; a component simulated here but missing from this list
@@ -177,6 +174,13 @@ pub struct FighterHitboxes {
pub active_hitboxes: Vec<usize> pub active_hitboxes: Vec<usize>
} }
impl FighterHitboxes {
pub fn clear(&mut self) {
self.attack_script = None;
self.active_hitboxes.clear();
}
}
pub fn spawn_fighter( pub fn spawn_fighter(
commands: &mut Commands, commands: &mut Commands,
player_handle: usize, player_handle: usize,
@@ -203,8 +207,11 @@ pub fn spawn_fighter(
attack_script: None, attack_script: None,
active_hitboxes: vec![] active_hitboxes: vec![]
}), }),
FighterVelocity::default(), (
FighterTranslation(spawn_position), FighterVelocity::default(),
FighterTranslation(spawn_position),
FighterPreviousTranslation(spawn_position),
),
visual_root, visual_root,
animations, animations,
attack_scripts, attack_scripts,
+78 -1
View File
@@ -2,8 +2,13 @@
//! this module is what turns that into a position. //! this module is what turns that into a position.
use bevy::prelude::*; use bevy::prelude::*;
use bevy_ggrs::GgrsFrameTiming;
use crate::{fighter::{FighterFacingDirection, baked_animation::FixedMat4}, math::{int::FGi32, vec::FGVec2, vec3::FGVec3}}; use crate::debug_tools::PresentationMotionMode;
use crate::{
fighter::{FighterECB, FighterFacingDirection, baked_animation::FixedMat4},
math::{int::FGi32, vec::FGVec2, vec3::FGVec3},
};
#[derive(Component, Clone, Copy)] #[derive(Component, Clone, Copy)]
pub struct Grounded; pub struct Grounded;
@@ -29,6 +34,49 @@ impl FighterTranslation {
} }
} }
/// Samples deterministic fighter positions for presentation without changing
/// gameplay state or rollback timing.
pub fn sample_presentation_translation(
previous: FighterPreviousTranslation,
current: FighterTranslation,
overstep_fraction: f32,
mode: PresentationMotionMode,
) -> Vec2 {
let previous = Vec2::new(previous.x.to_num(), previous.y.to_num());
let current = Vec2::new(current.x.to_num(), current.y.to_num());
match mode {
PresentationMotionMode::Interpolation => previous.lerp(current, overstep_fraction),
PresentationMotionMode::Extrapolation => current + (current - previous) * overstep_fraction,
}
}
pub fn apply_fighter_translation_to_visuals(
query: Query<(
&FighterTranslation,
&FighterPreviousTranslation,
&FighterECB,
&mut Transform,
)>,
timing: Res<GgrsFrameTiming>,
debug_settings: Res<crate::debug_tools::DebugSettings>,
) {
let overstep_fraction = timing.overstep_fraction();
for (translation, previous_translation, ecb, mut transform) in query {
let translation = sample_presentation_translation(
*previous_translation,
*translation,
overstep_fraction,
debug_settings.motion_sampling,
);
let bottom = ecb.get_bottom_point();
transform.translation = Vec3::new(
translation.x + bottom.x.to_num::<f32>(),
translation.y + bottom.y.to_num::<f32>(),
0.0,
);
}
}
pub fn apply_air_motion( pub fn apply_air_motion(
fighters: Query<( fighters: Query<(
&FighterVelocity, &FighterVelocity,
@@ -51,3 +99,32 @@ pub fn copy_fighter_transform_to_visuals(fighters: Query<(&FighterTranslation, &
} }
pub fn fighter_movement() {} pub fn fighter_movement() {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn presentation_translation_interpolates_between_fixed_samples() {
let position = sample_presentation_translation(
FighterPreviousTranslation(FGVec2::new(FGi32::lit("2"), FGi32::lit("4"))),
FighterTranslation(FGVec2::new(FGi32::lit("6"), FGi32::lit("12"))),
0.25,
PresentationMotionMode::Interpolation,
);
assert_eq!(position, Vec2::new(3.0, 6.0));
}
#[test]
fn presentation_translation_extrapolates_from_current_sample() {
let position = sample_presentation_translation(
FighterPreviousTranslation(FGVec2::new(FGi32::lit("2"), FGi32::lit("4"))),
FighterTranslation(FGVec2::new(FGi32::lit("6"), FGi32::lit("12"))),
0.25,
PresentationMotionMode::Extrapolation,
);
assert_eq!(position, Vec2::new(7.0, 14.0));
}
}
+11
View File
@@ -32,6 +32,17 @@ impl FighterStateImpl for GroundAttackState {
state_context.hitboxes.attack_script = Some(state_context.attack_script_assets.scripts[&self.attack_kind].clone()); state_context.hitboxes.attack_script = Some(state_context.attack_script_assets.scripts[&self.attack_kind].clone());
state_context.hitboxes.active_hitboxes.clear(); state_context.hitboxes.active_hitboxes.clear();
} }
fn on_exit(&mut self, state_context: &mut super::FighterStateContext) {
state_context.hitboxes.clear();
}
fn check_collision_interrupt(
&mut self,
_state_context: &mut super::FighterStateContext,
) -> Option<FighterState> {
None
}
} }
pub fn check_interrupt(state_context: &super::FighterStateContext, grounded_common: &GroundedStateCommon) -> Option<FighterState> { pub fn check_interrupt(state_context: &super::FighterStateContext, grounded_common: &GroundedStateCommon) -> Option<FighterState> {
+8 -1
View File
@@ -52,6 +52,10 @@ macro_rules! fighter_states {
match self { $(Self::$variant(s) => s.on_enter(ctx)),* } match self { $(Self::$variant(s) => s.on_enter(ctx)),* }
} }
pub fn on_exit(&mut self, ctx: &mut FighterStateContext) {
match self { $(Self::$variant(s) => s.on_exit(ctx)),* }
}
pub fn check_collision_interrupt(&mut self, ctx: &mut FighterStateContext) -> Option<FighterState> { pub fn check_collision_interrupt(&mut self, ctx: &mut FighterStateContext) -> Option<FighterState> {
match self { $(Self::$variant(s) => s.check_collision_interrupt(ctx)),* } match self { $(Self::$variant(s) => s.check_collision_interrupt(ctx)),* }
} }
@@ -68,6 +72,7 @@ pub trait FighterStateImpl: Sized {
fn check_interrupt(&self, state_context: &mut FighterStateContext) -> Option<FighterState>; fn check_interrupt(&self, state_context: &mut FighterStateContext) -> Option<FighterState>;
fn on_enter(&mut self, _state_context: &mut FighterStateContext) {} fn on_enter(&mut self, _state_context: &mut FighterStateContext) {}
fn on_exit(&mut self, _state_context: &mut FighterStateContext) {}
fn update(&mut self, _state_context: &mut FighterStateContext); fn update(&mut self, _state_context: &mut FighterStateContext);
fn check_collision_interrupt( fn check_collision_interrupt(
@@ -272,7 +277,7 @@ pub fn state_interrupt_system(
for fighter in &mut query { for fighter in &mut query {
let StatefulFighterQueryItem { let StatefulFighterQueryItem {
entity, entity,
state, mut state,
mut frame, mut frame,
animations, animations,
animation_player_link, animation_player_link,
@@ -301,6 +306,7 @@ pub fn state_interrupt_system(
let mut new_state = new_state; let mut new_state = new_state;
let old_state_name = state.name(); let old_state_name = state.name();
let new_state_name = new_state.name(); let new_state_name = new_state.name();
state.on_exit(&mut state_context);
new_state.on_enter(&mut state_context); new_state.on_enter(&mut state_context);
commands commands
.entity(entity) .entity(entity)
@@ -352,6 +358,7 @@ pub fn state_collision_interrupt_system(
let mut new_state = new_state; let mut new_state = new_state;
let old_state_name = state.name(); let old_state_name = state.name();
let new_state_name = new_state.name(); let new_state_name = new_state.name();
state.on_exit(&mut state_context);
new_state.on_enter(&mut state_context); new_state.on_enter(&mut state_context);
commands commands
.entity(entity) .entity(entity)
-1
View File
@@ -21,7 +21,6 @@ impl Plugin for FighterNetcodePlugin {
app.add_plugins(plugin) app.add_plugins(plugin)
// Engine-level components that every rolled-back entity may carry. // Engine-level components that every rolled-back entity may carry.
// Gameplay components are registered by the plugin that owns them. // Gameplay components are registered by the plugin that owns them.
.rollback_component_with_clone::<Transform>()
.rollback_component_with_clone::<Name>() .rollback_component_with_clone::<Name>()
.add_systems( .add_systems(
EguiPrimaryContextPass, EguiPrimaryContextPass,