Store baked animation matrices in a component.
This commit is contained in:
@@ -16,14 +16,20 @@ use bevy::{
|
|||||||
prelude::*,
|
prelude::*,
|
||||||
reflect::TypePath,
|
reflect::TypePath,
|
||||||
};
|
};
|
||||||
|
use bevy_ggrs::prelude::GgrsSchedule;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
fighter::{animation::AnimKind, manifest::FighterManifest},
|
fighter::{
|
||||||
|
animation::{AnimKind, FighterAnimationFrame},
|
||||||
|
manifest::FighterManifest,
|
||||||
|
visual::FighterAnimations,
|
||||||
|
},
|
||||||
math::{
|
math::{
|
||||||
int::{FGWide, FGi32},
|
int::{FGWide, FGi32},
|
||||||
vec3::FGVec3,
|
vec3::FGVec3,
|
||||||
},
|
},
|
||||||
|
schedule::GameplaySet,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const BAKED_ANIMATION_FPS: u32 = 60;
|
pub const BAKED_ANIMATION_FPS: u32 = 60;
|
||||||
@@ -42,7 +48,7 @@ impl FixedMat4 {
|
|||||||
[FGi32::ZERO, FGi32::ONE, FGi32::ZERO, FGi32::ZERO],
|
[FGi32::ZERO, FGi32::ONE, FGi32::ZERO, FGi32::ZERO],
|
||||||
[FGi32::ZERO, FGi32::ZERO, FGi32::ONE, FGi32::ZERO],
|
[FGi32::ZERO, FGi32::ZERO, FGi32::ONE, FGi32::ZERO],
|
||||||
[FGi32::ZERO, FGi32::ZERO, FGi32::ZERO, FGi32::ONE],
|
[FGi32::ZERO, FGi32::ZERO, FGi32::ZERO, FGi32::ONE],
|
||||||
]
|
],
|
||||||
};
|
};
|
||||||
fn from_mat4(matrix: Mat4) -> io::Result<Self> {
|
fn from_mat4(matrix: Mat4) -> io::Result<Self> {
|
||||||
let values = matrix.to_cols_array();
|
let values = matrix.to_cols_array();
|
||||||
@@ -151,11 +157,17 @@ pub struct BakedFighterAnimations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl BakedFighterAnimations {
|
impl BakedFighterAnimations {
|
||||||
pub fn sample(&self, kind: AnimKind, frame: u32, bone: &str) -> Option<FixedMat4> {
|
pub fn sample_pose(&self, frame: &FighterAnimationFrame) -> Option<&[FixedMat4]> {
|
||||||
let bone_index = self.bone_names.iter().position(|name| name == bone)?;
|
let frames = &self.clip(frame.kind)?.frames;
|
||||||
let frames = &self.clip(kind)?.frames;
|
let frame_index = if frame.repeat {
|
||||||
let pose = frames.get(frame as usize).or_else(|| frames.last())?;
|
frame.frame % frames.len() as u32
|
||||||
pose.get(bone_index).copied()
|
} else {
|
||||||
|
frame.frame
|
||||||
|
};
|
||||||
|
frames
|
||||||
|
.get(frame_index as usize)
|
||||||
|
.or_else(|| frames.last())
|
||||||
|
.map(Vec::as_slice)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn frame_count(&self, kind: AnimKind) -> Option<u32> {
|
pub fn frame_count(&self, kind: AnimKind) -> Option<u32> {
|
||||||
@@ -169,6 +181,62 @@ impl BakedFighterAnimations {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The current baked model-space pose for a fighter, recalculated every
|
||||||
|
/// rollback frame from its animation clock and immutable baked asset.
|
||||||
|
#[derive(Component, Clone, Debug, Default)]
|
||||||
|
pub struct FighterBoneMatrices {
|
||||||
|
bone_names: Vec<String>,
|
||||||
|
matrices: Vec<FixedMat4>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FighterBoneMatrices {
|
||||||
|
pub fn get(&self, bone: &str) -> Option<FixedMat4> {
|
||||||
|
let index = self.bone_names.iter().position(|name| name == bone)?;
|
||||||
|
self.matrices.get(index).copied()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter(&self) -> impl Iterator<Item = (&str, FixedMat4)> {
|
||||||
|
self.bone_names
|
||||||
|
.iter()
|
||||||
|
.map(String::as_str)
|
||||||
|
.zip(self.matrices.iter().copied())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_pose(&mut self, bone_names: &[String], matrices: &[FixedMat4]) {
|
||||||
|
assert_eq!(
|
||||||
|
bone_names.len(),
|
||||||
|
matrices.len(),
|
||||||
|
"baked animation pose does not match its bone names"
|
||||||
|
);
|
||||||
|
if self.bone_names.is_empty() {
|
||||||
|
self.bone_names.extend_from_slice(bone_names);
|
||||||
|
} else {
|
||||||
|
assert_eq!(self.bone_names, bone_names, "baked bone names changed");
|
||||||
|
}
|
||||||
|
self.matrices.clear();
|
||||||
|
self.matrices.extend_from_slice(matrices);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_fighter_bone_matrices(
|
||||||
|
mut fighters: Query<(
|
||||||
|
&FighterAnimationFrame,
|
||||||
|
&FighterAnimations,
|
||||||
|
&mut FighterBoneMatrices,
|
||||||
|
)>,
|
||||||
|
baked_assets: Res<Assets<BakedFighterAnimations>>,
|
||||||
|
) {
|
||||||
|
for (frame, animations, mut matrices) in &mut fighters {
|
||||||
|
let baked = baked_assets
|
||||||
|
.get(&animations.baked)
|
||||||
|
.expect("fighter baked animation asset should be loaded before the match starts");
|
||||||
|
let pose = baked
|
||||||
|
.sample_pose(frame)
|
||||||
|
.expect("fighter animation frame should select a baked pose");
|
||||||
|
matrices.set_pose(&baked.bone_names, pose);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct FighterBakeDeclaration {
|
struct FighterBakeDeclaration {
|
||||||
manifest_path: String,
|
manifest_path: String,
|
||||||
@@ -304,7 +372,11 @@ impl Plugin for BakedAnimationPlugin {
|
|||||||
FighterBakeTransformer,
|
FighterBakeTransformer,
|
||||||
BakedFighterAnimationsSaver,
|
BakedFighterAnimationsSaver,
|
||||||
))
|
))
|
||||||
.set_default_asset_processor::<FighterBakeProcessor>("fighterbake");
|
.set_default_asset_processor::<FighterBakeProcessor>("fighterbake")
|
||||||
|
.add_systems(
|
||||||
|
GgrsSchedule,
|
||||||
|
update_fighter_bone_matrices.in_set(GameplaySet::Animation),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -598,6 +670,50 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pose_sampling_wraps_repeating_frames_and_clamps_finished_frames() {
|
||||||
|
let first = FixedMat4::IDENTITY;
|
||||||
|
let mut second = FixedMat4::IDENTITY;
|
||||||
|
second.translate(FGVec3::lit("2", "0", "0"));
|
||||||
|
let baked = BakedFighterAnimations {
|
||||||
|
frame_rate: BAKED_ANIMATION_FPS,
|
||||||
|
bone_names: vec!["root".into()],
|
||||||
|
clips: vec![BakedAnimationClip {
|
||||||
|
frames: vec![vec![first], vec![second]],
|
||||||
|
}],
|
||||||
|
clip_indices: HashMap::from([(AnimKind::Wait, 0)]),
|
||||||
|
};
|
||||||
|
|
||||||
|
let repeating = FighterAnimationFrame {
|
||||||
|
kind: AnimKind::Wait,
|
||||||
|
frame: 3,
|
||||||
|
repeat: true,
|
||||||
|
};
|
||||||
|
let finished = FighterAnimationFrame {
|
||||||
|
kind: AnimKind::Wait,
|
||||||
|
frame: 3,
|
||||||
|
repeat: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(baked.sample_pose(&repeating), Some(&[second][..]));
|
||||||
|
assert_eq!(baked.sample_pose(&finished), Some(&[second][..]));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bone_matrix_component_keeps_names_aligned_with_the_current_pose() {
|
||||||
|
let first = FixedMat4::IDENTITY;
|
||||||
|
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]);
|
||||||
|
|
||||||
|
assert_eq!(matrices.get("hand"), Some(second));
|
||||||
|
assert_eq!(
|
||||||
|
matrices.iter().collect::<Vec<_>>(),
|
||||||
|
vec![("root", first), ("hand", second)]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn fixed_matrix_transforms_points() {
|
fn fixed_matrix_transforms_points() {
|
||||||
let transform = FixedMat4 {
|
let transform = FixedMat4 {
|
||||||
|
|||||||
+102
-77
@@ -4,19 +4,20 @@ use bevy::color::palettes::css::ORANGE;
|
|||||||
use bevy::{color::palettes::css::YELLOW, prelude::*};
|
use bevy::{color::palettes::css::YELLOW, prelude::*};
|
||||||
use bevy_egui::{EguiContexts, egui};
|
use bevy_egui::{EguiContexts, egui};
|
||||||
|
|
||||||
|
use crate::debug_tools::{DebugSettings, FighterDebugRowKind};
|
||||||
use crate::fighter::animation::FighterAnimationFrame;
|
use crate::fighter::animation::FighterAnimationFrame;
|
||||||
use crate::fighter::baked_animation::BakedFighterAnimations;
|
use crate::fighter::baked_animation::{BakedFighterAnimations, FighterBoneMatrices};
|
||||||
use crate::fighter::ecb::FighterPreviousECB;
|
use crate::fighter::ecb::FighterPreviousECB;
|
||||||
use crate::fighter::state::StateNameDebug;
|
use crate::fighter::state::StateNameDebug;
|
||||||
use crate::fighter::visual::FighterAnimations;
|
use crate::fighter::visual::FighterAnimations;
|
||||||
use crate::fighter::{
|
use crate::fighter::{
|
||||||
FighterECB, FighterFacingDirection, FighterHitboxes, FighterPreviousTranslation, FighterTranslation, FighterVelocity,
|
FighterECB, FighterFacingDirection, FighterHitboxes, FighterPreviousTranslation,
|
||||||
|
FighterTranslation, FighterVelocity,
|
||||||
};
|
};
|
||||||
use crate::input::FighterInput;
|
use crate::input::FighterInput;
|
||||||
use crate::math::vec3::FGVec3;
|
use crate::math::vec3::FGVec3;
|
||||||
use crate::player::Player;
|
use crate::player::Player;
|
||||||
use crate::scripting::FighterAttackScript;
|
use crate::scripting::FighterAttackScript;
|
||||||
use crate::debug_tools::{DebugSettings, FighterDebugRowKind};
|
|
||||||
|
|
||||||
pub fn debug_draw_ecb(
|
pub fn debug_draw_ecb(
|
||||||
ecbs: Query<(
|
ecbs: Query<(
|
||||||
@@ -57,7 +58,9 @@ pub fn fighter_debug(
|
|||||||
let mut fighters = query.iter().collect::<Vec<_>>();
|
let mut fighters = query.iter().collect::<Vec<_>>();
|
||||||
fighters.sort_unstable_by_key(|(player, ..)| player.handle);
|
fighters.sort_unstable_by_key(|(player, ..)| player.handle);
|
||||||
|
|
||||||
for (player, velocity, input, animation_frame, state_name, animations, translation, facing) in fighters {
|
for (player, velocity, input, animation_frame, state_name, animations, translation, facing) in
|
||||||
|
fighters
|
||||||
|
{
|
||||||
let Some(rows) = settings.player_rows(player.handle) else {
|
let Some(rows) = settings.player_rows(player.handle) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
@@ -65,95 +68,109 @@ pub fn fighter_debug(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
egui::Window::new(format!("Player {}", player.handle + 1)).show(contexts.ctx_mut()?, |ui| {
|
egui::Window::new(format!("Player {}", player.handle + 1)).show(
|
||||||
for row in rows {
|
contexts.ctx_mut()?,
|
||||||
match row {
|
|ui| {
|
||||||
FighterDebugRowKind::None => {}
|
for row in rows {
|
||||||
FighterDebugRowKind::CurrentState => {
|
match row {
|
||||||
let frame_count = anims
|
FighterDebugRowKind::None => {}
|
||||||
.get(&animations.baked)
|
FighterDebugRowKind::CurrentState => {
|
||||||
.and_then(|animation| animation.frame_count(animation_frame.kind));
|
let frame_count = anims
|
||||||
if let Some(frame_count) = frame_count {
|
.get(&animations.baked)
|
||||||
|
.and_then(|animation| animation.frame_count(animation_frame.kind));
|
||||||
|
if let Some(frame_count) = frame_count {
|
||||||
|
ui.label(format!(
|
||||||
|
"State: {:?} ({:?} {}/{})",
|
||||||
|
state_name.0,
|
||||||
|
animation_frame.kind,
|
||||||
|
animation_frame.frame,
|
||||||
|
frame_count
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
ui.label(format!(
|
||||||
|
"State: {:?} ({:?})",
|
||||||
|
state_name.0, animation_frame.kind
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FighterDebugRowKind::Velocity => {
|
||||||
ui.label(format!(
|
ui.label(format!(
|
||||||
"State: {:?} ({:?} {}/{})",
|
"Velocity: ({:.2}, {:.2})",
|
||||||
state_name.0, animation_frame.kind, animation_frame.frame, frame_count
|
velocity.0.x, velocity.0.y
|
||||||
));
|
));
|
||||||
} else {
|
}
|
||||||
ui.label(format!("State: {:?} ({:?})", state_name.0, animation_frame.kind));
|
FighterDebugRowKind::Input => {
|
||||||
|
const STICK_SIZE: f32 = 50.0;
|
||||||
|
const STICK_SIZE_FRACTION: f32 = 0.75;
|
||||||
|
let last_frame = input.get_last_frame();
|
||||||
|
let movement = egui::Vec2::new(
|
||||||
|
last_frame.movement.x.to_num(),
|
||||||
|
-last_frame.movement.y.to_num::<f32>(),
|
||||||
|
);
|
||||||
|
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
let (_, rect) = ui.allocate_space(egui::Vec2::new(
|
||||||
|
STICK_SIZE * (1.0 + STICK_SIZE_FRACTION),
|
||||||
|
STICK_SIZE * (1.0 + STICK_SIZE_FRACTION),
|
||||||
|
));
|
||||||
|
let painter = ui.painter();
|
||||||
|
let button_bg =
|
||||||
|
egui::Rgba::WHITE * egui::Rgba::from_white_alpha(0.25);
|
||||||
|
painter.circle_filled(rect.center(), STICK_SIZE * 0.5, button_bg);
|
||||||
|
painter.circle_filled(
|
||||||
|
rect.center() + STICK_SIZE * 0.5 * movement,
|
||||||
|
STICK_SIZE * STICK_SIZE_FRACTION * 0.5,
|
||||||
|
button_bg,
|
||||||
|
);
|
||||||
|
ui.label(format!(
|
||||||
|
"X: {:.2}\nY:{:.2}",
|
||||||
|
last_frame.movement.x, last_frame.movement.y
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
FighterDebugRowKind::Position => {
|
||||||
|
ui.label(format!("Position: {:?}", translation.0));
|
||||||
|
}
|
||||||
|
FighterDebugRowKind::Facing => {
|
||||||
|
ui.label(format!("Facing: {:?}", facing));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
FighterDebugRowKind::Velocity => {
|
|
||||||
ui.label(format!("Velocity: ({:.2}, {:.2})", velocity.0.x, velocity.0.y));
|
|
||||||
}
|
|
||||||
FighterDebugRowKind::Input => {
|
|
||||||
const STICK_SIZE: f32 = 50.0;
|
|
||||||
const STICK_SIZE_FRACTION: f32 = 0.75;
|
|
||||||
let last_frame = input.get_last_frame();
|
|
||||||
let movement = egui::Vec2::new(last_frame.movement.x.to_num(), -last_frame.movement.y.to_num::<f32>());
|
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
let (_, rect) = ui.allocate_space(egui::Vec2::new(STICK_SIZE*(1.0 + STICK_SIZE_FRACTION), STICK_SIZE*(1.0 + STICK_SIZE_FRACTION)));
|
|
||||||
let painter = ui.painter();
|
|
||||||
let button_bg = egui::Rgba::WHITE * egui::Rgba::from_white_alpha(0.25);
|
|
||||||
painter.circle_filled(rect.center(), STICK_SIZE * 0.5, button_bg);
|
|
||||||
painter.circle_filled(
|
|
||||||
rect.center() + STICK_SIZE * 0.5 * movement,
|
|
||||||
STICK_SIZE * STICK_SIZE_FRACTION * 0.5,
|
|
||||||
button_bg,
|
|
||||||
);
|
|
||||||
ui.label(format!("X: {:.2}\nY:{:.2}", last_frame.movement.x, last_frame.movement.y));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
FighterDebugRowKind::Position => {
|
|
||||||
ui.label(format!("Position: {:?}", translation.0));
|
|
||||||
},
|
|
||||||
FighterDebugRowKind::Facing => {
|
|
||||||
ui.label(format!("Facing: {:?}", facing));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn animation_debug(
|
pub fn animation_debug(query: Query<(&GlobalTransform, &FighterBoneMatrices)>, mut gizmos: Gizmos) {
|
||||||
query: Query<(&GlobalTransform, &FighterAnimationFrame, &FighterAnimations)>,
|
for (global_transform, matrices) in query {
|
||||||
baked_anims: Res<Assets<BakedFighterAnimations>>,
|
for (_, matrix) in matrices.iter() {
|
||||||
mut gizmos: Gizmos,
|
let pos = global_transform.transform_point(Vec3::new(
|
||||||
) {
|
matrix.cols[3][0].to_num(),
|
||||||
for (global_transform, frame, anims) in query {
|
matrix.cols[3][1].to_num(),
|
||||||
let baked: &BakedFighterAnimations = baked_anims
|
matrix.cols[3][2].to_num(),
|
||||||
.get(&anims.baked)
|
));
|
||||||
.expect("Baked anims should be in");
|
gizmos.cross(pos, 0.5, bevy::color::palettes::css::RED);
|
||||||
for bone in &baked.bone_names {
|
gizmos.axes(*global_transform, 1.0);
|
||||||
if bone != "Bone_30" {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let out = anims.sample_bone(&baked_anims, frame, bone);
|
|
||||||
if let Some(out) = out {
|
|
||||||
let pos = global_transform.transform_point(Vec3::new(
|
|
||||||
out.cols[3][0].to_num(),
|
|
||||||
out.cols[3][1].to_num(),
|
|
||||||
out.cols[3][2].to_num(),
|
|
||||||
));
|
|
||||||
//gizmos.text(pos, bone, 1.0, Vec2::new(0.0, 0.0), bevy::color::palettes::css::BLACK);
|
|
||||||
gizmos.cross(pos, 0.5, bevy::color::palettes::css::RED);
|
|
||||||
gizmos.axes(*global_transform, 1.0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn attack_debug(
|
pub fn attack_debug(
|
||||||
query: Query<(&FighterHitboxes, &FighterAnimationFrame, &FighterAnimations, &FighterTranslation, &FighterFacingDirection)>,
|
query: Query<(
|
||||||
baked_anims: Res<Assets<BakedFighterAnimations>>,
|
&FighterHitboxes,
|
||||||
|
&FighterBoneMatrices,
|
||||||
|
&FighterTranslation,
|
||||||
|
&FighterFacingDirection,
|
||||||
|
)>,
|
||||||
attack_scripts: Res<Assets<FighterAttackScript>>,
|
attack_scripts: Res<Assets<FighterAttackScript>>,
|
||||||
mut gizmos: Gizmos,
|
mut gizmos: Gizmos,
|
||||||
) {
|
) {
|
||||||
for (hitboxes, frame, animations, translation, facing_direction) in query {
|
for (hitboxes, matrices, translation, facing_direction) in query {
|
||||||
if let Some(script) = &hitboxes.attack_script {
|
if let Some(script) = &hitboxes.attack_script {
|
||||||
let script = attack_scripts.get(script).expect("Script ref should be valid");
|
let script = attack_scripts
|
||||||
|
.get(script)
|
||||||
|
.expect("Script ref should be valid");
|
||||||
let player_trf = translation.get_3d_transform(facing_direction);
|
let player_trf = translation.get_3d_transform(facing_direction);
|
||||||
|
|
||||||
for i in hitboxes.active_hitboxes.iter() {
|
for i in hitboxes.active_hitboxes.iter() {
|
||||||
@@ -161,16 +178,24 @@ pub fn attack_debug(
|
|||||||
let radius = script.hitboxes[*i].radius;
|
let radius = script.hitboxes[*i].radius;
|
||||||
let bone = &script.hitboxes[*i].bone;
|
let bone = &script.hitboxes[*i].bone;
|
||||||
|
|
||||||
let trf = animations.sample_bone(&baked_anims, frame, bone);
|
let trf = matrices.get(bone);
|
||||||
if let Some(trf) = trf {
|
if let Some(trf) = trf {
|
||||||
let trf = player_trf.mul(trf);
|
let trf = player_trf.mul(trf);
|
||||||
let start = player_trf.transform_point(FGVec3::lit("0.0", "1.0", "0.0"));
|
let start = player_trf.transform_point(FGVec3::lit("0.0", "1.0", "0.0"));
|
||||||
let end = player_trf.transform_point(FGVec3::lit("0.0", "1.0", "1.0"));
|
let end = player_trf.transform_point(FGVec3::lit("0.0", "1.0", "1.0"));
|
||||||
let pos = trf.transform_point(offset);
|
let pos = trf.transform_point(offset);
|
||||||
let pos_draw = Vec3::new(pos.x.to_num(), pos.y.to_num(), pos.z.to_num());
|
let pos_draw = Vec3::new(pos.x.to_num(), pos.y.to_num(), pos.z.to_num());
|
||||||
|
//Primitive
|
||||||
|
//gizmos.primitive_3d(, isometry, color)
|
||||||
gizmos.sphere(pos_draw, radius.to_num(), bevy::color::palettes::css::RED);
|
gizmos.sphere(pos_draw, radius.to_num(), bevy::color::palettes::css::RED);
|
||||||
|
|
||||||
gizmos.arrow(start.to_vec3(), end.to_vec3(),bevy::color::palettes::css::RED);
|
gizmos.arrow(
|
||||||
|
start.to_vec3(),
|
||||||
|
end.to_vec3(),
|
||||||
|
bevy::color::palettes::css::RED,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
warn!("Bone not found required by attack script: {}", bone);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+30
-16
@@ -7,6 +7,7 @@ use bevy_ggrs::prelude::*;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
pub mod animation;
|
pub mod animation;
|
||||||
|
pub mod attack;
|
||||||
pub mod attributes;
|
pub mod attributes;
|
||||||
pub mod baked_animation;
|
pub mod baked_animation;
|
||||||
pub mod collision;
|
pub mod collision;
|
||||||
@@ -16,7 +17,6 @@ pub mod manifest;
|
|||||||
pub mod motion;
|
pub mod motion;
|
||||||
pub mod state;
|
pub mod state;
|
||||||
pub mod visual;
|
pub mod visual;
|
||||||
pub mod attack;
|
|
||||||
|
|
||||||
// The fighter components are re-exported so the rest of the crate can say
|
// The fighter components are re-exported so the rest of the crate can say
|
||||||
// `fighter::FighterVelocity` without caring which file it lives in. Named
|
// `fighter::FighterVelocity` without caring which file it lives in. Named
|
||||||
@@ -29,8 +29,18 @@ pub use motion::{FighterPreviousTranslation, FighterTranslation, FighterVelocity
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
fighter::{
|
fighter::{
|
||||||
animation::animation_init, attack::FighterAttackScriptAssets, manifest::FighterManifest, state::{FighterState, fall::FallState}, visual::FighterAnimations,
|
animation::animation_init,
|
||||||
}, input::FighterInput, math::{int::FGi32, vec::FGVec2}, player::Player, schedule::GameplaySet, scripting::FighterAttackScript, stage::line::StageCollision,
|
attack::FighterAttackScriptAssets,
|
||||||
|
manifest::FighterManifest,
|
||||||
|
state::{FighterState, fall::FallState},
|
||||||
|
visual::FighterAnimations,
|
||||||
|
},
|
||||||
|
input::FighterInput,
|
||||||
|
math::{int::FGi32, vec::FGVec2},
|
||||||
|
player::Player,
|
||||||
|
schedule::GameplaySet,
|
||||||
|
scripting::FighterAttackScript,
|
||||||
|
stage::line::StageCollision,
|
||||||
};
|
};
|
||||||
use state::state_interrupt_system;
|
use state::state_interrupt_system;
|
||||||
|
|
||||||
@@ -126,7 +136,10 @@ impl Plugin for FighterPlugin {
|
|||||||
)
|
)
|
||||||
.add_systems(
|
.add_systems(
|
||||||
Update,
|
Update,
|
||||||
(motion::apply_fighter_translation_to_visuals, animation::apply_animation)
|
(
|
||||||
|
motion::apply_fighter_translation_to_visuals,
|
||||||
|
animation::apply_animation,
|
||||||
|
)
|
||||||
.chain()
|
.chain()
|
||||||
.run_if(in_state(crate::AppState::InMatch)),
|
.run_if(in_state(crate::AppState::InMatch)),
|
||||||
)
|
)
|
||||||
@@ -171,7 +184,7 @@ impl Plugin for FighterPlugin {
|
|||||||
#[derive(Component, Clone)]
|
#[derive(Component, Clone)]
|
||||||
pub struct FighterHitboxes {
|
pub struct FighterHitboxes {
|
||||||
pub attack_script: Option<Handle<FighterAttackScript>>,
|
pub attack_script: Option<Handle<FighterAttackScript>>,
|
||||||
pub active_hitboxes: Vec<usize>
|
pub active_hitboxes: Vec<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FighterHitboxes {
|
impl FighterHitboxes {
|
||||||
@@ -195,18 +208,18 @@ pub fn spawn_fighter(
|
|||||||
Player {
|
Player {
|
||||||
handle: player_handle,
|
handle: player_handle,
|
||||||
},
|
},
|
||||||
Fighter {
|
Fighter { manifest: manifest },
|
||||||
manifest: manifest,
|
|
||||||
},
|
|
||||||
FighterFacingDirection::Right,
|
FighterFacingDirection::Right,
|
||||||
(FighterECB {
|
(
|
||||||
vertical_half: FGi32::lit("5.0"),
|
FighterECB {
|
||||||
horizontal_half: FGi32::lit("2.5"),
|
vertical_half: FGi32::lit("5.0"),
|
||||||
},
|
horizontal_half: FGi32::lit("2.5"),
|
||||||
FighterHitboxes {
|
},
|
||||||
attack_script: None,
|
FighterHitboxes {
|
||||||
active_hitboxes: vec![]
|
attack_script: None,
|
||||||
}),
|
active_hitboxes: vec![],
|
||||||
|
},
|
||||||
|
),
|
||||||
(
|
(
|
||||||
FighterVelocity::default(),
|
FighterVelocity::default(),
|
||||||
FighterTranslation(spawn_position),
|
FighterTranslation(spawn_position),
|
||||||
@@ -217,6 +230,7 @@ pub fn spawn_fighter(
|
|||||||
attack_scripts,
|
attack_scripts,
|
||||||
FighterState::Fall(FallState),
|
FighterState::Fall(FallState),
|
||||||
animation::FighterAnimationFrame::new(animation::AnimKind::Wait, true),
|
animation::FighterAnimationFrame::new(animation::AnimKind::Wait, true),
|
||||||
|
baked_animation::FighterBoneMatrices::default(),
|
||||||
FighterVisual,
|
FighterVisual,
|
||||||
FighterInput::default(),
|
FighterInput::default(),
|
||||||
crate::camera::FighterCameraExtents::default(),
|
crate::camera::FighterCameraExtents::default(),
|
||||||
|
|||||||
@@ -7,12 +7,13 @@ use bevy::prelude::*;
|
|||||||
|
|
||||||
use crate::fighter::animation::{AnimKind, FighterAnimationFrame, FighterAnimationPlayerLink};
|
use crate::fighter::animation::{AnimKind, FighterAnimationFrame, FighterAnimationPlayerLink};
|
||||||
use crate::fighter::attack::FighterAttackScriptAssets;
|
use crate::fighter::attack::FighterAttackScriptAssets;
|
||||||
use crate::fighter::baked_animation::{BakedFighterAnimations, FixedMat4};
|
use crate::fighter::baked_animation::BakedFighterAnimations;
|
||||||
use crate::fighter::ecb::FighterPreviousECB;
|
use crate::fighter::ecb::FighterPreviousECB;
|
||||||
use crate::fighter::manifest::FighterManifest;
|
use crate::fighter::manifest::FighterManifest;
|
||||||
use crate::fighter::visual::FighterAnimations;
|
use crate::fighter::visual::FighterAnimations;
|
||||||
use crate::fighter::{
|
use crate::fighter::{
|
||||||
Fighter, FighterECB, FighterFacingDirection, FighterHitboxes, FighterPreviousTranslation, FighterTranslation, FighterVelocity,
|
Fighter, FighterECB, FighterFacingDirection, FighterHitboxes, FighterPreviousTranslation,
|
||||||
|
FighterTranslation, FighterVelocity,
|
||||||
};
|
};
|
||||||
use crate::game_settings::GameSettings;
|
use crate::game_settings::GameSettings;
|
||||||
use crate::input::FighterInput;
|
use crate::input::FighterInput;
|
||||||
@@ -24,13 +25,13 @@ pub mod air_dodge;
|
|||||||
pub mod dash;
|
pub mod dash;
|
||||||
pub mod fall;
|
pub mod fall;
|
||||||
pub mod ground;
|
pub mod ground;
|
||||||
|
pub mod ground_attack;
|
||||||
pub mod jump;
|
pub mod jump;
|
||||||
pub mod land;
|
pub mod land;
|
||||||
pub mod run;
|
pub mod run;
|
||||||
pub mod turn;
|
pub mod turn;
|
||||||
pub mod wait;
|
pub mod wait;
|
||||||
pub mod walk;
|
pub mod walk;
|
||||||
pub mod ground_attack;
|
|
||||||
|
|
||||||
macro_rules! fighter_states {
|
macro_rules! fighter_states {
|
||||||
($($variant:ident => $ty:ty),* $(,)?) => {
|
($($variant:ident => $ty:ty),* $(,)?) => {
|
||||||
@@ -131,11 +132,6 @@ impl FighterStateContext<'_> {
|
|||||||
.expect("Current animation should have baked frames")
|
.expect("Current animation should have baked frames")
|
||||||
< self.animation_frame.frame
|
< self.animation_frame.frame
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sample_bone(&self, bone: &str) -> Option<FixedMat4> {
|
|
||||||
self.animations
|
|
||||||
.sample_bone(self.baked_animations, self.animation_frame, bone)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fighter_states! {
|
fighter_states! {
|
||||||
@@ -282,7 +278,7 @@ pub fn state_interrupt_system(
|
|||||||
animations,
|
animations,
|
||||||
animation_player_link,
|
animation_player_link,
|
||||||
fighter,
|
fighter,
|
||||||
attack_script_assets
|
attack_script_assets,
|
||||||
} = fighter;
|
} = fighter;
|
||||||
let Ok((mut animation_player, mut animation_transitions)) =
|
let Ok((mut animation_player, mut animation_transitions)) =
|
||||||
animation_players.get_mut(animation_player_link.player())
|
animation_players.get_mut(animation_player_link.player())
|
||||||
|
|||||||
+10
-4
@@ -21,14 +21,19 @@
|
|||||||
//! 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.
|
||||||
|
|
||||||
use bevy::{asset::processor::{AssetProcessor, ProcessorState}, prelude::*, tasks::block_on};
|
use bevy::{
|
||||||
|
asset::processor::{AssetProcessor, ProcessorState},
|
||||||
|
prelude::*,
|
||||||
|
tasks::block_on,
|
||||||
|
};
|
||||||
use bevy_asset_loader::prelude::*;
|
use bevy_asset_loader::prelude::*;
|
||||||
use bevy_wind_waker_shader::prelude::*;
|
use bevy_wind_waker_shader::prelude::*;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use jackdaw_runtime::EditorCategory;
|
use jackdaw_runtime::EditorCategory;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
camera::MatchCameraPlugin, fighter::manifest::FighterManifest, game_settings::GameSettings, schedule::GameplaySchedulePlugin, stage::manifest::StageManifest,
|
camera::MatchCameraPlugin, fighter::manifest::FighterManifest, game_settings::GameSettings,
|
||||||
|
schedule::GameplaySchedulePlugin, stage::manifest::StageManifest,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod args;
|
mod args;
|
||||||
@@ -42,8 +47,8 @@ mod math;
|
|||||||
pub mod netcode;
|
pub mod netcode;
|
||||||
pub mod player;
|
pub mod player;
|
||||||
pub mod schedule;
|
pub mod schedule;
|
||||||
pub mod stage;
|
|
||||||
pub mod scripting;
|
pub mod scripting;
|
||||||
|
pub mod stage;
|
||||||
|
|
||||||
#[derive(States, Debug, Clone, Copy, Eq, PartialEq, Hash, Default)]
|
#[derive(States, Debug, Clone, Copy, Eq, PartialEq, Hash, Default)]
|
||||||
pub enum AppState {
|
pub enum AppState {
|
||||||
@@ -145,7 +150,8 @@ fn wait_for_asset_processor(
|
|||||||
processor: Option<Res<AssetProcessor>>,
|
processor: Option<Res<AssetProcessor>>,
|
||||||
mut next_state: ResMut<NextState<AppState>>,
|
mut next_state: ResMut<NextState<AppState>>,
|
||||||
) {
|
) {
|
||||||
if processor.is_none_or(|processor| block_on(processor.get_state()) == ProcessorState::Finished) {
|
if processor.is_none_or(|processor| block_on(processor.get_state()) == ProcessorState::Finished)
|
||||||
|
{
|
||||||
next_state.set(AppState::LoadCommonAssets);
|
next_state.set(AppState::LoadCommonAssets);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-15
@@ -6,8 +6,6 @@ use bevy::asset::AssetMode::Processed;
|
|||||||
use bevy::camera_controller::free_camera::FreeCameraPlugin;
|
use bevy::camera_controller::free_camera::FreeCameraPlugin;
|
||||||
use bevy::image::{ImageAddressMode, ImagePlugin, ImageSamplerDescriptor};
|
use bevy::image::{ImageAddressMode, ImagePlugin, ImageSamplerDescriptor};
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy::remote::RemotePlugin;
|
|
||||||
use bevy::remote::http::RemoteHttpPlugin;
|
|
||||||
use bevy_egui::EguiPlugin;
|
use bevy_egui::EguiPlugin;
|
||||||
use bevy_inspector_egui::quick::WorldInspectorPlugin;
|
use bevy_inspector_egui::quick::WorldInspectorPlugin;
|
||||||
|
|
||||||
@@ -15,17 +13,19 @@ fn main() -> AppExit {
|
|||||||
// A `Repeat` sampler so the tileable materials authored in the
|
// A `Repeat` sampler so the tileable materials authored in the
|
||||||
// editor render the same way at runtime; Bevy's default
|
// editor render the same way at runtime; Bevy's default
|
||||||
// `ClampToEdge` smears texture edges on brush geometry.
|
// `ClampToEdge` smears texture edges on brush geometry.
|
||||||
let default_plugins = DefaultPlugins.set(ImagePlugin {
|
let default_plugins = DefaultPlugins
|
||||||
default_sampler: ImageSamplerDescriptor {
|
.set(ImagePlugin {
|
||||||
address_mode_u: ImageAddressMode::Repeat,
|
default_sampler: ImageSamplerDescriptor {
|
||||||
address_mode_v: ImageAddressMode::Repeat,
|
address_mode_u: ImageAddressMode::Repeat,
|
||||||
address_mode_w: ImageAddressMode::Repeat,
|
address_mode_v: ImageAddressMode::Repeat,
|
||||||
..ImageSamplerDescriptor::linear()
|
address_mode_w: ImageAddressMode::Repeat,
|
||||||
},
|
..ImageSamplerDescriptor::linear()
|
||||||
}).set(AssetPlugin {
|
},
|
||||||
mode: Processed,
|
})
|
||||||
..default()
|
.set(AssetPlugin {
|
||||||
});
|
mode: Processed,
|
||||||
|
..default()
|
||||||
|
});
|
||||||
|
|
||||||
App::new()
|
App::new()
|
||||||
.add_plugins(default_plugins)
|
.add_plugins(default_plugins)
|
||||||
@@ -33,8 +33,6 @@ fn main() -> AppExit {
|
|||||||
.add_plugins(jackdaw_runtime::JackdawPlugin)
|
.add_plugins(jackdaw_runtime::JackdawPlugin)
|
||||||
.add_plugins(game_test::GamePlugin)
|
.add_plugins(game_test::GamePlugin)
|
||||||
.add_plugins(FreeCameraPlugin)
|
.add_plugins(FreeCameraPlugin)
|
||||||
.add_plugins(RemotePlugin::default())
|
|
||||||
.add_plugins(RemoteHttpPlugin::default())
|
|
||||||
.add_plugins(EguiPlugin::default())
|
.add_plugins(EguiPlugin::default())
|
||||||
.add_plugins(WorldInspectorPlugin::default()) // adds default options and `InspectorEguiImpl`s
|
.add_plugins(WorldInspectorPlugin::default()) // adds default options and `InspectorEguiImpl`s
|
||||||
.insert_resource(Time::<Fixed>::from_hz(60.0))
|
.insert_resource(Time::<Fixed>::from_hz(60.0))
|
||||||
|
|||||||
Reference in New Issue
Block a user