Compare commits

...

6 Commits

Author SHA1 Message Date
Mark d09158a324
Minor edits 2024-01-21 12:49:20 -08:00
Mark 64f930b391
Implemented hidden texture in render code 2024-01-21 12:43:55 -08:00
Mark 7820458404
Minor edits 2024-01-21 12:41:31 -08:00
Mark e61c580247
Added hidden texture support in content 2024-01-21 11:57:51 -08:00
Mark 267ec5a40a
Added hidden texture support in packer 2024-01-21 11:55:58 -08:00
Mark fa719d46b9
Minor rename 2024-01-20 15:18:12 -08:00
20 changed files with 515 additions and 219 deletions

1
Cargo.lock generated
View File

@ -642,6 +642,7 @@ dependencies = [
"galactica-packer", "galactica-packer",
"galactica-util", "galactica-util",
"image", "image",
"lazy_static",
"nalgebra", "nalgebra",
"rapier2d", "rapier2d",
"serde", "serde",

View File

@ -64,3 +64,4 @@ rand = "0.8.5"
walkdir = "2.4.0" walkdir = "2.4.0"
toml = "0.8.8" toml = "0.8.8"
glyphon = "0.4.1" glyphon = "0.4.1"
lazy_static = "1.4.0"

View File

@ -1,7 +1,6 @@
# Specific projects # Specific projects
## Currently working on: ## Currently working on:
- first: fix animation transition timings
- first: fix particles & physics - first: fix particles & physics
- clickable buttons - clickable buttons
- planet outfitter - planet outfitter
@ -218,6 +217,7 @@
- Naming: atlas, sprite, image, frame, texture - Naming: atlas, sprite, image, frame, texture
- Outfits may not change unless you've landed. They might not change ever for CC ships! - Outfits may not change unless you've landed. They might not change ever for CC ships!
- All angle adjustments happen in content & shaders - All angle adjustments happen in content & shaders
- Reserved texture: index zero
## Ideas ## Ideas

View File

@ -27,3 +27,4 @@ walkdir = { workspace = true }
nalgebra = { workspace = true } nalgebra = { workspace = true }
image = { workspace = true } image = { workspace = true }
rapier2d = { workspace = true } rapier2d = { workspace = true }
lazy_static = { workspace = true }

View File

@ -3,9 +3,9 @@
//! This subcrate is responsible for loading, parsing, validating game content, //! This subcrate is responsible for loading, parsing, validating game content,
//! which is usually stored in `./content`. //! which is usually stored in `./content`.
mod animautomaton;
mod handle; mod handle;
mod part; mod part;
mod spriteautomaton;
mod util; mod util;
use anyhow::{bail, Context, Result}; use anyhow::{bail, Context, Result};
@ -13,16 +13,16 @@ use galactica_packer::{SpriteAtlas, SpriteAtlasImage};
use std::{ use std::{
collections::HashMap, collections::HashMap,
fs::File, fs::File,
hash::Hash,
io::Read, io::Read,
num::NonZeroU32,
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
use toml; use toml;
use walkdir::WalkDir; use walkdir::WalkDir;
pub use animautomaton::*;
pub use handle::*; pub use handle::*;
pub use part::*; pub use part::*;
pub use spriteautomaton::*;
mod syntax { mod syntax {
use anyhow::{bail, Context, Result}; use anyhow::{bail, Context, Result};
@ -304,8 +304,8 @@ impl Content {
} }
/// Get a texture by its index /// Get a texture by its index
pub fn get_image(&self, idx: u32) -> &SpriteAtlasImage { pub fn get_image(&self, idx: NonZeroU32) -> &SpriteAtlasImage {
&self.sprite_atlas.index[idx as usize] &self.sprite_atlas.get_by_idx(idx)
} }
/// Get an outfit from a handle /// Get an outfit from a handle

View File

@ -1,4 +1,4 @@
use std::path::PathBuf; use std::{num::NonZeroU32, path::PathBuf};
pub(crate) mod syntax { pub(crate) mod syntax {
use anyhow::{bail, Result}; use anyhow::{bail, Result};
@ -36,8 +36,8 @@ pub(crate) mod syntax {
// An insufficient limit will result in some tiles not being drawn // An insufficient limit will result in some tiles not being drawn
let starfield_instance_limit = 12 * starfield_count as u64; let starfield_instance_limit = 12 * starfield_count as u64;
let starfield_texture = match atlas.path_map.get(&self.starfield.texture) { let starfield_texture = match atlas.get_idx_by_path(&self.starfield.texture) {
Some(s) => *s, Some(s) => s,
None => { None => {
bail!( bail!(
"starfield texture `{}` doesn't exist", "starfield texture `{}` doesn't exist",
@ -129,7 +129,7 @@ pub struct Config {
pub starfield_max_dist: f32, pub starfield_max_dist: f32,
/// Index of starfield texture /// Index of starfield texture
pub starfield_texture: u32, pub starfield_texture: NonZeroU32,
/// Size of a square starfield tile, in game units. /// Size of a square starfield tile, in game units.
/// A tile of size STARFIELD_Z_MAX * screen-size-in-game-units /// A tile of size STARFIELD_Z_MAX * screen-size-in-game-units

View File

@ -4,7 +4,7 @@ use std::collections::HashMap;
use crate::{ use crate::{
handle::SpriteHandle, Content, ContentBuildContext, EffectHandle, OutfitHandle, OutfitSpace, handle::SpriteHandle, Content, ContentBuildContext, EffectHandle, OutfitHandle, OutfitSpace,
StartEdge, SectionEdge,
}; };
pub(crate) mod syntax { pub(crate) mod syntax {
@ -148,10 +148,10 @@ pub struct Outfit {
pub engine_flare_sprite: Option<SpriteHandle>, pub engine_flare_sprite: Option<SpriteHandle>,
/// Jump to this edge when engines turn on /// Jump to this edge when engines turn on
pub engine_flare_on_start: Option<StartEdge>, pub engine_flare_on_start: Option<SectionEdge>,
/// Jump to this edge when engines turn off /// Jump to this edge when engines turn off
pub engine_flare_on_stop: Option<StartEdge>, pub engine_flare_on_stop: Option<SectionEdge>,
/// Shield hit points /// Shield hit points
pub shield_strength: f32, pub shield_strength: f32,
@ -289,30 +289,75 @@ impl crate::Build for Outfit {
}; };
o.engine_thrust = engine.thrust; o.engine_thrust = engine.thrust;
o.engine_flare_sprite = Some(sprite_handle); 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 = { o.engine_flare_on_start = {
let x = engine.flare.on_start; let x = engine.flare.on_start;
if x.is_none() { if x.is_none() {
None None
} else { } else {
let x = x.unwrap(); let x = x.unwrap();
Some( let mut e = x
x.resolve_as_start(sprite_handle, build_context) .resolve_as_edge(sprite_handle, build_context, 0.0)
.with_context(|| format!("in outfit `{}`", outfit_name))?, .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 = { o.engine_flare_on_stop = {
let x = engine.flare.on_stop; let x = engine.flare.on_stop;
if x.is_none() { if x.is_none() {
None None
} else { } else {
let x = x.unwrap(); let x = x.unwrap();
Some( let mut e = x
x.resolve_as_start(sprite_handle, build_context) .resolve_as_edge(sprite_handle, build_context, 0.0)
.with_context(|| format!("in outfit `{}`", outfit_name))?, .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)
} }
}; };
} }

View File

@ -1,4 +1,5 @@
use anyhow::{anyhow, bail, Context, Result}; use anyhow::{anyhow, bail, Context, Result};
use lazy_static::lazy_static;
use std::collections::HashMap; use std::collections::HashMap;
use crate::{handle::SpriteHandle, Content, ContentBuildContext}; 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 // Make sure all frames have the same size and add them
// to the frame vector // to the frame vector
let mut dim = None; let mut dim = None;
let mut frames = Vec::new(); let mut frames: Vec<u32> = Vec::new();
for f in &self.frames { for f in &self.frames {
let idx = match content.sprite_atlas.path_map.get(f) { let idx = match content.sprite_atlas.get_idx_by_path(f) {
Some(s) => *s, Some(s) => s,
None => { None => {
bail!("error: file `{}` isn't in the sprite atlas", f.display()); 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 { match dim {
None => dim = Some(img.true_size), 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 dim = dim.unwrap();
let frame_duration = match self.timing.variant { let frame_duration = match self.timing.variant {
@ -104,12 +110,12 @@ pub(crate) mod syntax {
} }
let edge_top = match &self.top { 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, None => super::SectionEdge::Stop,
}; };
let edge_bot = match &self.bot { 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, None => super::SectionEdge::Stop,
}; };
@ -139,11 +145,11 @@ pub(crate) mod syntax {
build_context: &ContentBuildContext, build_context: &ContentBuildContext,
) -> Result<super::StartEdge> { ) -> Result<super::StartEdge> {
let e = self let e = self
.resolve_as_edge(sprite, build_context) .resolve_as_edge(sprite, build_context, 0.0)
.with_context(|| format!("while resolving start edge"))?; .with_context(|| format!("while resolving start edge"))?;
match e { match e {
super::SectionEdge::Bot { section } => Ok(super::StartEdge::Bot { section }), super::SectionEdge::Bot { section, .. } => Ok(super::StartEdge::Bot { section }),
super::SectionEdge::Top { section } => Ok(super::StartEdge::Top { section }), super::SectionEdge::Top { section, .. } => Ok(super::StartEdge::Top { section }),
_ => { _ => {
bail!("bad section start specification `{}`", self.val); bail!("bad section start specification `{}`", self.val);
} }
@ -154,19 +160,27 @@ pub(crate) mod syntax {
&self, &self,
sprite: SpriteHandle, sprite: SpriteHandle,
build_context: &ContentBuildContext, build_context: &ContentBuildContext,
duration: f32,
) -> Result<super::SectionEdge> { ) -> Result<super::SectionEdge> {
let all_sections = build_context.sprite_section_index.get(&sprite).unwrap(); 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" { if self.val == "stop" {
return Ok(super::SectionEdge::Stop); return Ok(super::SectionEdge::Stop);
} }
if self.val == "reverse" { if self.val == "reverse" {
return Ok(super::SectionEdge::Reverse); return Ok(super::SectionEdge::Reverse { duration });
} }
if self.val == "restart" { if self.val == "repeat" {
return Ok(super::SectionEdge::Restart); return Ok(super::SectionEdge::Repeat { duration });
} }
let (s, p) = match self.val.split_once(":") { let (s, p) = match self.val.split_once(":") {
@ -185,8 +199,8 @@ pub(crate) mod syntax {
}; };
match p { match p {
"top" => Ok(super::SectionEdge::Top { section }), "top" => Ok(super::SectionEdge::Top { section, duration }),
"bot" => Ok(super::SectionEdge::Bot { section }), "bot" => Ok(super::SectionEdge::Bot { section, duration }),
_ => { _ => {
return Err(anyhow!("bad section edge specification `{}`", self.val)) return Err(anyhow!("bad section edge specification `{}`", self.val))
.with_context(|| format!("invalid target `{}`", p)); .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 /// A handle for an animation section inside a sprite
#[derive(Debug, Copy, Clone)] #[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<usize> {
match self {
Self::Hidden => None,
Self::Idx(idx) => Some(*idx),
}
}
}
/// An edge between two animation sections /// An edge between two animation sections
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
@ -211,19 +239,31 @@ pub enum SectionEdge {
Bot { Bot {
/// The section to play /// The section to play
section: AnimSectionHandle, section: AnimSectionHandle,
/// The length of this edge, in seconds
duration: f32,
}, },
/// Play the given section from the top /// Play the given section from the top
Top { Top {
/// The section to play /// The section to play
section: AnimSectionHandle, section: AnimSectionHandle,
/// The length of this edge, in seconds
duration: f32,
}, },
/// Replay this section in the opposite direction /// 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 this section from the opposite end
Restart, Repeat {
/// The length of this edge, in seconds
duration: f32,
},
} }
/// Where to start an animation /// Where to start an animation
@ -245,8 +285,14 @@ pub enum StartEdge {
impl Into<SectionEdge> for StartEdge { impl Into<SectionEdge> for StartEdge {
fn into(self) -> SectionEdge { fn into(self) -> SectionEdge {
match self { match self {
Self::Bot { section } => SectionEdge::Bot { section }, Self::Bot { section } => SectionEdge::Bot {
Self::Top { section } => SectionEdge::Top { section }, section,
duration: 0.0,
},
Self::Top { section } => SectionEdge::Top {
section,
duration: 0.0,
},
} }
} }
} }
@ -270,13 +316,25 @@ pub struct Sprite {
pub aspect: f32, 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 { impl Sprite {
/// Get an animation section from a handle /// Get an animation section from a handle
pub fn get_section(&self, section: AnimSectionHandle) -> &SpriteSection { 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 { pub fn get_first_frame(&self) -> u32 {
match self.start_at { match self.start_at {
StartEdge::Bot { section } => *self.get_section(section).frames.last().unwrap(), 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 { for (sprite_name, t) in sprites {
match t { match t {
syntax::Sprite::Static(t) => { syntax::Sprite::Static(t) => {
let idx = match content.sprite_atlas.path_map.get(&t.file) { let idx = match content.sprite_atlas.get_idx_by_path(&t.file) {
Some(s) => *s, Some(s) => s,
None => { None => {
return Err( return Err(
anyhow!("error while processing sprite `{}`", sprite_name,), 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 aspect = img.w / img.h;
let h = SpriteHandle { let h = SpriteHandle {
@ -350,16 +408,16 @@ impl crate::Build for Sprite {
content.sprite_index.insert(sprite_name.clone(), h); content.sprite_index.insert(sprite_name.clone(), h);
let mut smap = HashMap::new(); 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); build_context.sprite_section_index.insert(h, smap);
content.sprites.push(Self { content.sprites.push(Self {
name: sprite_name, name: sprite_name,
start_at: StartEdge::Top { start_at: StartEdge::Top {
section: AnimSectionHandle(0), section: AnimSectionHandle::Idx(0),
}, },
sections: vec![SpriteSection { sections: vec![SpriteSection {
frames: vec![img.idx], frames: vec![img.idx.into()],
// We implement unanimated sprites with a very fast framerate // We implement unanimated sprites with a very fast framerate
// and STOP endpoints. // and STOP endpoints.
frame_duration: 0.01, frame_duration: 0.01,
@ -373,7 +431,7 @@ impl crate::Build for Sprite {
syntax::Sprite::OneSection(s) => { syntax::Sprite::OneSection(s) => {
let mut section_names: HashMap<String, _> = HashMap::new(); let mut section_names: HashMap<String, _> = HashMap::new();
// Name the one section in this sprite "anim" // 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 { let sprite_handle = SpriteHandle {
index: content.sprites.len(), index: content.sprites.len(),
@ -382,7 +440,7 @@ impl crate::Build for Sprite {
.sprite_index .sprite_index
.insert(sprite_name.clone(), sprite_handle); .insert(sprite_name.clone(), sprite_handle);
let mut smap = HashMap::new(); let mut smap = HashMap::new();
smap.insert("anim".to_string(), AnimSectionHandle(0)); smap.insert("anim".to_string(), AnimSectionHandle::Idx(0));
build_context build_context
.sprite_section_index .sprite_section_index
.insert(sprite_handle, smap); .insert(sprite_handle, smap);
@ -398,19 +456,18 @@ impl crate::Build for Sprite {
content.sprites.push(Self { content.sprites.push(Self {
name: sprite_name, name: sprite_name,
sections, sections,
start_at: StartEdge::Bot { start_at: StartEdge::Top {
section: AnimSectionHandle(0), section: AnimSectionHandle::Idx(0),
}, },
handle: sprite_handle, handle: sprite_handle,
aspect, aspect,
}); });
} }
syntax::Sprite::Complete(s) => { syntax::Sprite::Complete(s) => {
let mut idx = 0;
let mut section_names = HashMap::new(); let mut section_names = HashMap::new();
for (name, _) in &s.section { for (name, _) in &s.section {
section_names.insert(name.to_owned(), AnimSectionHandle(idx)); section_names
idx += 1; .insert(name.to_owned(), AnimSectionHandle::Idx(section_names.len()));
} }
let sprite_handle = SpriteHandle { let sprite_handle = SpriteHandle {
@ -428,19 +485,19 @@ impl crate::Build for Sprite {
.resolve_as_start(sprite_handle, build_context) .resolve_as_start(sprite_handle, build_context)
.with_context(|| format!("while loading sprite `{}`", sprite_name))?; .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; let mut dim = None;
// Make sure we add sections in order // Make sure we add sections in order
let mut names = section_names.iter().collect::<Vec<_>>(); let mut names = section_names.iter().collect::<Vec<_>>();
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 { for (k, _) in names {
let v = s.section.get(k).unwrap(); let v = s.section.get(k).unwrap();
let (d, s) = v let (d, s) = v
.add_to(sprite_handle, build_context, content) .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 // Make sure all dimensions are the same
if dim.is_none() { if dim.is_none() {

View File

@ -25,7 +25,7 @@ impl AnimationState {
} }
/// What direction are we playing our animation in? /// What direction are we playing our animation in?
#[derive(Debug, Clone)] #[derive(Debug, Clone, Copy)]
enum AnimDirection { enum AnimDirection {
/// Top to bottom, with increasing frame indices /// Top to bottom, with increasing frame indices
/// (normal) /// (normal)
@ -35,13 +35,13 @@ enum AnimDirection {
/// (reverse) /// (reverse)
Down, Down,
/// Stopped, no animation /// Stopped on one frame
Stop, Stop,
} }
/// Manages a single sprite's animation state. /// Manages a single sprite's animation state.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct AnimAutomaton { pub struct SpriteAutomaton {
/// The sprite we're animating /// The sprite we're animating
sprite: SpriteHandle, sprite: SpriteHandle,
@ -53,8 +53,10 @@ pub struct AnimAutomaton {
current_frame: usize, current_frame: usize,
/// Where we are between frames. /// Where we are between frames.
/// Always between zero and one. current_edge_progress: f32,
current_fade: 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? /// In what direction are we playing the current section?
current_direction: AnimDirection, current_direction: AnimDirection,
@ -71,7 +73,7 @@ pub struct AnimAutomaton {
next_edge_override: Option<SectionEdge>, next_edge_override: Option<SectionEdge>,
} }
impl AnimAutomaton { impl SpriteAutomaton {
/// Create a new AnimAutomaton /// Create a new AnimAutomaton
pub fn new(ct: &Content, sprite_handle: SpriteHandle) -> Self { pub fn new(ct: &Content, sprite_handle: SpriteHandle) -> Self {
let sprite = ct.get_sprite(sprite_handle); let sprite = ct.get_sprite(sprite_handle);
@ -89,10 +91,19 @@ impl AnimAutomaton {
), ),
}; };
let sec = sprite.get_section(current_section);
Self { Self {
sprite: sprite.handle, sprite: sprite.handle,
current_frame: 0, 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, next_edge_override: None,
current_direction, current_direction,
@ -108,49 +119,108 @@ impl AnimAutomaton {
} }
/// Force a transition to the given section right now /// Force a transition to the given section right now
pub fn jump_to(&mut self, ct: &Content, start: StartEdge) { pub fn jump_to(&mut self, ct: &Content, start: SectionEdge) {
self.take_edge(ct, start.into()); self.take_edge(ct, start);
} }
fn take_edge(&mut self, ct: &Content, e: SectionEdge) { fn take_edge(&mut self, ct: &Content, e: SectionEdge) {
let sprite = ct.get_sprite(self.sprite); let sprite = ct.get_sprite(self.sprite);
let current_section = sprite.get_section(self.current_section); let current_section = sprite.get_section(self.current_section);
let last_direction = self.current_direction;
match e { match e {
SectionEdge::Stop => { 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 { match self.current_direction {
AnimDirection::Stop => {} AnimDirection::Stop => {}
AnimDirection::Up => { AnimDirection::Up => {
self.current_frame = 0; self.current_frame = 0;
self.current_direction = AnimDirection::Down;
} }
AnimDirection::Down => { AnimDirection::Down => {
self.current_frame = current_section.frames.len() - 1; 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_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 AnimAutomaton {
// and we switch to the next frame when it hits 1.0. If we are stepping foward, it increases, // 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. // and if we are stepping backwards, it decreases.
// This should always be positive, and this fact is enforced by the content loader. // Note that frame_duration may be zero!
// if we get here, something is very wrong. // This is only possible in the hidden texture, since
assert!(current_section.frame_duration > 0.0); // user-provided sections are always checked to be positive.
assert!(current_section.frame_duration >= 0.0);
match self.current_direction { 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 => { AnimDirection::Stop => {
// Edge case: we're stopped and got a request to transition. // Edge case: we're stopped and got a request to transition.
// we should transition right away. // we should transition right away.
@ -182,57 +251,57 @@ impl AnimAutomaton {
if let Some(e) = self.next_edge_override { if let Some(e) = self.next_edge_override {
self.take_edge(ct, e); self.take_edge(ct, e);
} }
}
}
// We're stepping foward and finished this frame return;
// (implies we're travelling downwards)
if self.current_fade > 1.0 {
while self.current_fade > 1.0 {
self.current_fade -= 1.0;
} }
if self.current_frame < current_section.frames.len() - 1 { AnimDirection::Down => {
self.current_frame += 1; self.current_edge_progress += t;
} else {
let e = { // We're stepping foward and finished this frame
if self.next_edge_override.is_some() { if self.current_edge_progress > self.current_edge_duration {
self.next_edge_override.take().unwrap() 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 { } 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); AnimDirection::Up => {
self.last_texture = self.next_texture; self.current_edge_progress -= t;
self.next_texture = current_section.frames[self.current_frame];
}
// We're stepping backward and finished this frame // We're stepping backward and finished this frame
// (implies we're travelling upwards) if self.current_edge_progress < 0.0 {
if self.current_fade < 0.0 { if self.current_frame > 0 {
while self.current_fade < 0.0 { self.current_frame -= 1;
self.current_fade += 1.0; self.next_texture = self.last_texture;
} self.last_texture = current_section.frames[self.current_frame];
self.current_edge_progress = current_section.frame_duration;
if self.current_frame > 0 { self.current_edge_duration = current_section.frame_duration;
self.current_frame -= 1;
} else {
let e = {
if self.next_edge_override.is_some() {
self.next_edge_override.take().unwrap()
} else { } 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 AnimAutomaton {
return AnimationState { return AnimationState {
texture_a: self.last_texture, texture_a: self.last_texture,
texture_b: self.next_texture, texture_b: self.next_texture,
fade: self.current_fade, fade: self.current_edge_progress / self.current_edge_duration,
}; };
} }
} }

View File

@ -17,7 +17,20 @@ use winit::{
window::WindowBuilder, window::WindowBuilder,
}; };
fn main() -> Result<()> { fn main() {
if let Err(err) = try_main() {
eprintln!("Galactica failed with an error");
let mut i = 0;
for e in err.chain().rev() {
eprintln!("{i:02}: {}", e);
i += 1;
}
std::process::exit(1);
}
}
fn try_main() -> Result<()> {
let cache_dir = Path::new(ASSET_CACHE); let cache_dir = Path::new(ASSET_CACHE);
fs::create_dir_all(cache_dir)?; fs::create_dir_all(cache_dir)?;

View File

@ -4,6 +4,7 @@ use image::{imageops, GenericImageView, ImageBuffer, Rgba, RgbaImage};
use std::{ use std::{
fs::File, fs::File,
io::{Read, Write}, io::{Read, Write},
num::NonZeroU32,
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
@ -205,19 +206,19 @@ impl AtlasSet {
) )
})?; })?;
self.index self.index.push(
.path_map p,
.insert(p.to_path_buf(), self.index.index.len() as u32); SpriteAtlasImage {
true_size: image_dim,
self.index.index.push(SpriteAtlasImage { // Add one to account for hidden texture
true_size: image_dim, idx: NonZeroU32::new(self.index.len() as u32 + 1).unwrap(),
idx: self.index.index.len() as u32, atlas: atlas_idx as u32,
atlas: atlas_idx as u32, x: (x + self.image_margin) as f32 / self.texture_width as f32,
x: (x + self.image_margin) as f32 / self.texture_width as f32, y: (y + self.image_margin) as f32 / self.texture_height as f32,
y: (y + self.image_margin) as f32 / self.texture_height as f32, w: image_dim.0 as f32 / self.texture_width as f32,
w: image_dim.0 as f32 / self.texture_width as f32, h: image_dim.1 as f32 / self.texture_height as f32,
h: image_dim.1 as f32 / self.texture_height as f32, },
}); );
return Ok(atlas_idx); return Ok(atlas_idx);
} }

View File

@ -2,7 +2,11 @@
//! This crate creates texture atlases from an asset tree. //! This crate creates texture atlases from an asset tree.
use std::{collections::HashMap, path::PathBuf}; use std::{
collections::HashMap,
num::NonZeroU32,
path::{Path, PathBuf},
};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -13,8 +17,9 @@ pub struct SpriteAtlasImage {
/// This is an index in SpriteAtlas.atlas_list /// This is an index in SpriteAtlas.atlas_list
pub atlas: u32, pub atlas: u32,
/// A globally unique, consecutively numbered index for this sprite /// A globally unique, consecutively numbered index for this sprite.
pub idx: u32, /// This is nonzero because index zero is reserved for the "hidden" texture.
pub idx: NonZeroU32,
/// The size of this image, in pixels /// The size of this image, in pixels
pub true_size: (u32, u32), pub true_size: (u32, u32),
@ -41,10 +46,10 @@ pub struct SpriteAtlasImage {
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone)]
pub struct SpriteAtlas { pub struct SpriteAtlas {
/// The images in this atlas /// The images in this atlas
pub index: Vec<SpriteAtlasImage>, pub(crate) index: Vec<SpriteAtlasImage>,
/// Map paths to image indices /// Map paths to image indices
pub path_map: HashMap<PathBuf, u32>, path_map: HashMap<PathBuf, NonZeroU32>,
/// The file names of the atlas textures we've generated /// The file names of the atlas textures we've generated
pub atlas_list: Vec<String>, pub atlas_list: Vec<String>,
@ -59,4 +64,30 @@ impl SpriteAtlas {
atlas_list: Vec::new(), atlas_list: Vec::new(),
} }
} }
/// Get a SpriteAtlasImage by index
pub fn get_by_idx(&self, idx: NonZeroU32) -> &SpriteAtlasImage {
&self.index[idx.get() as usize - 1]
}
/// Get an image index from its path
/// returns None if this path isn't in this index
pub fn get_idx_by_path(&self, path: &Path) -> Option<NonZeroU32> {
self.path_map.get(path).map(|x| *x)
}
/// Get the number of images in this atlas
pub fn len(&self) -> u32 {
self.index.len() as u32
}
/// Add an image with the given path to this index
pub fn push(&mut self, p: &Path, i: SpriteAtlasImage) {
self.path_map.insert(
p.to_path_buf(),
NonZeroU32::new(self.index.len() as u32 + 1).unwrap(),
);
self.index.push(i);
}
} }

View File

@ -103,7 +103,7 @@ fn transform_vertex(obj: ObjectData, vertex_position: vec2<f32>, texture_index:
); );
} }
return vec4<f32>(pos, 0.0, 1.0);; return vec4<f32>(pos, 0.0, 1.0);
} }
@vertex @vertex
@ -113,35 +113,63 @@ fn vertex_main(
) -> VertexOutput { ) -> VertexOutput {
var out: VertexOutput; var out: VertexOutput;
out.position = transform_vertex( // Pick texture size by the size of the visible texture
objects[instance.object_index], // (texture index 0 is special, it's the "hidden" texture)
vertex.position.xy, if instance.texture_index.x == 0u && instance.texture_index.y == 0u {
instance.texture_index.x out.position = vec4<f32>(0.0, 0.0, 0.0, 1.0);
); } else if instance.texture_index.x == 0u {
out.position = transform_vertex(
objects[instance.object_index],
vertex.position.xy,
instance.texture_index.y
);
} else if instance.texture_index.y == 0u {
out.position = transform_vertex(
objects[instance.object_index],
vertex.position.xy,
instance.texture_index.x
);
} else {
out.position = transform_vertex(
objects[instance.object_index],
vertex.position.xy,
instance.texture_index.x
);
}
// Compute texture coordinates
let age = global_data.current_time.x;
out.tween = instance.texture_fade; out.tween = instance.texture_fade;
let t = global_atlas[instance.texture_index.x]; // Texture 0 is special, it's the empty texture
out.texture_index_a = u32(t.atlas_texture); if instance.texture_index.x == 0u {
out.texture_coords_a = vec2(t.xpos, t.ypos); out.texture_index_a = 0u;
if vertex.texture_coords.x == 1.0 { out.texture_coords_a = vec2(0.0, 0.0);
out.texture_coords_a = out.texture_coords_a + vec2(t.width, 0.0); } else {
} let t = global_atlas[instance.texture_index.x];
if vertex.texture_coords.y == 1.0 { out.texture_index_a = t.atlas_texture;
out.texture_coords_a = out.texture_coords_a + vec2(0.0, t.height); out.texture_coords_a = vec2(t.xpos, t.ypos);
if vertex.texture_coords.x == 1.0 {
out.texture_coords_a = out.texture_coords_a + vec2(t.width, 0.0);
}
if vertex.texture_coords.y == 1.0 {
out.texture_coords_a = out.texture_coords_a + vec2(0.0, t.height);
}
} }
let b = global_atlas[instance.texture_index.y]; if instance.texture_index.y == 0u {
out.texture_index_b = u32(b.atlas_texture); out.texture_index_b = u32(0u);
out.texture_coords_b = vec2(b.xpos, b.ypos); out.texture_coords_b = vec2(0.0, 0.0);
if vertex.texture_coords.x == 1.0 { } else {
out.texture_coords_b = out.texture_coords_b + vec2(b.width, 0.0); let b = global_atlas[instance.texture_index.y];
} out.texture_index_b = u32(b.atlas_texture);
if vertex.texture_coords.y == 1.0 { out.texture_coords_b = vec2(b.xpos, b.ypos);
out.texture_coords_b = out.texture_coords_b + vec2(0.0, b.height); if vertex.texture_coords.x == 1.0 {
out.texture_coords_b = out.texture_coords_b + vec2(b.width, 0.0);
}
if vertex.texture_coords.y == 1.0 {
out.texture_coords_b = out.texture_coords_b + vec2(0.0, b.height);
}
} }
return out; return out;
@ -150,19 +178,42 @@ fn vertex_main(
@fragment @fragment
fn fragment_main(in: VertexOutput) -> @location(0) vec4<f32> { fn fragment_main(in: VertexOutput) -> @location(0) vec4<f32> {
return mix(
textureSampleLevel( var texture_a: vec4<f32> = vec4(0.0, 0.0, 0.0, 0.0);
if !(
(in.texture_index_a == 0u) &&
(in.texture_coords_a.x == 0.0) &&
(in.texture_coords_a.y == 0.0)
) {
texture_a = textureSampleLevel(
texture_array[in.texture_index_a], texture_array[in.texture_index_a],
sampler_array[0], sampler_array[0],
in.texture_coords_a, in.texture_coords_a,
0.0 0.0
).rgba, ).rgba;
textureSampleLevel( }
var texture_b: vec4<f32> = vec4(0.0, 0.0, 0.0, 0.0);
if !(
(in.texture_index_b == 0u) &&
(in.texture_coords_b.x == 0.0) &&
(in.texture_coords_b.y == 0.0)
) {
texture_b = textureSampleLevel(
texture_array[in.texture_index_b], texture_array[in.texture_index_b],
sampler_array[0], sampler_array[0],
in.texture_coords_b, in.texture_coords_b,
0.0 0.0
).rgba, ).rgba;
}
let color = mix(
texture_a,
texture_b,
in.tween in.tween
); );
}
return color;
}

View File

@ -33,13 +33,13 @@ var sampler_array: binding_array<sampler>;
// INCLUDE: anchor.wgsl // INCLUDE: anchor.wgsl
@vertex
fn vertex_main( fn transform_vertex(
vertex: VertexInput,
instance: InstanceInput, instance: InstanceInput,
) -> VertexOutput { vertex_position: vec3<f32>,
texture_index: u32,
) -> vec4<f32> {
let window_dim = global_data.window_size / global_data.window_scale.x; let window_dim = global_data.window_size / global_data.window_scale.x;
let scale = instance.size / window_dim.y; let scale = instance.size / window_dim.y;
let aspect = ( let aspect = (
@ -50,8 +50,8 @@ fn vertex_main(
// Apply scale and sprite aspect // Apply scale and sprite aspect
// Note that our mesh starts centered at (0, 0). This is important! // Note that our mesh starts centered at (0, 0). This is important!
var pos: vec2<f32> = vec2( var pos: vec2<f32> = vec2(
vertex.position.x * scale * aspect, vertex_position.x * scale * aspect,
vertex.position.y * scale vertex_position.y * scale
); );
// Apply rotation (and adjust sprite angle, since sprites point north) // Apply rotation (and adjust sprite angle, since sprites point north)
@ -72,10 +72,27 @@ fn vertex_main(
vec2(instance.size * aspect, instance.size) vec2(instance.size * aspect, instance.size)
); );
var out: VertexOutput; return vec4<f32>(pos, 0.0, 1.0);
out.position = vec4<f32>(pos, 1.0, 1.0); }
out.color_transform = instance.color_transform;
@vertex
fn vertex_main(
vertex: VertexInput,
instance: InstanceInput,
) -> VertexOutput {
// TODO: this will break if we try to use texture 0.
// implement animations for ui sprites & fix that here.
let pos = transform_vertex(
instance,
vertex.position,
instance.texture_index.x,
);
var out: VertexOutput;
out.position = pos;
out.color_transform = instance.color_transform;
// TODO: function to get texture from sprite // TODO: function to get texture from sprite
@ -108,7 +125,6 @@ fn vertex_main(
out.mask_index = vec2(0u, 0u); out.mask_index = vec2(0u, 0u);
} }
return out; return out;
} }

View File

@ -306,7 +306,7 @@ impl<'a> super::GPUState {
], ],
window_scale: [self.state.window.scale_factor() as f32, 0.0], window_scale: [self.state.window.scale_factor() as f32, 0.0],
window_aspect: [self.state.window_aspect, 0.0], window_aspect: [self.state.window_aspect, 0.0],
starfield_sprite: [input.ct.get_config().starfield_texture, 0], starfield_sprite: [input.ct.get_config().starfield_texture.into(), 0],
starfield_tile_size: [input.ct.get_config().starfield_size, 0.0], starfield_tile_size: [input.ct.get_config().starfield_size, 0.0],
starfield_size_limits: [ starfield_size_limits: [
input.ct.get_config().starfield_min_size, input.ct.get_config().starfield_min_size,

View File

@ -1,5 +1,6 @@
use crate::globaluniform::GlobalUniform; use crate::globaluniform::GlobalUniform;
// TODO: build script?
/// Basic wgsl preprocesser /// Basic wgsl preprocesser
pub(crate) fn preprocess_shader( pub(crate) fn preprocess_shader(
shader: &str, shader: &str,

View File

@ -112,15 +112,23 @@ impl TextureArray {
for sprite in &ct.sprites { for sprite in &ct.sprites {
for section in sprite.iter_sections() { for section in sprite.iter_sections() {
for idx in &section.frames { for idx in &section.frames {
let image = ct.get_image(*idx); // Some atlas entries may be written twice here,
image_locations.data[*idx as usize] = AtlasImageLocation { // but that's not really a problem. They're all the same!
xpos: image.x, //
ypos: image.y, // This happens rarely---only when two different sections
width: image.w, // use the same frame.
height: image.h, let idx = NonZeroU32::new(*idx);
atlas_texture: image.atlas, if idx.is_some() {
_padding: Default::default(), let image = ct.get_image(idx.unwrap());
}; image_locations.data[idx.unwrap().get() as usize] = AtlasImageLocation {
xpos: image.x,
ypos: image.y,
width: image.w,
height: image.h,
atlas_texture: image.atlas,
_padding: Default::default(),
};
}
} }
} }
} }

View File

@ -152,6 +152,7 @@ pub struct UiInstance {
/// What texture to use for this instance /// What texture to use for this instance
pub texture_index: [u32; 2], pub texture_index: [u32; 2],
/// Fade parameter between textures /// Fade parameter between textures
pub texture_fade: f32, pub texture_fade: f32,

View File

@ -1,4 +1,4 @@
use galactica_content::{AnimAutomaton, AnimationState, Content, FactionHandle, Projectile}; use galactica_content::{AnimationState, Content, FactionHandle, Projectile, SpriteAutomaton};
use rand::Rng; use rand::Rng;
use rapier2d::{dynamics::RigidBodyHandle, geometry::ColliderHandle}; use rapier2d::{dynamics::RigidBodyHandle, geometry::ColliderHandle};
@ -9,7 +9,7 @@ pub struct PhysProjectile {
pub content: Projectile, pub content: Projectile,
/// This projectile's sprite animation state /// This projectile's sprite animation state
anim: AnimAutomaton, anim: SpriteAutomaton,
/// The remaining lifetime of this projectile, in seconds /// The remaining lifetime of this projectile, in seconds
pub lifetime: f32, pub lifetime: f32,
@ -40,7 +40,7 @@ impl PhysProjectile {
let size_rng = content.size_rng; let size_rng = content.size_rng;
let lifetime = content.lifetime; let lifetime = content.lifetime;
PhysProjectile { PhysProjectile {
anim: AnimAutomaton::new(ct, content.sprite), anim: SpriteAutomaton::new(ct, content.sprite),
rigid_body, rigid_body,
collider, collider,
content, content,

View File

@ -1,5 +1,5 @@
use galactica_content::{ use galactica_content::{
AnimAutomaton, AnimationState, Content, EnginePoint, FactionHandle, OutfitHandle, ShipHandle, AnimationState, Content, EnginePoint, FactionHandle, OutfitHandle, ShipHandle, SpriteAutomaton,
}; };
use nalgebra::{point, vector, Rotation2, Vector2}; use nalgebra::{point, vector, Rotation2, Vector2};
use rand::Rng; use rand::Rng;
@ -56,10 +56,10 @@ pub struct PhysSimShip {
pub(crate) data: ShipData, pub(crate) data: ShipData,
/// This ship's sprite animation state /// This ship's sprite animation state
anim: AnimAutomaton, anim: SpriteAutomaton,
/// Animation state for each of this ship's engines /// Animation state for each of this ship's engines
engine_anim: Vec<(EnginePoint, AnimAutomaton)>, engine_anim: Vec<(EnginePoint, SpriteAutomaton)>,
/// This ship's controls /// This ship's controls
pub(crate) controls: ShipControls, pub(crate) controls: ShipControls,
@ -83,7 +83,7 @@ impl PhysSimShip {
) -> Self { ) -> Self {
let ship_ct = ct.get_ship(handle); let ship_ct = ct.get_ship(handle);
PhysSimShip { PhysSimShip {
anim: AnimAutomaton::new(ct, ship_ct.sprite), anim: SpriteAutomaton::new(ct, ship_ct.sprite),
rigid_body, rigid_body,
collider, collider,
data: ShipData::new(ct, handle, faction, personality), data: ShipData::new(ct, handle, faction, personality),
@ -273,7 +273,7 @@ impl PhysSimShip {
.get_ship(self.data.get_content()) .get_ship(self.data.get_content())
.engines .engines
.iter() .iter()
.map(|e| (e.clone(), AnimAutomaton::new(ct, flare))) .map(|e| (e.clone(), SpriteAutomaton::new(ct, flare)))
.collect(); .collect();
} }
@ -300,7 +300,7 @@ impl PhysSimShip {
} }
/// Get this ship's engine animations /// Get this ship's engine animations
pub fn iter_engine_anim(&self) -> impl Iterator<Item = &(EnginePoint, AnimAutomaton)> { pub fn iter_engine_anim(&self) -> impl Iterator<Item = &(EnginePoint, SpriteAutomaton)> {
self.engine_anim.iter() self.engine_anim.iter()
} }