use crate::math::Vec2;
#[derive(Debug, Clone)]
pub struct Animation {
pub name: String,
pub frame_size: Vec2,
pub frames: Vec<AnimationFrame>,
pub speed: f32,
pub looping: bool,
pub ping_pong: bool,
pub offset: Vec2,
}
#[derive(Debug, Clone)]
pub struct AnimationFrame {
pub column: u32,
pub row: u32,
pub duration: f32,
pub event: Option<String>,
}
impl Animation {
pub fn from_row(
name: &str,
row: u32,
start_col: u32,
end_col: u32,
frame_duration: f32,
) -> Self {
let frames = (start_col..=end_col)
.map(|col| AnimationFrame {
column: col,
row,
duration: frame_duration,
event: None,
})
.collect();
Self {
name: name.to_string(),
frame_size: Vec2::ZERO,
frames,
speed: 1.0,
looping: true,
ping_pong: false,
offset: Vec2::ZERO,
}
}
pub fn with_frame_size(mut self, w: f32, h: f32) -> Self {
self.frame_size = Vec2::new(w, h);
self
}
pub fn with_speed(mut self, speed: f32) -> Self {
self.speed = speed;
self
}
pub fn with_looping(mut self, looping: bool) -> Self {
self.looping = looping;
self
}
pub fn with_ping_pong(mut self) -> Self {
self.ping_pong = true;
self
}
pub fn with_offset(mut self, x: f32, y: f32) -> Self {
self.offset = Vec2::new(x, y);
self
}
pub fn with_frame_event(mut self, frame_index: usize, event: &str) -> Self {
if frame_index < self.frames.len() {
self.frames[frame_index].event = Some(event.to_string());
}
self
}
pub fn total_duration(&self) -> f32 {
self.frames.iter().map(|f| f.duration).sum::<f32>() / self.speed
}
pub fn frame_count(&self) -> usize {
self.frames.len()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AnimationState {
Playing,
Finished,
Paused,
}
#[derive(Debug)]
pub struct AnimationPlayer {
current: Option<Animation>,
frame_index: usize,
frame_timer: f32,
state: AnimationState,
forward: bool,
pending_events: Vec<String>,
}
impl Default for AnimationPlayer {
fn default() -> Self {
Self::new()
}
}
impl AnimationPlayer {
pub fn new() -> Self {
Self {
current: None,
frame_index: 0,
frame_timer: 0.0,
state: AnimationState::Finished,
forward: true,
pending_events: Vec::new(),
}
}
pub fn play(&mut self, anim: &Animation) {
let is_same = self.current.as_ref().map(|c| c.name == anim.name).unwrap_or(false);
if !is_same || self.state == AnimationState::Finished {
self.current = Some(anim.clone());
self.frame_index = 0;
self.frame_timer = 0.0;
self.state = AnimationState::Playing;
self.forward = true;
self.pending_events.clear();
}
}
pub fn restart(&mut self) {
self.frame_index = 0;
self.frame_timer = 0.0;
self.state = AnimationState::Playing;
self.forward = true;
}
pub fn pause(&mut self) {
if self.state == AnimationState::Playing {
self.state = AnimationState::Paused;
}
}
pub fn resume(&mut self) {
if self.state == AnimationState::Paused {
self.state = AnimationState::Playing;
}
}
pub fn stop(&mut self) {
self.current = None;
self.frame_index = 0;
self.frame_timer = 0.0;
self.state = AnimationState::Finished;
}
pub fn current_name(&self) -> Option<&str> {
self.current.as_ref().map(|a| a.name.as_str())
}
pub fn current_frame_rect(&self) -> Option<(u32, u32, f32, f32)> {
let anim = self.current.as_ref()?;
let frame = anim.frames.get(self.frame_index)?;
Some((frame.column, frame.row, anim.frame_size.x, anim.frame_size.y))
}
pub fn frame_index(&self) -> usize {
self.frame_index
}
pub fn state(&self) -> AnimationState {
self.state
}
pub fn offset(&self) -> Vec2 {
self.current.as_ref().map(|a| a.offset).unwrap_or(Vec2::ZERO)
}
pub fn update(&mut self, dt: f32) -> &[String] {
self.pending_events.clear();
if self.state != AnimationState::Playing {
return &self.pending_events;
}
let anim = match &self.current {
Some(a) => a,
None => return &self.pending_events,
};
if anim.frames.is_empty() {
self.state = AnimationState::Finished;
return &self.pending_events;
}
let frame = &anim.frames[self.frame_index];
let effective_duration = if frame.duration > 0.0 {
frame.duration / anim.speed
} else {
0.1 / anim.speed
};
self.frame_timer += dt;
if self.frame_timer >= effective_duration {
self.frame_timer -= effective_duration;
if let Some(event) = &frame.event {
self.pending_events.push(event.clone());
}
if anim.ping_pong {
if self.forward {
if self.frame_index + 1 >= anim.frames.len() {
self.forward = false;
self.frame_index = self.frame_index.saturating_sub(1);
} else {
self.frame_index += 1;
}
} else {
if self.frame_index == 0 {
if anim.looping {
self.forward = true;
self.frame_index = 1;
} else {
self.state = AnimationState::Finished;
}
} else {
self.frame_index -= 1;
}
}
} else {
if self.frame_index + 1 >= anim.frames.len() {
if anim.looping {
self.frame_index = 0;
} else {
self.state = AnimationState::Finished;
}
} else {
self.frame_index += 1;
}
}
}
&self.pending_events
}
}