feat: Player attacking

- Added BpmTimer.accuracy method
- Added Weapon.attack_offset method
- Replaced beats_remaining with timer in Attacking and Awaiting
- AttackArea spawning on Attacking end
This commit is contained in:
Alexey 2026-04-17 12:53:20 +03:00
commit fb1923fe53
9 changed files with 107 additions and 58 deletions

View file

@ -1,6 +1,8 @@
//! Player systems
use crate::timer::TickEvent;
use bevy_trait_query::One;
use crate::timer::BpmTimer;
use super::*;
@ -17,19 +19,33 @@ pub fn handle_input(
&mut KinematicCharacterController,
&mut Sprite,
Option<&mut AttackGraph>,
Option<One<&dyn Weapon>>,
Option<&states::Free>,
Option<&states::Choosing>,
Option<&states::Attacking>,
Option<&states::Awaiting>,
Option<&mut states::Attacking>,
Option<&mut states::Awaiting>,
)>,
timer_query: Query<&BpmTimer>,
) {
let Some(timer) = timer_query.iter().next() else {
error!("No BpmTimer provided");
return;
};
let bpm = timer.get_bpm();
for (
// Basic things
player_id,
player,
action_state,
mut controller,
mut sprite,
// Weapon
maybe_attack_graph,
maybe_weapon,
// States
maybe_free,
maybe_choosing,
maybe_attacking,
@ -46,18 +62,13 @@ pub fn handle_input(
} else if let Some(states::Choosing { log }) = maybe_choosing &&
let Some(mut attack_graph) = maybe_attack_graph {
if let Some(next_state) = attack_graph.next(*log) {
let accuracy = timer.accuracy();
let next_attack = match *log {
PlayerInput::LightAttack => {
states::Attacking {
phys: next_state,
beats_remaining: 1,
}
states::Attacking::new(next_state, accuracy, 1., bpm)
},
PlayerInput::HeavyAttack => {
states::Attacking {
phys: next_state,
beats_remaining: 2,
}
states::Attacking::new(next_state, accuracy, 2., bpm)
},
_ => unreachable!(),
};
@ -65,35 +76,28 @@ pub fn handle_input(
} else {
commands.entity(player_id).insert(Done::Failure);
}
} else if let Some(states::Attacking { phys, beats_remaining }) = maybe_attacking {
println!("{phys:#?}");
if *beats_remaining == 0 {
commands.entity(player_id).insert(Done::Success);
}
} else if let Some(states::Awaiting { beats_remaining, .. }) = maybe_awaiting {
if *beats_remaining == 0 {
commands.entity(player_id).insert(Done::Success);
}
}
}
}
} else if let Some(mut attacking) = maybe_attacking {
attacking.timer.tick(time.delta());
// TODO: change beats_remaining to more accurate counting
/// Observer that updates temporal states on timer tick
pub fn on_timer_tick(
_: On<TickEvent>,
player_query: Query<(
Option<&mut states::Attacking>,
Option<&mut states::Awaiting>,
), With<Player>>
) {
for (maybe_attacking, maybe_awaiting) in player_query {
if let Some(mut attacking) = maybe_attacking {
info!("attack tick");
attacking.beats_remaining -= 1;
if attacking.timer.just_finished() {
info!("{}", attacking.accuracy);
commands.entity(player_id).insert(Done::Success);
let Some(weapon) = maybe_weapon else {
error!("No weapon provided for player");
return;
};
if let Some(attack_area) = weapon.attack_area(attacking.phys, attacking.accuracy) {
let offset = weapon.attack_offset();
commands.entity(player_id)
.with_child(attack_area.into_bundle(offset, sprite.flip_x, GROUP_ENEMY));
}
}
} else if let Some(mut awaiting) = maybe_awaiting {
info!("await tick");
awaiting.beats_remaining -= 1;
awaiting.timer.tick(time.delta());
if awaiting.timer.just_finished() {
commands.entity(player_id).insert(Done::Success);
}
}
}
}