From e61c580247419d679283b6a468b0a70a5b0f9cef Mon Sep 17 00:00:00 2001 From: Mark Date: Sun, 21 Jan 2024 11:57:51 -0800 Subject: [PATCH] Added hidden texture support in content --- Cargo.lock | 1 + Cargo.toml | 1 + crates/content/Cargo.toml | 1 + crates/content/src/lib.rs | 6 +- crates/content/src/part/config.rs | 8 +- crates/content/src/part/outfit.rs | 67 ++++++-- crates/content/src/part/sprite.rs | 137 +++++++++++----- crates/content/src/spriteautomaton.rs | 221 +++++++++++++++++--------- 8 files changed, 308 insertions(+), 134 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aa9d59a..ed0c50a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -642,6 +642,7 @@ dependencies = [ "galactica-packer", "galactica-util", "image", + "lazy_static", "nalgebra", "rapier2d", "serde", diff --git a/Cargo.toml b/Cargo.toml index 4acc22d..c84faa4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,3 +64,4 @@ rand = "0.8.5" walkdir = "2.4.0" toml = "0.8.8" glyphon = "0.4.1" +lazy_static = "1.4.0" diff --git a/crates/content/Cargo.toml b/crates/content/Cargo.toml index 97e6de0..c76e98b 100644 --- a/crates/content/Cargo.toml +++ b/crates/content/Cargo.toml @@ -27,3 +27,4 @@ walkdir = { workspace = true } nalgebra = { workspace = true } image = { workspace = true } rapier2d = { workspace = true } +lazy_static = { workspace = true } diff --git a/crates/content/src/lib.rs b/crates/content/src/lib.rs index 96de355..8b57a39 100644 --- a/crates/content/src/lib.rs +++ b/crates/content/src/lib.rs @@ -13,8 +13,8 @@ use galactica_packer::{SpriteAtlas, SpriteAtlasImage}; use std::{ collections::HashMap, fs::File, - hash::Hash, io::Read, + num::NonZeroU32, path::{Path, PathBuf}, }; use toml; @@ -304,8 +304,8 @@ impl Content { } /// Get a texture by its index - pub fn get_image(&self, idx: u32) -> &SpriteAtlasImage { - &self.sprite_atlas.index[idx as usize] + pub fn get_image(&self, idx: NonZeroU32) -> &SpriteAtlasImage { + &self.sprite_atlas.get_by_idx(idx) } /// Get an outfit from a handle diff --git a/crates/content/src/part/config.rs b/crates/content/src/part/config.rs index b707dbe..1e04117 100644 --- a/crates/content/src/part/config.rs +++ b/crates/content/src/part/config.rs @@ -1,4 +1,4 @@ -use std::path::PathBuf; +use std::{num::NonZeroU32, path::PathBuf}; pub(crate) mod syntax { use anyhow::{bail, Result}; @@ -36,8 +36,8 @@ pub(crate) mod syntax { // An insufficient limit will result in some tiles not being drawn let starfield_instance_limit = 12 * starfield_count as u64; - let starfield_texture = match atlas.path_map.get(&self.starfield.texture) { - Some(s) => *s, + let starfield_texture = match atlas.get_idx_by_path(&self.starfield.texture) { + Some(s) => s, None => { bail!( "starfield texture `{}` doesn't exist", @@ -129,7 +129,7 @@ pub struct Config { pub starfield_max_dist: f32, /// Index of starfield texture - pub starfield_texture: u32, + pub starfield_texture: NonZeroU32, /// Size of a square starfield tile, in game units. /// A tile of size STARFIELD_Z_MAX * screen-size-in-game-units diff --git a/crates/content/src/part/outfit.rs b/crates/content/src/part/outfit.rs index 72c0b5f..c398759 100644 --- a/crates/content/src/part/outfit.rs +++ b/crates/content/src/part/outfit.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; use crate::{ handle::SpriteHandle, Content, ContentBuildContext, EffectHandle, OutfitHandle, OutfitSpace, - StartEdge, + SectionEdge, }; pub(crate) mod syntax { @@ -148,10 +148,10 @@ pub struct Outfit { pub engine_flare_sprite: Option, /// Jump to this edge when engines turn on - pub engine_flare_on_start: Option, + pub engine_flare_on_start: Option, /// Jump to this edge when engines turn off - pub engine_flare_on_stop: Option, + pub engine_flare_on_stop: Option, /// Shield hit points pub shield_strength: f32, @@ -289,30 +289,75 @@ impl crate::Build for Outfit { }; o.engine_thrust = engine.thrust; o.engine_flare_sprite = Some(sprite_handle); + let sprite = content.get_sprite(sprite_handle); + // Flare animation will traverse this edge when the player presses the thrust key + // This leads from the idle animation to the transition animation o.engine_flare_on_start = { let x = engine.flare.on_start; if x.is_none() { None } else { let x = x.unwrap(); - Some( - x.resolve_as_start(sprite_handle, build_context) - .with_context(|| format!("in outfit `{}`", outfit_name))?, - ) + let mut e = x + .resolve_as_edge(sprite_handle, build_context, 0.0) + .with_context(|| format!("in outfit `{}`", outfit_name))?; + match e { + // Inherit duration from transition sequence + SectionEdge::Top { + section, + ref mut duration, + } + | SectionEdge::Bot { + section, + ref mut duration, + } => { + *duration = sprite.get_section(section).frame_duration; + } + _ => { + return Err(anyhow!( + "bad edge `{}`: must be `top` or `bot`", + x.val + )) + .with_context(|| format!("in outfit `{}`", outfit_name)); + } + }; + Some(e) } }; + // Flare animation will traverse this edge when the player releases the thrust key + // This leads from the idle animation to the transition animation o.engine_flare_on_stop = { let x = engine.flare.on_stop; if x.is_none() { None } else { let x = x.unwrap(); - Some( - x.resolve_as_start(sprite_handle, build_context) - .with_context(|| format!("in outfit `{}`", outfit_name))?, - ) + let mut e = x + .resolve_as_edge(sprite_handle, build_context, 0.0) + .with_context(|| format!("in outfit `{}`", outfit_name))?; + match e { + // Inherit duration from transition sequence + SectionEdge::Top { + section, + ref mut duration, + } + | SectionEdge::Bot { + section, + ref mut duration, + } => { + *duration = sprite.get_section(section).frame_duration; + } + _ => { + return Err(anyhow!( + "bad edge `{}`: must be `top` or `bot`", + x.val + )) + .with_context(|| format!("in outfit `{}`", outfit_name)); + } + }; + Some(e) } }; } diff --git a/crates/content/src/part/sprite.rs b/crates/content/src/part/sprite.rs index f5e85d1..1043147 100644 --- a/crates/content/src/part/sprite.rs +++ b/crates/content/src/part/sprite.rs @@ -1,4 +1,5 @@ use anyhow::{anyhow, bail, Context, Result}; +use lazy_static::lazy_static; use std::collections::HashMap; use crate::{handle::SpriteHandle, Content, ContentBuildContext}; @@ -71,15 +72,15 @@ pub(crate) mod syntax { // Make sure all frames have the same size and add them // to the frame vector let mut dim = None; - let mut frames = Vec::new(); + let mut frames: Vec = Vec::new(); for f in &self.frames { - let idx = match content.sprite_atlas.path_map.get(f) { - Some(s) => *s, + let idx = match content.sprite_atlas.get_idx_by_path(f) { + Some(s) => s, None => { bail!("error: file `{}` isn't in the sprite atlas", f.display()); } }; - let img = &content.sprite_atlas.index[idx as usize]; + let img = &content.sprite_atlas.get_by_idx(idx); match dim { None => dim = Some(img.true_size), @@ -90,8 +91,13 @@ pub(crate) mod syntax { } } - frames.push(img.idx); + frames.push(img.idx.into()); } + + if frames.len() == 0 { + bail!("sprite sections must not be empty",) + } + let dim = dim.unwrap(); let frame_duration = match self.timing.variant { @@ -104,12 +110,12 @@ pub(crate) mod syntax { } let edge_top = match &self.top { - Some(x) => x.resolve_as_edge(this_sprite, build_context)?, + Some(x) => x.resolve_as_edge(this_sprite, build_context, frame_duration)?, None => super::SectionEdge::Stop, }; let edge_bot = match &self.bot { - Some(x) => x.resolve_as_edge(this_sprite, build_context)?, + Some(x) => x.resolve_as_edge(this_sprite, build_context, frame_duration)?, None => super::SectionEdge::Stop, }; @@ -139,11 +145,11 @@ pub(crate) mod syntax { build_context: &ContentBuildContext, ) -> Result { let e = self - .resolve_as_edge(sprite, build_context) + .resolve_as_edge(sprite, build_context, 0.0) .with_context(|| format!("while resolving start edge"))?; match e { - super::SectionEdge::Bot { section } => Ok(super::StartEdge::Bot { section }), - super::SectionEdge::Top { section } => Ok(super::StartEdge::Top { section }), + super::SectionEdge::Bot { section, .. } => Ok(super::StartEdge::Bot { section }), + super::SectionEdge::Top { section, .. } => Ok(super::StartEdge::Top { section }), _ => { bail!("bad section start specification `{}`", self.val); } @@ -154,19 +160,27 @@ pub(crate) mod syntax { &self, sprite: SpriteHandle, build_context: &ContentBuildContext, + duration: f32, ) -> Result { let all_sections = build_context.sprite_section_index.get(&sprite).unwrap(); + if self.val == "hidden" { + return Ok(super::SectionEdge::Top { + section: crate::AnimSectionHandle::Hidden, + duration, + }); + } + if self.val == "stop" { return Ok(super::SectionEdge::Stop); } if self.val == "reverse" { - return Ok(super::SectionEdge::Reverse); + return Ok(super::SectionEdge::Reverse { duration }); } - if self.val == "restart" { - return Ok(super::SectionEdge::Restart); + if self.val == "repeat" { + return Ok(super::SectionEdge::Repeat { duration }); } let (s, p) = match self.val.split_once(":") { @@ -185,8 +199,8 @@ pub(crate) mod syntax { }; match p { - "top" => Ok(super::SectionEdge::Top { section }), - "bot" => Ok(super::SectionEdge::Bot { section }), + "top" => Ok(super::SectionEdge::Top { section, duration }), + "bot" => Ok(super::SectionEdge::Bot { section, duration }), _ => { return Err(anyhow!("bad section edge specification `{}`", self.val)) .with_context(|| format!("invalid target `{}`", p)); @@ -196,10 +210,24 @@ pub(crate) mod syntax { } } -// TODO: should be pub crate /// A handle for an animation section inside a sprite #[derive(Debug, Copy, Clone)] -pub struct AnimSectionHandle(pub(crate) usize); +pub enum AnimSectionHandle { + /// The hidden section + Hidden, + + /// An index into this sprite's section array + Idx(usize), +} + +impl AnimSectionHandle { + fn get_idx(&self) -> Option { + match self { + Self::Hidden => None, + Self::Idx(idx) => Some(*idx), + } + } +} /// An edge between two animation sections #[derive(Debug, Clone, Copy)] @@ -211,19 +239,31 @@ pub enum SectionEdge { Bot { /// The section to play section: AnimSectionHandle, + + /// The length of this edge, in seconds + duration: f32, }, /// Play the given section from the top Top { /// The section to play section: AnimSectionHandle, + + /// The length of this edge, in seconds + duration: f32, }, /// Replay this section in the opposite direction - Reverse, + Reverse { + /// The length of this edge, in seconds + duration: f32, + }, /// Restart this section from the opposite end - Restart, + Repeat { + /// The length of this edge, in seconds + duration: f32, + }, } /// Where to start an animation @@ -245,8 +285,14 @@ pub enum StartEdge { impl Into for StartEdge { fn into(self) -> SectionEdge { match self { - Self::Bot { section } => SectionEdge::Bot { section }, - Self::Top { section } => SectionEdge::Top { section }, + Self::Bot { section } => SectionEdge::Bot { + section, + duration: 0.0, + }, + Self::Top { section } => SectionEdge::Top { + section, + duration: 0.0, + }, } } } @@ -270,13 +316,25 @@ pub struct Sprite { pub aspect: f32, } +lazy_static! { + static ref HIDDEN_SECTION: SpriteSection = SpriteSection { + frames: vec![0], + frame_duration: 0.0, + edge_bot: SectionEdge::Stop, + edge_top: SectionEdge::Stop, + }; +} + impl Sprite { /// Get an animation section from a handle pub fn get_section(&self, section: AnimSectionHandle) -> &SpriteSection { - &self.sections[section.0] + match section { + AnimSectionHandle::Hidden => &HIDDEN_SECTION, + AnimSectionHandle::Idx(idx) => &self.sections[idx], + } } - /// Get this sprite's first frame + /// Get the index of the texture of this sprite's first frame pub fn get_first_frame(&self) -> u32 { match self.start_at { StartEdge::Bot { section } => *self.get_section(section).frames.last().unwrap(), @@ -327,8 +385,8 @@ impl crate::Build for Sprite { for (sprite_name, t) in sprites { match t { syntax::Sprite::Static(t) => { - let idx = match content.sprite_atlas.path_map.get(&t.file) { - Some(s) => *s, + let idx = match content.sprite_atlas.get_idx_by_path(&t.file) { + Some(s) => s, None => { return Err( anyhow!("error while processing sprite `{}`", sprite_name,), @@ -341,7 +399,7 @@ impl crate::Build for Sprite { }); } }; - let img = &content.sprite_atlas.index[idx as usize]; + let img = &content.sprite_atlas.get_by_idx(idx); let aspect = img.w / img.h; let h = SpriteHandle { @@ -350,16 +408,16 @@ impl crate::Build for Sprite { content.sprite_index.insert(sprite_name.clone(), h); let mut smap = HashMap::new(); - smap.insert("anim".to_string(), AnimSectionHandle(0)); + smap.insert("anim".to_string(), AnimSectionHandle::Idx(0)); build_context.sprite_section_index.insert(h, smap); content.sprites.push(Self { name: sprite_name, start_at: StartEdge::Top { - section: AnimSectionHandle(0), + section: AnimSectionHandle::Idx(0), }, sections: vec![SpriteSection { - frames: vec![img.idx], + frames: vec![img.idx.into()], // We implement unanimated sprites with a very fast framerate // and STOP endpoints. frame_duration: 0.01, @@ -373,7 +431,7 @@ impl crate::Build for Sprite { syntax::Sprite::OneSection(s) => { let mut section_names: HashMap = HashMap::new(); // Name the one section in this sprite "anim" - section_names.insert("anim".to_owned(), AnimSectionHandle(0)); + section_names.insert("anim".to_owned(), AnimSectionHandle::Idx(0)); let sprite_handle = SpriteHandle { index: content.sprites.len(), @@ -382,7 +440,7 @@ impl crate::Build for Sprite { .sprite_index .insert(sprite_name.clone(), sprite_handle); let mut smap = HashMap::new(); - smap.insert("anim".to_string(), AnimSectionHandle(0)); + smap.insert("anim".to_string(), AnimSectionHandle::Idx(0)); build_context .sprite_section_index .insert(sprite_handle, smap); @@ -398,19 +456,18 @@ impl crate::Build for Sprite { content.sprites.push(Self { name: sprite_name, sections, - start_at: StartEdge::Bot { - section: AnimSectionHandle(0), + start_at: StartEdge::Top { + section: AnimSectionHandle::Idx(0), }, handle: sprite_handle, aspect, }); } syntax::Sprite::Complete(s) => { - let mut idx = 0; let mut section_names = HashMap::new(); for (name, _) in &s.section { - section_names.insert(name.to_owned(), AnimSectionHandle(idx)); - idx += 1; + section_names + .insert(name.to_owned(), AnimSectionHandle::Idx(section_names.len())); } let sprite_handle = SpriteHandle { @@ -428,19 +485,19 @@ impl crate::Build for Sprite { .resolve_as_start(sprite_handle, build_context) .with_context(|| format!("while loading sprite `{}`", sprite_name))?; - let mut sections = Vec::with_capacity(idx); + let mut sections = Vec::with_capacity(section_names.len()); let mut dim = None; // Make sure we add sections in order let mut names = section_names.iter().collect::>(); - names.sort_by(|a, b| (a.1).0.cmp(&(b.1).0)); + names.sort_by(|a, b| (a.1).get_idx().unwrap().cmp(&(b.1).get_idx().unwrap())); for (k, _) in names { let v = s.section.get(k).unwrap(); let (d, s) = v .add_to(sprite_handle, build_context, content) - .with_context(|| format!("while parsing sprite `{}`", sprite_name)) - .with_context(|| format!("while parsing section `{}`", k))?; + .with_context(|| format!("while parsing section `{}`", k)) + .with_context(|| format!("while parsing sprite `{}`", sprite_name))?; // Make sure all dimensions are the same if dim.is_none() { diff --git a/crates/content/src/spriteautomaton.rs b/crates/content/src/spriteautomaton.rs index 72f6f86..5aaaed1 100644 --- a/crates/content/src/spriteautomaton.rs +++ b/crates/content/src/spriteautomaton.rs @@ -25,7 +25,7 @@ impl AnimationState { } /// What direction are we playing our animation in? -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Copy)] enum AnimDirection { /// Top to bottom, with increasing frame indices /// (normal) @@ -35,7 +35,7 @@ enum AnimDirection { /// (reverse) Down, - /// Stopped, no animation + /// Stopped on one frame Stop, } @@ -53,8 +53,10 @@ pub struct SpriteAutomaton { current_frame: usize, /// Where we are between frames. - /// Always between zero and one. - current_fade: f32, + 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, @@ -89,10 +91,19 @@ impl SpriteAutomaton { ), }; + let sec = sprite.get_section(current_section); + Self { sprite: sprite.handle, current_frame: 0, - current_fade: 0.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, @@ -108,49 +119,108 @@ impl SpriteAutomaton { } /// Force a transition to the given section right now - pub fn jump_to(&mut self, ct: &Content, start: StartEdge) { - self.take_edge(ct, start.into()); + 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 => { - self.current_fade = 0.0; - self.current_frame = current_section.frames.len() - 1; - self.current_direction = AnimDirection::Stop; - } - SectionEdge::Top { section } => { - self.current_section = section; - self.current_frame = 0; - self.current_direction = AnimDirection::Down; - } - SectionEdge::Bot { section } => { - let s = sprite.get_section(section); - self.current_section = section; - self.current_frame = s.frames.len() - 1; - self.current_direction = AnimDirection::Up; - } - SectionEdge::Restart => { - self.current_frame = 0; - } - SectionEdge::Reverse => { - // Jump to SECOND frame, since we've already shown the - // first during the fade transition - match self.current_direction { AnimDirection::Stop => {} AnimDirection::Up => { self.current_frame = 0; - self.current_direction = AnimDirection::Down; } 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; } } } @@ -168,13 +238,12 @@ impl SpriteAutomaton { // 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. - // This should always be positive, and this fact is enforced by the content loader. - // if we get here, something is very wrong. - assert!(current_section.frame_duration > 0.0); + // 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::Down => self.current_fade += t / current_section.frame_duration, - AnimDirection::Up => self.current_fade -= t / current_section.frame_duration, AnimDirection::Stop => { // Edge case: we're stopped and got a request to transition. // we should transition right away. @@ -182,57 +251,57 @@ impl SpriteAutomaton { if let Some(e) = self.next_edge_override { self.take_edge(ct, e); } - } - } - // We're stepping foward and finished this frame - // (implies we're travelling downwards) - if self.current_fade > 1.0 { - while self.current_fade > 1.0 { - self.current_fade -= 1.0; + return; } - if self.current_frame < current_section.frames.len() - 1 { - self.current_frame += 1; - } else { - let e = { - if self.next_edge_override.is_some() { - self.next_edge_override.take().unwrap() + 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 { - current_section.edge_bot.clone() + 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); } - }; - self.take_edge(ct, e); + } } - let current_section = sprite.get_section(self.current_section); - self.last_texture = self.next_texture; - self.next_texture = current_section.frames[self.current_frame]; - } + AnimDirection::Up => { + self.current_edge_progress -= t; - // We're stepping backward and finished this frame - // (implies we're travelling upwards) - if self.current_fade < 0.0 { - while self.current_fade < 0.0 { - self.current_fade += 1.0; - } - - if self.current_frame > 0 { - self.current_frame -= 1; - } else { - let e = { - if self.next_edge_override.is_some() { - self.next_edge_override.take().unwrap() + // 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 { - current_section.edge_top.clone() + 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); } - }; - self.take_edge(ct, e); + } } - - let current_section = sprite.get_section(self.current_section); - self.next_texture = self.last_texture; - self.last_texture = current_section.frames[self.current_frame]; } } @@ -241,7 +310,7 @@ impl SpriteAutomaton { return AnimationState { texture_a: self.last_texture, texture_b: self.next_texture, - fade: self.current_fade, + fade: self.current_edge_progress / self.current_edge_duration, }; } }