Make the baked animation system drive the character animations
This commit is contained in:
@@ -1,23 +1,6 @@
|
|||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
|
||||||
use crate::{
|
use crate::fighter::{FighterFacingDirection, FighterVisual};
|
||||||
fighter::{
|
|
||||||
FighterFacingDirection, FighterVisual,
|
|
||||||
visual::FighterAnimations,
|
|
||||||
},
|
|
||||||
player::Player,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Component)]
|
|
||||||
pub struct FighterAnimationPlayerLink(Entity);
|
|
||||||
|
|
||||||
impl FighterAnimationPlayerLink {
|
|
||||||
pub fn player(&self) -> Entity {
|
|
||||||
self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The simulation-owned animation clock. Rendering may interpolate freely, but
|
/// The simulation-owned animation clock. Rendering may interpolate freely, but
|
||||||
/// gameplay samples baked data only with this rollback-tracked value.
|
/// 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(
|
pub fn apply_animation(
|
||||||
children: Query<
|
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(
|
#[derive(
|
||||||
Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Deserialize, serde::Serialize, Reflect,
|
Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Deserialize, serde::Serialize, Reflect,
|
||||||
)]
|
)]
|
||||||
@@ -142,7 +80,6 @@ pub enum AnimKind {
|
|||||||
Turn,
|
Turn,
|
||||||
|
|
||||||
// Attacks
|
// Attacks
|
||||||
|
|
||||||
AttackJab1,
|
AttackJab1,
|
||||||
AttackUpTilt,
|
AttackUpTilt,
|
||||||
}
|
}
|
||||||
|
|||||||
+188
-18
@@ -1,9 +1,10 @@
|
|||||||
//! Deterministic, fighter-local skeletal animation samples for gameplay.
|
//! Deterministic, fighter-local skeletal animation samples for gameplay and rendering.
|
||||||
//!
|
|
||||||
//! Rendering continues to use Bevy's floating-point animation player. Gameplay
|
|
||||||
//! reads this asset instead, so rollback never depends on render-time sampling.
|
|
||||||
|
|
||||||
use std::{collections::HashMap, io, path::Path};
|
use std::{
|
||||||
|
collections::{HashMap, HashSet},
|
||||||
|
io,
|
||||||
|
path::Path,
|
||||||
|
};
|
||||||
|
|
||||||
use bevy::{
|
use bevy::{
|
||||||
asset::{
|
asset::{
|
||||||
@@ -15,6 +16,7 @@ use bevy::{
|
|||||||
},
|
},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
reflect::TypePath,
|
reflect::TypePath,
|
||||||
|
world_serialization::{WorldInstanceReady, WorldInstanceSpawner},
|
||||||
};
|
};
|
||||||
use bevy_ggrs::prelude::GgrsSchedule;
|
use bevy_ggrs::prelude::GgrsSchedule;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@@ -64,6 +66,10 @@ impl FixedMat4 {
|
|||||||
Ok(Self { cols })
|
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 {
|
pub fn mul(self, rhs: Self) -> Self {
|
||||||
let mut cols = [[FGi32::ZERO; 4]; 4];
|
let mut cols = [[FGi32::ZERO; 4]; 4];
|
||||||
for column in 0..4 {
|
for column in 0..4 {
|
||||||
@@ -147,7 +153,7 @@ impl FixedMat4 {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct BakedAnimationClip {
|
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>>,
|
pub frames: Vec<Vec<FixedMat4>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,6 +162,8 @@ pub struct BakedFighterAnimations {
|
|||||||
pub frame_rate: u32,
|
pub frame_rate: u32,
|
||||||
/// Indexed exactly like every frame in each clip.
|
/// Indexed exactly like every frame in each clip.
|
||||||
pub bone_names: Vec<String>,
|
pub bone_names: Vec<String>,
|
||||||
|
/// Parent node for each stored node, indexed like `bone_names`.
|
||||||
|
pub parents: Vec<Option<usize>>,
|
||||||
clips: Vec<BakedAnimationClip>,
|
clips: Vec<BakedAnimationClip>,
|
||||||
clip_indices: HashMap<AnimKind, usize>,
|
clip_indices: HashMap<AnimKind, usize>,
|
||||||
}
|
}
|
||||||
@@ -190,8 +198,16 @@ impl BakedFighterAnimations {
|
|||||||
#[derive(Component, Clone, Debug, Default)]
|
#[derive(Component, Clone, Debug, Default)]
|
||||||
pub struct FighterBoneMatrices {
|
pub struct FighterBoneMatrices {
|
||||||
bone_names: Vec<String>,
|
bone_names: Vec<String>,
|
||||||
|
parents: Vec<Option<usize>>,
|
||||||
matrices: Vec<FixedMat4>,
|
matrices: Vec<FixedMat4>,
|
||||||
previous_frame_matrices: Vec<FixedMat4>,
|
previous_frame_matrices: Vec<FixedMat4>,
|
||||||
|
local_matrices: Vec<FixedMat4>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
struct BakedFighterBone {
|
||||||
|
fighter: Entity,
|
||||||
|
index: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FighterBoneMatrices {
|
impl FighterBoneMatrices {
|
||||||
@@ -221,20 +237,40 @@ impl FighterBoneMatrices {
|
|||||||
.zip(self.matrices.iter().copied())
|
.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!(
|
assert_eq!(
|
||||||
bone_names.len(),
|
bone_names.len(),
|
||||||
matrices.len(),
|
local.len(),
|
||||||
"baked animation pose does not match its bone names"
|
"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() {
|
if self.bone_names.is_empty() {
|
||||||
self.bone_names.extend_from_slice(bone_names);
|
self.bone_names.extend_from_slice(bone_names);
|
||||||
|
self.parents.extend_from_slice(parents);
|
||||||
} else {
|
} else {
|
||||||
assert_eq!(self.bone_names, bone_names, "baked bone names changed");
|
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);
|
std::mem::swap(&mut self.matrices, &mut self.previous_frame_matrices);
|
||||||
self.matrices.clear();
|
self.local_matrices.clear();
|
||||||
self.matrices.extend_from_slice(matrices);
|
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
|
let pose = baked
|
||||||
.sample_pose(frame)
|
.sample_pose(frame)
|
||||||
.expect("fighter animation frame should select a baked pose");
|
.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(
|
.add_systems(
|
||||||
GgrsSchedule,
|
GgrsSchedule,
|
||||||
update_fighter_bone_matrices.in_set(GameplaySet::Animation),
|
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()
|
.iter()
|
||||||
.map(|node| local_transform(node.clone(), &channels, time))
|
.map(|node| local_transform(node.clone(), &channels, time))
|
||||||
.collect::<io::Result<Vec<_>>>()?;
|
.collect::<io::Result<Vec<_>>>()?;
|
||||||
let mut model = vec![None; nodes.len()];
|
frames.push(local);
|
||||||
for index in 0..nodes.len() {
|
|
||||||
resolve_model_transform(index, &parents, &local, &mut model)?;
|
|
||||||
}
|
|
||||||
frames.push(model.into_iter().map(Option::unwrap).collect());
|
|
||||||
}
|
}
|
||||||
let index = clips.len();
|
let index = clips.len();
|
||||||
clips.push(BakedAnimationClip { frames });
|
clips.push(BakedAnimationClip { frames });
|
||||||
@@ -496,6 +630,7 @@ fn bake_gltf(
|
|||||||
Ok(BakedFighterAnimations {
|
Ok(BakedFighterAnimations {
|
||||||
frame_rate: BAKED_ANIMATION_FPS,
|
frame_rate: BAKED_ANIMATION_FPS,
|
||||||
bone_names: names,
|
bone_names: names,
|
||||||
|
parents,
|
||||||
clips,
|
clips,
|
||||||
clip_indices,
|
clip_indices,
|
||||||
})
|
})
|
||||||
@@ -688,6 +823,7 @@ mod tests {
|
|||||||
baked.clip(AnimKind::Wait).unwrap().frames[0].len(),
|
baked.clip(AnimKind::Wait).unwrap().frames[0].len(),
|
||||||
baked.bone_names.len()
|
baked.bone_names.len()
|
||||||
);
|
);
|
||||||
|
assert_eq!(baked.parents.len(), baked.bone_names.len());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -698,6 +834,7 @@ mod tests {
|
|||||||
let baked = BakedFighterAnimations {
|
let baked = BakedFighterAnimations {
|
||||||
frame_rate: BAKED_ANIMATION_FPS,
|
frame_rate: BAKED_ANIMATION_FPS,
|
||||||
bone_names: vec!["root".into()],
|
bone_names: vec!["root".into()],
|
||||||
|
parents: vec![None],
|
||||||
clips: vec![BakedAnimationClip {
|
clips: vec![BakedAnimationClip {
|
||||||
frames: vec![vec![first], vec![second]],
|
frames: vec![vec![first], vec![second]],
|
||||||
}],
|
}],
|
||||||
@@ -725,7 +862,11 @@ mod tests {
|
|||||||
let mut second = FixedMat4::IDENTITY;
|
let mut second = FixedMat4::IDENTITY;
|
||||||
second.translate(FGVec3::lit("0", "3", "0"));
|
second.translate(FGVec3::lit("0", "3", "0"));
|
||||||
let mut matrices = FighterBoneMatrices::default();
|
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!(matrices.get_current("hand"), Some(second));
|
||||||
assert_eq!(
|
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]
|
#[test]
|
||||||
fn fixed_matrix_transforms_points() {
|
fn fixed_matrix_transforms_points() {
|
||||||
let transform = FixedMat4 {
|
let transform = FixedMat4 {
|
||||||
|
|||||||
+41
-14
@@ -163,11 +163,13 @@ pub fn attack_debug(
|
|||||||
&FighterBoneMatrices,
|
&FighterBoneMatrices,
|
||||||
&FighterTranslation,
|
&FighterTranslation,
|
||||||
&FighterFacingDirection,
|
&FighterFacingDirection,
|
||||||
|
&FighterAnimationFrame,
|
||||||
|
&GlobalTransform
|
||||||
)>,
|
)>,
|
||||||
attack_scripts: Res<Assets<FighterAttackScript>>,
|
attack_scripts: Res<Assets<FighterAttackScript>>,
|
||||||
mut gizmos: Gizmos,
|
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 {
|
if let Some(script) = &hitboxes.attack_script {
|
||||||
let script = attack_scripts
|
let script = attack_scripts
|
||||||
.get(script)
|
.get(script)
|
||||||
@@ -180,34 +182,59 @@ pub fn attack_debug(
|
|||||||
let bone = &script.hitboxes[*i].bone;
|
let bone = &script.hitboxes[*i].bone;
|
||||||
|
|
||||||
let trfs = matrices.get(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);
|
if let Some((prev_trf, curr_trf)) = trfs {
|
||||||
|
let curr_trf = player_trf.mul(curr_trf);
|
||||||
let curr_pos = curr_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(
|
||||||
let curr_pos_draw = Vec3::new(curr_pos.x.to_num(), curr_pos.y.to_num(), curr_pos.z.to_num());
|
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);
|
let dist = Dir3::new_and_length(curr_pos_draw - prev_pos_draw);
|
||||||
|
|
||||||
if let Ok((aim_dir, dist)) = dist {
|
if let Ok((aim_dir, dist)) = dist {
|
||||||
let attack_capsule = Capsule3d {
|
let attack_capsule = Capsule3d {
|
||||||
half_length: dist * 0.5,
|
half_length: dist * 0.5,
|
||||||
radius: radius.to_num()
|
radius: radius.to_num(),
|
||||||
};
|
};
|
||||||
let mut capsule_draw_trf = Transform::IDENTITY;
|
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;
|
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.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);
|
||||||
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);
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
warn!("Bone not found required by attack script: {}", bone);
|
warn!("Bone not found required by attack script: {}", bone);
|
||||||
}
|
}
|
||||||
|
|||||||
+67
-71
@@ -29,7 +29,6 @@ pub use motion::{FighterPreviousTranslation, FighterTranslation, FighterVelocity
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
fighter::{
|
fighter::{
|
||||||
animation::animation_init,
|
|
||||||
attack::FighterAttackScriptAssets,
|
attack::FighterAttackScriptAssets,
|
||||||
manifest::FighterManifest,
|
manifest::FighterManifest,
|
||||||
state::{FighterState, fall::FallState},
|
state::{FighterState, fall::FallState},
|
||||||
@@ -106,78 +105,75 @@ pub struct Fighter {
|
|||||||
|
|
||||||
impl Plugin for FighterPlugin {
|
impl Plugin for FighterPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.add_systems(
|
app
|
||||||
Update,
|
// Every state gets a chance to interrupt itself, in priority order.
|
||||||
(animation_init, animation::setup_fighter_animation_player),
|
.add_systems(
|
||||||
)
|
GgrsSchedule,
|
||||||
// Every state gets a chance to interrupt itself, in priority order.
|
(
|
||||||
.add_systems(
|
ecb::snapshot_fighter_ecb.before(state_interrupt_system),
|
||||||
GgrsSchedule,
|
animation::advance_fighter_animation_frames.before(state_interrupt_system),
|
||||||
(
|
state::state_interrupt_system,
|
||||||
ecb::snapshot_fighter_ecb.before(state_interrupt_system),
|
)
|
||||||
animation::advance_fighter_animation_frames.before(state_interrupt_system),
|
.in_set(GameplaySet::Interrupt),
|
||||||
state::state_interrupt_system,
|
|
||||||
)
|
)
|
||||||
.in_set(GameplaySet::Interrupt),
|
.add_systems(
|
||||||
)
|
GgrsSchedule,
|
||||||
.add_systems(
|
(state::state_update_system,).in_set(GameplaySet::StateUpdate),
|
||||||
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,
|
|
||||||
)
|
)
|
||||||
.chain()
|
/*.add_systems(
|
||||||
.run_if(in_state(crate::AppState::InMatch)),
|
GgrsSchedule,
|
||||||
)
|
(motion::integrate_gravity, motion::apply_grounded_motion)
|
||||||
// Rollback registration lives next to the systems that write these
|
.chain()
|
||||||
// components; a component simulated here but missing from this list
|
.in_set(GameplaySet::Physics),
|
||||||
// is a desync waiting to happen.
|
)*/
|
||||||
.rollback_component_with_copy::<FighterECB>()
|
.add_systems(
|
||||||
.rollback_component_with_copy::<ecb::FighterPreviousECB>()
|
GgrsSchedule,
|
||||||
.rollback_component_with_copy::<FighterVelocity>()
|
state::state_collision_interrupt_system.in_set(GameplaySet::Collision),
|
||||||
.rollback_component_with_copy::<FighterTranslation>()
|
)
|
||||||
.rollback_component_with_copy::<FighterPreviousTranslation>()
|
.add_systems(
|
||||||
.rollback_component_with_copy::<animation::FighterAnimationFrame>()
|
Update,
|
||||||
.checksum_component_with_hash::<animation::FighterAnimationFrame>()
|
(
|
||||||
.rollback_component_with_copy::<Grounded>()
|
motion::apply_fighter_translation_to_visuals,
|
||||||
.rollback_component_with_copy::<FighterFacingDirection>()
|
animation::apply_animation,
|
||||||
.rollback_resource_with_reflect::<StageCollision>()
|
)
|
||||||
// Debug views.
|
.chain()
|
||||||
.add_systems(
|
.run_if(in_state(crate::AppState::InMatch)),
|
||||||
FixedPostUpdate,
|
)
|
||||||
debug::debug_draw_ecb.run_if(crate::debug_tools::ecb_enabled),
|
// Rollback registration lives next to the systems that write these
|
||||||
)
|
// components; a component simulated here but missing from this list
|
||||||
.add_systems(
|
// is a desync waiting to happen.
|
||||||
EguiPrimaryContextPass,
|
.rollback_component_with_copy::<FighterECB>()
|
||||||
debug::fighter_debug.run_if(crate::debug_tools::fighter_info_enabled),
|
.rollback_component_with_copy::<ecb::FighterPreviousECB>()
|
||||||
)
|
.rollback_component_with_copy::<FighterVelocity>()
|
||||||
.add_systems(
|
.rollback_component_with_copy::<FighterTranslation>()
|
||||||
FixedPostUpdate,
|
.rollback_component_with_copy::<FighterPreviousTranslation>()
|
||||||
debug::animation_debug.run_if(crate::debug_tools::animation_bones_enabled),
|
.rollback_component_with_copy::<animation::FighterAnimationFrame>()
|
||||||
)
|
.checksum_component_with_hash::<animation::FighterAnimationFrame>()
|
||||||
.add_systems(
|
.rollback_component_with_copy::<Grounded>()
|
||||||
FixedPostUpdate,
|
.rollback_component_with_copy::<FighterFacingDirection>()
|
||||||
debug::attack_debug.run_if(crate::debug_tools::attack_hitboxes_enabled),
|
.rollback_resource_with_reflect::<StageCollision>()
|
||||||
)
|
// Debug views.
|
||||||
.rollback_component_with_clone::<FighterState>()
|
.add_systems(
|
||||||
.rollback_component_with_clone::<FighterHitboxes>()
|
FixedPostUpdate,
|
||||||
.checksum_component_with_hash::<FighterState>()
|
debug::debug_draw_ecb.run_if(crate::debug_tools::ecb_enabled),
|
||||||
.checksum_component_with_hash::<FighterFacingDirection>()
|
)
|
||||||
.register_type::<FighterFacingDirection>();
|
.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>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
use bevy::ecs::query::QueryData;
|
use bevy::ecs::query::QueryData;
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
|
||||||
use crate::fighter::animation::{AnimKind, FighterAnimationFrame, FighterAnimationPlayerLink};
|
use crate::fighter::animation::{AnimKind, FighterAnimationFrame};
|
||||||
use crate::fighter::attack::FighterAttackScriptAssets;
|
use crate::fighter::attack::FighterAttackScriptAssets;
|
||||||
use crate::fighter::baked_animation::BakedFighterAnimations;
|
use crate::fighter::baked_animation::BakedFighterAnimations;
|
||||||
use crate::fighter::ecb::FighterPreviousECB;
|
use crate::fighter::ecb::FighterPreviousECB;
|
||||||
@@ -96,8 +96,6 @@ pub struct FighterStateContext<'a> {
|
|||||||
pub game_settings: &'a GameSettings,
|
pub game_settings: &'a GameSettings,
|
||||||
pub stage_collision: &'a StageCollision,
|
pub stage_collision: &'a StageCollision,
|
||||||
pub animations: &'a FighterAnimations,
|
pub animations: &'a FighterAnimations,
|
||||||
pub animation_player: &'a mut AnimationPlayer,
|
|
||||||
pub animation_transitions: &'a mut AnimationTransitions,
|
|
||||||
pub animation_frame: &'a mut FighterAnimationFrame,
|
pub animation_frame: &'a mut FighterAnimationFrame,
|
||||||
pub baked_animations: &'a Assets<BakedFighterAnimations>,
|
pub baked_animations: &'a Assets<BakedFighterAnimations>,
|
||||||
pub attack_scripts: &'a Assets<FighterAttackScript>,
|
pub attack_scripts: &'a Assets<FighterAttackScript>,
|
||||||
@@ -107,14 +105,6 @@ pub struct FighterStateContext<'a> {
|
|||||||
|
|
||||||
impl FighterStateContext<'_> {
|
impl FighterStateContext<'_> {
|
||||||
pub fn play_animation(&mut self, kind: AnimKind, repeat: bool) {
|
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);
|
self.animation_frame.reset(kind, repeat);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,8 +159,6 @@ impl<'w, 's> FighterFrameQueryItem<'w, 's> {
|
|||||||
game_settings: &'a GameSettings,
|
game_settings: &'a GameSettings,
|
||||||
stage_collision: &'a StageCollision,
|
stage_collision: &'a StageCollision,
|
||||||
animations: &'a FighterAnimations,
|
animations: &'a FighterAnimations,
|
||||||
animation_player: &'a mut AnimationPlayer,
|
|
||||||
animation_transitions: &'a mut AnimationTransitions,
|
|
||||||
baked_animations: &'a Assets<BakedFighterAnimations>,
|
baked_animations: &'a Assets<BakedFighterAnimations>,
|
||||||
attack_scripts: &'a Assets<FighterAttackScript>,
|
attack_scripts: &'a Assets<FighterAttackScript>,
|
||||||
attack_script_assets: &'a FighterAttackScriptAssets,
|
attack_script_assets: &'a FighterAttackScriptAssets,
|
||||||
@@ -188,8 +176,6 @@ impl<'w, 's> FighterFrameQueryItem<'w, 's> {
|
|||||||
game_settings,
|
game_settings,
|
||||||
stage_collision,
|
stage_collision,
|
||||||
animations,
|
animations,
|
||||||
animation_player,
|
|
||||||
animation_transitions,
|
|
||||||
animation_frame: &mut self.animation_frame,
|
animation_frame: &mut self.animation_frame,
|
||||||
baked_animations,
|
baked_animations,
|
||||||
attack_scripts,
|
attack_scripts,
|
||||||
@@ -206,7 +192,6 @@ pub struct StatefulFighterQuery {
|
|||||||
pub state: &'static mut FighterState,
|
pub state: &'static mut FighterState,
|
||||||
pub frame: FighterFrameQuery,
|
pub frame: FighterFrameQuery,
|
||||||
pub animations: &'static FighterAnimations,
|
pub animations: &'static FighterAnimations,
|
||||||
pub animation_player_link: &'static FighterAnimationPlayerLink,
|
|
||||||
pub fighter: &'static Fighter,
|
pub fighter: &'static Fighter,
|
||||||
pub attack_script_assets: &'static FighterAttackScriptAssets,
|
pub attack_script_assets: &'static FighterAttackScriptAssets,
|
||||||
}
|
}
|
||||||
@@ -222,7 +207,6 @@ impl std::fmt::Display for StateNameDebug {
|
|||||||
|
|
||||||
pub fn state_update_system(
|
pub fn state_update_system(
|
||||||
mut query: Query<StatefulFighterQuery>,
|
mut query: Query<StatefulFighterQuery>,
|
||||||
mut animation_players: Query<(&mut AnimationPlayer, &mut AnimationTransitions)>,
|
|
||||||
stage_collision: Res<StageCollision>,
|
stage_collision: Res<StageCollision>,
|
||||||
game_settings: Res<GameSettings>,
|
game_settings: Res<GameSettings>,
|
||||||
baked_animations: Res<Assets<BakedFighterAnimations>>,
|
baked_animations: Res<Assets<BakedFighterAnimations>>,
|
||||||
@@ -234,21 +218,13 @@ pub fn state_update_system(
|
|||||||
mut state,
|
mut state,
|
||||||
mut frame,
|
mut frame,
|
||||||
animations,
|
animations,
|
||||||
animation_player_link,
|
|
||||||
..
|
..
|
||||||
} = fighter;
|
} = 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(
|
let mut update_ctx = frame.context(
|
||||||
&game_settings,
|
&game_settings,
|
||||||
&stage_collision,
|
&stage_collision,
|
||||||
animations,
|
animations,
|
||||||
&mut animation_player,
|
|
||||||
&mut animation_transitions,
|
|
||||||
&baked_animations,
|
&baked_animations,
|
||||||
&attack_scripts,
|
&attack_scripts,
|
||||||
&fighter.attack_script_assets,
|
&fighter.attack_script_assets,
|
||||||
@@ -262,7 +238,6 @@ pub fn state_update_system(
|
|||||||
|
|
||||||
pub fn state_interrupt_system(
|
pub fn state_interrupt_system(
|
||||||
mut query: Query<StatefulFighterQuery>,
|
mut query: Query<StatefulFighterQuery>,
|
||||||
mut animation_players: Query<(&mut AnimationPlayer, &mut AnimationTransitions)>,
|
|
||||||
stage_collision: Res<StageCollision>,
|
stage_collision: Res<StageCollision>,
|
||||||
game_settings: Res<GameSettings>,
|
game_settings: Res<GameSettings>,
|
||||||
baked_animations: Res<Assets<BakedFighterAnimations>>,
|
baked_animations: Res<Assets<BakedFighterAnimations>>,
|
||||||
@@ -276,21 +251,13 @@ pub fn state_interrupt_system(
|
|||||||
mut state,
|
mut state,
|
||||||
mut frame,
|
mut frame,
|
||||||
animations,
|
animations,
|
||||||
animation_player_link,
|
|
||||||
fighter,
|
fighter,
|
||||||
attack_script_assets,
|
attack_script_assets,
|
||||||
} = fighter;
|
} = 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(
|
let mut state_context = frame.context(
|
||||||
&game_settings,
|
&game_settings,
|
||||||
&stage_collision,
|
&stage_collision,
|
||||||
animations,
|
animations,
|
||||||
&mut animation_player,
|
|
||||||
&mut animation_transitions,
|
|
||||||
&baked_animations,
|
&baked_animations,
|
||||||
&attack_scripts,
|
&attack_scripts,
|
||||||
attack_script_assets,
|
attack_script_assets,
|
||||||
@@ -314,7 +281,6 @@ pub fn state_interrupt_system(
|
|||||||
|
|
||||||
pub fn state_collision_interrupt_system(
|
pub fn state_collision_interrupt_system(
|
||||||
mut query: Query<StatefulFighterQuery>,
|
mut query: Query<StatefulFighterQuery>,
|
||||||
mut animation_players: Query<(&mut AnimationPlayer, &mut AnimationTransitions)>,
|
|
||||||
stage_collision: Res<StageCollision>,
|
stage_collision: Res<StageCollision>,
|
||||||
game_settings: Res<GameSettings>,
|
game_settings: Res<GameSettings>,
|
||||||
baked_animations: Res<Assets<BakedFighterAnimations>>,
|
baked_animations: Res<Assets<BakedFighterAnimations>>,
|
||||||
@@ -328,21 +294,13 @@ pub fn state_collision_interrupt_system(
|
|||||||
mut state,
|
mut state,
|
||||||
mut frame,
|
mut frame,
|
||||||
animations,
|
animations,
|
||||||
animation_player_link,
|
|
||||||
fighter,
|
fighter,
|
||||||
attack_script_assets,
|
attack_script_assets,
|
||||||
} = fighter;
|
} = 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(
|
let mut state_context = frame.context(
|
||||||
&game_settings,
|
&game_settings,
|
||||||
&stage_collision,
|
&stage_collision,
|
||||||
animations,
|
animations,
|
||||||
&mut animation_player,
|
|
||||||
&mut animation_transitions,
|
|
||||||
&baked_animations,
|
&baked_animations,
|
||||||
&attack_scripts,
|
&attack_scripts,
|
||||||
&attack_script_assets,
|
&attack_script_assets,
|
||||||
|
|||||||
@@ -1,13 +1,8 @@
|
|||||||
use bevy::prelude::*;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use crate::fighter::animation::AnimKind;
|
|
||||||
use crate::fighter::baked_animation::BakedFighterAnimations;
|
use crate::fighter::baked_animation::BakedFighterAnimations;
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
#[derive(Component, Clone)]
|
#[derive(Component, Clone)]
|
||||||
pub struct FighterAnimations {
|
pub struct FighterAnimations {
|
||||||
pub graph: Handle<AnimationGraph>,
|
/// The single animation source for gameplay and the visual skeleton.
|
||||||
pub clips: HashMap<AnimKind, AnimationNodeIndex>,
|
|
||||||
/// Gameplay samples this fixed-point asset; the graph remains visual-only.
|
|
||||||
pub baked: Handle<BakedFighterAnimations>,
|
pub baked: Handle<BakedFighterAnimations>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,12 +47,3 @@ impl FromWorld for GameSettings {
|
|||||||
.clone()
|
.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Reflect, Resource, Default)]
|
|
||||||
#[reflect(Resource)]
|
|
||||||
pub struct DebugSettings {
|
|
||||||
pub show_attack_hitboxes: bool,
|
|
||||||
pub show_stage_lines: bool,
|
|
||||||
pub show_ecb: bool,
|
|
||||||
pub debug_gizmos_draw_in_front: bool
|
|
||||||
}
|
|
||||||
+28
-35
@@ -7,9 +7,21 @@ use bevy_asset_loader::prelude::*;
|
|||||||
use bevy_ggrs::prelude::Session;
|
use bevy_ggrs::prelude::Session;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
AppState, args::Args, fighter::{
|
AppState,
|
||||||
FighterId, animation::AnimKind, attack::{AttackKind, FighterAttackScriptAssets}, baked_animation::BakedFighterAnimations, manifest::{FighterManifest, FighterManifestRegistry}, spawn_fighter, visual::FighterAnimations,
|
args::Args,
|
||||||
}, math::{int::FGi32, vec::FGVec2}, netcode::{GGRSCfg, session::create_session}, scripting::FighterAttackScript, stage::{
|
fighter::{
|
||||||
|
FighterId,
|
||||||
|
animation::AnimKind,
|
||||||
|
attack::{AttackKind, FighterAttackScriptAssets},
|
||||||
|
baked_animation::BakedFighterAnimations,
|
||||||
|
manifest::{FighterManifest, FighterManifestRegistry},
|
||||||
|
spawn_fighter,
|
||||||
|
visual::FighterAnimations,
|
||||||
|
},
|
||||||
|
math::{int::FGi32, vec::FGVec2},
|
||||||
|
netcode::{GGRSCfg, session::create_session},
|
||||||
|
scripting::FighterAttackScript,
|
||||||
|
stage::{
|
||||||
self,
|
self,
|
||||||
manifest::{StageId, StageManifest, StageManifestRegistry},
|
manifest::{StageId, StageManifest, StageManifestRegistry},
|
||||||
},
|
},
|
||||||
@@ -31,7 +43,7 @@ pub struct SelectedFighterAssets {
|
|||||||
pub fighter: FighterId,
|
pub fighter: FighterId,
|
||||||
pub model: Handle<Gltf>,
|
pub model: Handle<Gltf>,
|
||||||
pub baked_animations: Handle<BakedFighterAnimations>,
|
pub baked_animations: Handle<BakedFighterAnimations>,
|
||||||
pub attack_scripts: HashMap<AttackKind, Handle<FighterAttackScript>>
|
pub attack_scripts: HashMap<AttackKind, Handle<FighterAttackScript>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Resource)]
|
#[derive(Resource)]
|
||||||
@@ -73,7 +85,12 @@ impl MatchAssets {
|
|||||||
fighter: player.fighter,
|
fighter: player.fighter,
|
||||||
model: asset_server.load(manifest.model_path.clone()),
|
model: asset_server.load(manifest.model_path.clone()),
|
||||||
baked_animations: asset_server.load(manifest.baked_animation_path.clone()),
|
baked_animations: asset_server.load(manifest.baked_animation_path.clone()),
|
||||||
attack_scripts: manifest.attack_scripts.clone().into_iter().map(|(kind, path)| (kind, asset_server.load(path))).collect()
|
attack_scripts: manifest
|
||||||
|
.attack_scripts
|
||||||
|
.clone()
|
||||||
|
.into_iter()
|
||||||
|
.map(|(kind, path)| (kind, asset_server.load(path)))
|
||||||
|
.collect(),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
@@ -142,7 +159,6 @@ pub fn prepare_match(
|
|||||||
stage_registry: Res<StageManifestRegistry>,
|
stage_registry: Res<StageManifestRegistry>,
|
||||||
manifests: Res<Assets<FighterManifest>>,
|
manifests: Res<Assets<FighterManifest>>,
|
||||||
stage_manifests: Res<Assets<StageManifest>>,
|
stage_manifests: Res<Assets<StageManifest>>,
|
||||||
mut graphs: ResMut<Assets<AnimationGraph>>,
|
|
||||||
args: Res<Args>,
|
args: Res<Args>,
|
||||||
mut next_state: ResMut<NextState<AppState>>,
|
mut next_state: ResMut<NextState<AppState>>,
|
||||||
) {
|
) {
|
||||||
@@ -188,21 +204,7 @@ pub fn prepare_match(
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut graph = AnimationGraph::new();
|
for kind in manifest.animations.keys() {
|
||||||
let mut clips = HashMap::<AnimKind, AnimationNodeIndex>::new();
|
|
||||||
|
|
||||||
for (kind, clip_name) in &manifest.animations {
|
|
||||||
let Some(clip) = gltf.named_animations.get(clip_name.as_str()) else {
|
|
||||||
fail_match(
|
|
||||||
&mut next_state,
|
|
||||||
&format!(
|
|
||||||
"{:?} animation manifest references missing clip '{clip_name}'",
|
|
||||||
character.fighter
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
clips.insert(*kind, graph.add_clip(clip.clone(), 1.0, graph.root));
|
|
||||||
if baked.frame_count(*kind).is_none() {
|
if baked.frame_count(*kind).is_none() {
|
||||||
fail_match(
|
fail_match(
|
||||||
&mut next_state,
|
&mut next_state,
|
||||||
@@ -215,27 +217,20 @@ pub fn prepare_match(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !clips.contains_key(&AnimKind::Wait) {
|
if baked.frame_count(AnimKind::Wait).is_none() {
|
||||||
fail_match(&mut next_state, "a character has no Wait animation");
|
fail_match(&mut next_state, "a character has no Wait animation");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let animations = FighterAnimations {
|
let animations = FighterAnimations {
|
||||||
graph: graphs.add(graph),
|
|
||||||
clips,
|
|
||||||
baked: character.baked_animations.clone(),
|
baked: character.baked_animations.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let attack_scripts = FighterAttackScriptAssets {
|
let attack_scripts = FighterAttackScriptAssets {
|
||||||
scripts: character.attack_scripts.clone()
|
scripts: character.attack_scripts.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
prepared.push((
|
prepared.push((scene, animations, attack_scripts, manifest_handle));
|
||||||
scene,
|
|
||||||
animations,
|
|
||||||
attack_scripts,
|
|
||||||
manifest_handle,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let session = match create_session(&args, request.players.len()) {
|
let session = match create_session(&args, request.players.len()) {
|
||||||
@@ -265,10 +260,8 @@ pub fn prepare_match(
|
|||||||
stage_manifest.camera.clone(),
|
stage_manifest.camera.clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
for (
|
for (index, (player, (scene, animations, attack_scripts, manifest_handle))) in
|
||||||
index,
|
request.players.iter().zip(prepared).enumerate()
|
||||||
(player, (scene, animations, attack_scripts, manifest_handle)),
|
|
||||||
) in request.players.iter().zip(prepared).enumerate()
|
|
||||||
{
|
{
|
||||||
let spawn_x = (index as i32 * 2 + 1 - request.players.len() as i32) * 5;
|
let spawn_x = (index as i32 * 2 + 1 - request.players.len() as i32) * 5;
|
||||||
spawn_fighter(
|
spawn_fighter(
|
||||||
|
|||||||
@@ -1,4 +1,2 @@
|
|||||||
|
|
||||||
|
|
||||||
pub type FGi32 = fixed::FixedI32<fixed::types::extra::U16>;
|
pub type FGi32 = fixed::FixedI32<fixed::types::extra::U16>;
|
||||||
pub type FGWide = fixed::FixedI128<fixed::types::extra::U32>;
|
pub type FGWide = fixed::FixedI128<fixed::types::extra::U32>;
|
||||||
|
|||||||
@@ -1,7 +1,15 @@
|
|||||||
|
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
use bevy::{asset::{AssetLoader, AssetPath, AsyncReadExt, AsyncWriteExt, LoadContext, io::{Reader, Writer}, processor::LoadTransformAndSave, saver::{AssetSaver, SavedAsset}, transformer::{AssetTransformer, TransformedAsset}}, prelude::*};
|
use bevy::{
|
||||||
|
asset::{
|
||||||
|
AssetLoader, AssetPath, AsyncReadExt, AsyncWriteExt, LoadContext,
|
||||||
|
io::{Reader, Writer},
|
||||||
|
processor::LoadTransformAndSave,
|
||||||
|
saver::{AssetSaver, SavedAsset},
|
||||||
|
transformer::{AssetTransformer, TransformedAsset},
|
||||||
|
},
|
||||||
|
prelude::*,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::scripting::{FighterAttackScript, move_compiler};
|
use crate::scripting::{FighterAttackScript, move_compiler};
|
||||||
|
|
||||||
@@ -41,7 +49,7 @@ impl AssetTransformer for FighterAttackScriptTransformer {
|
|||||||
type Settings = ();
|
type Settings = ();
|
||||||
type Error = String;
|
type Error = String;
|
||||||
|
|
||||||
async fn transform<'a>(
|
async fn transform<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
source: TransformedAsset<Self::AssetInput>,
|
source: TransformedAsset<Self::AssetInput>,
|
||||||
_: &'a Self::Settings,
|
_: &'a Self::Settings,
|
||||||
@@ -51,7 +59,6 @@ impl AssetTransformer for FighterAttackScriptTransformer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Default, TypePath)]
|
#[derive(Default, TypePath)]
|
||||||
struct FighterAttackScriptLoader;
|
struct FighterAttackScriptLoader;
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
use bevy::{prelude::*};
|
use bevy::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{fighter::attack::AttackHitbox, math::int::FGi32};
|
use crate::{fighter::attack::AttackHitbox, math::int::FGi32};
|
||||||
|
|
||||||
pub mod move_compiler;
|
|
||||||
pub mod importer;
|
pub mod importer;
|
||||||
|
pub mod move_compiler;
|
||||||
|
|
||||||
pub enum MoveAngleKind {
|
pub enum MoveAngleKind {
|
||||||
Normal(FGi32),
|
Normal(FGi32),
|
||||||
Sakurai
|
Sakurai,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Asset, Reflect, Serialize, Deserialize)]
|
#[derive(Asset, Reflect, Serialize, Deserialize)]
|
||||||
|
|||||||
Reference in New Issue
Block a user