Skip to content

Commit 2a1e211

Browse files
bors[bot]odecay
andauthored
Merge #278
278: Basic attack chaining r=odecay a=odecay Only works for a specific sequence of attacks for Dev fighter currently. Hitting attack during an active attack will cancel the recovery frames (and a few startup frames on the followup) and move to the next attack. closes #179 but requires followup work. Followup should generalize the recovery canceling and attack chain sequence to work for any valid predefined sequence of available attacks on a fighter. Co-authored-by: otis <[email protected]>
2 parents f9ee040 + e22613c commit 2a1e211

File tree

2 files changed

+243
-26
lines changed

2 files changed

+243
-26
lines changed

assets/fighters/dev/dev.fighter.yaml

+29-1
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,14 @@ spritesheet:
3535
frames: [71, 76]
3636
dying:
3737
frames: [71, 76]
38-
#spritesheet does not contain unique attack animation
3938
attacking:
4039
frames: [85, 90]
40+
chaining:
41+
frames: [112, 116]
42+
followup:
43+
frames: [126, 131]
4144

45+
# attacks need longer recovery vs startup
4246
attacks:
4347
- name: "punch"
4448
damage: 35
@@ -51,6 +55,30 @@ attacks:
5155
offset: [32, 0]
5256
hitstun_duration: 0.2
5357

58+
- name: "flop"
59+
damage: 50
60+
frames:
61+
startup: 0
62+
active: 1
63+
recovery: 4
64+
hitbox:
65+
size: [32, 32]
66+
offset: [32, 0]
67+
hitstun_duration: 0.2
68+
69+
- name: "chain"
70+
damage: 20
71+
frames:
72+
startup: 2
73+
active: 3
74+
recovery: 4
75+
hitbox:
76+
size: [32, 32]
77+
offset: [32, 0]
78+
hitstun_duration: 0.2
79+
80+
81+
5482
audio:
5583
effects:
5684
attacking:

src/fighter_state.rs

