use crate::{AnimSectionHandle, Content, SectionEdge, SpriteHandle, StartEdge}; /// A single frame's state #[derive(Debug, Clone)] pub struct AnimationState { /// The index of the texture we're fading from pub texture_a: u32, /// The index of the texture we're fading to pub texture_b: u32, /// Between 0.0 and 1.0, denoting how far we are between /// texture_a and texture_b /// 0.0 means fully show texture_a; /// 1.0 means fully show texture_b. pub fade: f32, } impl AnimationState { /// Convenience method. /// Get texture index as an array pub fn texture_index(&self) -> [u32; 2] { [self.texture_a, self.texture_b] } } /// What direction are we playing our animation in? #[derive(Debug, Clone, Copy)] enum AnimDirection { /// Top to bottom, with increasing frame indices /// (normal) Up, /// Bottom to top, with decreasing frame indices /// (reverse) Down, /// Stopped on one frame Stop, } /// Manages a single sprite's animation state. #[derive(Debug, Clone)] pub struct SpriteAutomaton { /// The sprite we're animating sprite: SpriteHandle, /// Which animation section we're on /// This MUST be a section from this Automaton's sprite current_section: AnimSectionHandle, /// Which frame we're on current_frame: usize, /// Where we are between frames. current_edge_progress: f32, /// The length of the current edge we're on, in seconds current_edge_duration: f32, /// In what direction are we playing the current section? current_direction: AnimDirection, /// The texture we're fading from /// (if we're moving downwards) last_texture: u32, /// The texture we're fading to /// (if we're moving downwards) next_texture: u32, /// If this is some, take this edge next next_edge_override: Option, } impl SpriteAutomaton { /// Create a new AnimAutomaton pub fn new(ct: &Content, sprite_handle: SpriteHandle) -> Self { let sprite = ct.get_sprite(sprite_handle); let (current_section, texture, current_direction) = match sprite.start_at { StartEdge::Top { section } => ( section, *sprite.get_section(section).frames.first().unwrap(), AnimDirection::Down, ), StartEdge::Bot { section } => ( section, *sprite.get_section(section).frames.last().unwrap(), AnimDirection::Up, ), }; let sec = sprite.get_section(current_section); Self { sprite: sprite.handle, current_frame: 0, current_edge_progress: match current_direction { AnimDirection::Down => 0.0, AnimDirection::Up => sec.frame_duration, AnimDirection::Stop => unreachable!("how'd you get here?"), }, current_edge_duration: sec.frame_duration, next_edge_override: None, current_direction, current_section, last_texture: texture, next_texture: texture, } } /// Override the next section transition pub fn next_edge(&mut self, s: SectionEdge) { self.next_edge_override = Some(s); } /// Force a transition to the given section right now pub fn jump_to(&mut self, ct: &Content, start: SectionEdge) { self.take_edge(ct, start); } fn take_edge(&mut self, ct: &Content, e: SectionEdge) { let sprite = ct.get_sprite(self.sprite); let current_section = sprite.get_section(self.current_section); let last_direction = self.current_direction; match e { SectionEdge::Stop => { match self.current_direction { AnimDirection::Stop => {} AnimDirection::Up => { self.current_frame = 0; } AnimDirection::Down => { self.current_frame = current_section.frames.len() - 1; } } self.current_edge_duration = 1.0; self.current_direction = AnimDirection::Stop; } SectionEdge::Top { section, duration } => { self.current_section = section; self.current_edge_duration = duration; self.current_frame = 0; self.current_direction = AnimDirection::Down; } SectionEdge::Bot { section, duration } => { let s = sprite.get_section(section); self.current_section = section; self.current_frame = s.frames.len() - 1; self.current_edge_duration = duration; self.current_direction = AnimDirection::Up; } SectionEdge::Repeat { duration } => { match self.current_direction { AnimDirection::Stop => {} AnimDirection::Up => { self.current_frame = current_section.frames.len() - 1; } AnimDirection::Down => { self.current_frame = 0; } } self.current_edge_duration = duration; } SectionEdge::Reverse { duration } => { match self.current_direction { AnimDirection::Stop => {} AnimDirection::Up => { // Jump to SECOND frame, since we've already shown the // first during the fade transition self.current_frame = { if current_section.frames.len() == 1 { 0 } else { 1 } }; self.current_direction = AnimDirection::Down; } AnimDirection::Down => { self.current_frame = { if current_section.frames.len() == 1 { 0 } else { current_section.frames.len() - 2 } }; self.current_direction = AnimDirection::Up; } } self.current_edge_duration = duration; } } let last = match last_direction { AnimDirection::Stop => self.last_texture, AnimDirection::Down => self.next_texture, AnimDirection::Up => self.last_texture, }; match self.current_direction { AnimDirection::Stop => { let current_section = sprite.get_section(self.current_section); self.next_texture = current_section.frames[self.current_frame]; self.last_texture = current_section.frames[self.current_frame]; } AnimDirection::Down => { let current_section = sprite.get_section(self.current_section); self.last_texture = last; self.next_texture = current_section.frames[self.current_frame]; self.current_edge_progress = 0.0; } AnimDirection::Up => { let current_section = sprite.get_section(self.current_section); self.next_texture = last; self.last_texture = current_section.frames[self.current_frame]; self.current_edge_progress = self.current_edge_duration; } } } /// Step this animation by `t` seconds pub fn step(&mut self, ct: &Content, t: f32) { let sprite = ct.get_sprite(self.sprite); let current_section = sprite.get_section(self.current_section); // Current_fade and current_frame keep track of where we are in the current section. // current_frame indexes this section frames. When it exceeds the number of frames // or falls below zero (when moving in reverse), we switch to the next section. // // current_fade keeps track of our state between frames. It is zero once a frame starts, // and we switch to the next frame when it hits 1.0. If we are stepping foward, it increases, // and if we are stepping backwards, it decreases. // Note that frame_duration may be zero! // This is only possible in the hidden texture, since // user-provided sections are always checked to be positive. assert!(current_section.frame_duration >= 0.0); match self.current_direction { AnimDirection::Stop => { // Edge case: we're stopped and got a request to transition. // we should transition right away. if let Some(e) = self.next_edge_override { self.take_edge(ct, e); } return; } AnimDirection::Down => { self.current_edge_progress += t; // We're stepping foward and finished this frame if self.current_edge_progress > self.current_edge_duration { if self.current_frame < current_section.frames.len() - 1 { self.current_frame += 1; self.last_texture = self.next_texture; self.next_texture = current_section.frames[self.current_frame]; self.current_edge_progress = 0.0; self.current_edge_duration = current_section.frame_duration; } else { let e = { if self.next_edge_override.is_some() { self.next_edge_override.take().unwrap() } else { current_section.edge_bot.clone() } }; self.take_edge(ct, e); } } } AnimDirection::Up => { self.current_edge_progress -= t; // We're stepping backward and finished this frame if self.current_edge_progress < 0.0 { if self.current_frame > 0 { self.current_frame -= 1; self.next_texture = self.last_texture; self.last_texture = current_section.frames[self.current_frame]; self.current_edge_progress = current_section.frame_duration; self.current_edge_duration = current_section.frame_duration; } else { let e = { if self.next_edge_override.is_some() { self.next_edge_override.take().unwrap() } else { current_section.edge_top.clone() } }; self.take_edge(ct, e); } } } } } /// Get the current frame of this animation pub fn get_texture_idx(&self) -> AnimationState { return AnimationState { texture_a: self.last_texture, texture_b: self.next_texture, fade: self.current_edge_progress / self.current_edge_duration, }; } /// Get the sprite this automaton is using pub fn get_sprite(&self) -> SpriteHandle { self.sprite } }