Make the baked animation system drive the character animations

This commit is contained in:
2026-09-01 21:07:53 +02:00
parent 89eeb6a868
commit b2da4a737d
11 changed files with 345 additions and 273 deletions
+1 -64
View File
@@ -1,23 +1,6 @@
use std::time::Duration;
use bevy::prelude::*;
use crate::{
fighter::{
FighterFacingDirection, FighterVisual,
visual::FighterAnimations,
},
player::Player,
};
#[derive(Component)]
pub struct FighterAnimationPlayerLink(Entity);
impl FighterAnimationPlayerLink {
pub fn player(&self) -> Entity {
self.0
}
}
use crate::fighter::{FighterFacingDirection, FighterVisual};
/// The simulation-owned animation clock. Rendering may interpolate freely, but
/// gameplay samples baked data only with this rollback-tracked value.
@@ -54,29 +37,6 @@ pub fn advance_fighter_animation_frames(mut fighters: Query<&mut FighterAnimatio
}
}
pub fn setup_fighter_animation_player(
mut commands: Commands,
animated: Query<Entity, Added<AnimationPlayer>>,
parents: Query<&ChildOf>,
fighters: Query<Entity, With<Player>>,
) {
for child in &animated {
let Some(parent) = parents
.iter_ancestors(child)
.filter_map(|parent| fighters.get(parent).ok())
.next()
else {
continue;
};
commands.entity(child).insert(AnimationTransitions::new());
info!("LINK");
commands
.entity(parent)
.insert(FighterAnimationPlayerLink(child));
}
}
pub fn apply_animation(
children: Query<
(
@@ -102,28 +62,6 @@ pub fn apply_animation(
}
}
pub fn animation_init(
fighters: Query<
(&FighterAnimations, &FighterAnimationPlayerLink),
Added<FighterAnimationPlayerLink>,
>,
mut commands: Commands,
mut anim_player_query: Query<(&mut AnimationPlayer, &mut AnimationTransitions)>,
) {
for (anim, link) in fighters {
commands
.entity(link.0)
.insert(AnimationGraphHandle(anim.graph.clone()));
info!("BEGIN! {:?}", fighters);
if let Ok((mut player, mut transitions)) = anim_player_query.get_mut(link.0) {
info!("DOS");
transitions
.play(&mut player, anim.clips[&AnimKind::Wait], Duration::ZERO)
.repeat();
}
}
}
#[derive(
Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Deserialize, serde::Serialize, Reflect,
)]
@@ -142,7 +80,6 @@ pub enum AnimKind {
Turn,
// Attacks
AttackJab1,
AttackUpTilt,
}
+188 -18
View File
@@ -1,9 +1,10 @@
//! Deterministic, fighter-local skeletal animation samples for gameplay.
//!
//! Rendering continues to use Bevy's floating-point animation player. Gameplay
//! reads this asset instead, so rollback never depends on render-time sampling.
//! Deterministic, fighter-local skeletal animation samples for gameplay and rendering.
use std::{collections::HashMap, io, path::Path};
use std::{
collections::{HashMap, HashSet},
io,
path::Path,
};
use bevy::{
asset::{
@@ -15,6 +16,7 @@ use bevy::{
},
prelude::*,
reflect::TypePath,
world_serialization::{WorldInstanceReady, WorldInstanceSpawner},
};
use bevy_ggrs::prelude::GgrsSchedule;
use serde::{Deserialize, Serialize};
@@ -64,6 +66,10 @@ impl FixedMat4 {
Ok(Self { cols })
}
pub fn to_mat4(self) -> Mat4 {
Mat4::from_cols_array_2d(&self.cols.map(|column| column.map(|value| value.to_num())))
}
pub fn mul(self, rhs: Self) -> Self {
let mut cols = [[FGi32::ZERO; 4]; 4];
for column in 0..4 {
@@ -147,7 +153,7 @@ impl FixedMat4 {
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct BakedAnimationClip {
/// One model-space matrix per stored node, for each 60 Hz simulation frame.
/// One parent-relative matrix per stored node, for each 60 Hz simulation frame.
pub frames: Vec<Vec<FixedMat4>>,
}
@@ -156,6 +162,8 @@ pub struct BakedFighterAnimations {
pub frame_rate: u32,
/// Indexed exactly like every frame in each clip.
pub bone_names: Vec<String>,
/// Parent node for each stored node, indexed like `bone_names`.
pub parents: Vec<Option<usize>>,
clips: Vec<BakedAnimationClip>,
clip_indices: HashMap<AnimKind, usize>,
}
@@ -190,8 +198,16 @@ impl BakedFighterAnimations {
#[derive(Component, Clone, Debug, Default)]
pub struct FighterBoneMatrices {
bone_names: Vec<String>,
parents: Vec<Option<usize>>,
matrices: Vec<FixedMat4>,
previous_frame_matrices: Vec<FixedMat4>,
local_matrices: Vec<FixedMat4>,
}
#[derive(Component)]
struct BakedFighterBone {
fighter: Entity,
index: usize,
}
impl FighterBoneMatrices {
@@ -221,20 +237,40 @@ impl FighterBoneMatrices {
.zip(self.matrices.iter().copied())
}
fn set_pose(&mut self, bone_names: &[String], matrices: &[FixedMat4]) {
pub fn local(&self, index: usize) -> Option<FixedMat4> {
self.local_matrices.get(index).copied()
}
fn set_pose(&mut self, bone_names: &[String], parents: &[Option<usize>], local: &[FixedMat4]) {
assert_eq!(
bone_names.len(),
matrices.len(),
local.len(),
"baked animation pose does not match its bone names"
);
assert_eq!(
bone_names.len(),
parents.len(),
"baked bone parents do not match names"
);
if self.bone_names.is_empty() {
self.bone_names.extend_from_slice(bone_names);
self.parents.extend_from_slice(parents);
} else {
assert_eq!(self.bone_names, bone_names, "baked bone names changed");
assert_eq!(self.parents, parents, "baked bone parents changed");
}
std::mem::swap(&mut self.matrices, &mut self.previous_frame_matrices);
self.matrices.clear();
self.matrices.extend_from_slice(matrices);
self.local_matrices.clear();
self.local_matrices.extend_from_slice(local);
let mut model = vec![None; local.len()];
for index in 0..local.len() {
resolve_model_transform(index, parents, local, &mut model)
.expect("baked bone hierarchy should be valid");
}
self.matrices = model
.into_iter()
.map(|matrix| matrix.expect("baked bone hierarchy should be valid"))
.collect();
}
}
@@ -253,7 +289,107 @@ pub fn update_fighter_bone_matrices(
let pose = baked
.sample_pose(frame)
.expect("fighter animation frame should select a baked pose");
matrices.set_pose(&baked.bone_names, pose);
matrices.set_pose(&baked.bone_names, &baked.parents, pose);
}
}
fn bind_fighter_visual_bones(
ready: On<WorldInstanceReady>,
mut commands: Commands,
spawner: Res<WorldInstanceSpawner>,
fighters: Query<&FighterAnimations>,
baked_assets: Res<Assets<BakedFighterAnimations>>,
nodes: Query<(&Name, Option<&ChildOf>)>,
animation_players: Query<(), With<AnimationPlayer>>,
) {
let fighter = ready.entity;
let Ok(animations) = fighters.get(fighter) else {
return;
};
let baked = baked_assets
.get(&animations.baked)
.expect("fighter baked animation asset should be loaded before its visual scene");
let entities = spawner
.iter_instance_entities(ready.instance_id)
.collect::<HashSet<_>>();
for &entity in &entities {
if animation_players.contains(entity) {
commands.entity(entity).remove::<AnimationPlayer>();
}
}
let mut indices = HashMap::with_capacity(baked.bone_names.len());
for (index, name) in baked.bone_names.iter().enumerate() {
let key = (baked.parents[index], name.as_str());
assert!(
indices.insert(key, index).is_none(),
"fighter GLTF has duplicate node names under the same parent"
);
}
let mut assigned = HashMap::new();
let mut remaining = entities.iter().copied().collect::<Vec<_>>();
while !remaining.is_empty() {
let mut next = Vec::new();
let mut progress = false;
for entity in remaining {
let Ok((name, parent)) = nodes.get(entity) else {
continue;
};
let parent = parent.map(ChildOf::parent);
let parent_index = match parent {
Some(parent) if entities.contains(&parent) => {
if let Some(index) = assigned.get(&parent) {
Some(*index)
} else if nodes.get(parent).ok().is_some_and(|(name, _)| {
baked.bone_names.iter().any(|bone| bone == name.as_str())
}) {
next.push(entity);
continue;
} else {
// WorldAsset adds an unnamed instance root above the GLTF scene roots.
None
}
}
_ => None,
};
if let Some(&index) = indices.get(&(parent_index, name.as_str())) {
assigned.insert(entity, index);
progress = true;
}
}
if !progress {
break;
}
remaining = next;
}
if assigned.len() != baked.bone_names.len() {
warn!(
fighter = ?fighter,
bound = assigned.len(),
expected = baked.bone_names.len(),
"could not bind every baked fighter node to its visual scene"
);
}
for (entity, index) in assigned {
commands
.entity(entity)
.insert(BakedFighterBone { fighter, index });
}
}
fn apply_baked_fighter_visual_pose(
mut nodes: Query<(&mut Transform, &BakedFighterBone)>,
fighters: Query<&FighterBoneMatrices>,
) {
for (mut transform, bone) in &mut nodes {
let Ok(matrices) = fighters.get(bone.fighter) else {
continue;
};
let Some(local) = matrices.local(bone.index) else {
continue;
};
*transform = Transform::from_matrix(local.to_mat4());
}
}
@@ -396,7 +532,9 @@ impl Plugin for BakedAnimationPlugin {
.add_systems(
GgrsSchedule,
update_fighter_bone_matrices.in_set(GameplaySet::Animation),
);
)
.add_systems(Update, apply_baked_fighter_visual_pose)
.add_observer(bind_fighter_visual_bones);
}
}
@@ -464,11 +602,7 @@ fn bake_gltf(
.iter()
.map(|node| local_transform(node.clone(), &channels, time))
.collect::<io::Result<Vec<_>>>()?;
let mut model = vec![None; nodes.len()];
for index in 0..nodes.len() {
resolve_model_transform(index, &parents, &local, &mut model)?;
}
frames.push(model.into_iter().map(Option::unwrap).collect());
frames.push(local);
}
let index = clips.len();
clips.push(BakedAnimationClip { frames });
@@ -496,6 +630,7 @@ fn bake_gltf(
Ok(BakedFighterAnimations {
frame_rate: BAKED_ANIMATION_FPS,
bone_names: names,
parents,
clips,
clip_indices,
})
@@ -688,6 +823,7 @@ mod tests {
baked.clip(AnimKind::Wait).unwrap().frames[0].len(),
baked.bone_names.len()
);
assert_eq!(baked.parents.len(), baked.bone_names.len());
}
#[test]
@@ -698,6 +834,7 @@ mod tests {
let baked = BakedFighterAnimations {
frame_rate: BAKED_ANIMATION_FPS,
bone_names: vec!["root".into()],
parents: vec![None],
clips: vec![BakedAnimationClip {
frames: vec![vec![first], vec![second]],
}],
@@ -725,7 +862,11 @@ mod tests {
let mut second = FixedMat4::IDENTITY;
second.translate(FGVec3::lit("0", "3", "0"));
let mut matrices = FighterBoneMatrices::default();
matrices.set_pose(&["root".into(), "hand".into()], &[first, second]);
matrices.set_pose(
&["root".into(), "hand".into()],
&[None, Some(0)],
&[first, second],
);
assert_eq!(matrices.get_current("hand"), Some(second));
assert_eq!(
@@ -734,6 +875,35 @@ mod tests {
);
}
#[test]
fn local_pose_reconstructs_model_space_for_gameplay() {
let mut root = FixedMat4::IDENTITY;
root.translate(FGVec3::lit("10", "0", "0"));
let mut hand = FixedMat4::IDENTITY;
hand.translate(FGVec3::lit("0", "3", "0"));
let mut matrices = FighterBoneMatrices::default();
matrices.set_pose(
&["root".into(), "hand".into()],
&[None, Some(0)],
&[root, hand],
);
assert_eq!(matrices.local(1), Some(hand));
assert_eq!(
matrices.get_current("hand").unwrap().get_translation(),
FGVec3::lit("10", "3", "0")
);
assert_eq!(
matrices
.local(1)
.unwrap()
.to_mat4()
.transform_point3(Vec3::ZERO),
Vec3::new(0.0, 3.0, 0.0)
);
}
#[test]
fn fixed_matrix_transforms_points() {
let transform = FixedMat4 {
+41 -14
View File
@@ -163,11 +163,13 @@ pub fn attack_debug(
&FighterBoneMatrices,
&FighterTranslation,
&FighterFacingDirection,
&FighterAnimationFrame,
&GlobalTransform
)>,
attack_scripts: Res<Assets<FighterAttackScript>>,
mut gizmos: Gizmos,
) {
for (hitboxes, matrices, translation, facing_direction) in query {
for (hitboxes, matrices, translation, facing_direction, frame, global_trf) in query {
if let Some(script) = &hitboxes.attack_script {
let script = attack_scripts
.get(script)
@@ -180,34 +182,59 @@ pub fn attack_debug(
let bone = &script.hitboxes[*i].bone;
let trfs = matrices.get(bone);
if let Some((prev_trf, curr_trf)) = trfs {
let prev_trf = player_trf.mul(prev_trf);
let curr_trf = player_trf.mul(curr_trf);
let prev_pos = prev_trf.transform_point(offset);
let curr_pos = curr_trf.transform_point(offset);
let prev_pos_draw = Vec3::new(prev_pos.x.to_num(), prev_pos.y.to_num(), prev_pos.z.to_num());
let curr_pos_draw = Vec3::new(curr_pos.x.to_num(), curr_pos.y.to_num(), curr_pos.z.to_num());
let curr_pos_draw = Vec3::new(
curr_pos.x.to_num(),
curr_pos.y.to_num(),
curr_pos.z.to_num(),
);
if script.hitboxes[*i].start_frame == frame.frame {
gizmos.sphere(curr_pos_draw, radius.to_num(), bevy::color::palettes::css::RED);
continue;
}
let prev_trf = player_trf.mul(prev_trf);
let prev_pos = prev_trf.transform_point(offset);
let prev_pos_draw = Vec3::new(
prev_pos.x.to_num(),
prev_pos.y.to_num(),
prev_pos.z.to_num(),
);
let dist = Dir3::new_and_length(curr_pos_draw - prev_pos_draw);
if let Ok((aim_dir, dist)) = dist {
let attack_capsule = Capsule3d {
half_length: dist * 0.5,
radius: radius.to_num()
radius: radius.to_num(),
};
let mut capsule_draw_trf = Transform::IDENTITY;
capsule_draw_trf.align(Dir3::Y, aim_dir, Dir3::X, aim_dir.any_orthonormal_vector());
capsule_draw_trf.align(
Dir3::Y,
aim_dir,
Dir3::X,
aim_dir.any_orthonormal_vector(),
);
capsule_draw_trf.translation = (prev_pos_draw + curr_pos_draw) * 0.5;
gizmos.primitive_3d(&attack_capsule, capsule_draw_trf.to_isometry(), bevy::color::palettes::css::RED);
gizmos.primitive_3d(
&attack_capsule,
capsule_draw_trf.to_isometry(),
bevy::color::palettes::css::RED,
);
}
gizmos.sphere(prev_pos_draw, radius.to_num(), bevy::color::palettes::css::RED.darker(0.5));
gizmos.sphere(curr_pos_draw, radius.to_num(), bevy::color::palettes::css::RED);
gizmos.cross(curr_trf.transform_point(FGVec3::lit("0.0", "0.0", "0.0")).to_vec3(), 1.0, bevy::color::palettes::css::BLUE);
gizmos.cross(curr_trf.to_mat4().transform_point(Vec3::ZERO), 1.0, bevy::color::palettes::css::HOT_PINK);
gizmos.axes(curr_trf.to_mat4(), 1.0);
} else {
warn!("Bone not found required by attack script: {}", bone);
}
+67 -71
View File
@@ -29,7 +29,6 @@ pub use motion::{FighterPreviousTranslation, FighterTranslation, FighterVelocity
use crate::{
fighter::{
animation::animation_init,
attack::FighterAttackScriptAssets,
manifest::FighterManifest,
state::{FighterState, fall::FallState},
@@ -106,78 +105,75 @@ pub struct Fighter {
impl Plugin for FighterPlugin {
fn build(&self, app: &mut App) {
app.add_systems(
Update,
(animation_init, animation::setup_fighter_animation_player),
)
// Every state gets a chance to interrupt itself, in priority order.
.add_systems(
GgrsSchedule,
(
ecb::snapshot_fighter_ecb.before(state_interrupt_system),
animation::advance_fighter_animation_frames.before(state_interrupt_system),
state::state_interrupt_system,
app
// Every state gets a chance to interrupt itself, in priority order.
.add_systems(
GgrsSchedule,
(
ecb::snapshot_fighter_ecb.before(state_interrupt_system),
animation::advance_fighter_animation_frames.before(state_interrupt_system),
state::state_interrupt_system,
)
.in_set(GameplaySet::Interrupt),
)
.in_set(GameplaySet::Interrupt),
)
.add_systems(
GgrsSchedule,
(state::state_update_system,).in_set(GameplaySet::StateUpdate),
)
/*.add_systems(
GgrsSchedule,
(motion::integrate_gravity, motion::apply_grounded_motion)
.chain()
.in_set(GameplaySet::Physics),
)*/
.add_systems(
GgrsSchedule,
state::state_collision_interrupt_system.in_set(GameplaySet::Collision),
)
.add_systems(
Update,
(
motion::apply_fighter_translation_to_visuals,
animation::apply_animation,
.add_systems(
GgrsSchedule,
(state::state_update_system,).in_set(GameplaySet::StateUpdate),
)
.chain()
.run_if(in_state(crate::AppState::InMatch)),
)
// Rollback registration lives next to the systems that write these
// components; a component simulated here but missing from this list
// is a desync waiting to happen.
.rollback_component_with_copy::<FighterECB>()
.rollback_component_with_copy::<ecb::FighterPreviousECB>()
.rollback_component_with_copy::<FighterVelocity>()
.rollback_component_with_copy::<FighterTranslation>()
.rollback_component_with_copy::<FighterPreviousTranslation>()
.rollback_component_with_copy::<animation::FighterAnimationFrame>()
.checksum_component_with_hash::<animation::FighterAnimationFrame>()
.rollback_component_with_copy::<Grounded>()
.rollback_component_with_copy::<FighterFacingDirection>()
.rollback_resource_with_reflect::<StageCollision>()
// Debug views.
.add_systems(
FixedPostUpdate,
debug::debug_draw_ecb.run_if(crate::debug_tools::ecb_enabled),
)
.add_systems(
EguiPrimaryContextPass,
debug::fighter_debug.run_if(crate::debug_tools::fighter_info_enabled),
)
.add_systems(
FixedPostUpdate,
debug::animation_debug.run_if(crate::debug_tools::animation_bones_enabled),
)
.add_systems(
FixedPostUpdate,
debug::attack_debug.run_if(crate::debug_tools::attack_hitboxes_enabled),
)
.rollback_component_with_clone::<FighterState>()
.rollback_component_with_clone::<FighterHitboxes>()
.checksum_component_with_hash::<FighterState>()
.checksum_component_with_hash::<FighterFacingDirection>()
.register_type::<FighterFacingDirection>();
/*.add_systems(
GgrsSchedule,
(motion::integrate_gravity, motion::apply_grounded_motion)
.chain()
.in_set(GameplaySet::Physics),
)*/
.add_systems(
GgrsSchedule,
state::state_collision_interrupt_system.in_set(GameplaySet::Collision),
)
.add_systems(
Update,
(
motion::apply_fighter_translation_to_visuals,
animation::apply_animation,
)
.chain()
.run_if(in_state(crate::AppState::InMatch)),
)
// Rollback registration lives next to the systems that write these
// components; a component simulated here but missing from this list
// is a desync waiting to happen.
.rollback_component_with_copy::<FighterECB>()
.rollback_component_with_copy::<ecb::FighterPreviousECB>()
.rollback_component_with_copy::<FighterVelocity>()
.rollback_component_with_copy::<FighterTranslation>()
.rollback_component_with_copy::<FighterPreviousTranslation>()
.rollback_component_with_copy::<animation::FighterAnimationFrame>()
.checksum_component_with_hash::<animation::FighterAnimationFrame>()
.rollback_component_with_copy::<Grounded>()
.rollback_component_with_copy::<FighterFacingDirection>()
.rollback_resource_with_reflect::<StageCollision>()
// Debug views.
.add_systems(
FixedPostUpdate,
debug::debug_draw_ecb.run_if(crate::debug_tools::ecb_enabled),
)
.add_systems(
EguiPrimaryContextPass,
debug::fighter_debug.run_if(crate::debug_tools::fighter_info_enabled),
)
.add_systems(
FixedPostUpdate,
debug::animation_debug.run_if(crate::debug_tools::animation_bones_enabled),
)
.add_systems(
FixedPostUpdate,
debug::attack_debug.run_if(crate::debug_tools::attack_hitboxes_enabled),
)
.rollback_component_with_clone::<FighterState>()
.rollback_component_with_clone::<FighterHitboxes>()
.checksum_component_with_hash::<FighterState>()
.checksum_component_with_hash::<FighterFacingDirection>()
.register_type::<FighterFacingDirection>();
}
}
+1 -43
View File
@@ -5,7 +5,7 @@
use bevy::ecs::query::QueryData;
use bevy::prelude::*;
use crate::fighter::animation::{AnimKind, FighterAnimationFrame, FighterAnimationPlayerLink};
use crate::fighter::animation::{AnimKind, FighterAnimationFrame};
use crate::fighter::attack::FighterAttackScriptAssets;
use crate::fighter::baked_animation::BakedFighterAnimations;
use crate::fighter::ecb::FighterPreviousECB;
@@ -96,8 +96,6 @@ pub struct FighterStateContext<'a> {
pub game_settings: &'a GameSettings,
pub stage_collision: &'a StageCollision,
pub animations: &'a FighterAnimations,
pub animation_player: &'a mut AnimationPlayer,
pub animation_transitions: &'a mut AnimationTransitions,
pub animation_frame: &'a mut FighterAnimationFrame,
pub baked_animations: &'a Assets<BakedFighterAnimations>,
pub attack_scripts: &'a Assets<FighterAttackScript>,
@@ -107,14 +105,6 @@ pub struct FighterStateContext<'a> {
impl FighterStateContext<'_> {
pub fn play_animation(&mut self, kind: AnimKind, repeat: bool) {
let active = self.animation_transitions.play(
self.animation_player,
self.animations.clips[&kind],
std::time::Duration::ZERO,
);
if repeat {
active.repeat();
}
self.animation_frame.reset(kind, repeat);
}
@@ -169,8 +159,6 @@ impl<'w, 's> FighterFrameQueryItem<'w, 's> {
game_settings: &'a GameSettings,
stage_collision: &'a StageCollision,
animations: &'a FighterAnimations,
animation_player: &'a mut AnimationPlayer,
animation_transitions: &'a mut AnimationTransitions,
baked_animations: &'a Assets<BakedFighterAnimations>,
attack_scripts: &'a Assets<FighterAttackScript>,
attack_script_assets: &'a FighterAttackScriptAssets,
@@ -188,8 +176,6 @@ impl<'w, 's> FighterFrameQueryItem<'w, 's> {
game_settings,
stage_collision,
animations,
animation_player,
animation_transitions,
animation_frame: &mut self.animation_frame,
baked_animations,
attack_scripts,
@@ -206,7 +192,6 @@ pub struct StatefulFighterQuery {
pub state: &'static mut FighterState,
pub frame: FighterFrameQuery,
pub animations: &'static FighterAnimations,
pub animation_player_link: &'static FighterAnimationPlayerLink,
pub fighter: &'static Fighter,
pub attack_script_assets: &'static FighterAttackScriptAssets,
}
@@ -222,7 +207,6 @@ impl std::fmt::Display for StateNameDebug {
pub fn state_update_system(
mut query: Query<StatefulFighterQuery>,
mut animation_players: Query<(&mut AnimationPlayer, &mut AnimationTransitions)>,
stage_collision: Res<StageCollision>,
game_settings: Res<GameSettings>,
baked_animations: Res<Assets<BakedFighterAnimations>>,
@@ -234,21 +218,13 @@ pub fn state_update_system(
mut state,
mut frame,
animations,
animation_player_link,
..
} = fighter;
let Ok((mut animation_player, mut animation_transitions)) =
animation_players.get_mut(animation_player_link.player())
else {
continue;
};
let mut update_ctx = frame.context(
&game_settings,
&stage_collision,
animations,
&mut animation_player,
&mut animation_transitions,
&baked_animations,
&attack_scripts,
&fighter.attack_script_assets,
@@ -262,7 +238,6 @@ pub fn state_update_system(
pub fn state_interrupt_system(
mut query: Query<StatefulFighterQuery>,
mut animation_players: Query<(&mut AnimationPlayer, &mut AnimationTransitions)>,
stage_collision: Res<StageCollision>,
game_settings: Res<GameSettings>,
baked_animations: Res<Assets<BakedFighterAnimations>>,
@@ -276,21 +251,13 @@ pub fn state_interrupt_system(
mut state,
mut frame,
animations,
animation_player_link,
fighter,
attack_script_assets,
} = fighter;
let Ok((mut animation_player, mut animation_transitions)) =
animation_players.get_mut(animation_player_link.player())
else {
continue;
};
let mut state_context = frame.context(
&game_settings,
&stage_collision,
animations,
&mut animation_player,
&mut animation_transitions,
&baked_animations,
&attack_scripts,
attack_script_assets,
@@ -314,7 +281,6 @@ pub fn state_interrupt_system(
pub fn state_collision_interrupt_system(
mut query: Query<StatefulFighterQuery>,
mut animation_players: Query<(&mut AnimationPlayer, &mut AnimationTransitions)>,
stage_collision: Res<StageCollision>,
game_settings: Res<GameSettings>,
baked_animations: Res<Assets<BakedFighterAnimations>>,
@@ -328,21 +294,13 @@ pub fn state_collision_interrupt_system(
mut state,
mut frame,
animations,
animation_player_link,
fighter,
attack_script_assets,
} = fighter;
let Ok((mut animation_player, mut animation_transitions)) =
animation_players.get_mut(animation_player_link.player())
else {
continue;
};
let mut state_context = frame.context(
&game_settings,
&stage_collision,
animations,
&mut animation_player,
&mut animation_transitions,
&baked_animations,
&attack_scripts,
&attack_script_assets,
+2 -7
View File
@@ -1,13 +1,8 @@
use bevy::prelude::*;
use std::collections::HashMap;
use crate::fighter::animation::AnimKind;
use crate::fighter::baked_animation::BakedFighterAnimations;
use bevy::prelude::*;
#[derive(Component, Clone)]
pub struct FighterAnimations {
pub graph: Handle<AnimationGraph>,
pub clips: HashMap<AnimKind, AnimationNodeIndex>,
/// Gameplay samples this fixed-point asset; the graph remains visual-only.
/// The single animation source for gameplay and the visual skeleton.
pub baked: Handle<BakedFighterAnimations>,
}