+214-25
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ impl Plugin for FighterStatePlugin {
6060
.after(FighterStateCollectSystems)
6161
.run_in_state(GameState::InGame)
6262
.with_system(transition_from_idle)
63+
.with_system(transition_from_chain)
6364
.with_system(transition_from_flopping)
6465
.with_system(transition_from_punching)
6566
.with_system(transition_from_ground_slam)
@@ -75,6 +76,7 @@ impl Plugin for FighterStatePlugin {
7576
ConditionSet::new()
7677
.run_in_state(GameState::InGame)
7778
.with_system(idling)
79+
.with_system(chaining)
7880
.with_system(flopping)
7981
.with_system(punching)
8082
.with_system(ground_slam)
@@ -238,7 +240,7 @@ impl Flopping {
238240
pub const ANIMATION: &'static str = "attacking";
239241
}
240242

241-
/// Component indicating the player is punching
243+
/// Component indicating the player is performing a groundslam
242244
#[derive(Component, Reflect, Default, Debug)]
243245
#[component(storage = "SparseSet")]
244246
pub struct GroundSlam {
@@ -276,6 +278,23 @@ impl Punching {
276278
pub const ANIMATION: &'static str = "attacking";
277279
}
278280

281+
#[derive(Component, Default, Reflect)]
282+
#[component(storage = "SparseSet")]
283+
pub struct Chaining {
284+
pub has_started: bool,
285+
pub continue_chain: bool,
286+
pub can_extend: bool,
287+
pub transition_to_final: bool,
288+
pub transition_to_idle: bool,
289+
pub link: u32,
290+
}
291+
impl Chaining {
292+
pub const PRIORITY: i32 = 30;
293+
pub const ANIMATION: &'static str = "chaining";
294+
pub const FOLLOWUP_ANIMATION: &'static str = "followup";
295+
pub const LENGTH: u32 = 2;
296+
}
297+
279298
#[derive(Component, Reflect, Default, Debug)]
280299
#[component(storage = "SparseSet")]
281300
pub struct MeleeAttacking {
@@ -350,39 +369,61 @@ fn collect_player_actions(
350369
&Inventory,
351370
&Stats,
352371
Option<&Holding>,
372+
Option<&mut Chaining>,
353373
&AvailableAttacks,
354374
),
355375
With<Player>,
356376
>,
357377
) {
358-
for (action_state, mut transition_intents, inventory, stats, holding, available_attacks) in
359-
&mut players
378+
for (
379+
action_state,
380+
mut transition_intents,
381+
inventory,
382+
stats,
383+
holding,
384+
chaining,
385+
available_attacks,
386+
) in &mut players
360387
{
361388
// Trigger attacks
362389
//TODO: can use flop attack again after input buffer/chaining
363390
if action_state.just_pressed(PlayerAction::Attack) && holding.is_none() {
364-
match available_attacks.current_attack().name.as_str() {
365-
"punch" => transition_intents.push_back(StateTransition::new(
366-
Flopping::default(),
367-
Flopping::PRIORITY,
368-
false,
369-
)),
370-
"flop" => transition_intents.push_back(StateTransition::new(
371-
Flopping::default(),
372-
Flopping::PRIORITY,
373-
false,
374-
)),
375-
"melee" => transition_intents.push_back(StateTransition::new(
376-
MeleeAttacking::default(),
377-
MeleeAttacking::PRIORITY,
378-
false,
379-
)),
380-
"projectile" => transition_intents.push_back(StateTransition::new(
381-
Shooting::default(),
382-
Shooting::PRIORITY,
383-
false,
384-
)),
385-
_ => {}
391+
if chaining.is_none() {
392+
match available_attacks.current_attack().name.as_str() {
393+
"chain" => transition_intents.push_back(StateTransition::new(
394+
//need to construct a chain with correct inputs
395+
Chaining::default(),
396+
Chaining::PRIORITY,
397+
false,
398+
)),
399+
"punch" => transition_intents.push_back(StateTransition::new(
400+
Punching::default(),
401+
Punching::PRIORITY,
402+
false,
403+
)),
404+
"flop" => transition_intents.push_back(StateTransition::new(
405+
Flopping::default(),
406+
Flopping::PRIORITY,
407+
false,
408+
)),
409+
"melee" => transition_intents.push_back(StateTransition::new(
410+
MeleeAttacking::default(),
411+
MeleeAttacking::PRIORITY,
412+
false,
413+
)),
414+
"projectile" => transition_intents.push_back(StateTransition::new(
415+
Shooting::default(),
416+
Shooting::PRIORITY,
417+
false,
418+
)),
419+
_ => {}
420+
}
421+
//todo, change to pushing states and making it additive
422+
//move variable setting/continue_chain to exit condition
423+
} else if let Some(mut chaining) = chaining {
424+
// if chaining.can_extend {
425+
chaining.continue_chain = true;
426+
// }
386427
}
387428
}
388429

@@ -530,6 +571,37 @@ fn transition_from_punching(
530571
}
531572
}
532573

574+
fn transition_from_chain(
575+
mut commands: Commands,
576+
mut fighters: Query<(Entity, &mut StateTransitionIntents, &mut Chaining)>,
577+
) {
578+
'entity: for (entity, mut transition_intents, chain) in &mut fighters {
579+
// Transition to any higher priority states
580+
let current_state_removed = transition_intents
581+
.transition_to_higher_priority_states::<Chaining>(
582+
entity,
583+
Chaining::PRIORITY,
584+
&mut commands,
585+
);
586+
587+
// If our current state was removed, don't continue processing this fighter
588+
if current_state_removed {
589+
continue 'entity;
590+
}
591+
592+
// If we're done attacking
593+
if chain.transition_to_final {
594+
// Go back to idle
595+
commands
596+
.entity(entity)
597+
.remove::<Chaining>()
598+
.insert(Flopping::default());
599+
} else if chain.transition_to_idle {
600+
commands.entity(entity).remove::<Chaining>().insert(Idling);
601+
}
602+
}
603+
}
604+
533605
fn transition_from_ground_slam(
534606
mut commands: Commands,
535607
mut fighters: Query<(Entity, &mut StateTransitionIntents, &GroundSlam)>,
@@ -822,6 +894,123 @@ fn flopping(
822894
}
823895
}
824896

897+
fn chaining(
898+
mut commands: Commands,
899+
mut fighters: Query<
900+
(
901+
Entity,
902+
&mut Animation,
903+
&mut LinearVelocity,
904+
&Facing,
905+
&Handle<FighterMeta>,
906+
&AvailableAttacks,
907+
&mut Chaining,
908+
),
909+
With<Player>,
910+
>,
911+
fighter_assets: Res<Assets<FighterMeta>>,
912+
) {
913+
for (
914+
entity,
915+
mut animation,
916+
mut velocity,
917+
facing,
918+
meta_handle,
919+
available_attacks,
920+
mut chaining,
921+
) in &mut fighters
922+
{
923+
// this seems... potentially panicky
924+
if let Some(attack) = available_attacks
925+
.attacks
926+
.iter()
927+
.filter(|a| a.name == *"chain")
928+
.last()
929+
{
930+
if let Some(fighter) = fighter_assets.get(meta_handle) {
931+
//if we havent started the chain yet or if we have input during chain window
932+
if !chaining.has_started || chaining.continue_chain && chaining.can_extend {
933+
if !chaining.has_started {
934+
chaining.has_started = true;
935+
animation.play(Chaining::ANIMATION, false);
936+
}
937+
// Start the attack from the beginning
938+
939+
//if we are on chain followup, skip the first frame of the animation
940+
if chaining.continue_chain {
941+
animation.play(Chaining::FOLLOWUP_ANIMATION, false);
942+
animation.current_frame = 2;
943+
chaining.continue_chain = false;
944+
chaining.link += 1;
945+
if chaining.link >= Chaining::LENGTH {
946+
chaining.transition_to_final = true;
947+
}
948+
}
949+
chaining.can_extend = false;
950+
951+
let mut offset = attack.hitbox.offset;
952+
if facing.is_left() {
953+
offset.x *= -1.0
954+
}
955+
offset.y += fighter.collision_offset;
956+
// Spawn the attack entity
957+
let attack_entity = commands
958+
.spawn_bundle(TransformBundle::from_transform(
959+
Transform::from_translation(offset.extend(0.0)),
960+
))
961+
.insert(CollisionGroups::new(
962+
BodyLayers::PLAYER_ATTACK,
963+
BodyLayers::ENEMY | BodyLayers::BREAKABLE_ITEM,
964+
))
965+
.insert(Attack {
966+
damage: attack.damage,
967+
velocity: if facing.is_left() {
968+
Vec2::NEG_X
969+
} else {
970+
Vec2::X
971+
} * attack.velocity.unwrap_or(Vec2::ZERO),
972+
hitstun_duration: attack.hitstun_duration,
973+
hitbox_meta: Some(attack.hitbox),
974+
})
975+
.insert(attack.frames)
976+
.id();
977+
commands.entity(entity).push_children(&[attack_entity]);
978+
979+
// Play attack sound effect
980+
if let Some(effects) = fighter.audio.effect_handles.get(Chaining::ANIMATION) {
981+
let fx_playback = AnimationAudioPlayback::new(
982+
Chaining::ANIMATION.to_owned(),
983+
effects.clone(),
984+
);
985+
commands.entity(entity).insert(fx_playback);
986+
}
987+
}
988+
}
989+
990+
if animation.current_frame > attack.frames.active {
991+
chaining.can_extend = true;
992+
}
993+
// Reset velocity
994+
**velocity = Vec2::ZERO;
995+
996+
//move forward a bit during active frames
997+
if animation.current_frame > attack.frames.startup
998+
&& animation.current_frame < attack.frames.recovery
999+
{
1000+
if facing.is_left() {
1001+
velocity.x -= 100.0;
1002+
} else {
1003+
velocity.x += 100.0;
1004+
}
1005+
}
1006+
}
1007+
1008+
if animation.is_finished() {
1009+
chaining.transition_to_idle = true;
1010+
}
1011+
}
1012+
}
1013+
8251014
fn punching(
8261015
mut commands: Commands,
8271016
mut fighters: Query<(

0 commit comments

Comments
 (0)