Add live hitbox viewer

This commit is contained in:
2026-10-03 13:16:23 +02:00
parent 02d444f749
commit 6c0d266daf
8 changed files with 750 additions and 14 deletions
+22 -11
View File
@@ -154,6 +154,7 @@ type FighterBakeProcessor = LoadTransformAndSave<
>;
pub struct BakedAnimationPlugin;
pub struct BakedAnimationVisualPlugin;
pub struct BakedAnimationAssetProcessingPlugin;
@@ -172,17 +173,27 @@ impl Plugin for BakedAnimationAssetProcessingPlugin {
impl Plugin for BakedAnimationPlugin {
fn build(&self, app: &mut App) {
app.add_plugins(BakedAnimationAssetProcessingPlugin)
.add_systems(
bevy_ggrs::prelude::GgrsSchedule,
super::runtime::update_fighter_bone_matrices
.in_set(crate::schedule::GameplaySet::Animation),
)
.add_systems(
Update,
super::visual_binding::apply_baked_fighter_visual_pose,
)
.add_observer(super::visual_binding::bind_fighter_visual_bones);
app.add_plugins((
BakedAnimationAssetProcessingPlugin,
BakedAnimationVisualPlugin,
))
.add_systems(
bevy_ggrs::prelude::GgrsSchedule,
super::runtime::update_fighter_bone_matrices
.in_set(super::BakedAnimationSamplingSet)
.in_set(crate::schedule::GameplaySet::Animation),
);
}
}
impl Plugin for BakedAnimationVisualPlugin {
fn build(&self, app: &mut App) {
app.add_systems(
Update,
super::visual_binding::apply_baked_fighter_visual_pose
.after(super::BakedAnimationSamplingSet),
)
.add_observer(super::visual_binding::bind_fighter_visual_bones);
}
}
+6 -1
View File
@@ -5,12 +5,17 @@ mod runtime;
mod visual_binding;
pub use crate::math::fixed_transform::FixedMat4;
pub use asset_baking::{BakedAnimationAssetProcessingPlugin, BakedAnimationPlugin};
pub use asset_baking::{
BakedAnimationAssetProcessingPlugin, BakedAnimationPlugin, BakedAnimationVisualPlugin,
};
pub use runtime::{
BakedAnimationClip, BakedFighterAnimations, FighterBoneIndexMap, FighterBoneMatrices,
resample_fighter_bone_matrices, update_fighter_bone_matrices,
};
#[derive(bevy::prelude::SystemSet, Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct BakedAnimationSamplingSet;
pub const BAKED_ANIMATION_FPS: u32 = 60;
#[cfg(test)]
+43
View File
@@ -55,6 +55,29 @@ impl BakedFighterAnimations {
pub fn frame_count(&self, kind: AnimKind) -> Option<u32> {
self.clip(kind).map(|clip| clip.frames.len() as u32)
}
/// Samples one bone in model space without depending on the runtime's
/// previous-frame buffers. Tooling uses this when the user scrubs between
/// unrelated frames.
pub fn sample_bone_model_transform(
&self,
frame: &FighterAnimationFrame,
bone: &str,
model_scale: FGi32,
) -> Option<FixedMat4> {
let local = self.sample_pose(frame)?;
let index = self.bone_names.iter().position(|name| name == bone)?;
let mut model = vec![None; local.len()];
resolve_model_transform(index, &self.parents, local, &mut model).ok()?;
let scale = FixedMat4 {
cols: [
[model_scale, FGi32::ZERO, FGi32::ZERO, FGi32::ZERO],
[FGi32::ZERO, model_scale, FGi32::ZERO, FGi32::ZERO],
[FGi32::ZERO, FGi32::ZERO, model_scale, FGi32::ZERO],
[FGi32::ZERO, FGi32::ZERO, FGi32::ZERO, FGi32::ONE],
],
};
Some(scale.mul(model[index]?))
}
pub(super) fn clip(&self, kind: AnimKind) -> Option<&BakedAnimationClip> {
self.clip_indices
.get(&kind)
@@ -78,6 +101,26 @@ pub struct FighterBoneMatrices {
pub struct FighterBoneIndexMap(pub HashMap<String, usize>);
impl FighterBoneMatrices {
/// Samples a baked pose for a preview or other non-gameplay caller.
pub fn sample_baked_pose(
&mut self,
baked: &BakedFighterAnimations,
frame: &FighterAnimationFrame,
model_scale: FGi32,
) -> bool {
let Some(pose) = baked.sample_pose(frame) else {
return false;
};
self.set_pose(
frame.kind,
&baked.bone_names,
&baked.parents,
pose,
model_scale,
);
true
}
#[cfg(test)]
pub(super) fn buffer_capacities(&self) -> [usize; 5] {
[
@@ -92,9 +92,12 @@ pub(super) fn bind_fighter_visual_bones(
pub(super) fn apply_baked_fighter_visual_pose(
mut nodes: Query<(&mut Transform, &BakedFighterBone)>,
fighters: Query<&FighterBoneMatrices>,
timing: Res<GgrsFrameTiming>,
timing: Option<Res<GgrsFrameTiming>>,
) {
let overstep_fraction = timing.overstep_fraction();
let overstep_fraction = timing
.as_deref()
.map(GgrsFrameTiming::overstep_fraction)
.unwrap_or(0.0);
for (mut transform, bone) in &mut nodes {
let Ok(matrices) = fighters.get(bone.fighter) else {
continue;