use crate::prelude::*;
use bevy::prelude::*;
#[derive(Component, Default, Reflect, Clone, Debug)]
#[reflect(Component, Default, Debug)]
#[require(Visibility, Transform, Sprite)]
pub struct TiledAnimation {
pub frames: Vec<(usize, f32)>,
pub current_frame: usize,
pub timer: Timer,
}
pub(crate) fn plugin(app: &mut App) {
app.register_type::<TiledAnimation>();
app.add_systems(
Update,
animate_sprite.in_set(TiledUpdateSystems::AnimateSprite),
);
}
fn step_animation(anim: &mut TiledAnimation, delta: std::time::Duration) -> Option<usize> {
if anim.frames.is_empty() {
return None;
}
anim.timer.tick(delta);
if anim.timer.just_finished() {
anim.current_frame = (anim.current_frame + 1) % anim.frames.len();
let (atlas_index, duration) = anim.frames[anim.current_frame];
anim.timer = Timer::from_seconds(duration, TimerMode::Once);
Some(atlas_index)
} else {
None
}
}
fn animate_sprite(time: Res<Time>, mut query: Query<(&mut TiledAnimation, &mut Sprite)>) {
for (mut anim, mut sprite) in query.iter_mut() {
if let Some(atlas_index) = step_animation(&mut anim, time.delta()) {
if let Some(atlas) = &mut sprite.texture_atlas {
atlas.index = atlas_index;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
fn make_anim(frames: Vec<(usize, f32)>) -> TiledAnimation {
let first_duration = frames.first().map(|(_, d)| *d).unwrap_or(0.1);
TiledAnimation {
frames,
current_frame: 0,
timer: Timer::from_seconds(first_duration, TimerMode::Once),
}
}
#[test]
fn empty_frames_does_nothing() {
let mut anim = TiledAnimation::default();
assert!(step_animation(&mut anim, Duration::from_secs(1)).is_none());
assert_eq!(anim.current_frame, 0);
}
#[test]
fn does_not_advance_before_timer_expires() {
let mut anim = make_anim(vec![(0, 0.5), (1, 0.5)]);
let result = step_animation(&mut anim, Duration::from_millis(100));
assert!(result.is_none());
assert_eq!(anim.current_frame, 0);
}
#[test]
fn advances_to_next_frame_on_expiry() {
let mut anim = make_anim(vec![(0, 0.1), (5, 0.1)]);
let result = step_animation(&mut anim, Duration::from_millis(101));
assert_eq!(result, Some(5)); assert_eq!(anim.current_frame, 1);
}
#[test]
fn wraps_from_last_frame_to_first() {
let mut anim = make_anim(vec![(0, 0.1), (5, 0.1)]);
step_animation(&mut anim, Duration::from_millis(101)); let result = step_animation(&mut anim, Duration::from_millis(101)); assert_eq!(result, Some(0));
assert_eq!(anim.current_frame, 0);
}
#[test]
fn uses_per_frame_duration() {
let mut anim = make_anim(vec![(0, 0.1), (1, 0.5)]);
step_animation(&mut anim, Duration::from_millis(101)); let result = step_animation(&mut anim, Duration::from_millis(100));
assert!(result.is_none());
assert_eq!(anim.current_frame, 1);
}
}