Integrate basic attack script debug.

This commit is contained in:
2026-08-30 14:45:26 +02:00
parent af0e567332
commit 1d5a07324d
19 changed files with 353 additions and 43 deletions
+4
View File
@@ -149,4 +149,8 @@ pub enum AnimKind {
AirDodge, AirDodge,
Fall, Fall,
Turn, Turn,
// Attacks
AttackJab1,
} }
+27 -4
View File
@@ -1,7 +1,9 @@
use bevy::reflect::Reflect; use std::collections::HashMap;
use bevy::{asset::Handle, ecs::component::Component, reflect::Reflect};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::math::{int::FGi32, vec3::FGVec3}; use crate::{fighter::animation::AnimKind, math::{int::FGi32, vec3::FGVec3}, scripting::FighterAttackScript};
#[derive(Debug, Default, Clone, Serialize, Deserialize)] #[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct FighterDamage(FGi32); pub struct FighterDamage(FGi32);
@@ -59,9 +61,19 @@ impl AttackAngle {
} }
} }
#[derive(Serialize, Deserialize, Reflect, PartialEq, Eq, Hash, Clone)] #[derive(Serialize, Deserialize, Reflect, PartialEq, Eq, Hash, Clone, Debug)]
pub enum AttackKind { pub enum AttackKind {
NAir NAir,
Jab
}
impl AttackKind {
pub fn get_animation(&self) -> AnimKind {
match self {
AttackKind::NAir => AnimKind::AttackJab1,
AttackKind::Jab => AnimKind::AttackJab1,
}
}
} }
#[derive(Debug, Clone, Reflect, Serialize, Deserialize)] #[derive(Debug, Clone, Reflect, Serialize, Deserialize)]
@@ -79,3 +91,14 @@ pub struct AttackHitbox {
pub start_frame: u32, pub start_frame: u32,
pub end_frame: u32 pub end_frame: u32
} }
pub fn update_active_hitbox_list(script: &FighterAttackScript, list: &mut Vec<usize>, frame: u32) {
*list = script.hitboxes.iter().enumerate().filter(|(i, hb)| {
hb.start_frame <= frame && hb.end_frame > frame
}).map(|(i, _)| i).collect()
}
#[derive(Component)]
pub struct FighterAttackScriptAssets {
pub scripts: HashMap<AttackKind, Handle<FighterAttackScript>>
}
+120 -2
View File
@@ -20,7 +20,10 @@ use serde::{Deserialize, Serialize};
use crate::{ use crate::{
fighter::{animation::AnimKind, manifest::FighterManifest}, fighter::{animation::AnimKind, manifest::FighterManifest},
math::int::{FGWide, FGi32}, math::{
int::{FGWide, FGi32},
vec3::FGVec3,
},
}; };
pub const BAKED_ANIMATION_FPS: u32 = 60; pub const BAKED_ANIMATION_FPS: u32 = 60;
@@ -33,6 +36,14 @@ pub struct FixedMat4 {
} }
impl FixedMat4 { impl FixedMat4 {
pub const IDENTITY: Self = Self {
cols: [
[FGi32::ONE, FGi32::ZERO, 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::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();
let mut cols = [[FGi32::ZERO; 4]; 4]; let mut cols = [[FGi32::ZERO; 4]; 4];
@@ -47,7 +58,7 @@ impl FixedMat4 {
Ok(Self { cols }) Ok(Self { cols })
} }
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 {
for row in 0..4 { for row in 0..4 {
@@ -61,6 +72,67 @@ impl FixedMat4 {
} }
Self { cols } Self { cols }
} }
pub fn transform_point(self, point: FGVec3) -> FGVec3 {
let input = [point.x, point.y, point.z, FGi32::ONE];
let mut output = [FGi32::ZERO; 3];
for row in 0..3 {
let mut value = FGWide::ZERO;
for (column, input) in input.into_iter().enumerate() {
value += FGWide::from_num(self.cols[column][row]) * FGWide::from_num(input);
}
output[row] = FGi32::from_num(value);
}
FGVec3 {
x: output[0],
y: output[1],
z: output[2],
}
}
pub fn translate(&mut self, translation: FGVec3) {
self.cols[3][0] = translation.x;
self.cols[3][1] = translation.y;
self.cols[3][2] = translation.z;
}
pub fn rotate_y(self, quarter_turns: u8) -> Self {
let rotation = match quarter_turns % 4 {
0 => Self {
cols: [
[FGi32::ONE, FGi32::ZERO, 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::ZERO, FGi32::ONE],
],
},
1 => Self {
cols: [
[FGi32::ZERO, FGi32::ZERO, FGi32::NEG_ONE, FGi32::ZERO],
[FGi32::ZERO, FGi32::ONE, FGi32::ZERO, FGi32::ZERO],
[FGi32::ONE, FGi32::ZERO, FGi32::ZERO, FGi32::ZERO],
[FGi32::ZERO, FGi32::ZERO, FGi32::ZERO, FGi32::ONE],
],
},
2 => Self {
cols: [
[FGi32::NEG_ONE, FGi32::ZERO, FGi32::ZERO, FGi32::ZERO],
[FGi32::ZERO, FGi32::ONE, FGi32::ZERO, FGi32::ZERO],
[FGi32::ZERO, FGi32::ZERO, FGi32::NEG_ONE, FGi32::ZERO],
[FGi32::ZERO, FGi32::ZERO, FGi32::ZERO, FGi32::ONE],
],
},
_ => Self {
cols: [
[FGi32::ZERO, FGi32::ZERO, FGi32::ONE, FGi32::ZERO],
[FGi32::ZERO, FGi32::ONE, FGi32::ZERO, FGi32::ZERO],
[FGi32::NEG_ONE, FGi32::ZERO, FGi32::ZERO, FGi32::ZERO],
[FGi32::ZERO, FGi32::ZERO, FGi32::ZERO, FGi32::ONE],
],
},
};
self.mul(rotation)
}
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
@@ -525,4 +597,50 @@ mod tests {
baked.bone_names.len() baked.bone_names.len()
); );
} }
#[test]
fn fixed_matrix_transforms_points() {
let transform = FixedMat4 {
cols: [
[FGi32::lit("2"), FGi32::ZERO, FGi32::ZERO, FGi32::ZERO],
[FGi32::ZERO, FGi32::lit("3"), FGi32::ZERO, FGi32::ZERO],
[FGi32::ZERO, FGi32::ZERO, FGi32::lit("4"), FGi32::ZERO],
[
FGi32::lit("10"),
FGi32::lit("-5"),
FGi32::lit("1.5"),
FGi32::ONE,
],
],
};
assert_eq!(
transform.transform_point(FGVec3::lit("1.25", "-2", "0.5")),
FGVec3::lit("12.5", "-11", "3.5")
);
}
#[test]
fn fixed_matrix_rotates_points_around_y() {
let transform = FixedMat4 {
cols: [
[FGi32::ONE, FGi32::ZERO, FGi32::ZERO, FGi32::ZERO],
[FGi32::ZERO, FGi32::ONE, FGi32::ZERO, FGi32::ZERO],
[FGi32::ZERO, FGi32::ZERO, FGi32::ONE, FGi32::ZERO],
[
FGi32::lit("10"),
FGi32::lit("2"),
FGi32::lit("-3"),
FGi32::ONE,
],
],
};
assert_eq!(
transform
.rotate_y(5)
.transform_point(FGVec3::lit("1", "0", "0")),
FGVec3::lit("10", "2", "-4")
);
}
} }
+41 -3
View File
@@ -10,10 +10,11 @@ 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, FighterPreviousTranslation, FighterTranslation, FighterECB, FighterFacingDirection, FighterHitboxes, FighterPreviousTranslation, FighterTranslation, FighterVelocity,
FighterVelocity,
}; };
use crate::input::FighterInput; use crate::input::FighterInput;
use crate::math::vec3::FGVec3;
use crate::scripting::FighterAttackScript;
pub fn debug_draw_ecb( pub fn debug_draw_ecb(
ecbs: Query<( ecbs: Query<(
@@ -60,14 +61,51 @@ pub fn animation_debug(
.get(&anims.baked) .get(&anims.baked)
.expect("Baked anims should be in"); .expect("Baked anims should be in");
for bone in &baked.bone_names { for bone in &baked.bone_names {
let out = anims.sample_bone(&baked_anims, *frame, bone); if bone != "Bone_30" {
continue;
}
let out = anims.sample_bone(&baked_anims, frame, bone);
if let Some(out) = out { if let Some(out) = out {
let pos = global_transform.transform_point(Vec3::new( let pos = global_transform.transform_point(Vec3::new(
out.cols[3][0].to_num(), out.cols[3][0].to_num(),
out.cols[3][1].to_num(), out.cols[3][1].to_num(),
out.cols[3][2].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.cross(pos, 0.5, bevy::color::palettes::css::RED);
gizmos.axes(*global_transform, 1.0);
}
}
}
}
pub fn attack_debug(
query: Query<(&FighterHitboxes, &FighterAnimationFrame, &FighterAnimations, &FighterTranslation, &FighterFacingDirection)>,
baked_anims: Res<Assets<BakedFighterAnimations>>,
attack_scripts: Res<Assets<FighterAttackScript>>,
mut gizmos: Gizmos,
) {
for (hitboxes, frame, animations, translation, facing_direction) in query {
if let Some(script) = &hitboxes.attack_script {
let script = attack_scripts.get(script).expect("Script ref should be valid");
let player_trf = translation.get_3d_transform(facing_direction);
for i in hitboxes.active_hitboxes.iter() {
let offset = script.hitboxes[*i].offset;
let radius = script.hitboxes[*i].radius;
let bone = &script.hitboxes[*i].bone;
let trf = animations.sample_bone(&baked_anims, frame, bone);
if let Some(trf) = trf {
let trf = player_trf.mul(trf);
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 pos = trf.transform_point(offset);
let pos_draw = Vec3::new(pos.x.to_num(), pos.y.to_num(), pos.z.to_num());
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);
}
} }
} }
} }
+17 -11
View File
@@ -29,16 +29,8 @@ pub use motion::{FighterPreviousTranslation, FighterTranslation, FighterVelocity
use crate::{ use crate::{
fighter::{ fighter::{
animation::animation_init, animation::animation_init, attack::FighterAttackScriptAssets, manifest::FighterManifest, state::{FighterState, fall::FallState}, visual::FighterAnimations,
manifest::FighterManifest, }, input::FighterInput, math::{int::FGi32, vec::FGVec2}, player::Player, schedule::GameplaySet, scripting::FighterAttackScript, stage::line::StageCollision,
state::{FighterState, fall::FallState},
visual::FighterAnimations,
},
input::FighterInput,
math::{int::FGi32, vec::FGVec2},
player::Player,
schedule::GameplaySet,
stage::line::StageCollision,
}; };
use state::state_interrupt_system; use state::state_interrupt_system;
@@ -158,20 +150,29 @@ impl Plugin for FighterPlugin {
.add_systems(FixedPostUpdate, debug::debug_draw_ecb) .add_systems(FixedPostUpdate, debug::debug_draw_ecb)
.add_systems(EguiPrimaryContextPass, debug::fighter_debug) .add_systems(EguiPrimaryContextPass, debug::fighter_debug)
.add_systems(FixedPostUpdate, debug::animation_debug) .add_systems(FixedPostUpdate, debug::animation_debug)
.add_systems(FixedPostUpdate, debug::attack_debug)
.add_systems(EguiPrimaryContextPass, debug::update_config) .add_systems(EguiPrimaryContextPass, debug::update_config)
.rollback_component_with_clone::<FighterState>() .rollback_component_with_clone::<FighterState>()
.rollback_component_with_clone::<FighterHitboxes>()
.checksum_component_with_hash::<FighterState>() .checksum_component_with_hash::<FighterState>()
.checksum_component_with_hash::<FighterFacingDirection>() .checksum_component_with_hash::<FighterFacingDirection>()
.register_type::<FighterFacingDirection>(); .register_type::<FighterFacingDirection>();
} }
} }
#[derive(Component, Clone)]
pub struct FighterHitboxes {
pub attack_script: Option<Handle<FighterAttackScript>>,
pub active_hitboxes: Vec<usize>
}
pub fn spawn_fighter( pub fn spawn_fighter(
commands: &mut Commands, commands: &mut Commands,
player_handle: usize, player_handle: usize,
spawn_position: FGVec2, spawn_position: FGVec2,
manifest: Handle<FighterManifest>, manifest: Handle<FighterManifest>,
animations: FighterAnimations, animations: FighterAnimations,
attack_scripts: FighterAttackScriptAssets,
visual_root: WorldAssetRoot, visual_root: WorldAssetRoot,
) { ) {
commands.spawn(( commands.spawn((
@@ -183,14 +184,19 @@ pub fn spawn_fighter(
manifest: manifest, manifest: manifest,
}, },
FighterFacingDirection::Right, FighterFacingDirection::Right,
FighterECB { (FighterECB {
vertical_half: FGi32::lit("5.0"), vertical_half: FGi32::lit("5.0"),
horizontal_half: FGi32::lit("2.5"), horizontal_half: FGi32::lit("2.5"),
}, },
FighterHitboxes {
attack_script: None,
active_hitboxes: vec![]
}),
FighterVelocity::default(), FighterVelocity::default(),
FighterTranslation(spawn_position), FighterTranslation(spawn_position),
visual_root, visual_root,
animations, animations,
attack_scripts,
FighterState::Fall(FallState), FighterState::Fall(FallState),
animation::FighterAnimationFrame::new(animation::AnimKind::Wait, true), animation::FighterAnimationFrame::new(animation::AnimKind::Wait, true),
FighterVisual, FighterVisual,
+13 -1
View File
@@ -2,8 +2,9 @@
//! this module is what turns that into a position. //! this module is what turns that into a position.
use bevy::prelude::*; use bevy::prelude::*;
use fixed::types::I16F16;
use crate::math::vec::FGVec2; use crate::{fighter::{FighterFacingDirection, baked_animation::FixedMat4}, math::{int::FGi32, vec::FGVec2, vec3::FGVec3}};
#[derive(Component, Clone, Copy)] #[derive(Component, Clone, Copy)]
pub struct Grounded; pub struct Grounded;
@@ -18,6 +19,17 @@ pub struct FighterPreviousTranslation(pub FGVec2);
#[require(FighterPreviousTranslation)] #[require(FighterPreviousTranslation)]
pub struct FighterTranslation(pub FGVec2); pub struct FighterTranslation(pub FGVec2);
impl FighterTranslation {
pub fn get_3d_transform(&self, facing_dir: &FighterFacingDirection) -> FixedMat4 {
let mut o = match facing_dir {
FighterFacingDirection::Left => FixedMat4::IDENTITY.rotate_y(3),
FighterFacingDirection::Right => FixedMat4::IDENTITY.rotate_y(1),
};
o.translate(FGVec3::new(self.0.x, self.0.y, FGi32::ZERO));
o
}
}
pub fn apply_air_motion( pub fn apply_air_motion(
fighters: Query<( fighters: Query<(
&FighterVelocity, &FighterVelocity,
+4 -2
View File
@@ -2,7 +2,7 @@
//! ECS types so it stays cheap to reason about and to unit-test — it has to be //! ECS types so it stays cheap to reason about and to unit-test — it has to be
//! bit-for-bit deterministic for rollback. //! bit-for-bit deterministic for rollback.
use crate::fighter::state::{FighterState, FighterStateContext, dash, turn, wait, walk}; use crate::fighter::state::{FighterState, FighterStateContext, dash, ground_attack, turn, wait, walk};
use crate::fighter::{ use crate::fighter::{
FighterAttributes, FighterPreviousTranslation, FighterTranslation, FighterVelocity, FighterAttributes, FighterPreviousTranslation, FighterTranslation, FighterVelocity,
}; };
@@ -151,7 +151,9 @@ pub fn grounded_movement_standstill_common_interrupts(
state_context: &FighterStateContext, state_context: &FighterStateContext,
ground_common: &GroundedStateCommon, ground_common: &GroundedStateCommon,
) -> Option<FighterState> { ) -> Option<FighterState> {
if let Some(state) = dash::check_input(state_context, &ground_common) { if let Some(state) = ground_attack::check_interrupt(state_context, &ground_common) {
Some(state)
} else if let Some(state) = dash::check_input(state_context, &ground_common) {
Some(state) Some(state)
} else if let Some(state) = turn::check_input(state_context, &ground_common) { } else if let Some(state) = turn::check_input(state_context, &ground_common) {
Some(state) Some(state)
+48
View File
@@ -0,0 +1,48 @@
use crate::fighter::{attack::{AttackKind, update_active_hitbox_list}, state::{FighterState, FighterStateImpl, ground::GroundedStateCommon, wait::WaitState}};
use bevy::prelude::*;
#[derive(Clone, Debug, Hash)]
pub struct GroundAttackState {
pub attack_kind: AttackKind,
pub grounded_common: GroundedStateCommon,
}
impl FighterStateImpl for GroundAttackState {
const NAME: &'static str = "GroundAttack";
fn check_interrupt(&self, state_context: &mut super::FighterStateContext) -> Option<FighterState> {
if state_context.is_current_animation_finished() {
Some(FighterState::Wait(WaitState {
grounded_common: self.grounded_common.clone()
}))
} else {
None
}
}
fn update(&mut self, state_context: &mut super::FighterStateContext) {
let script = state_context.attack_scripts.get(&state_context.attack_script_assets.scripts[&self.attack_kind]);
if let Some(script) = script {
let animation_frame = state_context.get_current_animation_frame();
update_active_hitbox_list(script, &mut state_context.hitboxes.active_hitboxes, animation_frame);
}
}
fn on_enter(&mut self, state_context: &mut super::FighterStateContext) {
state_context.play_animation(self.attack_kind.get_animation(), false);
state_context.hitboxes.attack_script = Some(state_context.attack_script_assets.scripts[&self.attack_kind].clone());
state_context.hitboxes.active_hitboxes.clear();
}
}
pub fn check_interrupt(state_context: &super::FighterStateContext, grounded_common: &GroundedStateCommon) -> Option<FighterState> {
if state_context.input.has_command(crate::input::FighterCommands::Attack) {
Some(FighterState::GroundAttack(GroundAttackState {
attack_kind: AttackKind::Jab,
grounded_common: grounded_common.clone()
}))
} else {
None
}
}
+29 -4
View File
@@ -6,16 +6,18 @@ use bevy::ecs::query::QueryData;
use bevy::prelude::*; 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::baked_animation::{BakedFighterAnimations, FixedMat4}; use crate::fighter::baked_animation::{BakedFighterAnimations, FixedMat4};
use crate::fighter::ecb::FighterPreviousECB; use crate::fighter::ecb::FighterPreviousECB;
use crate::fighter::manifest::FighterManifest; use crate::fighter::manifest::FighterManifest;
use crate::fighter::state::ground_attack::GroundAttackState;
use crate::fighter::visual::FighterAnimations; use crate::fighter::visual::FighterAnimations;
use crate::fighter::{ use crate::fighter::{
Fighter, FighterECB, FighterFacingDirection, FighterPreviousTranslation, Fighter, FighterECB, FighterFacingDirection, FighterHitboxes, FighterPreviousTranslation, FighterTranslation, FighterVelocity,
FighterTranslation, FighterVelocity,
}; };
use crate::game_settings::GameSettings; use crate::game_settings::GameSettings;
use crate::input::FighterInput; use crate::input::FighterInput;
use crate::scripting::FighterAttackScript;
use crate::stage::line::StageCollision; use crate::stage::line::StageCollision;
pub mod air; pub mod air;
@@ -29,6 +31,7 @@ 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),* $(,)?) => {
@@ -80,6 +83,7 @@ pub struct FighterStateContext<'a> {
pub translation: &'a mut FighterTranslation, pub translation: &'a mut FighterTranslation,
pub prev_translation: &'a mut FighterPreviousTranslation, pub prev_translation: &'a mut FighterPreviousTranslation,
pub input: &'a mut FighterInput, pub input: &'a mut FighterInput,
pub hitboxes: &'a mut FighterHitboxes,
pub velocity: &'a mut FighterVelocity, pub velocity: &'a mut FighterVelocity,
pub ecb: &'a mut FighterECB, pub ecb: &'a mut FighterECB,
pub prev_ecb: &'a mut FighterPreviousECB, pub prev_ecb: &'a mut FighterPreviousECB,
@@ -91,7 +95,9 @@ pub struct FighterStateContext<'a> {
pub animation_transitions: &'a mut AnimationTransitions, 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 fighter_manifest: &'a FighterManifest, pub fighter_manifest: &'a FighterManifest,
pub attack_script_assets: &'a FighterAttackScriptAssets,
} }
impl FighterStateContext<'_> { impl FighterStateContext<'_> {
@@ -124,7 +130,7 @@ impl FighterStateContext<'_> {
pub fn sample_bone(&self, bone: &str) -> Option<FixedMat4> { pub fn sample_bone(&self, bone: &str) -> Option<FixedMat4> {
self.animations self.animations
.sample_bone(self.baked_animations, *self.animation_frame, bone) .sample_bone(self.baked_animations, self.animation_frame, bone)
} }
} }
@@ -138,7 +144,8 @@ fighter_states! {
Jump => jump::JumpState, Jump => jump::JumpState,
Land => land::LandingState, Land => land::LandingState,
AirDodge => air_dodge::AirDodgeState, AirDodge => air_dodge::AirDodgeState,
Turn => turn::TurnState Turn => turn::TurnState,
GroundAttack => ground_attack::GroundAttackState,
} }
#[derive(QueryData)] #[derive(QueryData)]
@@ -147,6 +154,7 @@ pub struct FighterFrameQuery {
pub translation: &'static mut FighterTranslation, pub translation: &'static mut FighterTranslation,
pub prev_translation: &'static mut FighterPreviousTranslation, pub prev_translation: &'static mut FighterPreviousTranslation,
pub input: &'static mut FighterInput, pub input: &'static mut FighterInput,
pub hitboxes: &'static mut FighterHitboxes,
pub velocity: &'static mut FighterVelocity, pub velocity: &'static mut FighterVelocity,
pub ecb: &'static mut FighterECB, pub ecb: &'static mut FighterECB,
pub prev_ecb: &'static mut FighterPreviousECB, pub prev_ecb: &'static mut FighterPreviousECB,
@@ -164,12 +172,15 @@ impl<'w, 's> FighterFrameQueryItem<'w, 's> {
animation_player: &'a mut AnimationPlayer, animation_player: &'a mut AnimationPlayer,
animation_transitions: &'a mut AnimationTransitions, animation_transitions: &'a mut AnimationTransitions,
baked_animations: &'a Assets<BakedFighterAnimations>, baked_animations: &'a Assets<BakedFighterAnimations>,
attack_scripts: &'a Assets<FighterAttackScript>,
attack_script_assets: &'a FighterAttackScriptAssets,
manifest: &'a FighterManifest, manifest: &'a FighterManifest,
) -> FighterStateContext<'a> { ) -> FighterStateContext<'a> {
FighterStateContext { FighterStateContext {
translation: &mut self.translation, translation: &mut self.translation,
prev_translation: &mut self.prev_translation, prev_translation: &mut self.prev_translation,
input: &mut self.input, input: &mut self.input,
hitboxes: &mut self.hitboxes,
velocity: &mut self.velocity, velocity: &mut self.velocity,
ecb: &mut self.ecb, ecb: &mut self.ecb,
prev_ecb: &mut self.prev_ecb, prev_ecb: &mut self.prev_ecb,
@@ -181,6 +192,8 @@ impl<'w, 's> FighterFrameQueryItem<'w, 's> {
animation_transitions, animation_transitions,
animation_frame: &mut self.animation_frame, animation_frame: &mut self.animation_frame,
baked_animations, baked_animations,
attack_scripts,
attack_script_assets,
fighter_manifest: manifest, fighter_manifest: manifest,
} }
} }
@@ -195,6 +208,7 @@ pub struct StatefulFighterQuery {
pub animations: &'static FighterAnimations, pub animations: &'static FighterAnimations,
pub animation_player_link: &'static FighterAnimationPlayerLink, pub animation_player_link: &'static FighterAnimationPlayerLink,
pub fighter: &'static Fighter, pub fighter: &'static Fighter,
pub attack_script_assets: &'static FighterAttackScriptAssets,
} }
#[derive(Component)] #[derive(Component)]
@@ -212,6 +226,7 @@ pub fn state_update_system(
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>>,
attack_scripts: Res<Assets<FighterAttackScript>>,
manifests: Res<Assets<FighterManifest>>, manifests: Res<Assets<FighterManifest>>,
) { ) {
for fighter in &mut query { for fighter in &mut query {
@@ -235,6 +250,8 @@ pub fn state_update_system(
&mut animation_player, &mut animation_player,
&mut animation_transitions, &mut animation_transitions,
&baked_animations, &baked_animations,
&attack_scripts,
&fighter.attack_script_assets,
&manifests &manifests
.get(&(fighter.fighter.manifest)) .get(&(fighter.fighter.manifest))
.expect("Fighter manifest should be valid"), .expect("Fighter manifest should be valid"),
@@ -250,6 +267,7 @@ pub fn state_interrupt_system(
game_settings: Res<GameSettings>, game_settings: Res<GameSettings>,
baked_animations: Res<Assets<BakedFighterAnimations>>, baked_animations: Res<Assets<BakedFighterAnimations>>,
fighter_manifests: Res<Assets<FighterManifest>>, fighter_manifests: Res<Assets<FighterManifest>>,
attack_scripts: Res<Assets<FighterAttackScript>>,
mut commands: Commands, mut commands: Commands,
) { ) {
for fighter in &mut query { for fighter in &mut query {
@@ -260,6 +278,7 @@ pub fn state_interrupt_system(
animations, animations,
animation_player_link, animation_player_link,
fighter, fighter,
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())
@@ -273,6 +292,8 @@ pub fn state_interrupt_system(
&mut animation_player, &mut animation_player,
&mut animation_transitions, &mut animation_transitions,
&baked_animations, &baked_animations,
&attack_scripts,
attack_script_assets,
fighter_manifests fighter_manifests
.get(&fighter.manifest) .get(&fighter.manifest)
.expect("Fighter manifest should be valid"), .expect("Fighter manifest should be valid"),
@@ -297,6 +318,7 @@ pub fn state_collision_interrupt_system(
game_settings: Res<GameSettings>, game_settings: Res<GameSettings>,
baked_animations: Res<Assets<BakedFighterAnimations>>, baked_animations: Res<Assets<BakedFighterAnimations>>,
fighter_manifests: Res<Assets<FighterManifest>>, fighter_manifests: Res<Assets<FighterManifest>>,
attack_scripts: Res<Assets<FighterAttackScript>>,
mut commands: Commands, mut commands: Commands,
) { ) {
for fighter in &mut query { for fighter in &mut query {
@@ -307,6 +329,7 @@ pub fn state_collision_interrupt_system(
animations, animations,
animation_player_link, animation_player_link,
fighter, fighter,
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())
@@ -320,6 +343,8 @@ pub fn state_collision_interrupt_system(
&mut animation_player, &mut animation_player,
&mut animation_transitions, &mut animation_transitions,
&baked_animations, &baked_animations,
&attack_scripts,
&attack_script_assets,
fighter_manifests fighter_manifests
.get(&fighter.manifest) .get(&fighter.manifest)
.expect("Fighter manifest should be valid"), .expect("Fighter manifest should be valid"),
+1 -1
View File
@@ -16,7 +16,7 @@ impl FighterAnimations {
pub fn sample_bone( pub fn sample_bone(
&self, &self,
baked_assets: &Assets<BakedFighterAnimations>, baked_assets: &Assets<BakedFighterAnimations>,
frame: FighterAnimationFrame, frame: &FighterAnimationFrame,
bone: &str, bone: &str,
) -> Option<crate::fighter::baked_animation::FixedMat4> { ) -> Option<crate::fighter::baked_animation::FixedMat4> {
let baked = baked_assets.get(&self.baked)?; let baked = baked_assets.get(&self.baked)?;
+11
View File
@@ -20,6 +20,7 @@ pub enum FighterCommands {
SmashMoveRight, SmashMoveRight,
Jump, Jump,
Shield, Shield,
Attack,
} }
/// Frames of life remaining for each buffered command. Indexed by /// Frames of life remaining for each buffered command. Indexed by
@@ -30,6 +31,7 @@ pub struct FighterCommandLifetimes {
smash_move_right: u8, smash_move_right: u8,
jump: u8, jump: u8,
shield: u8, shield: u8,
attack: u8,
} }
impl Index<FighterCommands> for FighterCommandLifetimes { impl Index<FighterCommands> for FighterCommandLifetimes {
@@ -41,6 +43,7 @@ impl Index<FighterCommands> for FighterCommandLifetimes {
FighterCommands::SmashMoveRight => &self.smash_move_right, FighterCommands::SmashMoveRight => &self.smash_move_right,
FighterCommands::Jump => &self.jump, FighterCommands::Jump => &self.jump,
FighterCommands::Shield => &self.shield, FighterCommands::Shield => &self.shield,
FighterCommands::Attack => &self.attack,
} }
} }
} }
@@ -52,6 +55,7 @@ impl IndexMut<FighterCommands> for FighterCommandLifetimes {
FighterCommands::SmashMoveRight => &mut self.smash_move_right, FighterCommands::SmashMoveRight => &mut self.smash_move_right,
FighterCommands::Jump => &mut self.jump, FighterCommands::Jump => &mut self.jump,
FighterCommands::Shield => &mut self.shield, FighterCommands::Shield => &mut self.shield,
FighterCommands::Attack => &mut self.attack,
} }
} }
} }
@@ -148,6 +152,13 @@ pub fn postprocess_input(
); );
} }
if !input.prev_frame.attack && input.current_frame.attack {
input.set_lifetime(
FighterCommands::Attack,
game_settings.input_common.input_buffer_size,
);
}
if x_abs >= axis_threshold && input.frames_in_smash_move_deadzone <= frame_threshold { if x_abs >= axis_threshold && input.frames_in_smash_move_deadzone <= frame_threshold {
input.clear_command(FighterCommands::SmashMoveLeft); input.clear_command(FighterCommands::SmashMoveLeft);
input.clear_command(FighterCommands::SmashMoveRight); input.clear_command(FighterCommands::SmashMoveRight);
+1 -1
View File
@@ -1,6 +1,6 @@
use bevy_egui::prelude::*; use bevy_egui::prelude::*;
use crate::input::FighterInput; use crate::{fighter::{FighterHitboxes, animation::FighterAnimationFrame, visual::FighterAnimations}, input::FighterInput};
use bevy::prelude::*; use bevy::prelude::*;
pub fn input_debug(mut contexts: EguiContexts, query: Query<&FighterInput>) -> Result { pub fn input_debug(mut contexts: EguiContexts, query: Query<&FighterInput>) -> Result {
+1
View File
@@ -10,4 +10,5 @@ pub struct FighterInputFrame {
pub directional_attack: FGVec2, pub directional_attack: FGVec2,
pub jump: bool, pub jump: bool,
pub shield: bool, pub shield: bool,
pub attack: bool,
} }
+1
View File
@@ -17,6 +17,7 @@ pub fn sample_gamepad(pad: &Gamepad, input_map: &BaseInputMap) -> FighterInputFr
match gamepad_element.action { match gamepad_element.action {
InputMapAction::Jump => input_frame.jump |= pressed, InputMapAction::Jump => input_frame.jump |= pressed,
InputMapAction::Shield => input_frame.shield |= pressed, InputMapAction::Shield => input_frame.shield |= pressed,
InputMapAction::Attack => input_frame.attack |= pressed,
// Digital buttons driving movement isn't used by the // Digital buttons driving movement isn't used by the
// default bindings (the stick covers that), but stay // default bindings (the stick covers that), but stay
// consistent with the keyboard backend if one is added. // consistent with the keyboard backend if one is added.
+1
View File
@@ -32,6 +32,7 @@ pub fn sample_keyboard(key: &ButtonInput<KeyCode>, input_map: &BaseInputMap) ->
} }
InputMapAction::Jump => input_frame.jump = action_strength != FGi32::ZERO, InputMapAction::Jump => input_frame.jump = action_strength != FGi32::ZERO,
InputMapAction::Shield => input_frame.shield = action_strength != FGi32::ZERO, InputMapAction::Shield => input_frame.shield = action_strength != FGi32::ZERO,
InputMapAction::Attack => input_frame.attack = action_strength != FGi32::ZERO,
} }
} }
+1
View File
@@ -16,6 +16,7 @@ pub enum InputMapAction {
MovementYDir(i32), MovementYDir(i32),
Jump, Jump,
Shield, Shield,
Attack,
} }
#[derive(Debug)] #[derive(Debug)]
+10
View File
@@ -67,6 +67,11 @@ impl Plugin for FighterInputPlugin {
key: KeyCode::ShiftLeft, key: KeyCode::ShiftLeft,
action: InputMapAction::Shield, action: InputMapAction::Shield,
}, },
// attack
KeyboardInputMapElement {
key: KeyCode::KeyJ,
action: InputMapAction::Attack,
},
], ],
gamepad: vec![ gamepad: vec![
// left stick // left stick
@@ -92,6 +97,11 @@ impl Plugin for FighterInputPlugin {
binding: GamepadBinding::Button(GamepadButton::RightTrigger), binding: GamepadBinding::Button(GamepadButton::RightTrigger),
action: InputMapAction::Shield, action: InputMapAction::Shield,
}, },
// attack
GamepadInputMapElement {
binding: GamepadBinding::Button(GamepadButton::East),
action: InputMapAction::Attack,
},
], ],
}) })
.add_plugins(gamecube::GamecubeAdapterPlugin) .add_plugins(gamecube::GamecubeAdapterPlugin)
+9 -13
View File
@@ -8,7 +8,7 @@ use bevy_ggrs::prelude::Session;
use crate::{ use crate::{
AppState, args::Args, fighter::{ AppState, args::Args, fighter::{
FighterId, animation::AnimKind, attack::AttackKind, baked_animation::BakedFighterAnimations, manifest::{FighterManifest, FighterManifestRegistry}, spawn_fighter, visual::FighterAnimations, 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::{ }, math::{int::FGi32, vec::FGVec2}, netcode::{GGRSCfg, session::create_session}, scripting::FighterAttackScript, stage::{
self, self,
manifest::{StageId, StageManifest, StageManifestRegistry}, manifest::{StageId, StageManifest, StageManifestRegistry},
@@ -163,7 +163,6 @@ pub fn prepare_match(
} }
let mut prepared = Vec::with_capacity(assets.fighters.len()); let mut prepared = Vec::with_capacity(assets.fighters.len());
let mut shared_animations = HashMap::<FighterId, FighterAnimations>::new();
for character in &assets.fighters { for character in &assets.fighters {
let manifest_handle = registry let manifest_handle = registry
@@ -189,15 +188,6 @@ pub fn prepare_match(
return; return;
}; };
if let Some(animations) = shared_animations.get(&character.fighter) {
prepared.push((
scene,
animations.clone(),
manifest_handle,
));
continue;
}
let mut graph = AnimationGraph::new(); let mut graph = AnimationGraph::new();
let mut clips = HashMap::<AnimKind, AnimationNodeIndex>::new(); let mut clips = HashMap::<AnimKind, AnimationNodeIndex>::new();
@@ -235,10 +225,15 @@ pub fn prepare_match(
clips, clips,
baked: character.baked_animations.clone(), baked: character.baked_animations.clone(),
}; };
shared_animations.insert(character.fighter, animations.clone());
let attack_scripts = FighterAttackScriptAssets {
scripts: character.attack_scripts.clone()
};
prepared.push(( prepared.push((
scene, scene,
animations, animations,
attack_scripts,
manifest_handle, manifest_handle,
)); ));
} }
@@ -272,7 +267,7 @@ pub fn prepare_match(
for ( for (
index, index,
(player, (scene, animations, manifest_handle)), (player, (scene, animations, attack_scripts, manifest_handle)),
) in request.players.iter().zip(prepared).enumerate() ) 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;
@@ -282,6 +277,7 @@ pub fn prepare_match(
FGVec2::new(FGi32::from_num(spawn_x), FGi32::lit("12.5")), FGVec2::new(FGi32::from_num(spawn_x), FGi32::lit("12.5")),
manifest_handle.clone(), manifest_handle.clone(),
animations, animations,
attack_scripts,
WorldAssetRoot(scene), WorldAssetRoot(scene),
); );
} }
+13
View File
@@ -12,6 +12,15 @@ pub struct FGVec3 {
} }
impl FGVec3 { impl FGVec3 {
pub fn new(x: FGi32, y: FGi32, z: FGi32) -> Self {
Self {
x,
y,
z
}
}
pub fn lit(x: &str, y: &str, z: &str) -> Self { pub fn lit(x: &str, y: &str, z: &str) -> Self {
Self { Self {
x: FGi32::lit(x), x: FGi32::lit(x),
@@ -19,4 +28,8 @@ impl FGVec3 {
z: FGi32::lit(z), z: FGi32::lit(z),
} }
} }
pub fn to_vec3(&self) -> Vec3 {
Vec3::new(self.x.to_num(), self.y.to_num(), self.z.to_num())
}
} }