Compare commits
3 Commits
cb65b67530
...
97720ea6fe
Author | SHA1 | Date |
---|---|---|
Mark | 97720ea6fe | |
Mark | 67c4a8c259 | |
Mark | 088d1f7fb9 |
5
TODO.md
5
TODO.md
|
@ -1,11 +1,13 @@
|
|||
# Specific projects
|
||||
|
||||
## Currently working on:
|
||||
- player selection
|
||||
- first: fix particles & physics
|
||||
- clickable buttons
|
||||
- planet outfitter
|
||||
|
||||
|
||||
## Small jobs
|
||||
- 🌟 clean up and document short sprite sections
|
||||
- 🌟 Better planet desc formatting
|
||||
- Procedural suns
|
||||
- 🌟 Back arrow -> reverse
|
||||
|
@ -27,7 +29,6 @@
|
|||
- Turn flares (physics by location?)
|
||||
- Angled engines & guns
|
||||
- Fix effect interaction with sprite sections
|
||||
- Clean up starfieldsprite
|
||||
|
||||
## Misc fixes & Optimizations
|
||||
- 🌟 Better errors when content/asset dirs don't exist
|
||||
|
|
2
assets
2
assets
|
@ -1 +1 @@
|
|||
Subproject commit b4fe8111c7b1cb17987bbff095bd50e61e2d8998
|
||||
Subproject commit f749d99ae63bcdefb4a62774d21f30a7eebc20d3
|
|
@ -3,7 +3,9 @@
|
|||
space.engine = 20
|
||||
|
||||
engine.thrust = 100
|
||||
engine.flare_sprite = "flare::ion"
|
||||
engine.flare.sprite = "flare::ion"
|
||||
engine.flare.on_start = "rise:top"
|
||||
engine.flare.on_stop = "rise:bot"
|
||||
steering.power = 20
|
||||
|
||||
|
||||
|
|
|
@ -66,6 +66,9 @@ pub struct AnimAutomaton {
|
|||
/// 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<SectionEdge>,
|
||||
}
|
||||
|
||||
impl AnimAutomaton {
|
||||
|
@ -90,6 +93,7 @@ impl AnimAutomaton {
|
|||
sprite: sprite.handle,
|
||||
current_frame: 0,
|
||||
current_fade: 0.0,
|
||||
next_edge_override: None,
|
||||
|
||||
current_direction,
|
||||
current_section,
|
||||
|
@ -98,21 +102,56 @@ impl AnimAutomaton {
|
|||
}
|
||||
}
|
||||
|
||||
/// Reset this animation
|
||||
pub fn reset(&mut self, ct: &Content) {
|
||||
*self = Self::new(ct, self.sprite);
|
||||
/// Override the next section transition
|
||||
pub fn next_edge(&mut self, s: SectionEdge) {
|
||||
self.next_edge_override = Some(s);
|
||||
}
|
||||
|
||||
/// Reverse this animation's direction
|
||||
pub fn reverse(&mut self) {
|
||||
match self.current_direction {
|
||||
AnimDirection::Stop => {}
|
||||
AnimDirection::Up => {
|
||||
/// 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());
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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;
|
||||
}
|
||||
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_direction = AnimDirection::Up;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -129,15 +168,21 @@ impl AnimAutomaton {
|
|||
// 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.
|
||||
|
||||
// If this is zero, this section isn't animated.
|
||||
if current_section.frame_duration == 0.0 {
|
||||
return;
|
||||
}
|
||||
// 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);
|
||||
|
||||
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.
|
||||
// we should transition right away.
|
||||
|
||||
if let Some(e) = self.next_edge_override {
|
||||
self.take_edge(ct, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We're stepping foward and finished this frame
|
||||
|
@ -150,32 +195,14 @@ impl AnimAutomaton {
|
|||
if self.current_frame < current_section.frames.len() - 1 {
|
||||
self.current_frame += 1;
|
||||
} else {
|
||||
match current_section.edge_bot {
|
||||
SectionEdge::Stop => {
|
||||
self.current_fade = 0.0;
|
||||
self.current_frame = current_section.frames.len() - 1;
|
||||
self.current_direction = AnimDirection::Stop;
|
||||
let e = {
|
||||
if self.next_edge_override.is_some() {
|
||||
self.next_edge_override.take().unwrap()
|
||||
} else {
|
||||
current_section.edge_bot.clone()
|
||||
}
|
||||
SectionEdge::Top { section } => {
|
||||
self.current_section = section;
|
||||
self.current_frame = 0;
|
||||
}
|
||||
SectionEdge::Bot { section } => {
|
||||
let s = sprite.get_section(section);
|
||||
self.current_section = section;
|
||||
self.current_frame = s.frames.len() - 1;
|
||||
self.reverse();
|
||||
}
|
||||
SectionEdge::Restart => {
|
||||
self.current_frame = 0;
|
||||
}
|
||||
SectionEdge::Reverse => {
|
||||
// Jump to SECOND frame, since we've already shown the
|
||||
// first during the fade transition
|
||||
self.current_frame = current_section.frames.len() - 1;
|
||||
self.reverse()
|
||||
}
|
||||
}
|
||||
};
|
||||
self.take_edge(ct, e);
|
||||
}
|
||||
|
||||
let current_section = sprite.get_section(self.current_section);
|
||||
|
@ -193,30 +220,14 @@ impl AnimAutomaton {
|
|||
if self.current_frame > 0 {
|
||||
self.current_frame -= 1;
|
||||
} else {
|
||||
match current_section.edge_top {
|
||||
SectionEdge::Stop => {
|
||||
self.current_fade = 0.0;
|
||||
self.current_frame = 0;
|
||||
self.current_direction = AnimDirection::Stop;
|
||||
let e = {
|
||||
if self.next_edge_override.is_some() {
|
||||
self.next_edge_override.take().unwrap()
|
||||
} else {
|
||||
current_section.edge_top.clone()
|
||||
}
|
||||
SectionEdge::Top { section } => {
|
||||
self.current_section = section;
|
||||
self.current_frame = 0;
|
||||
self.reverse();
|
||||
}
|
||||
SectionEdge::Bot { section } => {
|
||||
let s = sprite.get_section(section);
|
||||
self.current_section = section;
|
||||
self.current_frame = s.frames.len() - 1;
|
||||
}
|
||||
SectionEdge::Reverse => {
|
||||
self.current_frame = 0;
|
||||
self.reverse();
|
||||
}
|
||||
SectionEdge::Restart => {
|
||||
self.current_frame = current_section.frames.len() - 1;
|
||||
}
|
||||
}
|
||||
};
|
||||
self.take_edge(ct, e);
|
||||
}
|
||||
|
||||
let current_section = sprite.get_section(self.current_section);
|
||||
|
|
|
@ -8,25 +8,9 @@
|
|||
use std::{cmp::Eq, hash::Hash};
|
||||
|
||||
/// A lightweight representation of a sprite
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct SpriteHandle {
|
||||
pub(crate) index: usize,
|
||||
|
||||
/// The aspect ratio of this sprite (width / height)
|
||||
pub aspect: f32,
|
||||
}
|
||||
|
||||
impl Hash for SpriteHandle {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.index.hash(state)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for SpriteHandle {}
|
||||
impl PartialEq for SpriteHandle {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.index.eq(&other.index)
|
||||
}
|
||||
}
|
||||
|
||||
/// A lightweight representation of system body
|
||||
|
|
|
@ -13,6 +13,7 @@ use galactica_packer::{SpriteAtlas, SpriteAtlasImage};
|
|||
use std::{
|
||||
collections::HashMap,
|
||||
fs::File,
|
||||
hash::Hash,
|
||||
io::Read,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
@ -122,13 +123,18 @@ trait Build {
|
|||
/// Stores temporary data while building context objects
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct ContentBuildContext {
|
||||
/// Map effect names to handles
|
||||
pub effect_index: HashMap<String, EffectHandle>,
|
||||
|
||||
/// Maps sprite handles to a map of section name -> section index
|
||||
pub sprite_section_index: HashMap<SpriteHandle, HashMap<String, AnimSectionHandle>>,
|
||||
}
|
||||
|
||||
impl ContentBuildContext {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
effect_index: HashMap::new(),
|
||||
sprite_section_index: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
use anyhow::{bail, Context, Result};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::{
|
||||
handle::SpriteHandle, Content, ContentBuildContext, EffectHandle, OutfitHandle, OutfitSpace,
|
||||
StartEdge,
|
||||
};
|
||||
|
||||
pub(crate) mod syntax {
|
||||
use crate::{effect, part::outfitspace, ContentBuildContext};
|
||||
use crate::{effect, part::outfitspace, sprite::syntax::SectionEdge, ContentBuildContext};
|
||||
use anyhow::{bail, Result};
|
||||
use galactica_util::to_radians;
|
||||
use serde::Deserialize;
|
||||
|
@ -34,7 +35,14 @@ pub(crate) mod syntax {
|
|||
#[derive(Debug, Deserialize)]
|
||||
pub struct Engine {
|
||||
pub thrust: f32,
|
||||
pub flare_sprite: String,
|
||||
pub flare: EngineFlare,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct EngineFlare {
|
||||
pub sprite: String,
|
||||
pub on_start: Option<SectionEdge>,
|
||||
pub on_stop: Option<SectionEdge>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
|
@ -139,6 +147,12 @@ pub struct Outfit {
|
|||
/// engine points.
|
||||
pub engine_flare_sprite: Option<SpriteHandle>,
|
||||
|
||||
/// Jump to this edge when engines turn on
|
||||
pub engine_flare_on_start: Option<StartEdge>,
|
||||
|
||||
/// Jump to this edge when engines turn off
|
||||
pub engine_flare_on_stop: Option<StartEdge>,
|
||||
|
||||
/// Shield hit points
|
||||
pub shield_strength: f32,
|
||||
|
||||
|
@ -253,6 +267,8 @@ impl crate::Build for Outfit {
|
|||
engine_thrust: 0.0,
|
||||
steer_power: 0.0,
|
||||
engine_flare_sprite: None,
|
||||
engine_flare_on_start: None,
|
||||
engine_flare_on_stop: None,
|
||||
space: OutfitSpace::from(outfit.space),
|
||||
shield_delay: 0.0,
|
||||
shield_generation: 0.0,
|
||||
|
@ -261,16 +277,44 @@ impl crate::Build for Outfit {
|
|||
|
||||
// Engine stats
|
||||
if let Some(engine) = outfit.engine {
|
||||
let th = match content.sprite_index.get(&engine.flare_sprite) {
|
||||
None => bail!(
|
||||
"In outfit `{}`: flare sprite `{}` doesn't exist",
|
||||
outfit_name,
|
||||
engine.flare_sprite
|
||||
),
|
||||
let sprite_handle = match content.sprite_index.get(&engine.flare.sprite) {
|
||||
None => {
|
||||
return Err(anyhow!(
|
||||
"flare sprite `{}` doesn't exist",
|
||||
engine.flare.sprite
|
||||
))
|
||||
.with_context(|| format!("in outfit `{}`", outfit_name));
|
||||
}
|
||||
Some(t) => *t,
|
||||
};
|
||||
o.engine_thrust = engine.thrust;
|
||||
o.engine_flare_sprite = Some(th);
|
||||
o.engine_flare_sprite = Some(sprite_handle);
|
||||
|
||||
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))?,
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
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))?,
|
||||
)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Steering stats
|
||||
|
|
|
@ -4,13 +4,11 @@ use std::collections::HashMap;
|
|||
use crate::{handle::SpriteHandle, Content, ContentBuildContext};
|
||||
|
||||
pub(crate) mod syntax {
|
||||
use crate::{Content, ContentBuildContext};
|
||||
use crate::{Content, ContentBuildContext, SpriteHandle};
|
||||
use anyhow::{anyhow, bail, Context, Ok, Result};
|
||||
use serde::Deserialize;
|
||||
use std::{collections::HashMap, path::PathBuf};
|
||||
|
||||
use super::AnimSectionHandle;
|
||||
|
||||
// Raw serde syntax structs.
|
||||
// These are never seen by code outside this crate.
|
||||
|
||||
|
@ -66,12 +64,9 @@ pub(crate) mod syntax {
|
|||
impl SpriteSection {
|
||||
pub fn add_to(
|
||||
&self,
|
||||
_build_context: &mut ContentBuildContext,
|
||||
this_sprite: SpriteHandle,
|
||||
build_context: &mut ContentBuildContext,
|
||||
content: &mut Content,
|
||||
|
||||
// An index of all sections in this sprite, used to resolve
|
||||
// top and bot edges.
|
||||
all_sections: &HashMap<String, AnimSectionHandle>,
|
||||
) -> Result<((u32, u32), super::SpriteSection)> {
|
||||
// Make sure all frames have the same size and add them
|
||||
// to the frame vector
|
||||
|
@ -109,12 +104,12 @@ pub(crate) mod syntax {
|
|||
}
|
||||
|
||||
let edge_top = match &self.top {
|
||||
Some(x) => x.resolve_as_edge(all_sections)?,
|
||||
Some(x) => x.resolve_as_edge(this_sprite, build_context)?,
|
||||
None => super::SectionEdge::Stop,
|
||||
};
|
||||
|
||||
let edge_bot = match &self.bot {
|
||||
Some(x) => x.resolve_as_edge(all_sections)?,
|
||||
Some(x) => x.resolve_as_edge(this_sprite, build_context)?,
|
||||
None => super::SectionEdge::Stop,
|
||||
};
|
||||
|
||||
|
@ -140,10 +135,11 @@ pub(crate) mod syntax {
|
|||
impl SectionEdge {
|
||||
pub fn resolve_as_start(
|
||||
&self,
|
||||
all_sections: &HashMap<String, AnimSectionHandle>,
|
||||
sprite: SpriteHandle,
|
||||
build_context: &ContentBuildContext,
|
||||
) -> Result<super::StartEdge> {
|
||||
let e = self
|
||||
.resolve_as_edge(all_sections)
|
||||
.resolve_as_edge(sprite, build_context)
|
||||
.with_context(|| format!("while resolving start edge"))?;
|
||||
match e {
|
||||
super::SectionEdge::Bot { section } => Ok(super::StartEdge::Bot { section }),
|
||||
|
@ -156,8 +152,11 @@ pub(crate) mod syntax {
|
|||
|
||||
pub fn resolve_as_edge(
|
||||
&self,
|
||||
all_sections: &HashMap<String, AnimSectionHandle>,
|
||||
sprite: SpriteHandle,
|
||||
build_context: &ContentBuildContext,
|
||||
) -> Result<super::SectionEdge> {
|
||||
let all_sections = build_context.sprite_section_index.get(&sprite).unwrap();
|
||||
|
||||
if self.val == "stop" {
|
||||
return Ok(super::SectionEdge::Stop);
|
||||
}
|
||||
|
@ -203,7 +202,7 @@ pub(crate) mod syntax {
|
|||
pub struct AnimSectionHandle(pub(crate) usize);
|
||||
|
||||
/// An edge between two animation sections
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum SectionEdge {
|
||||
/// Stop at the last frame of this section
|
||||
Stop,
|
||||
|
@ -228,7 +227,7 @@ pub enum SectionEdge {
|
|||
}
|
||||
|
||||
/// Where to start an animation
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum StartEdge {
|
||||
/// Play the given section from the bottm
|
||||
Bot {
|
||||
|
@ -243,6 +242,15 @@ pub enum StartEdge {
|
|||
},
|
||||
}
|
||||
|
||||
impl Into<SectionEdge> for StartEdge {
|
||||
fn into(self) -> SectionEdge {
|
||||
match self {
|
||||
Self::Bot { section } => SectionEdge::Bot { section },
|
||||
Self::Top { section } => SectionEdge::Top { section },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a sprite that may be used in the game.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Sprite {
|
||||
|
@ -276,6 +284,14 @@ impl Sprite {
|
|||
}
|
||||
}
|
||||
|
||||
/// Get this sprite's starting section
|
||||
pub fn get_start_section(&self) -> AnimSectionHandle {
|
||||
match self.start_at {
|
||||
StartEdge::Bot { section } => section,
|
||||
StartEdge::Top { section } => section,
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterate this sprite's sections
|
||||
pub fn iter_sections(&self) -> impl Iterator<Item = &SpriteSection> {
|
||||
self.sections.iter()
|
||||
|
@ -330,10 +346,12 @@ impl crate::Build for Sprite {
|
|||
|
||||
let h = SpriteHandle {
|
||||
index: content.sprites.len(),
|
||||
aspect,
|
||||
};
|
||||
|
||||
content.sprite_index.insert(sprite_name.clone(), h);
|
||||
let mut smap = HashMap::new();
|
||||
smap.insert("anim".to_string(), AnimSectionHandle(0));
|
||||
build_context.sprite_section_index.insert(h, smap);
|
||||
|
||||
content.sprites.push(Self {
|
||||
name: sprite_name,
|
||||
|
@ -357,26 +375,33 @@ impl crate::Build for Sprite {
|
|||
// Name the one section in this sprite "anim"
|
||||
section_names.insert("anim".to_owned(), AnimSectionHandle(0));
|
||||
|
||||
let sprite_handle = SpriteHandle {
|
||||
index: content.sprites.len(),
|
||||
};
|
||||
content
|
||||
.sprite_index
|
||||
.insert(sprite_name.clone(), sprite_handle);
|
||||
let mut smap = HashMap::new();
|
||||
smap.insert("anim".to_string(), AnimSectionHandle(0));
|
||||
build_context
|
||||
.sprite_section_index
|
||||
.insert(sprite_handle, smap);
|
||||
|
||||
let (dim, section) = s
|
||||
.add_to(build_context, content, §ion_names)
|
||||
.add_to(sprite_handle, build_context, content)
|
||||
.with_context(|| format!("while parsing sprite `{}`", sprite_name))?;
|
||||
let aspect = dim.0 as f32 / dim.1 as f32;
|
||||
let h = SpriteHandle {
|
||||
index: content.sprites.len(),
|
||||
aspect,
|
||||
};
|
||||
|
||||
let mut sections = Vec::new();
|
||||
sections.push(section);
|
||||
|
||||
content.sprite_index.insert(sprite_name.clone(), h);
|
||||
content.sprites.push(Self {
|
||||
name: sprite_name,
|
||||
sections,
|
||||
start_at: StartEdge::Bot {
|
||||
section: AnimSectionHandle(0),
|
||||
},
|
||||
handle: h,
|
||||
handle: sprite_handle,
|
||||
aspect,
|
||||
});
|
||||
}
|
||||
|
@ -388,9 +413,19 @@ impl crate::Build for Sprite {
|
|||
idx += 1;
|
||||
}
|
||||
|
||||
let sprite_handle = SpriteHandle {
|
||||
index: content.sprites.len(),
|
||||
};
|
||||
content
|
||||
.sprite_index
|
||||
.insert(sprite_name.clone(), sprite_handle);
|
||||
build_context
|
||||
.sprite_section_index
|
||||
.insert(sprite_handle, section_names.clone());
|
||||
|
||||
let start_at = s
|
||||
.start_at
|
||||
.resolve_as_start(§ion_names)
|
||||
.resolve_as_start(sprite_handle, build_context)
|
||||
.with_context(|| format!("while loading sprite `{}`", sprite_name))?;
|
||||
|
||||
let mut sections = Vec::with_capacity(idx);
|
||||
|
@ -403,7 +438,7 @@ impl crate::Build for Sprite {
|
|||
for (k, _) in names {
|
||||
let v = s.section.get(k).unwrap();
|
||||
let (d, s) = v
|
||||
.add_to(build_context, content, §ion_names)
|
||||
.add_to(sprite_handle, build_context, content)
|
||||
.with_context(|| format!("while parsing sprite `{}`", sprite_name))
|
||||
.with_context(|| format!("while parsing section `{}`", k))?;
|
||||
|
||||
|
@ -423,17 +458,11 @@ impl crate::Build for Sprite {
|
|||
let dim = dim.unwrap();
|
||||
let aspect = dim.0 as f32 / dim.1 as f32;
|
||||
|
||||
let h = SpriteHandle {
|
||||
index: content.sprites.len(),
|
||||
aspect,
|
||||
};
|
||||
|
||||
content.sprite_index.insert(sprite_name.clone(), h);
|
||||
content.sprites.push(Self {
|
||||
name: sprite_name,
|
||||
sections,
|
||||
start_at,
|
||||
handle: h,
|
||||
handle: sprite_handle,
|
||||
aspect,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -39,6 +39,7 @@ impl GPUState {
|
|||
(device, queue) = adapter
|
||||
.request_device(
|
||||
&wgpu::DeviceDescriptor {
|
||||
// TODO: remove nonuniform sampled textures
|
||||
features: wgpu::Features::TEXTURE_BINDING_ARRAY | wgpu::Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING,
|
||||
// We may need limits if we compile for wasm
|
||||
limits: wgpu::Limits::default(),
|
||||
|
|
|
@ -12,11 +12,11 @@ use crate::{
|
|||
impl GPUState {
|
||||
pub(super) fn phys_push_ship(
|
||||
&mut self,
|
||||
state: &RenderInput,
|
||||
input: &RenderInput,
|
||||
// NE and SW corners of screen
|
||||
screen_clip: (Point2<f32>, Point2<f32>),
|
||||
) {
|
||||
for ship in state.systemsim.iter_ships() {
|
||||
for ship in input.systemsim.iter_ships() {
|
||||
// TODO: move collapse sequence here?
|
||||
|
||||
let ship_pos;
|
||||
|
@ -26,21 +26,21 @@ impl GPUState {
|
|||
ShipState::Dead | ShipState::Landed { .. } => continue,
|
||||
|
||||
ShipState::Collapsing { .. } | ShipState::Flying { .. } => {
|
||||
let r = state.systemsim.get_rigid_body(ship.rigid_body).unwrap();
|
||||
let r = input.systemsim.get_rigid_body(ship.rigid_body).unwrap();
|
||||
let pos = *r.translation();
|
||||
ship_pos = Point3::new(pos.x, pos.y, 1.0);
|
||||
let ship_rot = r.rotation();
|
||||
ship_ang = ship_rot.angle();
|
||||
ship_cnt = state.ct.get_ship(ship.get_data().get_content());
|
||||
ship_cnt = input.ct.get_ship(ship.get_data().get_content());
|
||||
}
|
||||
|
||||
ShipState::UnLanding { current_z, .. } | ShipState::Landing { current_z, .. } => {
|
||||
let r = state.systemsim.get_rigid_body(ship.rigid_body).unwrap();
|
||||
let r = input.systemsim.get_rigid_body(ship.rigid_body).unwrap();
|
||||
let pos = *r.translation();
|
||||
ship_pos = Point3::new(pos.x, pos.y, *current_z);
|
||||
let ship_rot = r.rotation();
|
||||
ship_ang = ship_rot.angle();
|
||||
ship_cnt = state.ct.get_ship(ship.get_data().get_content());
|
||||
ship_cnt = input.ct.get_ship(ship.get_data().get_content());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -48,14 +48,15 @@ impl GPUState {
|
|||
// TODO: adjust parallax for zoom?
|
||||
// 1.0 is z-coordinate, which is constant for ships
|
||||
let pos: Point2<f32> =
|
||||
(Point2::new(ship_pos.x, ship_pos.y) - state.camera_pos) / ship_pos.z;
|
||||
(Point2::new(ship_pos.x, ship_pos.y) - input.camera_pos) / ship_pos.z;
|
||||
|
||||
// Game dimensions of this sprite post-scale.
|
||||
// Post-scale width or height, whichever is larger.
|
||||
// This is in game units.
|
||||
//
|
||||
// We take the maximum to account for rotated sprites.
|
||||
let m = (ship_cnt.size / ship_pos.z) * ship_cnt.sprite.aspect.max(1.0);
|
||||
let m =
|
||||
(ship_cnt.size / ship_pos.z) * input.ct.get_sprite(ship_cnt.sprite).aspect.max(1.0);
|
||||
|
||||
// Don't draw sprites that are off the screen
|
||||
if pos.x < screen_clip.0.x - m
|
||||
|
@ -98,7 +99,7 @@ impl GPUState {
|
|||
| ShipState::Landing { .. } => true,
|
||||
_ => false,
|
||||
};
|
||||
ship.get_controls().thrust && is_flying
|
||||
is_flying
|
||||
} {
|
||||
for (engine_point, anim) in ship.iter_engine_anim() {
|
||||
self.state.queue.write_buffer(
|
||||
|
@ -136,12 +137,12 @@ impl GPUState {
|
|||
|
||||
pub(super) fn phys_push_projectile(
|
||||
&mut self,
|
||||
state: &RenderInput,
|
||||
input: &RenderInput,
|
||||
// NE and SW corners of screen
|
||||
screen_clip: (Point2<f32>, Point2<f32>),
|
||||
) {
|
||||
for p in state.systemsim.iter_projectiles() {
|
||||
let r = state.systemsim.get_rigid_body(p.rigid_body).unwrap();
|
||||
for p in input.systemsim.iter_projectiles() {
|
||||
let r = input.systemsim.get_rigid_body(p.rigid_body).unwrap();
|
||||
let proj_pos = *r.translation();
|
||||
let proj_rot = r.rotation();
|
||||
let proj_ang = proj_rot.angle();
|
||||
|
@ -150,14 +151,14 @@ impl GPUState {
|
|||
// Position adjusted for parallax
|
||||
// TODO: adjust parallax for zoom?
|
||||
// 1.0 is z-coordinate, which is constant for projectiles
|
||||
let pos = (proj_pos - state.camera_pos) / 1.0;
|
||||
let pos = (proj_pos - input.camera_pos) / 1.0;
|
||||
|
||||
// Game dimensions of this sprite post-scale.
|
||||
// Post-scale width or height, whichever is larger.
|
||||
// This is in game units.
|
||||
//
|
||||
// We take the maximum to account for rotated sprites.
|
||||
let m = (proj_cnt.size / 1.0) * proj_cnt.sprite.aspect.max(1.0);
|
||||
let m = (proj_cnt.size / 1.0) * input.ct.get_sprite(proj_cnt.sprite).aspect.max(1.0);
|
||||
|
||||
// Don't draw sprites that are off the screen
|
||||
if pos.x < screen_clip.0.x - m
|
||||
|
@ -196,22 +197,22 @@ impl GPUState {
|
|||
|
||||
pub(super) fn phys_push_system(
|
||||
&mut self,
|
||||
state: &RenderInput,
|
||||
input: &RenderInput,
|
||||
// NE and SW corners of screen
|
||||
screen_clip: (Point2<f32>, Point2<f32>),
|
||||
) {
|
||||
let system = state.ct.get_system(state.current_system);
|
||||
let system = input.ct.get_system(input.current_system);
|
||||
|
||||
for o in &system.objects {
|
||||
// Position adjusted for parallax
|
||||
let pos: Point2<f32> = (Point2::new(o.pos.x, o.pos.y) - state.camera_pos) / o.pos.z;
|
||||
let pos: Point2<f32> = (Point2::new(o.pos.x, o.pos.y) - input.camera_pos) / o.pos.z;
|
||||
|
||||
// Game dimensions of this sprite post-scale.
|
||||
// Post-scale width or height, whichever is larger.
|
||||
// This is in game units.
|
||||
//
|
||||
// We take the maximum to account for rotated sprites.
|
||||
let m = (o.size / o.pos.z) * o.sprite.aspect.max(1.0);
|
||||
let m = (o.size / o.pos.z) * input.ct.get_sprite(o.sprite).aspect.max(1.0);
|
||||
|
||||
// Don't draw sprites that are off the screen
|
||||
if pos.x < screen_clip.0.x - m
|
||||
|
@ -239,7 +240,7 @@ impl GPUState {
|
|||
}]),
|
||||
);
|
||||
|
||||
let sprite = state.ct.get_sprite(o.sprite);
|
||||
let sprite = input.ct.get_sprite(o.sprite);
|
||||
let texture_a = sprite.get_first_frame(); // ANIMATE
|
||||
|
||||
// Push this object's instance
|
||||
|
|
|
@ -53,6 +53,7 @@ impl Planet {
|
|||
current_object: None,
|
||||
|
||||
planet_desc: UiTextArea::new(
|
||||
ct,
|
||||
state,
|
||||
ct.get_sprite_handle("ui::planet"),
|
||||
Point2::new(0.0, 0.0),
|
||||
|
@ -67,6 +68,7 @@ impl Planet {
|
|||
),
|
||||
|
||||
planet_name: UiTextArea::new(
|
||||
ct,
|
||||
state,
|
||||
ct.get_sprite_handle("ui::planet"),
|
||||
Point2::new(0.0, 0.0),
|
||||
|
@ -135,10 +137,10 @@ impl Planet {
|
|||
self.sprite.push_to_buffer(input, state);
|
||||
}
|
||||
|
||||
pub fn get_textarea(&self, _input: &RenderInput, state: &RenderState) -> [TextArea; 2] {
|
||||
pub fn get_textarea(&self, input: &RenderInput, state: &RenderState) -> [TextArea; 2] {
|
||||
[
|
||||
self.planet_desc.get_textarea(state),
|
||||
self.planet_name.get_textarea(state),
|
||||
self.planet_desc.get_textarea(input, state),
|
||||
self.planet_name.get_textarea(input, state),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -133,7 +133,7 @@ impl Radar {
|
|||
([c[0], c[1], c[2], 1.0], 1.0)
|
||||
}
|
||||
};
|
||||
let size = (ship.size * ship.sprite.aspect) * ship_scale * z_scale;
|
||||
let size = (ship.size * input.ct.get_sprite(ship.sprite).aspect) * ship_scale * z_scale;
|
||||
let p: Point2<f32> = {
|
||||
if s.collider == input.player.ship.unwrap() {
|
||||
self.last_player_position
|
||||
|
|
|
@ -38,7 +38,10 @@ impl UiSprite {
|
|||
/// Add this image to the gpu sprite buffer
|
||||
pub fn push_to_buffer(&self, input: &RenderInput, state: &mut RenderState) {
|
||||
let pos = Point2::new(self.rect.pos.x, self.rect.pos.y);
|
||||
let dim = Vector2::new(self.rect.dim.y * self.sprite.aspect, self.rect.dim.y);
|
||||
let dim = Vector2::new(
|
||||
self.rect.dim.y * input.ct.get_sprite(self.sprite).aspect,
|
||||
self.rect.dim.y,
|
||||
);
|
||||
|
||||
for c in &self.children_under {
|
||||
c.push_to_buffer_child(input, state, pos, dim);
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use galactica_content::SpriteHandle;
|
||||
use galactica_content::{Content, SpriteHandle};
|
||||
use glyphon::{cosmic_text::Align, Attrs, Buffer, Color, Metrics, Shaping, TextArea, TextBounds};
|
||||
use nalgebra::{Point2, Vector2};
|
||||
|
||||
use super::SpriteRect;
|
||||
use crate::RenderState;
|
||||
use crate::{RenderInput, RenderState};
|
||||
|
||||
/// Represents a text area inside a sprite.
|
||||
pub(crate) struct UiTextArea {
|
||||
|
@ -32,6 +32,7 @@ pub(crate) struct UiTextArea {
|
|||
|
||||
impl UiTextArea {
|
||||
pub fn new(
|
||||
ct: &Content,
|
||||
state: &mut RenderState,
|
||||
sprite: SpriteHandle,
|
||||
sprite_position: Point2<f32>,
|
||||
|
@ -50,16 +51,16 @@ impl UiTextArea {
|
|||
align,
|
||||
color,
|
||||
};
|
||||
s.set_size(state, sprite_size);
|
||||
s.set_size(ct, state, sprite_size);
|
||||
return s;
|
||||
}
|
||||
|
||||
pub fn set_size(&mut self, state: &mut RenderState, sprite_size: f32) {
|
||||
pub fn set_size(&mut self, ct: &Content, state: &mut RenderState, sprite_size: f32) {
|
||||
self.sprite_size = sprite_size;
|
||||
self.buffer.set_size(
|
||||
&mut state.text_font_system,
|
||||
(self.rect.dim.x * self.sprite_size) * state.window.scale_factor() as f32,
|
||||
(self.rect.dim.y * self.sprite_size * self.sprite.aspect)
|
||||
(self.rect.dim.y * self.sprite_size * ct.get_sprite(self.sprite).aspect)
|
||||
* state.window.scale_factor() as f32,
|
||||
);
|
||||
}
|
||||
|
@ -74,9 +75,9 @@ impl UiTextArea {
|
|||
self.buffer.shape_until_scroll(&mut state.text_font_system);
|
||||
}
|
||||
|
||||
pub fn get_textarea(&self, state: &RenderState) -> TextArea {
|
||||
pub fn get_textarea(&self, input: &RenderInput, state: &RenderState) -> TextArea {
|
||||
let h = self.sprite_size;
|
||||
let w = self.sprite.aspect * h;
|
||||
let w = input.ct.get_sprite(self.sprite).aspect * h;
|
||||
|
||||
// Glypon works with physical pixels, so we must convert
|
||||
let fac = state.window.scale_factor() as f32;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use galactica_content::{CollapseEvent, Ship};
|
||||
use galactica_content::{CollapseEvent, Content, Ship};
|
||||
use nalgebra::{Point2, Vector2};
|
||||
use rand::{rngs::ThreadRng, Rng};
|
||||
use rapier2d::{dynamics::RigidBody, geometry::Collider};
|
||||
|
@ -32,13 +32,14 @@ impl ShipCollapseSequence {
|
|||
}
|
||||
|
||||
/// Pick a random points inside a ship's collider
|
||||
fn random_in_ship(&mut self, ship: &Ship, collider: &Collider) -> Vector2<f32> {
|
||||
fn random_in_ship(&mut self, ct: &Content, ship: &Ship, collider: &Collider) -> Vector2<f32> {
|
||||
let mut y = 0.0;
|
||||
let mut x = 0.0;
|
||||
let mut a = false;
|
||||
while !a {
|
||||
x = self.rng.gen_range(-1.0..=1.0) * ship.size / 2.0;
|
||||
y = self.rng.gen_range(-1.0..=1.0) * ship.size * ship.sprite.aspect / 2.0;
|
||||
y = self.rng.gen_range(-1.0..=1.0) * ship.size * ct.get_sprite(ship.sprite).aspect
|
||||
/ 2.0;
|
||||
a = collider.shape().contains_local_point(&Point2::new(x, y));
|
||||
}
|
||||
Vector2::new(x, y)
|
||||
|
@ -76,7 +77,7 @@ impl ShipCollapseSequence {
|
|||
let pos: Vector2<f32> = if let Some(pos) = spawner.pos {
|
||||
Vector2::new(pos.x, pos.y)
|
||||
} else {
|
||||
self.random_in_ship(ship_content, collider)
|
||||
self.random_in_ship(res.ct, ship_content, collider)
|
||||
};
|
||||
let pos = ship_pos + (ship_rot * pos);
|
||||
|
||||
|
@ -120,7 +121,7 @@ impl ShipCollapseSequence {
|
|||
let pos = if let Some(pos) = spawner.pos {
|
||||
Vector2::new(pos.x, pos.y)
|
||||
} else {
|
||||
self.random_in_ship(ship_content, collider)
|
||||
self.random_in_ship(res.ct, ship_content, collider)
|
||||
};
|
||||
|
||||
// Position, adjusted for ship rotation
|
||||
|
|
|
@ -64,6 +64,9 @@ pub struct PhysSimShip {
|
|||
/// This ship's controls
|
||||
pub(crate) controls: ShipControls,
|
||||
|
||||
/// This ship's controls during the last frame
|
||||
last_controls: ShipControls,
|
||||
|
||||
/// This ship's collapse sequence
|
||||
collapse_sequence: Option<ShipCollapseSequence>,
|
||||
}
|
||||
|
@ -86,6 +89,7 @@ impl PhysSimShip {
|
|||
data: ShipData::new(ct, handle, faction, personality),
|
||||
engine_anim: Vec::new(),
|
||||
controls: ShipControls::new(),
|
||||
last_controls: ShipControls::new(),
|
||||
collapse_sequence: Some(ShipCollapseSequence::new(ship_ct.collapse.length)),
|
||||
}
|
||||
}
|
||||
|
@ -100,14 +104,32 @@ impl PhysSimShip {
|
|||
self.data.step(res.t);
|
||||
self.anim.step(res.ct, res.t);
|
||||
|
||||
if self.controls.thrust {
|
||||
for (_, e) in &mut self.engine_anim {
|
||||
e.step(res.ct, res.t);
|
||||
}
|
||||
} else {
|
||||
for (_, e) in &mut self.engine_anim {
|
||||
e.reset(res.ct);
|
||||
}
|
||||
for (_, e) in &mut self.engine_anim {
|
||||
e.step(res.ct, res.t);
|
||||
}
|
||||
|
||||
if !self.controls.thrust && self.last_controls.thrust {
|
||||
let flare = self.get_flare(res.ct);
|
||||
if flare.is_some() {
|
||||
let flare_outfit = flare.unwrap();
|
||||
let flare = res.ct.get_outfit(flare_outfit);
|
||||
if flare.engine_flare_on_stop.is_some() {
|
||||
for (_, e) in &mut self.engine_anim {
|
||||
e.next_edge(flare.engine_flare_on_stop.unwrap().into());
|
||||
}
|
||||
}
|
||||
};
|
||||
} else if self.controls.thrust && !self.last_controls.thrust {
|
||||
let flare = self.get_flare(res.ct);
|
||||
if flare.is_some() {
|
||||
let flare_outfit = flare.unwrap();
|
||||
let flare = res.ct.get_outfit(flare_outfit);
|
||||
if flare.engine_flare_on_start.is_some() {
|
||||
for (_, e) in &mut self.engine_anim {
|
||||
e.next_edge(flare.engine_flare_on_start.unwrap().into());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
match self.data.get_state() {
|
||||
|
@ -133,6 +155,8 @@ impl PhysSimShip {
|
|||
|
||||
ShipState::Dead | ShipState::Landed { .. } => {}
|
||||
}
|
||||
|
||||
self.last_controls = self.controls.clone();
|
||||
}
|
||||
|
||||
/// Update this frame's physics
|
||||
|
@ -196,7 +220,7 @@ impl PhysSimShip {
|
|||
while !a {
|
||||
x = rng.gen_range(-1.0..=1.0) * ship_content.size / 2.0;
|
||||
y = rng.gen_range(-1.0..=1.0)
|
||||
* ship_content.size * ship_content.sprite.aspect
|
||||
* ship_content.size * res.ct.get_sprite(ship_content.sprite).aspect
|
||||
/ 2.0;
|
||||
a = collider.shape().contains_local_point(&point![x, y]);
|
||||
}
|
||||
|
@ -221,25 +245,30 @@ impl PhysSimShip {
|
|||
|
||||
/// Public mutable
|
||||
impl PhysSimShip {
|
||||
/// Re-create this ship's engine flare animations
|
||||
/// Should be called whenever we change outfits
|
||||
fn update_flares(&mut self, ct: &Content) {
|
||||
fn get_flare(&mut self, ct: &Content) -> Option<OutfitHandle> {
|
||||
// TODO: better way to pick flare sprite
|
||||
let mut flare = None;
|
||||
for (h, _) in self.data.get_outfits().iter_outfits() {
|
||||
let c = ct.get_outfit(*h);
|
||||
if c.engine_flare_sprite.is_some() {
|
||||
flare = c.engine_flare_sprite;
|
||||
break;
|
||||
return Some(*h);
|
||||
}
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
if flare.is_none() {
|
||||
/// Re-create this ship's engine flare animations
|
||||
/// Should be called whenever we change outfits
|
||||
fn update_flares(&mut self, ct: &Content) {
|
||||
let flare_outfit = self.get_flare(ct);
|
||||
if flare_outfit.is_none() {
|
||||
self.engine_anim = Vec::new();
|
||||
return;
|
||||
}
|
||||
let flare = ct
|
||||
.get_outfit(flare_outfit.unwrap())
|
||||
.engine_flare_sprite
|
||||
.unwrap();
|
||||
|
||||
let flare = flare.unwrap();
|
||||
self.engine_anim = ct
|
||||
.get_ship(self.data.get_content())
|
||||
.engines
|
||||
|
|
Loading…
Reference in New Issue