Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e7a35a095e | ||
|
|
81e60d9acb |
+5
-9
@@ -5,7 +5,7 @@ use bevy_ggrs::GgrsFrameTiming;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
debug_tools::DebugSettings,
|
debug_tools::DebugSettings,
|
||||||
fighter::{manifest::FighterManifest, motion::{sample_presentation_translation_with_snap, FighterPresentationSnap}},
|
fighter::{manifest::FighterManifest, motion::sample_presentation_translation},
|
||||||
stage::manifest::StageCameraProfile,
|
stage::manifest::StageCameraProfile,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -313,7 +313,6 @@ fn update_match_camera(
|
|||||||
&crate::fighter::motion::FighterPreviousTranslation,
|
&crate::fighter::motion::FighterPreviousTranslation,
|
||||||
&crate::fighter::Fighter,
|
&crate::fighter::Fighter,
|
||||||
&crate::fighter::FighterFacingDirection,
|
&crate::fighter::FighterFacingDirection,
|
||||||
&FighterPresentationSnap,
|
|
||||||
&mut FighterCameraExtents,
|
&mut FighterCameraExtents,
|
||||||
),
|
),
|
||||||
With<crate::player::Player>,
|
With<crate::player::Player>,
|
||||||
@@ -330,17 +329,16 @@ fn update_match_camera(
|
|||||||
let subjects = fighters
|
let subjects = fighters
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.map(
|
.map(
|
||||||
|(translation, previous_translation, fighter, facing, presentation_snap, mut extents)| {
|
|(translation, previous_translation, fighter, facing, mut extents)| {
|
||||||
let manifest = manifests
|
let manifest = manifests
|
||||||
.get(&fighter.manifest)
|
.get(&fighter.manifest)
|
||||||
.expect("Fighter manifest should be valid");
|
.expect("Fighter manifest should be valid");
|
||||||
fighter_subject_bounds(
|
fighter_subject_bounds(
|
||||||
sample_presentation_translation_with_snap(
|
sample_presentation_translation(
|
||||||
*previous_translation,
|
*previous_translation,
|
||||||
*translation,
|
*translation,
|
||||||
timing.overstep_fraction(),
|
timing.overstep_fraction(),
|
||||||
debug_settings.motion_sampling,
|
debug_settings.motion_sampling,
|
||||||
*presentation_snap,
|
|
||||||
),
|
),
|
||||||
manifest.camera,
|
manifest.camera,
|
||||||
*facing,
|
*facing,
|
||||||
@@ -467,7 +465,6 @@ fn debug_draw_camera(
|
|||||||
&crate::fighter::motion::FighterPreviousTranslation,
|
&crate::fighter::motion::FighterPreviousTranslation,
|
||||||
&crate::fighter::Fighter,
|
&crate::fighter::Fighter,
|
||||||
&crate::fighter::FighterFacingDirection,
|
&crate::fighter::FighterFacingDirection,
|
||||||
&FighterPresentationSnap,
|
|
||||||
&FighterCameraExtents,
|
&FighterCameraExtents,
|
||||||
),
|
),
|
||||||
With<crate::player::Player>,
|
With<crate::player::Player>,
|
||||||
@@ -489,16 +486,15 @@ 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, previous_translation, fighter, _facing, presentation_snap, 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 = sample_presentation_translation_with_snap(
|
let center = sample_presentation_translation(
|
||||||
*previous_translation,
|
*previous_translation,
|
||||||
*translation,
|
*translation,
|
||||||
timing.overstep_fraction(),
|
timing.overstep_fraction(),
|
||||||
debug_settings.motion_sampling,
|
debug_settings.motion_sampling,
|
||||||
*presentation_snap,
|
|
||||||
) + Vec2::Y * manifest.camera.vertical_origin_offset.to_num::<f32>();
|
) + Vec2::Y * manifest.camera.vertical_origin_offset.to_num::<f32>();
|
||||||
draw_rectangle(
|
draw_rectangle(
|
||||||
&mut gizmos,
|
&mut gizmos,
|
||||||
|
|||||||
+23
-41
@@ -1,9 +1,10 @@
|
|||||||
//! Fighter debug views: gizmos and the egui inspector window.
|
//! Fighter debug views: gizmos and the egui inspector window.
|
||||||
|
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
use bevy::color::palettes::css::{ORANGE, ORANGE_RED, WHITE};
|
use bevy::color::palettes::css::ORANGE;
|
||||||
use bevy::prelude::*;
|
use bevy::{color::palettes::css::YELLOW, prelude::*};
|
||||||
use bevy_egui::{egui, EguiContexts};
|
use bevy_egui::{EguiContexts, egui};
|
||||||
|
|
||||||
use crate::debug_tools::{DebugSettings, FighterDebugRowKind};
|
use crate::debug_tools::{DebugSettings, FighterDebugRowKind};
|
||||||
use crate::fighter::animation::FighterAnimationFrame;
|
use crate::fighter::animation::FighterAnimationFrame;
|
||||||
@@ -14,14 +15,14 @@ use crate::fighter::attack::{
|
|||||||
use crate::fighter::baked_animation::{BakedFighterAnimations, FighterBoneMatrices};
|
use crate::fighter::baked_animation::{BakedFighterAnimations, FighterBoneMatrices};
|
||||||
use crate::fighter::ecb::FighterPreviousECB;
|
use crate::fighter::ecb::FighterPreviousECB;
|
||||||
use crate::fighter::errors::FighterErrors;
|
use crate::fighter::errors::FighterErrors;
|
||||||
|
use crate::fighter::hurtbox::FixedAffineCapsule;
|
||||||
use crate::fighter::manifest::FighterManifest;
|
use crate::fighter::manifest::FighterManifest;
|
||||||
use crate::fighter::state::{air, FighterState};
|
use crate::fighter::state::{FighterState, air};
|
||||||
use crate::fighter::visual::FighterAnimations;
|
use crate::fighter::visual::FighterAnimations;
|
||||||
use crate::fighter::{
|
use crate::fighter::{
|
||||||
Fighter, FighterECB, FighterFacingDirection, FighterHitboxes, FighterPreviousTranslation,
|
Fighter, FighterECB, FighterFacingDirection, FighterHitboxes, FighterPreviousTranslation,
|
||||||
FighterTranslation, FighterVelocity,
|
FighterTranslation, FighterVelocity,
|
||||||
};
|
};
|
||||||
use crate::filled_gizmos::FilledGizmos;
|
|
||||||
use crate::game_settings::GameSettings;
|
use crate::game_settings::GameSettings;
|
||||||
use crate::input::FighterInput;
|
use crate::input::FighterInput;
|
||||||
use crate::math::int::FGi32;
|
use crate::math::int::FGi32;
|
||||||
@@ -37,7 +38,6 @@ pub fn debug_draw_ecb(
|
|||||||
&FighterPreviousTranslation,
|
&FighterPreviousTranslation,
|
||||||
)>,
|
)>,
|
||||||
mut gizmos: Gizmos,
|
mut gizmos: Gizmos,
|
||||||
mut filled_gizmos: FilledGizmos,
|
|
||||||
) {
|
) {
|
||||||
for (ecb, trf, ecb_prev, trf_prev) in ecbs {
|
for (ecb, trf, ecb_prev, trf_prev) in ecbs {
|
||||||
gizmos.cross(
|
gizmos.cross(
|
||||||
@@ -46,18 +46,15 @@ pub fn debug_draw_ecb(
|
|||||||
bevy::color::palettes::css::BLUE,
|
bevy::color::palettes::css::BLUE,
|
||||||
);
|
);
|
||||||
|
|
||||||
let prev_transformed = ecb_prev.to_3d_lineloop(Transform::from_xyz(
|
|
||||||
trf_prev.x.to_num::<f32>(),
|
|
||||||
trf_prev.y.to_num(),
|
|
||||||
0.0,
|
|
||||||
));
|
|
||||||
filled_gizmos.polygon(&prev_transformed, ORANGE_RED);
|
|
||||||
|
|
||||||
let transformed =
|
let transformed =
|
||||||
ecb.to_3d_lineloop(Transform::from_xyz(trf.x.to_num(), trf.y.to_num(), 0.0));
|
ecb.to_3d_lineloop(Transform::from_xyz(trf.x.to_num(), trf.y.to_num(), 0.0));
|
||||||
filled_gizmos.polygon(&transformed, ORANGE);
|
gizmos.lineloop(transformed, YELLOW);
|
||||||
gizmos.lineloop(transformed, WHITE);
|
let prev_transformed = ecb_prev.to_3d_lineloop(Transform::from_xyz(
|
||||||
|
trf_prev.x.to_num(),
|
||||||
|
trf_prev.y.to_num(),
|
||||||
|
-0.1,
|
||||||
|
));
|
||||||
|
gizmos.lineloop(prev_transformed, ORANGE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn fighter_debug(
|
pub fn fighter_debug(
|
||||||
@@ -186,23 +183,15 @@ pub fn animation_debug(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hurtbox_debug(
|
pub fn hurtbox_debug(query: Query<&FighterSolvedHurtboxes>, mut gizmos: Gizmos) {
|
||||||
query: Query<&FighterSolvedHurtboxes>,
|
|
||||||
mut filled_gizmos: FilledGizmos,
|
|
||||||
) {
|
|
||||||
for hurtboxes in &query {
|
for hurtboxes in &query {
|
||||||
for hurtbox in &hurtboxes.0 {
|
for hurtbox in &hurtboxes.0 {
|
||||||
filled_gizmos.affine_capsule(
|
draw_affine_capsule(&mut gizmos, hurtbox.capsule, YELLOW);
|
||||||
hurtbox.capsule.transform.to_mat4(),
|
|
||||||
hurtbox.capsule.radius.to_num(),
|
|
||||||
hurtbox.capsule.half_length.to_num(),
|
|
||||||
Color::srgba(1.0, 1.0, 0.0, 0.75),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*fn draw_affine_capsule(
|
fn draw_affine_capsule(
|
||||||
gizmos: &mut Gizmos,
|
gizmos: &mut Gizmos,
|
||||||
capsule: FixedAffineCapsule,
|
capsule: FixedAffineCapsule,
|
||||||
color: impl Into<Color> + Copy,
|
color: impl Into<Color> + Copy,
|
||||||
@@ -224,9 +213,9 @@ pub fn hurtbox_debug(
|
|||||||
for &(start, end) in &wireframe.lines {
|
for &(start, end) in &wireframe.lines {
|
||||||
gizmos.line(vertices[start], vertices[end], color);
|
gizmos.line(vertices[start], vertices[end], color);
|
||||||
}
|
}
|
||||||
}*/
|
}
|
||||||
|
|
||||||
/*struct CapsuleWireVertex {
|
struct CapsuleWireVertex {
|
||||||
direction: Vec3,
|
direction: Vec3,
|
||||||
center_sign: f32,
|
center_sign: f32,
|
||||||
}
|
}
|
||||||
@@ -234,9 +223,9 @@ pub fn hurtbox_debug(
|
|||||||
struct CapsuleWireframe {
|
struct CapsuleWireframe {
|
||||||
vertices: Vec<CapsuleWireVertex>,
|
vertices: Vec<CapsuleWireVertex>,
|
||||||
lines: Vec<(usize, usize)>,
|
lines: Vec<(usize, usize)>,
|
||||||
}*/
|
}
|
||||||
|
|
||||||
/*fn capsule_wireframe() -> &'static CapsuleWireframe {
|
fn capsule_wireframe() -> &'static CapsuleWireframe {
|
||||||
const RESOLUTION: usize = 16;
|
const RESOLUTION: usize = 16;
|
||||||
const HEMISPHERE_STEPS: usize = 4;
|
const HEMISPHERE_STEPS: usize = 4;
|
||||||
static WIREFRAME: OnceLock<CapsuleWireframe> = OnceLock::new();
|
static WIREFRAME: OnceLock<CapsuleWireframe> = OnceLock::new();
|
||||||
@@ -317,7 +306,7 @@ struct CapsuleWireframe {
|
|||||||
|
|
||||||
CapsuleWireframe { vertices, lines }
|
CapsuleWireframe { vertices, lines }
|
||||||
})
|
})
|
||||||
}*/
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod capsule_debug_tests {
|
mod capsule_debug_tests {
|
||||||
@@ -337,7 +326,7 @@ mod capsule_debug_tests {
|
|||||||
assert!(marker.abs_diff_eq(Vec3::new(3.0, 4.0, 3.0), 1e-5));
|
assert!(marker.abs_diff_eq(Vec3::new(3.0, 4.0, 3.0), 1e-5));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*#[test]
|
#[test]
|
||||||
fn capsule_wireframe_caps_extend_in_opposite_directions() {
|
fn capsule_wireframe_caps_extend_in_opposite_directions() {
|
||||||
let wireframe = capsule_wireframe();
|
let wireframe = capsule_wireframe();
|
||||||
let lower: Vec<_> = wireframe
|
let lower: Vec<_> = wireframe
|
||||||
@@ -355,7 +344,7 @@ mod capsule_debug_tests {
|
|||||||
assert!(upper.iter().all(|vertex| vertex.direction.y >= 0.0));
|
assert!(upper.iter().all(|vertex| vertex.direction.y >= 0.0));
|
||||||
assert!(lower.iter().any(|vertex| vertex.direction == Vec3::NEG_Y));
|
assert!(lower.iter().any(|vertex| vertex.direction == Vec3::NEG_Y));
|
||||||
assert!(upper.iter().any(|vertex| vertex.direction == Vec3::Y));
|
assert!(upper.iter().any(|vertex| vertex.direction == Vec3::Y));
|
||||||
}*/
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn attack_debug(
|
pub fn attack_debug(
|
||||||
@@ -367,7 +356,6 @@ pub fn attack_debug(
|
|||||||
&FighterAnimationFrame,
|
&FighterAnimationFrame,
|
||||||
)>,
|
)>,
|
||||||
mut gizmos: Gizmos,
|
mut gizmos: Gizmos,
|
||||||
mut filled_gizmos: FilledGizmos,
|
|
||||||
) {
|
) {
|
||||||
for (hitboxes, _matrices, _translation, _facing_direction, _frame) in query {
|
for (hitboxes, _matrices, _translation, _facing_direction, _frame) in query {
|
||||||
if let Some(active_attack) = &hitboxes.0 {
|
if let Some(active_attack) = &hitboxes.0 {
|
||||||
@@ -394,12 +382,6 @@ pub fn attack_debug(
|
|||||||
capsule_draw_trf.to_isometry(),
|
capsule_draw_trf.to_isometry(),
|
||||||
bevy::color::palettes::css::RED,
|
bevy::color::palettes::css::RED,
|
||||||
);
|
);
|
||||||
filled_gizmos.capsule(
|
|
||||||
capsule_draw_trf.to_isometry(),
|
|
||||||
solved_hitbox.capsule.radius.to_num(),
|
|
||||||
length * 0.5,
|
|
||||||
Color::srgba(1.0, 0.0, 0.0, 0.18),
|
|
||||||
);
|
|
||||||
|
|
||||||
gizmos.cross(start, 1.0, bevy::color::palettes::css::GREEN);
|
gizmos.cross(start, 1.0, bevy::color::palettes::css::GREEN);
|
||||||
gizmos.cross(end, 1.0, bevy::color::palettes::css::GREEN);
|
gizmos.cross(end, 1.0, bevy::color::palettes::css::GREEN);
|
||||||
|
|||||||
+1
-9
@@ -29,8 +29,7 @@ pub use attributes::FighterAttributes;
|
|||||||
pub use ecb::FighterECB;
|
pub use ecb::FighterECB;
|
||||||
pub use manifest::FighterId;
|
pub use manifest::FighterId;
|
||||||
pub use motion::{
|
pub use motion::{
|
||||||
FighterPresentationSnap, FighterPreviousFacingDirection, FighterPreviousTranslation,
|
FighterPreviousFacingDirection, FighterPreviousTranslation, FighterTranslation, FighterVelocity,
|
||||||
FighterTranslation, FighterVelocity,
|
|
||||||
};
|
};
|
||||||
pub use surface::FighterSurfaceState;
|
pub use surface::FighterSurfaceState;
|
||||||
|
|
||||||
@@ -148,10 +147,6 @@ impl Plugin for FighterPlugin {
|
|||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.add_plugins(FighterAttackPlugin)
|
app.add_plugins(FighterAttackPlugin)
|
||||||
// Every state gets a chance to interrupt itself, in priority order.
|
// 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(
|
.add_systems(
|
||||||
GgrsSchedule,
|
GgrsSchedule,
|
||||||
(
|
(
|
||||||
@@ -207,8 +202,6 @@ impl Plugin for FighterPlugin {
|
|||||||
.checksum_component_with_hash::<FighterTranslation>()
|
.checksum_component_with_hash::<FighterTranslation>()
|
||||||
.rollback_component_with_copy::<FighterPreviousTranslation>()
|
.rollback_component_with_copy::<FighterPreviousTranslation>()
|
||||||
.checksum_component_with_hash::<FighterPreviousTranslation>()
|
.checksum_component_with_hash::<FighterPreviousTranslation>()
|
||||||
.rollback_component_with_copy::<FighterPresentationSnap>()
|
|
||||||
.checksum_component_with_hash::<FighterPresentationSnap>()
|
|
||||||
.rollback_component_with_copy::<FighterPreviousFacingDirection>()
|
.rollback_component_with_copy::<FighterPreviousFacingDirection>()
|
||||||
.checksum_component_with_hash::<FighterPreviousFacingDirection>()
|
.checksum_component_with_hash::<FighterPreviousFacingDirection>()
|
||||||
.rollback_component_with_copy::<animation::FighterAnimationFrame>()
|
.rollback_component_with_copy::<animation::FighterAnimationFrame>()
|
||||||
@@ -311,7 +304,6 @@ pub fn spawn_fighter(
|
|||||||
FighterVelocity::default(),
|
FighterVelocity::default(),
|
||||||
FighterTranslation(spawn_position),
|
FighterTranslation(spawn_position),
|
||||||
FighterPreviousTranslation(spawn_position),
|
FighterPreviousTranslation(spawn_position),
|
||||||
FighterPresentationSnap::default(),
|
|
||||||
),
|
),
|
||||||
visual_root,
|
visual_root,
|
||||||
animations,
|
animations,
|
||||||
|
|||||||
+2
-42
@@ -21,11 +21,6 @@ pub struct FighterVelocity(pub FGVec2);
|
|||||||
#[derive(Component, Deref, DerefMut, Default, Clone, Copy, Hash)]
|
#[derive(Component, Deref, DerefMut, Default, Clone, Copy, Hash)]
|
||||||
pub struct FighterPreviousTranslation(pub FGVec2);
|
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)]
|
#[derive(Component, Deref, DerefMut, Default, Clone, Copy, Hash)]
|
||||||
#[require(FighterPreviousTranslation)]
|
#[require(FighterPreviousTranslation)]
|
||||||
pub struct FighterTranslation(pub FGVec2);
|
pub struct FighterTranslation(pub FGVec2);
|
||||||
@@ -57,31 +52,10 @@ 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(
|
pub fn apply_fighter_translation_to_visuals(
|
||||||
query: Query<(
|
query: Query<(
|
||||||
&FighterTranslation,
|
&FighterTranslation,
|
||||||
&FighterPreviousTranslation,
|
&FighterPreviousTranslation,
|
||||||
&FighterPresentationSnap,
|
|
||||||
&FighterHitstopShake,
|
&FighterHitstopShake,
|
||||||
&mut Transform,
|
&mut Transform,
|
||||||
)>,
|
)>,
|
||||||
@@ -90,13 +64,12 @@ pub fn apply_fighter_translation_to_visuals(
|
|||||||
game_settings: Res<GameSettings>,
|
game_settings: Res<GameSettings>,
|
||||||
) {
|
) {
|
||||||
let overstep_fraction = timing.overstep_fraction();
|
let overstep_fraction = timing.overstep_fraction();
|
||||||
for (translation, previous_translation, presentation_snap, hitstop_shake, mut transform) in query {
|
for (translation, previous_translation, hitstop_shake, mut transform) in query {
|
||||||
let translation = sample_presentation_translation_with_snap(
|
let translation = sample_presentation_translation(
|
||||||
*previous_translation,
|
*previous_translation,
|
||||||
*translation,
|
*translation,
|
||||||
overstep_fraction,
|
overstep_fraction,
|
||||||
debug_settings.motion_sampling,
|
debug_settings.motion_sampling,
|
||||||
*presentation_snap,
|
|
||||||
);
|
);
|
||||||
transform.translation = Vec3::new(
|
transform.translation = Vec3::new(
|
||||||
translation.x,
|
translation.x,
|
||||||
@@ -196,19 +169,6 @@ mod tests {
|
|||||||
assert_eq!(position, Vec2::new(7.0, 14.0));
|
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]
|
#[test]
|
||||||
fn snapshot_fighter_motion_copies_current_world_state() {
|
fn snapshot_fighter_motion_copies_current_world_state() {
|
||||||
let mut world = World::new();
|
let mut world = World::new();
|
||||||
|
|||||||
@@ -123,7 +123,6 @@ pub fn collision_interrupt(
|
|||||||
ctx.ecb.left -= diff;
|
ctx.ecb.left -= diff;
|
||||||
ctx.ecb.right -= diff;
|
ctx.ecb.right -= diff;
|
||||||
ctx.translation.0 = res.hit_position;
|
ctx.translation.0 = res.hit_position;
|
||||||
ctx.presentation_snap.0 = true;
|
|
||||||
ctx.surface.land(res.line_id);
|
ctx.surface.land(res.line_id);
|
||||||
Some(FighterState::Land(LandingState::create(landing_type)))
|
Some(FighterState::Land(LandingState::create(landing_type)))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -376,7 +376,6 @@ mod tests {
|
|||||||
right: FGVec2::lit("0.5", "0.0"),
|
right: FGVec2::lit("0.5", "0.0"),
|
||||||
};
|
};
|
||||||
let previous_ecb = FighterPreviousECB::from(ecb);
|
let previous_ecb = FighterPreviousECB::from(ecb);
|
||||||
let mut presentation_snap = crate::fighter::FighterPresentationSnap::default();
|
|
||||||
let mut context = CollisionContext {
|
let mut context = CollisionContext {
|
||||||
translation: &mut translation,
|
translation: &mut translation,
|
||||||
prev_translation: &previous_translation,
|
prev_translation: &previous_translation,
|
||||||
@@ -387,7 +386,6 @@ mod tests {
|
|||||||
prev_ecb: &previous_ecb,
|
prev_ecb: &previous_ecb,
|
||||||
stage_collision: &stage_collision,
|
stage_collision: &stage_collision,
|
||||||
leave_ground_ecb_lock_duration: 10,
|
leave_ground_ecb_lock_duration: 10,
|
||||||
presentation_snap: &mut presentation_snap,
|
|
||||||
downward_stick_threshold: FGi32::ONE,
|
downward_stick_threshold: FGi32::ONE,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -431,7 +429,6 @@ mod tests {
|
|||||||
right: FGVec2::lit("0.5", "0.0"),
|
right: FGVec2::lit("0.5", "0.0"),
|
||||||
};
|
};
|
||||||
let previous_ecb = FighterPreviousECB::from(ecb);
|
let previous_ecb = FighterPreviousECB::from(ecb);
|
||||||
let mut presentation_snap = crate::fighter::FighterPresentationSnap::default();
|
|
||||||
let mut context = CollisionContext {
|
let mut context = CollisionContext {
|
||||||
translation: &mut translation,
|
translation: &mut translation,
|
||||||
prev_translation: &previous_translation,
|
prev_translation: &previous_translation,
|
||||||
@@ -442,7 +439,6 @@ mod tests {
|
|||||||
prev_ecb: &previous_ecb,
|
prev_ecb: &previous_ecb,
|
||||||
stage_collision: &stage_collision,
|
stage_collision: &stage_collision,
|
||||||
leave_ground_ecb_lock_duration: 10,
|
leave_ground_ecb_lock_duration: 10,
|
||||||
presentation_snap: &mut presentation_snap,
|
|
||||||
downward_stick_threshold: FGi32::ONE,
|
downward_stick_threshold: FGi32::ONE,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ use crate::fighter::{
|
|||||||
Fighter, FighterECB, FighterFacingDirection, FighterHitboxes, FighterHitstop,
|
Fighter, FighterECB, FighterFacingDirection, FighterHitboxes, FighterHitstop,
|
||||||
FighterHitstopShake, FighterHitstopShakeKind, FighterHitstopShakeRole,
|
FighterHitstopShake, FighterHitstopShakeKind, FighterHitstopShakeRole,
|
||||||
FighterPreviousTranslation, FighterSurfaceState, FighterTranslation, FighterVelocity,
|
FighterPreviousTranslation, FighterSurfaceState, FighterTranslation, FighterVelocity,
|
||||||
FighterPresentationSnap,
|
|
||||||
};
|
};
|
||||||
use crate::fighter::surface::EcbLockPolicy;
|
use crate::fighter::surface::EcbLockPolicy;
|
||||||
use crate::game_settings::GameSettings;
|
use crate::game_settings::GameSettings;
|
||||||
@@ -201,7 +200,6 @@ pub struct CollisionContext<'a> {
|
|||||||
pub prev_ecb: &'a FighterPreviousECB,
|
pub prev_ecb: &'a FighterPreviousECB,
|
||||||
pub stage_collision: &'a StageCollision,
|
pub stage_collision: &'a StageCollision,
|
||||||
pub leave_ground_ecb_lock_duration: u32,
|
pub leave_ground_ecb_lock_duration: u32,
|
||||||
pub presentation_snap: &'a mut FighterPresentationSnap,
|
|
||||||
pub downward_stick_threshold: FGi32,
|
pub downward_stick_threshold: FGi32,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,7 +244,6 @@ fighter_states! {
|
|||||||
pub struct FighterFrameQuery {
|
pub struct FighterFrameQuery {
|
||||||
pub translation: &'static mut FighterTranslation,
|
pub translation: &'static mut FighterTranslation,
|
||||||
pub prev_translation: &'static mut FighterPreviousTranslation,
|
pub prev_translation: &'static mut FighterPreviousTranslation,
|
||||||
pub presentation_snap: &'static mut FighterPresentationSnap,
|
|
||||||
pub input: &'static mut FighterInput,
|
pub input: &'static mut FighterInput,
|
||||||
pub hitboxes: &'static mut FighterHitboxes,
|
pub hitboxes: &'static mut FighterHitboxes,
|
||||||
pub velocity: &'static mut FighterVelocity,
|
pub velocity: &'static mut FighterVelocity,
|
||||||
@@ -357,7 +354,6 @@ impl<'w, 's> FighterFrameQueryItem<'w, 's> {
|
|||||||
CollisionContext {
|
CollisionContext {
|
||||||
translation: &mut self.translation,
|
translation: &mut self.translation,
|
||||||
prev_translation: &self.prev_translation,
|
prev_translation: &self.prev_translation,
|
||||||
presentation_snap: &mut self.presentation_snap,
|
|
||||||
input: &self.input,
|
input: &self.input,
|
||||||
velocity: &mut self.velocity,
|
velocity: &mut self.velocity,
|
||||||
surface: &mut self.surface,
|
surface: &mut self.surface,
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
#import bevy_pbr::mesh_functions::{get_world_from_local, mesh_position_local_to_clip}
|
|
||||||
|
|
||||||
@group(#{MATERIAL_BIND_GROUP}) @binding(0) var<uniform> material_color: vec4<f32>;
|
|
||||||
|
|
||||||
struct Vertex {
|
|
||||||
@builtin(instance_index) instance_index: u32,
|
|
||||||
@location(0) position: vec3<f32>,
|
|
||||||
@location(1) color: vec4<f32>,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct VertexOutput {
|
|
||||||
@builtin(position) clip_position: vec4<f32>,
|
|
||||||
@location(0) color: vec4<f32>,
|
|
||||||
};
|
|
||||||
|
|
||||||
@vertex
|
|
||||||
fn vertex(vertex: Vertex) -> VertexOutput {
|
|
||||||
var output: VertexOutput;
|
|
||||||
output.clip_position = mesh_position_local_to_clip(
|
|
||||||
get_world_from_local(vertex.instance_index),
|
|
||||||
vec4<f32>(vertex.position, 1.0),
|
|
||||||
);
|
|
||||||
output.color = vertex.color;
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
@fragment
|
|
||||||
fn fragment(input: VertexOutput) -> @location(0) vec4<f32> {
|
|
||||||
return material_color * input.color;
|
|
||||||
}
|
|
||||||
@@ -1,417 +0,0 @@
|
|||||||
//! Immediate-mode filled debug geometry.
|
|
||||||
//!
|
|
||||||
//! Unlike Bevy's line-only `Gizmos`, this collects triangles into one dynamic
|
|
||||||
//! mesh. It is intended for small, transient debug overlays rather than game
|
|
||||||
//! geometry.
|
|
||||||
|
|
||||||
use std::f32::consts::{FRAC_PI_2, PI, TAU};
|
|
||||||
|
|
||||||
use bevy::{
|
|
||||||
asset::{load_internal_asset, uuid_handle, RenderAssetUsages},
|
|
||||||
camera::visibility::NoFrustumCulling,
|
|
||||||
ecs::system::SystemParam,
|
|
||||||
mesh::{Indices, MeshVertexBufferLayoutRef, PrimitiveTopology},
|
|
||||||
pbr::{MaterialPipeline, MaterialPipelineKey},
|
|
||||||
prelude::*,
|
|
||||||
render::render_resource::{
|
|
||||||
AsBindGroup, RenderPipelineDescriptor, SpecializedMeshPipelineError,
|
|
||||||
},
|
|
||||||
shader::ShaderRef,
|
|
||||||
};
|
|
||||||
|
|
||||||
const SPHERE_SECTORS: usize = 16;
|
|
||||||
const SPHERE_STACKS: usize = 8;
|
|
||||||
const CAPSULE_SECTORS: usize = 16;
|
|
||||||
const CAPSULE_HEMISPHERE_STEPS: usize = 4;
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct FilledGizmoGeometry {
|
|
||||||
positions: Vec<[f32; 3]>,
|
|
||||||
normals: Vec<[f32; 3]>,
|
|
||||||
colors: Vec<[f32; 4]>,
|
|
||||||
indices: Vec<u32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Resource, Default)]
|
|
||||||
struct FilledGizmoBuffer {
|
|
||||||
/// Geometry requested by the current schedule invocation.
|
|
||||||
pending: FilledGizmoGeometry,
|
|
||||||
/// The last completed fixed-timestep geometry. This is retained between
|
|
||||||
/// render frames, just like Bevy's fixed-timestep gizmos.
|
|
||||||
retained: FilledGizmoGeometry,
|
|
||||||
mesh: Option<Handle<Mesh>>,
|
|
||||||
fixed_commit: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Immediate-mode API for drawing filled debug shapes.
|
|
||||||
#[derive(SystemParam)]
|
|
||||||
pub struct FilledGizmos<'w> {
|
|
||||||
buffer: ResMut<'w, FilledGizmoBuffer>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FilledGizmos<'_> {
|
|
||||||
/// Draw a filled planar polygon. Vertices must be ordered around the
|
|
||||||
/// perimeter and the polygon must be convex.
|
|
||||||
pub fn polygon(&mut self, vertices: &[Vec3], color: impl Into<Color> + Copy) {
|
|
||||||
if vertices.len() < 3 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let color = color.into().to_linear().to_f32_array();
|
|
||||||
let normal = (vertices[1] - vertices[0])
|
|
||||||
.cross(vertices[2] - vertices[0])
|
|
||||||
.normalize_or_zero();
|
|
||||||
let base = self.buffer.pending.positions.len() as u32;
|
|
||||||
for &vertex in vertices {
|
|
||||||
self.push_vertex(vertex, normal, color);
|
|
||||||
}
|
|
||||||
for index in 2..vertices.len() as u32 {
|
|
||||||
self.buffer
|
|
||||||
.pending
|
|
||||||
.indices
|
|
||||||
.extend([base, base + index - 1, base + index]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sphere(&mut self, isometry: Isometry3d, radius: f32, color: impl Into<Color> + Copy) {
|
|
||||||
if radius <= 0.0 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let color = color.into().to_linear().to_f32_array();
|
|
||||||
let base = self.buffer.pending.positions.len() as u32;
|
|
||||||
for stack in 0..=SPHERE_STACKS {
|
|
||||||
let latitude = PI * 0.5 - stack as f32 * PI / SPHERE_STACKS as f32;
|
|
||||||
let (sin_lat, cos_lat) = latitude.sin_cos();
|
|
||||||
for sector in 0..=SPHERE_SECTORS {
|
|
||||||
let longitude = sector as f32 * TAU / SPHERE_SECTORS as f32;
|
|
||||||
let (sin_lon, cos_lon) = longitude.sin_cos();
|
|
||||||
let normal = Vec3::new(cos_lat * cos_lon, sin_lat, cos_lat * sin_lon);
|
|
||||||
self.push_vertex(
|
|
||||||
isometry.transform_point(normal * radius).into(),
|
|
||||||
isometry.rotation * normal,
|
|
||||||
color,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let stride = SPHERE_SECTORS + 1;
|
|
||||||
for stack in 0..SPHERE_STACKS {
|
|
||||||
for sector in 0..SPHERE_SECTORS {
|
|
||||||
let a = base + (stack * stride + sector) as u32;
|
|
||||||
let b = a + stride as u32;
|
|
||||||
self.buffer
|
|
||||||
.pending
|
|
||||||
.indices
|
|
||||||
.extend([a, b, a + 1, a + 1, b, b + 1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn capsule(
|
|
||||||
&mut self,
|
|
||||||
isometry: Isometry3d,
|
|
||||||
radius: f32,
|
|
||||||
half_length: f32,
|
|
||||||
color: impl Into<Color> + Copy,
|
|
||||||
) {
|
|
||||||
let transform = Mat4::from_scale_rotation_translation(
|
|
||||||
Vec3::ONE,
|
|
||||||
isometry.rotation,
|
|
||||||
isometry.translation.into(),
|
|
||||||
);
|
|
||||||
self.affine_capsule(transform, radius, half_length, color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Draw a capsule transformed by an arbitrary affine transform.
|
|
||||||
pub fn affine_capsule(
|
|
||||||
&mut self,
|
|
||||||
transform: Mat4,
|
|
||||||
radius: f32,
|
|
||||||
half_length: f32,
|
|
||||||
color: impl Into<Color> + Copy,
|
|
||||||
) {
|
|
||||||
if radius <= 0.0 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let color = color.into().to_linear().to_f32_array();
|
|
||||||
let base = self.buffer.pending.positions.len() as u32;
|
|
||||||
let mut rings = Vec::new();
|
|
||||||
for step in 1..=CAPSULE_HEMISPHERE_STEPS {
|
|
||||||
let angle = -FRAC_PI_2 + step as f32 * FRAC_PI_2 / CAPSULE_HEMISPHERE_STEPS as f32;
|
|
||||||
rings.push((
|
|
||||||
radius * angle.cos(),
|
|
||||||
-half_length + radius * angle.sin(),
|
|
||||||
angle.sin(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
rings.push((radius, half_length, 0.0));
|
|
||||||
for step in 1..CAPSULE_HEMISPHERE_STEPS {
|
|
||||||
let angle = step as f32 * FRAC_PI_2 / CAPSULE_HEMISPHERE_STEPS as f32;
|
|
||||||
rings.push((
|
|
||||||
radius * angle.cos(),
|
|
||||||
half_length + radius * angle.sin(),
|
|
||||||
angle.sin(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut ring_indices = Vec::new();
|
|
||||||
let bottom_pole = self.push_capsule_vertex(
|
|
||||||
&mut ring_indices,
|
|
||||||
transform,
|
|
||||||
Vec3::NEG_Y,
|
|
||||||
-half_length,
|
|
||||||
radius,
|
|
||||||
color,
|
|
||||||
);
|
|
||||||
for &(ring_radius, y, normal_y) in &rings {
|
|
||||||
let mut ring = Vec::with_capacity(CAPSULE_SECTORS);
|
|
||||||
for sector in 0..CAPSULE_SECTORS {
|
|
||||||
let angle = sector as f32 * TAU / CAPSULE_SECTORS as f32;
|
|
||||||
let (sin, cos) = angle.sin_cos();
|
|
||||||
let normal = Vec3::new(
|
|
||||||
cos * (1.0 - normal_y * normal_y).sqrt(),
|
|
||||||
normal_y,
|
|
||||||
sin * (1.0 - normal_y * normal_y).sqrt(),
|
|
||||||
);
|
|
||||||
ring.push(self.push_transformed_vertex(
|
|
||||||
transform,
|
|
||||||
Vec3::new(cos * ring_radius, y, sin * ring_radius),
|
|
||||||
normal,
|
|
||||||
color,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
ring_indices.push(ring);
|
|
||||||
}
|
|
||||||
let top_pole = self.push_capsule_vertex(
|
|
||||||
&mut ring_indices,
|
|
||||||
transform,
|
|
||||||
Vec3::Y,
|
|
||||||
half_length,
|
|
||||||
radius,
|
|
||||||
color,
|
|
||||||
);
|
|
||||||
for sector in 0..CAPSULE_SECTORS {
|
|
||||||
let next = (sector + 1) % CAPSULE_SECTORS;
|
|
||||||
self.buffer.pending.indices.extend([
|
|
||||||
bottom_pole,
|
|
||||||
ring_indices[0][next],
|
|
||||||
ring_indices[0][sector],
|
|
||||||
]);
|
|
||||||
for ring in ring_indices.windows(2) {
|
|
||||||
self.buffer.pending.indices.extend([
|
|
||||||
ring[0][sector],
|
|
||||||
ring[0][next],
|
|
||||||
ring[1][sector],
|
|
||||||
ring[1][sector],
|
|
||||||
ring[0][next],
|
|
||||||
ring[1][next],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
let last = ring_indices.len() - 1;
|
|
||||||
self.buffer.pending.indices.extend([
|
|
||||||
ring_indices[last][sector],
|
|
||||||
ring_indices[last][next],
|
|
||||||
top_pole,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
// Keep the base used by this shape observable to avoid accidental
|
|
||||||
// future changes making the generated indices refer to another shape.
|
|
||||||
debug_assert!(base <= bottom_pole);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push_capsule_vertex(
|
|
||||||
&mut self,
|
|
||||||
_rings: &mut Vec<Vec<u32>>,
|
|
||||||
transform: Mat4,
|
|
||||||
normal: Vec3,
|
|
||||||
y: f32,
|
|
||||||
radius: f32,
|
|
||||||
color: [f32; 4],
|
|
||||||
) -> u32 {
|
|
||||||
self.push_transformed_vertex(
|
|
||||||
transform,
|
|
||||||
Vec3::new(0.0, y + normal.y * radius, 0.0),
|
|
||||||
normal,
|
|
||||||
color,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push_transformed_vertex(
|
|
||||||
&mut self,
|
|
||||||
transform: Mat4,
|
|
||||||
position: Vec3,
|
|
||||||
normal: Vec3,
|
|
||||||
color: [f32; 4],
|
|
||||||
) -> u32 {
|
|
||||||
let transformed_normal = transform
|
|
||||||
.inverse()
|
|
||||||
.transpose()
|
|
||||||
.transform_vector3(normal)
|
|
||||||
.normalize_or_zero();
|
|
||||||
let index = self.buffer.pending.positions.len() as u32;
|
|
||||||
self.push_vertex(
|
|
||||||
transform.transform_point3(position),
|
|
||||||
transformed_normal,
|
|
||||||
color,
|
|
||||||
);
|
|
||||||
index
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push_vertex(&mut self, position: Vec3, normal: Vec3, color: [f32; 4]) {
|
|
||||||
self.buffer.pending.positions.push(position.to_array());
|
|
||||||
self.buffer.pending.normals.push(normal.to_array());
|
|
||||||
self.buffer.pending.colors.push(color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct FilledGizmosPlugin;
|
|
||||||
|
|
||||||
const FILLED_GIZMO_SHADER_HANDLE: Handle<Shader> =
|
|
||||||
uuid_handle!("0e4a979f-ea11-4e5a-91cd-19c89d4e9b53");
|
|
||||||
|
|
||||||
/// A deliberately minimal material: unlike `StandardMaterial`, its fragment
|
|
||||||
/// shader writes vertex colors directly and therefore bypasses PBR lighting
|
|
||||||
/// and camera tonemapping.
|
|
||||||
#[derive(Asset, AsBindGroup, Reflect, Clone, Debug)]
|
|
||||||
struct FilledGizmoMaterial {
|
|
||||||
#[uniform(0)]
|
|
||||||
color: LinearRgba,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Material for FilledGizmoMaterial {
|
|
||||||
fn vertex_shader() -> ShaderRef {
|
|
||||||
FILLED_GIZMO_SHADER_HANDLE.clone().into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fragment_shader() -> ShaderRef {
|
|
||||||
FILLED_GIZMO_SHADER_HANDLE.clone().into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn alpha_mode(&self) -> AlphaMode {
|
|
||||||
AlphaMode::Blend
|
|
||||||
}
|
|
||||||
|
|
||||||
fn enable_prepass() -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn enable_shadows() -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn specialize(
|
|
||||||
_pipeline: &MaterialPipeline,
|
|
||||||
descriptor: &mut RenderPipelineDescriptor,
|
|
||||||
layout: &MeshVertexBufferLayoutRef,
|
|
||||||
_key: MaterialPipelineKey<Self>,
|
|
||||||
) -> Result<(), SpecializedMeshPipelineError> {
|
|
||||||
descriptor.vertex.buffers = vec![layout.0.get_layout(&[
|
|
||||||
Mesh::ATTRIBUTE_POSITION.at_shader_location(0),
|
|
||||||
Mesh::ATTRIBUTE_COLOR.at_shader_location(1),
|
|
||||||
])?];
|
|
||||||
descriptor.primitive.cull_mode = None;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Plugin for FilledGizmosPlugin {
|
|
||||||
fn build(&self, app: &mut App) {
|
|
||||||
load_internal_asset!(
|
|
||||||
app,
|
|
||||||
FILLED_GIZMO_SHADER_HANDLE,
|
|
||||||
"filled_gizmo.wgsl",
|
|
||||||
Shader::from_wgsl
|
|
||||||
);
|
|
||||||
app.add_plugins(MaterialPlugin::<FilledGizmoMaterial>::default())
|
|
||||||
.init_resource::<FilledGizmoBuffer>()
|
|
||||||
.add_systems(Startup, setup_filled_gizmo_mesh)
|
|
||||||
.add_systems(FixedFirst, begin_fixed_filled_gizmos)
|
|
||||||
.add_systems(FixedLast, commit_fixed_filled_gizmos)
|
|
||||||
.add_systems(Last, upload_filled_gizmo_mesh);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn setup_filled_gizmo_mesh(
|
|
||||||
mut commands: Commands,
|
|
||||||
mut meshes: ResMut<Assets<Mesh>>,
|
|
||||||
mut materials: ResMut<Assets<FilledGizmoMaterial>>,
|
|
||||||
mut buffer: ResMut<FilledGizmoBuffer>,
|
|
||||||
) {
|
|
||||||
let mesh = meshes.add(
|
|
||||||
Mesh::new(
|
|
||||||
PrimitiveTopology::TriangleList,
|
|
||||||
RenderAssetUsages::default(),
|
|
||||||
)
|
|
||||||
.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, vec![[0.0; 3]; 3])
|
|
||||||
.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, vec![[0.0, 0.0, 1.0]; 3])
|
|
||||||
.with_inserted_attribute(Mesh::ATTRIBUTE_COLOR, vec![[0.0, 0.0, 0.0, 0.0]; 3])
|
|
||||||
.with_inserted_indices(Indices::U32(vec![0, 1, 2])),
|
|
||||||
);
|
|
||||||
let material = materials.add(FilledGizmoMaterial {
|
|
||||||
color: LinearRgba::WHITE,
|
|
||||||
});
|
|
||||||
commands.spawn((
|
|
||||||
Mesh3d(mesh.clone()),
|
|
||||||
MeshMaterial3d(material),
|
|
||||||
NoFrustumCulling,
|
|
||||||
));
|
|
||||||
buffer.mesh = Some(mesh);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn begin_fixed_filled_gizmos(mut buffer: ResMut<FilledGizmoBuffer>) {
|
|
||||||
buffer.pending = FilledGizmoGeometry::default();
|
|
||||||
buffer.fixed_commit = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn commit_fixed_filled_gizmos(mut buffer: ResMut<FilledGizmoBuffer>) {
|
|
||||||
let pending = std::mem::take(&mut buffer.pending);
|
|
||||||
buffer.pending = std::mem::replace(&mut buffer.retained, pending);
|
|
||||||
buffer.fixed_commit = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn upload_filled_gizmo_mesh(
|
|
||||||
mut meshes: ResMut<Assets<Mesh>>,
|
|
||||||
mut buffer: ResMut<FilledGizmoBuffer>,
|
|
||||||
) {
|
|
||||||
// No fixed tick and no render-frame requests means the existing mesh is
|
|
||||||
// already the correct image. In particular, do not replace it with the
|
|
||||||
// empty placeholder between fixed updates.
|
|
||||||
if !buffer.fixed_commit && buffer.pending.positions.is_empty() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let Some(handle) = buffer.mesh.clone() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let (positions, normals, colors, indices) = if buffer.fixed_commit {
|
|
||||||
// Retained geometry must remain in the CPU-side buffer after upload;
|
|
||||||
// otherwise the next render frame would have nothing to draw.
|
|
||||||
(
|
|
||||||
buffer.retained.positions.clone(),
|
|
||||||
buffer.retained.normals.clone(),
|
|
||||||
buffer.retained.colors.clone(),
|
|
||||||
buffer.retained.indices.clone(),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(
|
|
||||||
std::mem::take(&mut buffer.pending.positions),
|
|
||||||
std::mem::take(&mut buffer.pending.normals),
|
|
||||||
std::mem::take(&mut buffer.pending.colors),
|
|
||||||
std::mem::take(&mut buffer.pending.indices),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(mut mesh) = meshes.get_mut(&handle) {
|
|
||||||
if positions.is_empty() {
|
|
||||||
mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, vec![[0.0; 3]; 3]);
|
|
||||||
mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, vec![[0.0, 0.0, 1.0]; 3]);
|
|
||||||
mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, vec![[0.0, 0.0, 0.0, 0.0]; 3]);
|
|
||||||
mesh.insert_indices(Indices::U32(vec![0, 1, 2]));
|
|
||||||
} else {
|
|
||||||
mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions);
|
|
||||||
mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals);
|
|
||||||
mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, colors);
|
|
||||||
mesh.insert_indices(Indices::U32(indices));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buffer.pending = FilledGizmoGeometry::default();
|
|
||||||
buffer.fixed_commit = false;
|
|
||||||
}
|
|
||||||
+3
-2
@@ -116,12 +116,13 @@ impl Plugin for FighterInputPlugin {
|
|||||||
binding: GamepadBinding::Button(GamepadButton::North),
|
binding: GamepadBinding::Button(GamepadButton::North),
|
||||||
action: InputMapAction::Jump,
|
action: InputMapAction::Jump,
|
||||||
},
|
},
|
||||||
|
// shield/airdodge -- both shoulder buttons, Melee-style
|
||||||
GamepadInputMapElement {
|
GamepadInputMapElement {
|
||||||
binding: GamepadBinding::Button(GamepadButton::LeftTrigger2),
|
binding: GamepadBinding::Button(GamepadButton::LeftTrigger),
|
||||||
action: InputMapAction::Shield,
|
action: InputMapAction::Shield,
|
||||||
},
|
},
|
||||||
GamepadInputMapElement {
|
GamepadInputMapElement {
|
||||||
binding: GamepadBinding::Button(GamepadButton::RightTrigger2),
|
binding: GamepadBinding::Button(GamepadButton::RightTrigger),
|
||||||
action: InputMapAction::Shield,
|
action: InputMapAction::Shield,
|
||||||
},
|
},
|
||||||
// attack
|
// attack
|
||||||
|
|||||||
@@ -21,8 +21,6 @@
|
|||||||
//! save, click Rebuild in jackdaw (or run `jd build`) and it
|
//! save, click Rebuild in jackdaw (or run `jd build`) and it
|
||||||
//! appears in `Add Component`. No registration code is needed.
|
//! appears in `Add Component`. No registration code is needed.
|
||||||
|
|
||||||
#![recursion_limit = "512"]
|
|
||||||
|
|
||||||
use bevy::{
|
use bevy::{
|
||||||
asset::processor::{AssetProcessor, ProcessorState},
|
asset::processor::{AssetProcessor, ProcessorState},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
@@ -44,7 +42,6 @@ pub mod blend_asset;
|
|||||||
pub mod camera;
|
pub mod camera;
|
||||||
pub mod debug_tools;
|
pub mod debug_tools;
|
||||||
pub mod fighter;
|
pub mod fighter;
|
||||||
pub mod filled_gizmos;
|
|
||||||
pub mod game_settings;
|
pub mod game_settings;
|
||||||
pub mod input;
|
pub mod input;
|
||||||
pub mod match_loading;
|
pub mod match_loading;
|
||||||
@@ -117,7 +114,6 @@ impl Plugin for GamePlugin {
|
|||||||
input::FighterInputPlugin,
|
input::FighterInputPlugin,
|
||||||
replay::ReplayPlugin,
|
replay::ReplayPlugin,
|
||||||
debug_tools::DebugToolsPlugin,
|
debug_tools::DebugToolsPlugin,
|
||||||
filled_gizmos::FilledGizmosPlugin,
|
|
||||||
fighter::FighterPlugin,
|
fighter::FighterPlugin,
|
||||||
fighter::baked_animation::BakedAnimationPlugin,
|
fighter::baked_animation::BakedAnimationPlugin,
|
||||||
scripting::importer::FighterScriptImportPlugin,
|
scripting::importer::FighterScriptImportPlugin,
|
||||||
|
|||||||
+3
-59
@@ -1,14 +1,9 @@
|
|||||||
//! Rollback-simulated state for an active match.
|
//! Rollback-simulated state for an active match.
|
||||||
|
|
||||||
use bevy::input::InputSystems;
|
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy_ggrs::{RunGgrsSystems, prelude::*};
|
use bevy_ggrs::prelude::*;
|
||||||
|
|
||||||
use crate::{
|
use crate::match_loading::{MatchConfig, MatchRules};
|
||||||
AppState,
|
|
||||||
match_loading::{MatchConfig, MatchRules},
|
|
||||||
netcode::session::SessionConfig,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Resource, Clone, Debug, Hash, PartialEq, Eq)]
|
#[derive(Resource, Clone, Debug, Hash, PartialEq, Eq)]
|
||||||
pub struct MatchState {
|
pub struct MatchState {
|
||||||
@@ -24,11 +19,6 @@ pub struct PlayerMatchState {
|
|||||||
pub deaths: u32,
|
pub deaths: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Resource, Debug, Default, Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub struct PauseState {
|
|
||||||
pub paused: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MatchState {
|
impl MatchState {
|
||||||
pub fn from_config(config: &MatchConfig) -> Self {
|
pub fn from_config(config: &MatchConfig) -> Self {
|
||||||
let stocks_remaining = match &config.rules {
|
let stocks_remaining = match &config.rules {
|
||||||
@@ -56,56 +46,10 @@ pub struct MatchRuntimePlugin;
|
|||||||
impl Plugin for MatchRuntimePlugin {
|
impl Plugin for MatchRuntimePlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.rollback_resource_with_clone::<MatchState>()
|
app.rollback_resource_with_clone::<MatchState>()
|
||||||
.checksum_resource_with_hash::<MatchState>()
|
.checksum_resource_with_hash::<MatchState>();
|
||||||
.init_resource::<PauseState>()
|
|
||||||
.configure_sets(PreUpdate, RunGgrsSystems.run_if(ggrs_should_run))
|
|
||||||
.add_systems(OnEnter(AppState::InMatch), reset_pause)
|
|
||||||
.add_systems(OnExit(AppState::InMatch), reset_pause)
|
|
||||||
.add_systems(
|
|
||||||
PreUpdate,
|
|
||||||
toggle_pause
|
|
||||||
.after(InputSystems)
|
|
||||||
.before(RunGgrsSystems)
|
|
||||||
.run_if(in_state(AppState::InMatch)),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ggrs_should_run(pause: Option<Res<PauseState>>) -> bool {
|
|
||||||
!pause.is_some_and(|pause| pause.paused)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn toggle_pause(
|
|
||||||
key: Res<ButtonInput<KeyCode>>,
|
|
||||||
gamepads: Query<&Gamepad>,
|
|
||||||
session: Res<SessionConfig>,
|
|
||||||
mut pause: ResMut<PauseState>,
|
|
||||||
mut virtual_time: ResMut<Time<Virtual>>,
|
|
||||||
) {
|
|
||||||
if session.is_multiplayer() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let pressed = key.just_pressed(KeyCode::Escape)
|
|
||||||
|| gamepads
|
|
||||||
.iter()
|
|
||||||
.any(|gamepad| gamepad.just_pressed(GamepadButton::Start));
|
|
||||||
if !pressed {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
pause.paused = !pause.paused;
|
|
||||||
virtual_time.toggle();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reset_pause(
|
|
||||||
mut pause: ResMut<PauseState>,
|
|
||||||
mut virtual_time: ResMut<Time<Virtual>>,
|
|
||||||
) {
|
|
||||||
pause.paused = false;
|
|
||||||
virtual_time.unpause();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
+1
-1
@@ -226,7 +226,7 @@ fn gamepad_input(
|
|||||||
input.accept.accumulate(&status);
|
input.accept.accumulate(&status);
|
||||||
}
|
}
|
||||||
MenuInputMapAction::Start => {
|
MenuInputMapAction::Start => {
|
||||||
input.start.accumulate(&status);
|
input.accept.accumulate(&status);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,15 +42,6 @@ pub enum SessionConfigError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SessionConfig {
|
impl SessionConfig {
|
||||||
/// Returns whether this match has more than one remote participant.
|
|
||||||
pub fn is_multiplayer(&self) -> bool {
|
|
||||||
self.players.len() > 1
|
|
||||||
|| self
|
|
||||||
.players
|
|
||||||
.iter()
|
|
||||||
.any(|player| matches!(player, PlayerConnection::Remote(_)))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_args(args: &Args, player_count: usize) -> Result<Self, SessionConfigError> {
|
pub fn from_args(args: &Args, player_count: usize) -> Result<Self, SessionConfigError> {
|
||||||
if player_count == 0 {
|
if player_count == 0 {
|
||||||
return Err(SessionConfigError::NoPlayers);
|
return Err(SessionConfigError::NoPlayers);
|
||||||
@@ -205,13 +196,6 @@ mod tests {
|
|||||||
config.players,
|
config.players,
|
||||||
[PlayerConnection::Local, PlayerConnection::Local]
|
[PlayerConnection::Local, PlayerConnection::Local]
|
||||||
);
|
);
|
||||||
assert!(config.is_multiplayer());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn single_local_player_is_not_multiplayer() {
|
|
||||||
let config = SessionConfig::from_args(&args(&["game", "--players", "1"]), 1).unwrap();
|
|
||||||
assert!(!config.is_multiplayer());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -223,7 +207,6 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(matches!(config.players[0], PlayerConnection::Local));
|
assert!(matches!(config.players[0], PlayerConnection::Local));
|
||||||
assert!(matches!(config.players[1], PlayerConnection::Remote(_)));
|
assert!(matches!(config.players[1], PlayerConnection::Remote(_)));
|
||||||
assert!(config.is_multiplayer());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
+2
-53
@@ -3,7 +3,7 @@ use bevy::prelude::*;
|
|||||||
use crate::{
|
use crate::{
|
||||||
AppState,
|
AppState,
|
||||||
match_loading::{MatchConfig, MatchRules},
|
match_loading::{MatchConfig, MatchRules},
|
||||||
match_runtime::{MatchState, PauseState},
|
match_runtime::MatchState,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod battle_ui;
|
pub mod battle_ui;
|
||||||
@@ -11,9 +11,6 @@ pub mod errors;
|
|||||||
pub mod fighter_info;
|
pub mod fighter_info;
|
||||||
pub mod stock_icon;
|
pub mod stock_icon;
|
||||||
|
|
||||||
#[derive(Component)]
|
|
||||||
pub struct PauseOverlay;
|
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct InGameUIPlugin;
|
pub struct InGameUIPlugin;
|
||||||
|
|
||||||
@@ -35,54 +32,6 @@ impl Plugin for InGameUIPlugin {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
)
|
);
|
||||||
.add_systems(Update, sync_pause_overlay);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sync_pause_overlay(
|
|
||||||
pause: Res<PauseState>,
|
|
||||||
overlay: Query<Entity, With<PauseOverlay>>,
|
|
||||||
mut commands: Commands,
|
|
||||||
) {
|
|
||||||
if !pause.is_changed() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if pause.paused {
|
|
||||||
if overlay.is_empty() {
|
|
||||||
commands.spawn((
|
|
||||||
PauseOverlay,
|
|
||||||
Node {
|
|
||||||
position_type: PositionType::Absolute,
|
|
||||||
left: px(0),
|
|
||||||
right: px(0),
|
|
||||||
top: px(0),
|
|
||||||
bottom: px(0),
|
|
||||||
align_items: AlignItems::Center,
|
|
||||||
justify_content: JustifyContent::Center,
|
|
||||||
flex_direction: FlexDirection::Column,
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
BackgroundColor(Color::srgba(0.0, 0.0, 0.0, 0.7)),
|
|
||||||
ZIndex(100),
|
|
||||||
children![
|
|
||||||
(
|
|
||||||
Text::new("Paused"),
|
|
||||||
TextFont::from_font_size(crate::menus::style::H1_SIZE as f32),
|
|
||||||
TextColor(Color::WHITE),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
Text::new("Press Esc or Start to resume"),
|
|
||||||
TextFont::from_font_size(crate::menus::style::TEXT_SIZE as f32),
|
|
||||||
TextColor(Color::WHITE),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for entity in &overlay {
|
|
||||||
commands.entity(entity).despawn();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user