Implement animation root motion deltas (#84)
Implements root motion and adds a dash attack. Reviewed-on: #84 Co-authored-by: Álex Román Núñez <eirexe@eirteam.moe>
This commit was merged in pull request #84.
This commit is contained in:
@@ -4,7 +4,7 @@ use super::{
|
||||
};
|
||||
use crate::fighter::FighterFacingDirection;
|
||||
use crate::fighter::animation::AnimKind;
|
||||
use crate::fighter::state::FighterState;
|
||||
use crate::fighter::state::{FighterState, ground_attack};
|
||||
use crate::input::FighterCommands::{SmashMoveLeft, SmashMoveRight};
|
||||
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
@@ -23,6 +23,16 @@ impl FighterStateImpl for DashState {
|
||||
fn check_interrupt(&self, ctx: &mut InterruptContext) -> Option<FighterState> {
|
||||
let attributes = ctx.fighter_attributes;
|
||||
|
||||
if ctx.get_current_animation_frame() <= 2 {
|
||||
if let Some(state) = ground_attack::check_interrupt(ctx) {
|
||||
return Some(state);
|
||||
}
|
||||
} else {
|
||||
if let Some(state) = ground_attack::check_interrupt_dash_attack(ctx) {
|
||||
return Some(state);
|
||||
}
|
||||
}
|
||||
|
||||
// A reverse dash can interrupt before IASA
|
||||
if let Some(direction) =
|
||||
check_smash_input_with_dir(ctx).filter(|direction| *direction != self.direction)
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
use crate::fighter::{
|
||||
attack::{AttackKind, update_active_attack},
|
||||
state::{
|
||||
CollisionContext, EnterContext, ExitContext, FighterState, FighterStateImpl,
|
||||
InterruptContext, UpdateContext, wait::WaitState,
|
||||
use bevy::{asset::Assets, log::error};
|
||||
|
||||
use crate::{
|
||||
fighter::{
|
||||
attack::{AttackKind, FighterAttackScriptAssets, update_active_attack},
|
||||
state::{
|
||||
CollisionContext, EnterContext, ExitContext, FighterState, FighterStateImpl,
|
||||
InterruptContext, UpdateContext, ground, wait::WaitState,
|
||||
},
|
||||
},
|
||||
scripting::FighterAttackScript,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, Hash)]
|
||||
pub struct GroundAttackState {
|
||||
pub attack_kind: AttackKind,
|
||||
pub use_root_motion: bool,
|
||||
}
|
||||
|
||||
impl FighterStateImpl for GroundAttackState {
|
||||
@@ -37,6 +43,18 @@ impl FighterStateImpl for GroundAttackState {
|
||||
.expect("ground attack must have active hitboxes"),
|
||||
animation_frame,
|
||||
);
|
||||
|
||||
// root motion
|
||||
if script.use_root_motion {
|
||||
state_context.velocity.0.x = state_context.animation_root_motion_delta.x;
|
||||
ground::apply_grounded_motion(
|
||||
state_context.velocity,
|
||||
state_context.translation,
|
||||
state_context.prev_translation,
|
||||
);
|
||||
} else {
|
||||
ground::update_friction_motion(state_context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +66,7 @@ impl FighterStateImpl for GroundAttackState {
|
||||
let script = state_context.attack_script_assets.scripts[&self.attack_kind].clone();
|
||||
state_context.hitboxes.0 = Some(crate::fighter::FighterActiveAttack {
|
||||
kind: self.attack_kind,
|
||||
script,
|
||||
script: script.clone(),
|
||||
active_hitboxes: Vec::new(),
|
||||
solved_hitboxes: Vec::new(),
|
||||
hit_groups: Vec::new(),
|
||||
@@ -62,9 +80,9 @@ impl FighterStateImpl for GroundAttackState {
|
||||
|
||||
fn check_collision_interrupt(
|
||||
&mut self,
|
||||
_state_context: &mut CollisionContext,
|
||||
state_context: &mut CollisionContext,
|
||||
) -> Option<FighterState> {
|
||||
None
|
||||
ground::collision_interrupt(state_context, !self.use_root_motion)
|
||||
}
|
||||
|
||||
fn is_visually_grounded<'a>(&'a self) -> bool {
|
||||
@@ -72,6 +90,51 @@ impl FighterStateImpl for GroundAttackState {
|
||||
}
|
||||
}
|
||||
|
||||
impl GroundAttackState {
|
||||
fn create(
|
||||
attack_kind: AttackKind,
|
||||
attack_script_assets: &FighterAttackScriptAssets,
|
||||
attack_scripts: &Assets<FighterAttackScript>,
|
||||
) -> Result<Self, AirAttackStateCreateError> {
|
||||
let asset_script = attack_script_assets.scripts.get(&attack_kind).ok_or(
|
||||
AirAttackStateCreateError::AttackScriptAssetNotFound {
|
||||
attack: attack_kind,
|
||||
},
|
||||
)?;
|
||||
|
||||
let script = attack_scripts.get(asset_script).ok_or(
|
||||
AirAttackStateCreateError::AttackScriptNotFound {
|
||||
attack: attack_kind,
|
||||
},
|
||||
)?;
|
||||
|
||||
Ok(Self {
|
||||
attack_kind,
|
||||
use_root_motion: script.use_root_motion
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum AirAttackStateCreateError {
|
||||
#[error("Attack script: {attack:?} not found")]
|
||||
AttackScriptNotFound { attack: AttackKind },
|
||||
#[error("Attack script asset: {attack:?} not found")]
|
||||
AttackScriptAssetNotFound { attack: AttackKind },
|
||||
}
|
||||
|
||||
pub fn check_interrupt_shared(state_context: &InterruptContext, attack_kind: AttackKind) -> Option<FighterState> {
|
||||
let state = GroundAttackState::create(attack_kind, state_context.attack_script_assets, state_context.attack_scripts);
|
||||
|
||||
match state {
|
||||
Ok(state) => Some(FighterState::GroundAttack(state)),
|
||||
Err(create_error) => {
|
||||
error!("{}", create_error);
|
||||
None
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_interrupt(state_context: &InterruptContext) -> Option<FighterState> {
|
||||
if state_context
|
||||
.input
|
||||
@@ -81,9 +144,18 @@ pub fn check_interrupt(state_context: &InterruptContext) -> Option<FighterState>
|
||||
.input
|
||||
.get_attack_command_type()
|
||||
.to_attack_kind_grounded();
|
||||
Some(FighterState::GroundAttack(GroundAttackState {
|
||||
attack_kind,
|
||||
}))
|
||||
check_interrupt_shared(state_context, attack_kind)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_interrupt_dash_attack(state_context: &InterruptContext) -> Option<FighterState> {
|
||||
if state_context
|
||||
.input
|
||||
.has_command(crate::input::FighterCommands::Attack)
|
||||
{
|
||||
check_interrupt_shared(state_context, AttackKind::DashAttack)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ use crate::fighter::attack::{
|
||||
FighterStaleMoveQueue, HitStop,
|
||||
};
|
||||
use crate::fighter::attributes::FighterAttributes;
|
||||
use crate::fighter::baked_animation::BakedFighterAnimations;
|
||||
use crate::fighter::baked_animation::{BakedFighterAnimations, FighterBoneMatrices};
|
||||
use crate::fighter::ecb::FighterPreviousECB;
|
||||
use crate::fighter::manifest::FighterManifest;
|
||||
use crate::fighter::state::hitstun::HitstunState;
|
||||
@@ -29,6 +29,7 @@ use crate::match_loading::MatchConfig;
|
||||
use crate::match_runtime::MatchState;
|
||||
use crate::math::int::FGi32;
|
||||
use crate::math::vec::FGVec2;
|
||||
use crate::math::vec3::FGVec3;
|
||||
use crate::player::Player;
|
||||
use crate::scripting::FighterAttackScript;
|
||||
use crate::stage::line::StageCollision;
|
||||
@@ -191,6 +192,9 @@ pub struct UpdateContext<'a> {
|
||||
pub knockback_velocity: &'a mut FighterKnockbackVelocity,
|
||||
pub vulnerability: &'a mut FighterInvulnerabilityState,
|
||||
pub game_settings: &'a GameSettings,
|
||||
/// Root motion in the fighter's gameplay plane: X is world-right and Y is
|
||||
/// world-up. Animation +Z is forward and is mirrored by facing direction.
|
||||
pub animation_root_motion_delta: FGVec2,
|
||||
pub animation_frame: u32,
|
||||
pub fighter_attributes: &'a FighterAttributes,
|
||||
pub attack_scripts: &'a Assets<FighterAttackScript>,
|
||||
@@ -198,6 +202,13 @@ pub struct UpdateContext<'a> {
|
||||
pub facing_direction: &'a FighterFacingDirection,
|
||||
}
|
||||
|
||||
fn animation_root_motion_to_gameplay(
|
||||
delta: FGVec3,
|
||||
facing_direction: &FighterFacingDirection,
|
||||
) -> FGVec2 {
|
||||
FGVec2::new(delta.z * facing_direction.to_sign(), delta.y)
|
||||
}
|
||||
|
||||
impl UpdateContext<'_> {
|
||||
pub fn get_current_animation_frame(&self) -> u32 {
|
||||
self.animation_frame
|
||||
@@ -275,6 +286,7 @@ pub struct FighterFrameQuery {
|
||||
pub animation_frame: &'static mut FighterAnimationFrame,
|
||||
pub vulnerability: &'static mut FighterInvulnerabilityState,
|
||||
pub fighter: &'static Fighter,
|
||||
pub bone_matrices: &'static FighterBoneMatrices,
|
||||
}
|
||||
|
||||
impl<'w, 's> FighterFrameQueryItem<'w, 's> {
|
||||
@@ -365,6 +377,10 @@ impl<'w, 's> FighterFrameQueryItem<'w, 's> {
|
||||
surface: &mut self.surface,
|
||||
knockback_velocity: &mut self.knockback_velocity,
|
||||
game_settings,
|
||||
animation_root_motion_delta: animation_root_motion_to_gameplay(
|
||||
self.bone_matrices.root_motion_delta(),
|
||||
&self.facing_direction,
|
||||
),
|
||||
animation_frame: self.animation_frame.frame,
|
||||
vulnerability: &mut self.vulnerability,
|
||||
fighter_attributes: attributes,
|
||||
|
||||
Reference in New Issue
Block a user