-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fixes: #94 --------- Co-authored-by: Ben Frankel <[email protected]> Co-authored-by: Jan Hohenheim <[email protected]>
- Loading branch information
1 parent
a7a594a
commit 8c7cd5d
Showing
7 changed files
with
209 additions
and
71 deletions.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
//! Player sprite animation. | ||
//! This is based on multiple examples and may be very different for your game. | ||
//! - [Sprite flipping](https://github.com/bevyengine/bevy/blob/latest/examples/2d/sprite_flipping.rs) | ||
//! - [Sprite animation](https://github.com/bevyengine/bevy/blob/latest/examples/2d/sprite_animation.rs) | ||
//! - [Timers](https://github.com/bevyengine/bevy/blob/latest/examples/time/timers.rs) | ||
|
||
use std::time::Duration; | ||
|
||
use bevy::prelude::*; | ||
|
||
use super::{audio::sfx::Sfx, movement::MovementController}; | ||
use crate::AppSet; | ||
|
||
pub(super) fn plugin(app: &mut App) { | ||
// Animate and play sound effects based on controls. | ||
app.register_type::<PlayerAnimation>(); | ||
app.add_systems( | ||
Update, | ||
( | ||
update_animation_timer.in_set(AppSet::TickTimers), | ||
( | ||
update_animation_movement, | ||
update_animation_atlas, | ||
trigger_step_sfx, | ||
) | ||
.chain() | ||
.in_set(AppSet::Update), | ||
), | ||
); | ||
} | ||
|
||
/// Update the sprite direction and animation state (idling/walking). | ||
fn update_animation_movement( | ||
mut player_query: Query<(&MovementController, &mut Sprite, &mut PlayerAnimation)>, | ||
) { | ||
for (controller, mut sprite, mut animation) in &mut player_query { | ||
let dx = controller.0.x; | ||
if dx != 0.0 { | ||
sprite.flip_x = dx < 0.0; | ||
} | ||
|
||
let animation_state = if controller.0 == Vec2::ZERO { | ||
PlayerAnimationState::Idling | ||
} else { | ||
PlayerAnimationState::Walking | ||
}; | ||
animation.update_state(animation_state); | ||
} | ||
} | ||
|
||
/// Update the animation timer. | ||
fn update_animation_timer(time: Res<Time>, mut query: Query<&mut PlayerAnimation>) { | ||
for mut animation in &mut query { | ||
animation.update_timer(time.delta()); | ||
} | ||
} | ||
|
||
/// Update the texture atlas to reflect changes in the animation. | ||
fn update_animation_atlas(mut query: Query<(&PlayerAnimation, &mut TextureAtlas)>) { | ||
for (animation, mut atlas) in &mut query { | ||
if animation.changed() { | ||
atlas.index = animation.get_atlas_index(); | ||
} | ||
} | ||
} | ||
|
||
/// If the player is moving, play a step sound effect synchronized with the animation. | ||
fn trigger_step_sfx(mut commands: Commands, mut step_query: Query<&PlayerAnimation>) { | ||
for animation in &mut step_query { | ||
if animation.state == PlayerAnimationState::Walking | ||
&& animation.changed() | ||
&& (animation.frame == 2 || animation.frame == 5) | ||
{ | ||
commands.trigger(Sfx::Step); | ||
} | ||
} | ||
} | ||
|
||
/// Component that tracks player's animation state. | ||
/// It is tightly bound to the texture atlas we use. | ||
#[derive(Component, Reflect)] | ||
#[reflect(Component)] | ||
pub struct PlayerAnimation { | ||
timer: Timer, | ||
frame: usize, | ||
state: PlayerAnimationState, | ||
} | ||
|
||
#[derive(Reflect, PartialEq)] | ||
pub enum PlayerAnimationState { | ||
Idling, | ||
Walking, | ||
} | ||
|
||
impl PlayerAnimation { | ||
/// The number of idle frames. | ||
const IDLE_FRAMES: usize = 2; | ||
/// The duration of each idle frame. | ||
const IDLE_INTERVAL: Duration = Duration::from_millis(500); | ||
|
||
fn idling() -> Self { | ||
Self { | ||
timer: Timer::new(Self::IDLE_INTERVAL, TimerMode::Repeating), | ||
frame: 0, | ||
state: PlayerAnimationState::Idling, | ||
} | ||
} | ||
|
||
/// The number of walking frames. | ||
const WALKING_FRAMES: usize = 6; | ||
/// The duration of each walking frame. | ||
const WALKING_INTERVAL: Duration = Duration::from_millis(50); | ||
|
||
fn walking() -> Self { | ||
Self { | ||
timer: Timer::new(Self::WALKING_INTERVAL, TimerMode::Repeating), | ||
frame: 0, | ||
state: PlayerAnimationState::Walking, | ||
} | ||
} | ||
|
||
pub fn new() -> Self { | ||
Self::idling() | ||
} | ||
|
||
/// Update animation timers. | ||
pub fn update_timer(&mut self, delta: Duration) { | ||
self.timer.tick(delta); | ||
if !self.timer.finished() { | ||
return; | ||
} | ||
self.frame = (self.frame + 1) | ||
% match self.state { | ||
PlayerAnimationState::Idling => Self::IDLE_FRAMES, | ||
PlayerAnimationState::Walking => Self::WALKING_FRAMES, | ||
}; | ||
} | ||
|
||
/// Update animation state if it changes. | ||
pub fn update_state(&mut self, state: PlayerAnimationState) { | ||
if self.state != state { | ||
match state { | ||
PlayerAnimationState::Idling => *self = Self::idling(), | ||
PlayerAnimationState::Walking => *self = Self::walking(), | ||
} | ||
} | ||
} | ||
|
||
/// Whether animation changed this tick. | ||
pub fn changed(&self) -> bool { | ||
self.timer.finished() | ||
} | ||
|
||
/// Return sprite index in the atlas. | ||
pub fn get_atlas_index(&self) -> usize { | ||
match self.state { | ||
PlayerAnimationState::Idling => self.frame, | ||
PlayerAnimationState::Walking => 6 + self.frame, | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters