Compare commits
2 Commits
a9e9b6a8ba
...
f05f2fbc45
Author | SHA1 | Date |
---|---|---|
Mark | f05f2fbc45 | |
Mark | a892e4e763 |
5
TODO.md
5
TODO.md
|
@ -1,6 +1,7 @@
|
||||||
## Specific Jobs
|
## Specific Jobs
|
||||||
- Finish particles
|
- Define projectile colliders & particles
|
||||||
- Projectile colliders & particles
|
- Particle variation
|
||||||
|
- Animated sprites
|
||||||
- UI: health, shield, fuel, heat, energy bars
|
- UI: health, shield, fuel, heat, energy bars
|
||||||
- UI: text arranger
|
- UI: text arranger
|
||||||
- Sound system
|
- Sound system
|
||||||
|
|
|
@ -10,7 +10,7 @@ rate_rng = 0.1
|
||||||
|
|
||||||
projectile.sprite_texture = "projectile::blaster"
|
projectile.sprite_texture = "projectile::blaster"
|
||||||
# Height of projectile in game units
|
# Height of projectile in game units
|
||||||
projectile.size = 10
|
projectile.size = 6
|
||||||
projectile.size_rng = 0.0
|
projectile.size_rng = 0.0
|
||||||
# Speed of projectile, in game units/second
|
# Speed of projectile, in game units/second
|
||||||
projectile.speed = 300
|
projectile.speed = 300
|
||||||
|
|
|
@ -1,38 +1,45 @@
|
||||||
[texture."starfield"]
|
[texture."starfield"]
|
||||||
path = "starfield.png"
|
file = "starfield.png"
|
||||||
|
|
||||||
[texture."star::star"]
|
[texture."star::star"]
|
||||||
path = "star/B-09.png"
|
file = "star/B-09.png"
|
||||||
|
|
||||||
[texture."flare::ion"]
|
[texture."flare::ion"]
|
||||||
path = "flare/1.png"
|
file = "flare/1.png"
|
||||||
|
|
||||||
[texture."planet::earth"]
|
[texture."planet::earth"]
|
||||||
path = "planet/earth.png"
|
file = "planet/earth.png"
|
||||||
|
|
||||||
[texture."planet::luna"]
|
[texture."planet::luna"]
|
||||||
path = "planet/luna.png"
|
file = "planet/luna.png"
|
||||||
|
|
||||||
[texture."projectile::blaster"]
|
[texture."projectile::blaster"]
|
||||||
path = "projectile/blaster.png"
|
file = "projectile/blaster.png"
|
||||||
|
|
||||||
[texture."ship::gypsum"]
|
[texture."ship::gypsum"]
|
||||||
path = "ship/gypsum.png"
|
file = "ship/gypsum.png"
|
||||||
|
|
||||||
[texture."ui::radar"]
|
[texture."ui::radar"]
|
||||||
path = "ui/radar.png"
|
file = "ui/radar.png"
|
||||||
|
|
||||||
[texture."ui::shipblip"]
|
[texture."ui::shipblip"]
|
||||||
path = "ui/ship-blip.png"
|
file = "ui/ship-blip.png"
|
||||||
|
|
||||||
[texture."ui::planetblip"]
|
[texture."ui::planetblip"]
|
||||||
path = "ui/planet-blip.png"
|
file = "ui/planet-blip.png"
|
||||||
|
|
||||||
[texture."ui::radarframe"]
|
[texture."ui::radarframe"]
|
||||||
path = "ui/radarframe.png"
|
file = "ui/radarframe.png"
|
||||||
|
|
||||||
[texture."ui::centerarrow"]
|
[texture."ui::centerarrow"]
|
||||||
path = "ui/center-arrow.png"
|
file = "ui/center-arrow.png"
|
||||||
|
|
||||||
[texture."particle::blaster"]
|
[texture."particle::blaster"]
|
||||||
path = "particle/blaster-01.png"
|
duration = 0.15
|
||||||
|
repeat = "once"
|
||||||
|
frames = [
|
||||||
|
"particle/blaster-01.png",
|
||||||
|
"particle/blaster-02.png",
|
||||||
|
"particle/blaster-03.png",
|
||||||
|
"particle/blaster-04.png",
|
||||||
|
]
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "galactica-behavior"
|
name = "galactica-behavior"
|
||||||
description = "AI behaviors for Galaictica"
|
description = "AI behaviors for Galactica"
|
||||||
categories = { workspace = true }
|
categories = { workspace = true }
|
||||||
keywords = { workspace = true }
|
keywords = { workspace = true }
|
||||||
version = { workspace = true }
|
version = { workspace = true }
|
||||||
|
|
|
@ -26,7 +26,7 @@ impl ShipBehavior for Point {
|
||||||
s.controls.guns = false;
|
s.controls.guns = false;
|
||||||
s.controls.thrust = false;
|
s.controls.thrust = false;
|
||||||
|
|
||||||
let (my_s, my_r) = physics.get_ship_body(&self.handle).unwrap();
|
let (my_s, my_r) = physics.get_ship_body(self.handle).unwrap();
|
||||||
let my_position = util::rigidbody_position(my_r);
|
let my_position = util::rigidbody_position(my_r);
|
||||||
let my_rotation = util::rigidbody_rotation(my_r);
|
let my_rotation = util::rigidbody_rotation(my_r);
|
||||||
let my_angvel = my_r.angvel();
|
let my_angvel = my_r.angvel();
|
||||||
|
|
|
@ -19,8 +19,8 @@ use walkdir::WalkDir;
|
||||||
|
|
||||||
pub use handle::{FactionHandle, GunHandle, OutfitHandle, ShipHandle, SystemHandle, TextureHandle};
|
pub use handle::{FactionHandle, GunHandle, OutfitHandle, ShipHandle, SystemHandle, TextureHandle};
|
||||||
pub use part::{
|
pub use part::{
|
||||||
EnginePoint, Faction, Gun, GunPoint, Outfit, OutfitSpace, Projectile, Relationship, Ship,
|
EnginePoint, Faction, Gun, GunPoint, Outfit, OutfitSpace, Projectile, Relationship, RepeatMode,
|
||||||
System, Texture,
|
Ship, System, Texture,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod syntax {
|
mod syntax {
|
||||||
|
|
|
@ -14,4 +14,4 @@ pub use outfit::Outfit;
|
||||||
pub use shared::OutfitSpace;
|
pub use shared::OutfitSpace;
|
||||||
pub use ship::{EnginePoint, GunPoint, Ship};
|
pub use ship::{EnginePoint, GunPoint, Ship};
|
||||||
pub use system::{Object, System};
|
pub use system::{Object, System};
|
||||||
pub use texture::Texture;
|
pub use texture::{RepeatMode, Texture};
|
||||||
|
|
|
@ -1,20 +1,60 @@
|
||||||
use std::{collections::HashMap, path::PathBuf};
|
|
||||||
|
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{bail, Context, Result};
|
||||||
use image::io::Reader;
|
use image::io::Reader;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use std::{collections::HashMap, path::PathBuf};
|
||||||
|
|
||||||
use crate::{handle::TextureHandle, Content};
|
use crate::{handle::TextureHandle, Content};
|
||||||
|
|
||||||
pub(crate) mod syntax {
|
pub(crate) mod syntax {
|
||||||
|
use serde::Deserialize;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use serde::Deserialize;
|
use super::RepeatMode;
|
||||||
|
|
||||||
// Raw serde syntax structs.
|
// Raw serde syntax structs.
|
||||||
// These are never seen by code outside this crate.
|
// These are never seen by code outside this crate.
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct Texture {
|
#[serde(untagged)]
|
||||||
pub path: PathBuf,
|
pub enum Texture {
|
||||||
|
Static(StaticTexture),
|
||||||
|
Frames(FramesTexture),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct StaticTexture {
|
||||||
|
pub file: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: loop mode
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct FramesTexture {
|
||||||
|
pub frames: Vec<PathBuf>,
|
||||||
|
pub duration: f32,
|
||||||
|
pub repeat: RepeatMode,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// How to replay a texture's animation
|
||||||
|
#[derive(Debug, Deserialize, Clone, Copy)]
|
||||||
|
pub enum RepeatMode {
|
||||||
|
/// Play this animation once, and stop at the last frame
|
||||||
|
#[serde(rename = "once")]
|
||||||
|
Once,
|
||||||
|
|
||||||
|
/// After the first frame, jump to the last frame
|
||||||
|
#[serde(rename = "repeat")]
|
||||||
|
Repeat,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RepeatMode {
|
||||||
|
/// Represent this repeatmode as an integer
|
||||||
|
/// Used to pass this enum into shaders
|
||||||
|
pub fn as_int(&self) -> u32 {
|
||||||
|
match self {
|
||||||
|
Self::Once => 0,
|
||||||
|
Self::Repeat => 1,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,8 +67,16 @@ pub struct Texture {
|
||||||
/// The handle for this texture
|
/// The handle for this texture
|
||||||
pub handle: TextureHandle,
|
pub handle: TextureHandle,
|
||||||
|
|
||||||
/// The path to this texture's image file
|
/// The frames of this texture
|
||||||
pub path: PathBuf,
|
/// (static textures have one frame)
|
||||||
|
pub frames: Vec<PathBuf>,
|
||||||
|
|
||||||
|
/// The speed of this texture's animation
|
||||||
|
/// (static textures have zero fps)
|
||||||
|
pub fps: f32,
|
||||||
|
|
||||||
|
/// How to replay this texture's animation
|
||||||
|
pub repeat: RepeatMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl crate::Build for Texture {
|
impl crate::Build for Texture {
|
||||||
|
@ -36,42 +84,105 @@ impl crate::Build for Texture {
|
||||||
|
|
||||||
fn build(texture: Self::InputSyntax, ct: &mut Content) -> Result<()> {
|
fn build(texture: Self::InputSyntax, ct: &mut Content) -> Result<()> {
|
||||||
for (texture_name, t) in texture {
|
for (texture_name, t) in texture {
|
||||||
let path = ct.texture_root.join(t.path);
|
match t {
|
||||||
let reader = Reader::open(&path).with_context(|| {
|
syntax::Texture::Static(t) => {
|
||||||
format!(
|
let file = ct.texture_root.join(t.file);
|
||||||
"Failed to read texture `{}` from file `{}`",
|
let reader = Reader::open(&file).with_context(|| {
|
||||||
texture_name,
|
format!(
|
||||||
path.display()
|
"Failed to read texture `{}` from file `{}`",
|
||||||
)
|
texture_name,
|
||||||
})?;
|
file.display()
|
||||||
let dim = reader.into_dimensions().with_context(|| {
|
)
|
||||||
format!(
|
})?;
|
||||||
"Failed to dimensions of texture `{}` from file `{}`",
|
let dim = reader.into_dimensions().with_context(|| {
|
||||||
texture_name,
|
format!(
|
||||||
path.display()
|
"Failed to get dimensions of texture `{}` from file `{}`",
|
||||||
)
|
texture_name,
|
||||||
})?;
|
file.display()
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
let h = TextureHandle {
|
let h = TextureHandle {
|
||||||
index: ct.textures.len(),
|
index: ct.textures.len(),
|
||||||
aspect: dim.0 as f32 / dim.1 as f32,
|
aspect: dim.0 as f32 / dim.1 as f32,
|
||||||
};
|
};
|
||||||
|
|
||||||
if texture_name == ct.starfield_texture_name {
|
if texture_name == ct.starfield_texture_name {
|
||||||
if ct.starfield_handle.is_none() {
|
if ct.starfield_handle.is_none() {
|
||||||
ct.starfield_handle = Some(h)
|
ct.starfield_handle = Some(h)
|
||||||
} else {
|
} else {
|
||||||
// This can't happen, since this is a hashmap.
|
// This can't happen, since this is a hashmap.
|
||||||
unreachable!("Found two starfield textures! Something is very wrong.")
|
unreachable!("Found two starfield textures! Something is very wrong.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ct.texture_index.insert(texture_name.clone(), h);
|
||||||
|
|
||||||
|
ct.textures.push(Self {
|
||||||
|
name: texture_name,
|
||||||
|
frames: vec![file],
|
||||||
|
fps: 0.0,
|
||||||
|
handle: h,
|
||||||
|
repeat: RepeatMode::Once,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
syntax::Texture::Frames(t) => {
|
||||||
|
let mut dim = None;
|
||||||
|
for f in &t.frames {
|
||||||
|
let file = ct.texture_root.join(f);
|
||||||
|
let reader = Reader::open(&file).with_context(|| {
|
||||||
|
format!(
|
||||||
|
"Failed to read texture `{}` from file `{}`",
|
||||||
|
texture_name,
|
||||||
|
file.display()
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let d = reader.into_dimensions().with_context(|| {
|
||||||
|
format!(
|
||||||
|
"Failed to get dimensions of texture `{}` from file `{}`",
|
||||||
|
texture_name,
|
||||||
|
file.display()
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
match dim {
|
||||||
|
None => dim = Some(d),
|
||||||
|
Some(e) => {
|
||||||
|
if d != e {
|
||||||
|
bail!(
|
||||||
|
"Failed to load frames of texture `{}`. Frames have different sizes `{}`",
|
||||||
|
texture_name,
|
||||||
|
file.display()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let h = TextureHandle {
|
||||||
|
index: ct.textures.len(),
|
||||||
|
aspect: dim.unwrap().0 as f32 / dim.unwrap().1 as f32,
|
||||||
|
};
|
||||||
|
|
||||||
|
if texture_name == ct.starfield_texture_name {
|
||||||
|
unreachable!("Starfield texture may not be animated")
|
||||||
|
}
|
||||||
|
|
||||||
|
let fps = t.duration / t.frames.len() as f32;
|
||||||
|
|
||||||
|
ct.texture_index.insert(texture_name.clone(), h);
|
||||||
|
ct.textures.push(Self {
|
||||||
|
name: texture_name,
|
||||||
|
frames: t
|
||||||
|
.frames
|
||||||
|
.into_iter()
|
||||||
|
.map(|f| ct.texture_root.join(f))
|
||||||
|
.collect(),
|
||||||
|
fps,
|
||||||
|
handle: h,
|
||||||
|
repeat: t.repeat,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ct.texture_index.insert(texture_name.clone(), h);
|
|
||||||
ct.textures.push(Self {
|
|
||||||
name: texture_name,
|
|
||||||
path,
|
|
||||||
handle: h,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ct.starfield_handle.is_none() {
|
if ct.starfield_handle.is_none() {
|
||||||
|
|
|
@ -10,29 +10,26 @@ use galactica_behavior::{behavior, ShipBehavior};
|
||||||
use galactica_constants;
|
use galactica_constants;
|
||||||
use galactica_content as content;
|
use galactica_content as content;
|
||||||
use galactica_gameobject as object;
|
use galactica_gameobject as object;
|
||||||
use galactica_render::{ObjectSprite, ParticleBuilder, UiSprite};
|
use galactica_render::{FrameState, ObjectSprite, ParticleBuilder, UiSprite};
|
||||||
use galactica_ui as ui;
|
use galactica_ui as ui;
|
||||||
use galactica_world::{util, ShipPhysicsHandle, World};
|
use galactica_world::{util, ShipPhysicsHandle, World};
|
||||||
|
|
||||||
pub struct Game {
|
pub struct Game {
|
||||||
pub input: InputStatus,
|
input: InputStatus,
|
||||||
pub last_update: Instant,
|
last_update: Instant,
|
||||||
pub player: ShipPhysicsHandle,
|
player: ShipPhysicsHandle,
|
||||||
paused: bool,
|
paused: bool,
|
||||||
pub time_scale: f32,
|
time_scale: f32,
|
||||||
|
start_instant: Instant,
|
||||||
|
camera: Camera,
|
||||||
|
|
||||||
|
system: object::System,
|
||||||
shipbehaviors: Vec<Box<dyn ShipBehavior>>,
|
shipbehaviors: Vec<Box<dyn ShipBehavior>>,
|
||||||
playerbehavior: behavior::Player,
|
playerbehavior: behavior::Player,
|
||||||
|
|
||||||
content: content::Content,
|
content: content::Content,
|
||||||
|
|
||||||
pub system: object::System,
|
|
||||||
pub camera: Camera,
|
|
||||||
world: World,
|
world: World,
|
||||||
pub start_instant: Instant,
|
new_particles: Vec<ParticleBuilder>,
|
||||||
|
|
||||||
// TODO: clean this up
|
|
||||||
pub new_particles: Vec<ParticleBuilder>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Game {
|
impl Game {
|
||||||
|
@ -112,6 +109,10 @@ impl Game {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_camera_aspect(&mut self, v: f32) {
|
||||||
|
self.camera.aspect = v
|
||||||
|
}
|
||||||
|
|
||||||
pub fn process_key(&mut self, state: &ElementState, key: &VirtualKeyCode) {
|
pub fn process_key(&mut self, state: &ElementState, key: &VirtualKeyCode) {
|
||||||
self.input.process_key(state, key)
|
self.input.process_key(state, key)
|
||||||
}
|
}
|
||||||
|
@ -173,6 +174,17 @@ impl Game {
|
||||||
self.last_update = Instant::now();
|
self.last_update = Instant::now();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_frame_state(&mut self) -> FrameState {
|
||||||
|
FrameState {
|
||||||
|
camera_pos: self.camera.pos,
|
||||||
|
camera_zoom: self.camera.zoom,
|
||||||
|
object_sprites: self.get_object_sprites(),
|
||||||
|
ui_sprites: self.get_ui_sprites(),
|
||||||
|
new_particles: &mut self.new_particles,
|
||||||
|
current_time: self.start_instant.elapsed().as_secs_f32(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_object_sprites(&self) -> Vec<ObjectSprite> {
|
pub fn get_object_sprites(&self) -> Vec<ObjectSprite> {
|
||||||
let mut sprites: Vec<ObjectSprite> = Vec::new();
|
let mut sprites: Vec<ObjectSprite> = Vec::new();
|
||||||
|
|
||||||
|
@ -195,7 +207,7 @@ impl Game {
|
||||||
pub fn get_ui_sprites(&self) -> Vec<UiSprite> {
|
pub fn get_ui_sprites(&self) -> Vec<UiSprite> {
|
||||||
return ui::build_radar(
|
return ui::build_radar(
|
||||||
&self.content,
|
&self.content,
|
||||||
&self.player,
|
self.player,
|
||||||
&self.world,
|
&self.world,
|
||||||
&self.system,
|
&self.system,
|
||||||
self.camera.zoom,
|
self.camera.zoom,
|
||||||
|
|
|
@ -27,22 +27,12 @@ fn main() -> Result<()> {
|
||||||
|
|
||||||
let mut game = game::Game::new(content);
|
let mut game = game::Game::new(content);
|
||||||
gpu.update_starfield_buffer();
|
gpu.update_starfield_buffer();
|
||||||
game.camera.aspect = gpu.window_size.width as f32 / gpu.window_size.height as f32;
|
game.set_camera_aspect(gpu.window_size.width as f32 / gpu.window_size.height as f32);
|
||||||
|
|
||||||
event_loop.run(move |event, _, control_flow| {
|
event_loop.run(move |event, _, control_flow| {
|
||||||
match event {
|
match event {
|
||||||
Event::RedrawRequested(window_id) if window_id == gpu.window().id() => {
|
Event::RedrawRequested(window_id) if window_id == gpu.window().id() => {
|
||||||
match gpu.render(
|
match gpu.render(game.get_frame_state()) {
|
||||||
game.camera.pos,
|
|
||||||
game.camera.zoom,
|
|
||||||
&game.get_object_sprites(),
|
|
||||||
&game.get_ui_sprites(),
|
|
||||||
// TODO: clean this up, single game data struct
|
|
||||||
// Game in another crate?
|
|
||||||
// Shipbehavior needs game state too...
|
|
||||||
&mut game.new_particles,
|
|
||||||
game.start_instant,
|
|
||||||
) {
|
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(wgpu::SurfaceError::Lost) => gpu.resize(),
|
Err(wgpu::SurfaceError::Lost) => gpu.resize(),
|
||||||
Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit,
|
Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit,
|
||||||
|
@ -81,13 +71,15 @@ fn main() -> Result<()> {
|
||||||
}
|
}
|
||||||
WindowEvent::Resized(_) => {
|
WindowEvent::Resized(_) => {
|
||||||
gpu.resize();
|
gpu.resize();
|
||||||
game.camera.aspect =
|
game.set_camera_aspect(
|
||||||
gpu.window_size.width as f32 / gpu.window_size.height as f32;
|
gpu.window_size.width as f32 / gpu.window_size.height as f32,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
WindowEvent::ScaleFactorChanged { .. } => {
|
WindowEvent::ScaleFactorChanged { .. } => {
|
||||||
gpu.resize();
|
gpu.resize();
|
||||||
game.camera.aspect =
|
game.set_camera_aspect(
|
||||||
gpu.window_size.width as f32 / gpu.window_size.height as f32;
|
gpu.window_size.width as f32 / gpu.window_size.height as f32,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
|
|
|
@ -72,8 +72,7 @@ impl OutfitStatSum {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct OutfitSet {
|
pub struct OutfitSet {
|
||||||
/// The total sum of the stats this set of outfits provides
|
/// The total sum of the stats this set of outfits provides
|
||||||
/// TODO: this shouldn't be pub
|
stats: OutfitStatSum,
|
||||||
pub stats: OutfitStatSum,
|
|
||||||
|
|
||||||
//pub total_space: content::OutfitSpace,
|
//pub total_space: content::OutfitSpace,
|
||||||
available_space: content::OutfitSpace,
|
available_space: content::OutfitSpace,
|
||||||
|
@ -110,6 +109,10 @@ impl<'a> OutfitSet {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn stat_sum(&self) -> &OutfitStatSum {
|
||||||
|
&self.stats
|
||||||
|
}
|
||||||
|
|
||||||
/// Add an outfit to this ship.
|
/// Add an outfit to this ship.
|
||||||
/// Returns true on success, and false on failure
|
/// Returns true on success, and false on failure
|
||||||
/// TODO: failure reason enum
|
/// TODO: failure reason enum
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
struct InstanceInput {
|
struct InstanceInput {
|
||||||
@location(2) position: vec3<f32>,
|
@location(2) position: vec2<f32>,
|
||||||
@location(3) size: f32,
|
@location(3) velocity: vec2<f32>,
|
||||||
@location(4) expires: f32,
|
@location(4) rotation_0: vec2<f32>,
|
||||||
@location(5) texture_index: u32,
|
@location(5) rotation_1: vec2<f32>,
|
||||||
|
@location(6) size: f32,
|
||||||
|
@location(7) created: f32,
|
||||||
|
@location(8) expires: f32,
|
||||||
|
@location(9) texture_index_len_rep: vec3<u32>,
|
||||||
|
@location(10) texture_aspect_fps: vec2<f32>,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct VertexInput {
|
struct VertexInput {
|
||||||
|
@ -38,7 +43,9 @@ var texture_array: binding_array<texture_2d<f32>>;
|
||||||
var sampler_array: binding_array<sampler>;
|
var sampler_array: binding_array<sampler>;
|
||||||
|
|
||||||
|
|
||||||
|
fn fmod(x: f32, m: f32) -> f32 {
|
||||||
|
return x - floor(x / m) * m;
|
||||||
|
}
|
||||||
|
|
||||||
@vertex
|
@vertex
|
||||||
fn vertex_main(
|
fn vertex_main(
|
||||||
|
@ -48,28 +55,55 @@ fn vertex_main(
|
||||||
|
|
||||||
var out: VertexOutput;
|
var out: VertexOutput;
|
||||||
out.texture_coords = vertex.texture_coords;
|
out.texture_coords = vertex.texture_coords;
|
||||||
out.texture_index = instance.texture_index;
|
|
||||||
|
|
||||||
if instance.expires < global.current_time.x {
|
if instance.expires < global.current_time.x {
|
||||||
|
out.texture_index = instance.texture_index_len_rep.x;
|
||||||
out.position = vec4<f32>(2.0, 2.0, 0.0, 1.0);
|
out.position = vec4<f32>(2.0, 2.0, 0.0, 1.0);
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let age = global.current_time.x - instance.created;
|
||||||
|
|
||||||
|
var frame: u32 = u32(0);
|
||||||
|
if instance.texture_index_len_rep.z == u32(1) {
|
||||||
|
// Repeat
|
||||||
|
frame = u32(fmod(
|
||||||
|
(age / instance.texture_aspect_fps.y),
|
||||||
|
f32(instance.texture_index_len_rep.y)
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
// Once
|
||||||
|
frame = u32(min(
|
||||||
|
(age / instance.texture_aspect_fps.y),
|
||||||
|
f32(instance.texture_index_len_rep.y) - 1.0
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
out.texture_index = instance.texture_index_len_rep.x + frame;
|
||||||
|
let rotation = mat2x2(instance.rotation_0, instance.rotation_1);
|
||||||
|
|
||||||
var scale: f32 = instance.size / global.camera_zoom.x;
|
var scale: f32 = instance.size / global.camera_zoom.x;
|
||||||
var pos: vec2<f32> = vec2(vertex.position.x, vertex.position.y);
|
var pos: vec2<f32> = vec2(vertex.position.x, vertex.position.y);
|
||||||
|
|
||||||
pos = pos * vec2<f32>(
|
pos = pos * vec2<f32>(
|
||||||
1.0 * scale / global.window_aspect.x,
|
instance.texture_aspect_fps.x * scale / global.window_aspect.x,
|
||||||
scale
|
scale
|
||||||
);
|
);
|
||||||
|
pos = rotation * pos;
|
||||||
|
|
||||||
|
var ipos: vec2<f32> = (
|
||||||
|
vec2(instance.position.x, instance.position.y)
|
||||||
|
+ (instance.velocity * age)
|
||||||
|
- global.camera_position
|
||||||
|
);
|
||||||
|
|
||||||
var ipos: vec2<f32> = vec2(instance.position.x, instance.position.y) - global.camera_position;
|
|
||||||
pos = pos + vec2<f32>(
|
pos = pos + vec2<f32>(
|
||||||
ipos.x / (global.camera_zoom.x/2.0) / global.window_aspect.x,
|
ipos.x / (global.camera_zoom.x/2.0) / global.window_aspect.x,
|
||||||
ipos.y / (global.camera_zoom.x/2.0)
|
ipos.y / (global.camera_zoom.x/2.0)
|
||||||
);
|
);
|
||||||
|
|
||||||
out.position = vec4<f32>(pos, 0.0, 1.0) * instance.position.z;
|
out.position = vec4<f32>(pos, 0.0, 1.0);
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
use cgmath::Point2;
|
||||||
|
|
||||||
|
use crate::{ObjectSprite, ParticleBuilder, UiSprite};
|
||||||
|
|
||||||
|
/// Bundles parameters passed to a single call to GPUState::render
|
||||||
|
pub struct FrameState<'a> {
|
||||||
|
/// Camera position, in world units
|
||||||
|
pub camera_pos: Point2<f32>,
|
||||||
|
|
||||||
|
/// Height of screen, in world units
|
||||||
|
pub camera_zoom: f32,
|
||||||
|
|
||||||
|
/// World object sprites
|
||||||
|
pub object_sprites: Vec<ObjectSprite>,
|
||||||
|
|
||||||
|
/// UI sprites
|
||||||
|
pub ui_sprites: Vec<UiSprite>,
|
||||||
|
|
||||||
|
/// Particles to create during this frame.
|
||||||
|
/// this array will be cleared.
|
||||||
|
pub new_particles: &'a mut Vec<ParticleBuilder>,
|
||||||
|
|
||||||
|
// TODO: handle overflow
|
||||||
|
/// The current time, in seconds
|
||||||
|
pub current_time: f32,
|
||||||
|
}
|
|
@ -1,8 +1,8 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use bytemuck;
|
use bytemuck;
|
||||||
use cgmath::{Deg, EuclideanSpace, Matrix4, Point2, Vector3};
|
use cgmath::{Deg, EuclideanSpace, Matrix2, Matrix4, Point2, Vector3};
|
||||||
use galactica_constants;
|
use galactica_constants;
|
||||||
use std::{iter, rc::Rc, time::Instant};
|
use std::{iter, rc::Rc};
|
||||||
use wgpu;
|
use wgpu;
|
||||||
use winit::{self, dpi::LogicalSize, window::Window};
|
use winit::{self, dpi::LogicalSize, window::Window};
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ use crate::{
|
||||||
content,
|
content,
|
||||||
globaldata::{GlobalData, GlobalDataContent},
|
globaldata::{GlobalData, GlobalDataContent},
|
||||||
pipeline::PipelineBuilder,
|
pipeline::PipelineBuilder,
|
||||||
sprite::{ObjectSubSprite, ParticleBuilder},
|
sprite::ObjectSubSprite,
|
||||||
starfield::Starfield,
|
starfield::Starfield,
|
||||||
texturearray::TextureArray,
|
texturearray::TextureArray,
|
||||||
vertexbuffer::{
|
vertexbuffer::{
|
||||||
|
@ -18,7 +18,7 @@ use crate::{
|
||||||
types::{ObjectInstance, ParticleInstance, StarfieldInstance, TexturedVertex, UiInstance},
|
types::{ObjectInstance, ParticleInstance, StarfieldInstance, TexturedVertex, UiInstance},
|
||||||
BufferObject, VertexBuffer,
|
BufferObject, VertexBuffer,
|
||||||
},
|
},
|
||||||
ObjectSprite, UiSprite, OPENGL_TO_WGPU_MATRIX,
|
FrameState, ObjectSprite, UiSprite, OPENGL_TO_WGPU_MATRIX,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A high-level GPU wrapper. Consumes game state,
|
/// A high-level GPU wrapper. Consumes game state,
|
||||||
|
@ -456,28 +456,22 @@ impl GPUState {
|
||||||
/// Make an instance for all the game's sprites
|
/// Make an instance for all the game's sprites
|
||||||
/// (Objects and UI)
|
/// (Objects and UI)
|
||||||
/// This will Will panic if any X_SPRITE_INSTANCE_LIMIT is exceeded.
|
/// This will Will panic if any X_SPRITE_INSTANCE_LIMIT is exceeded.
|
||||||
fn update_sprite_instances(
|
fn update_sprite_instances(&self, framestate: FrameState) -> (usize, usize) {
|
||||||
&self,
|
|
||||||
camera_zoom: f32,
|
|
||||||
camera_pos: Point2<f32>,
|
|
||||||
objects: &Vec<ObjectSprite>,
|
|
||||||
ui: &Vec<UiSprite>,
|
|
||||||
) -> (usize, usize) {
|
|
||||||
let mut object_instances: Vec<ObjectInstance> = Vec::new();
|
let mut object_instances: Vec<ObjectInstance> = Vec::new();
|
||||||
|
|
||||||
// Game coordinates (relative to camera) of ne and sw corners of screen.
|
// Game coordinates (relative to camera) of ne and sw corners of screen.
|
||||||
// Used to skip off-screen sprites.
|
// Used to skip off-screen sprites.
|
||||||
let clip_ne = Point2::from((-self.window_aspect, 1.0)) * camera_zoom;
|
let clip_ne = Point2::from((-self.window_aspect, 1.0)) * framestate.camera_zoom;
|
||||||
let clip_sw = Point2::from((self.window_aspect, -1.0)) * camera_zoom;
|
let clip_sw = Point2::from((self.window_aspect, -1.0)) * framestate.camera_zoom;
|
||||||
|
|
||||||
for s in objects {
|
for s in framestate.object_sprites {
|
||||||
self.push_object_sprite(
|
self.push_object_sprite(
|
||||||
camera_zoom,
|
framestate.camera_zoom,
|
||||||
camera_pos,
|
framestate.camera_pos,
|
||||||
&mut object_instances,
|
&mut object_instances,
|
||||||
clip_ne,
|
clip_ne,
|
||||||
clip_sw,
|
clip_sw,
|
||||||
s,
|
&s,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -495,8 +489,8 @@ impl GPUState {
|
||||||
|
|
||||||
let mut ui_instances: Vec<UiInstance> = Vec::new();
|
let mut ui_instances: Vec<UiInstance> = Vec::new();
|
||||||
|
|
||||||
for s in ui {
|
for s in framestate.ui_sprites {
|
||||||
self.push_ui_sprite(&mut ui_instances, s);
|
self.push_ui_sprite(&mut ui_instances, &s);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ui_instances.len() as u64 > galactica_constants::UI_SPRITE_INSTANCE_LIMIT {
|
if ui_instances.len() as u64 > galactica_constants::UI_SPRITE_INSTANCE_LIMIT {
|
||||||
|
@ -525,17 +519,7 @@ impl GPUState {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Main render function. Draws sprites on a window.
|
/// Main render function. Draws sprites on a window.
|
||||||
pub fn render(
|
pub fn render(&mut self, framestate: FrameState) -> Result<(), wgpu::SurfaceError> {
|
||||||
&mut self,
|
|
||||||
camera_pos: Point2<f32>,
|
|
||||||
camera_zoom: f32,
|
|
||||||
|
|
||||||
// TODO: clean this up, pass one struct
|
|
||||||
object_sprites: &Vec<ObjectSprite>,
|
|
||||||
ui_sprites: &Vec<UiSprite>,
|
|
||||||
new_particles: &mut Vec<ParticleBuilder>,
|
|
||||||
start_instant: Instant,
|
|
||||||
) -> Result<(), wgpu::SurfaceError> {
|
|
||||||
let output = self.surface.get_current_texture()?;
|
let output = self.surface.get_current_texture()?;
|
||||||
let view = output
|
let view = output
|
||||||
.texture
|
.texture
|
||||||
|
@ -568,16 +552,13 @@ impl GPUState {
|
||||||
timestamp_writes: None,
|
timestamp_writes: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: handle overflow
|
|
||||||
let time_now = start_instant.elapsed().as_secs_f32();
|
|
||||||
|
|
||||||
// Update global values
|
// Update global values
|
||||||
self.queue.write_buffer(
|
self.queue.write_buffer(
|
||||||
&self.global_data.buffer,
|
&self.global_data.buffer,
|
||||||
0,
|
0,
|
||||||
bytemuck::cast_slice(&[GlobalDataContent {
|
bytemuck::cast_slice(&[GlobalDataContent {
|
||||||
camera_position: camera_pos.into(),
|
camera_position: framestate.camera_pos.into(),
|
||||||
camera_zoom: [camera_zoom, 0.0],
|
camera_zoom: [framestate.camera_zoom, 0.0],
|
||||||
camera_zoom_limits: [galactica_constants::ZOOM_MIN, galactica_constants::ZOOM_MAX],
|
camera_zoom_limits: [galactica_constants::ZOOM_MIN, galactica_constants::ZOOM_MAX],
|
||||||
window_size: [
|
window_size: [
|
||||||
self.window_size.width as f32,
|
self.window_size.width as f32,
|
||||||
|
@ -590,20 +571,25 @@ impl GPUState {
|
||||||
galactica_constants::STARFIELD_SIZE_MIN,
|
galactica_constants::STARFIELD_SIZE_MIN,
|
||||||
galactica_constants::STARFIELD_SIZE_MAX,
|
galactica_constants::STARFIELD_SIZE_MAX,
|
||||||
],
|
],
|
||||||
current_time: [time_now, 0.0],
|
current_time: [framestate.current_time, 0.0],
|
||||||
}]),
|
}]),
|
||||||
);
|
);
|
||||||
|
|
||||||
for i in new_particles.iter() {
|
// Write all new particles to GPU buffer
|
||||||
|
for i in framestate.new_particles.iter() {
|
||||||
let texture = self.texture_array.get_texture(i.texture);
|
let texture = self.texture_array.get_texture(i.texture);
|
||||||
self.queue.write_buffer(
|
self.queue.write_buffer(
|
||||||
&self.vertex_buffers.particle.instances,
|
&self.vertex_buffers.particle.instances,
|
||||||
ParticleInstance::SIZE * self.vertex_buffers.particle_array_head,
|
ParticleInstance::SIZE * self.vertex_buffers.particle_array_head,
|
||||||
bytemuck::cast_slice(&[ParticleInstance {
|
bytemuck::cast_slice(&[ParticleInstance {
|
||||||
position: [i.pos.x, i.pos.y, 1.0],
|
position: [i.pos.x, i.pos.y],
|
||||||
|
velocity: i.velocity.into(),
|
||||||
|
rotation: Matrix2::from_angle(i.angle).into(),
|
||||||
size: i.size,
|
size: i.size,
|
||||||
texture_index: texture.index,
|
texture_index_len_rep: [texture.index, texture.len, texture.repeat],
|
||||||
expires: time_now + i.lifetime,
|
texture_aspect_fps: [texture.aspect, texture.fps],
|
||||||
|
created: framestate.current_time,
|
||||||
|
expires: framestate.current_time + i.lifetime,
|
||||||
}]),
|
}]),
|
||||||
);
|
);
|
||||||
self.vertex_buffers.particle_array_head += 1;
|
self.vertex_buffers.particle_array_head += 1;
|
||||||
|
@ -613,11 +599,10 @@ impl GPUState {
|
||||||
self.vertex_buffers.particle_array_head = 0;
|
self.vertex_buffers.particle_array_head = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
new_particles.clear();
|
framestate.new_particles.clear();
|
||||||
|
|
||||||
// Create sprite instances
|
// Create sprite instances
|
||||||
let (n_object, n_ui) =
|
let (n_object, n_ui) = self.update_sprite_instances(framestate);
|
||||||
self.update_sprite_instances(camera_zoom, camera_pos, object_sprites, ui_sprites);
|
|
||||||
|
|
||||||
// These should match the indices in each shader,
|
// These should match the indices in each shader,
|
||||||
// and should each have a corresponding bind group layout.
|
// and should each have a corresponding bind group layout.
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
//! and the only one external code should interact with.
|
//! and the only one external code should interact with.
|
||||||
//! (Excluding data structs, like [`ObjectSprite`])
|
//! (Excluding data structs, like [`ObjectSprite`])
|
||||||
|
|
||||||
|
mod framestate;
|
||||||
mod globaldata;
|
mod globaldata;
|
||||||
mod gpustate;
|
mod gpustate;
|
||||||
mod pipeline;
|
mod pipeline;
|
||||||
|
@ -15,6 +16,7 @@ mod starfield;
|
||||||
mod texturearray;
|
mod texturearray;
|
||||||
mod vertexbuffer;
|
mod vertexbuffer;
|
||||||
|
|
||||||
|
pub use framestate::FrameState;
|
||||||
use galactica_content as content;
|
use galactica_content as content;
|
||||||
pub use gpustate::GPUState;
|
pub use gpustate::GPUState;
|
||||||
pub use sprite::{AnchoredUiPosition, ObjectSprite, ObjectSubSprite, ParticleBuilder, UiSprite};
|
pub use sprite::{AnchoredUiPosition, ObjectSprite, ObjectSubSprite, ParticleBuilder, UiSprite};
|
||||||
|
|
|
@ -1,14 +1,19 @@
|
||||||
use crate::content;
|
use crate::content;
|
||||||
use cgmath::{Deg, Point2, Point3};
|
use cgmath::{Deg, Point2, Point3, Vector2};
|
||||||
|
|
||||||
/// Instructions to create a new particle
|
/// Instructions to create a new particle
|
||||||
pub struct ParticleBuilder {
|
pub struct ParticleBuilder {
|
||||||
/// The texture to use for this particle
|
/// The texture to use for this particle
|
||||||
pub texture: content::TextureHandle,
|
pub texture: content::TextureHandle,
|
||||||
|
|
||||||
// TODO: rotation, velocity
|
|
||||||
/// This object's center, in world coordinates.
|
/// This object's center, in world coordinates.
|
||||||
pub pos: Point3<f32>,
|
pub pos: Point2<f32>,
|
||||||
|
|
||||||
|
/// This particle's velocity, in world coordinates
|
||||||
|
pub velocity: Vector2<f32>,
|
||||||
|
|
||||||
|
/// This particle's angle, in world coordinates
|
||||||
|
pub angle: Deg<f32>,
|
||||||
|
|
||||||
/// This particle's lifetime, in seconds
|
/// This particle's lifetime, in seconds
|
||||||
pub lifetime: f32,
|
pub lifetime: f32,
|
||||||
|
|
|
@ -70,7 +70,10 @@ impl RawTexture {
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct Texture {
|
pub struct Texture {
|
||||||
pub index: u32, // Index in texture array
|
pub index: u32, // Index in texture array
|
||||||
|
pub len: u32, // Number of frames
|
||||||
|
pub fps: f32, // Frames per second
|
||||||
pub aspect: f32, // width / height
|
pub aspect: f32, // width / height
|
||||||
|
pub repeat: u32, // How to re-play this texture
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TextureArray {
|
pub struct TextureArray {
|
||||||
|
@ -98,18 +101,23 @@ impl TextureArray {
|
||||||
let mut textures = HashMap::new();
|
let mut textures = HashMap::new();
|
||||||
|
|
||||||
for t in &ct.textures {
|
for t in &ct.textures {
|
||||||
let mut f = File::open(&t.path)?;
|
let index = texture_data.len() as u32;
|
||||||
let mut bytes = Vec::new();
|
for f in &t.frames {
|
||||||
f.read_to_end(&mut bytes)?;
|
let mut f = File::open(&f)?;
|
||||||
|
let mut bytes = Vec::new();
|
||||||
|
f.read_to_end(&mut bytes)?;
|
||||||
|
texture_data.push(RawTexture::from_bytes(&device, &queue, &bytes, &t.name)?);
|
||||||
|
}
|
||||||
textures.insert(
|
textures.insert(
|
||||||
t.handle,
|
t.handle,
|
||||||
Texture {
|
Texture {
|
||||||
index: texture_data.len() as u32,
|
index,
|
||||||
aspect: t.handle.aspect,
|
aspect: t.handle.aspect,
|
||||||
|
fps: t.fps,
|
||||||
|
len: t.frames.len() as u32,
|
||||||
|
repeat: t.repeat.as_int(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
texture_data.push(RawTexture::from_bytes(&device, &queue, &bytes, &t.name)?);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||||
|
|
|
@ -200,22 +200,32 @@ impl BufferObject for UiInstance {
|
||||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||||
pub struct ParticleInstance {
|
pub struct ParticleInstance {
|
||||||
/// World position of this particle
|
/// World position of this particle
|
||||||
pub position: [f32; 3],
|
pub position: [f32; 2],
|
||||||
|
|
||||||
|
/// Velocity of this sprite, in world coordinates
|
||||||
|
pub velocity: [f32; 2],
|
||||||
|
|
||||||
|
/// Rotation matrix for this sprite, in world coordinates
|
||||||
|
pub rotation: [[f32; 2]; 2],
|
||||||
|
|
||||||
// TODO: docs: particle frames must all have the same size
|
// TODO: docs: particle frames must all have the same size
|
||||||
// TODO: is transparency trimmed? That's not ideal for animated particles!
|
// TODO: is transparency trimmed? That's not ideal for animated particles!
|
||||||
/// The height of this particle, in world units
|
/// The height of this particle, in world units
|
||||||
pub size: f32,
|
pub size: f32,
|
||||||
|
|
||||||
// TODO: rotation, velocity vector
|
|
||||||
// TODO: animated sprites
|
// TODO: animated sprites
|
||||||
// TODO: texture aspect ratio
|
// TODO: texture aspect ratio
|
||||||
|
/// The time, in seconds, at which this particle was created.
|
||||||
|
/// Time is kept by a variable in the global uniform.
|
||||||
|
pub created: f32,
|
||||||
|
|
||||||
/// The time, in seconds, at which this particle expires.
|
/// The time, in seconds, at which this particle expires.
|
||||||
/// Time is kept by a variable in the global uniform.
|
/// Time is kept by a variable in the global uniform.
|
||||||
pub expires: f32,
|
pub expires: f32,
|
||||||
|
|
||||||
/// What texture to use for this particle
|
/// What texture to use for this particle
|
||||||
pub texture_index: u32,
|
pub texture_index_len_rep: [u32; 3],
|
||||||
|
pub texture_aspect_fps: [f32; 2],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BufferObject for ParticleInstance {
|
impl BufferObject for ParticleInstance {
|
||||||
|
@ -228,25 +238,54 @@ impl BufferObject for ParticleInstance {
|
||||||
wgpu::VertexAttribute {
|
wgpu::VertexAttribute {
|
||||||
offset: 0,
|
offset: 0,
|
||||||
shader_location: 2,
|
shader_location: 2,
|
||||||
format: wgpu::VertexFormat::Float32x3,
|
format: wgpu::VertexFormat::Float32x2,
|
||||||
|
},
|
||||||
|
// Velocity
|
||||||
|
wgpu::VertexAttribute {
|
||||||
|
offset: mem::size_of::<[f32; 2]>() as wgpu::BufferAddress,
|
||||||
|
shader_location: 3,
|
||||||
|
format: wgpu::VertexFormat::Float32x2,
|
||||||
|
},
|
||||||
|
// Rotation
|
||||||
|
wgpu::VertexAttribute {
|
||||||
|
offset: mem::size_of::<[f32; 4]>() as wgpu::BufferAddress,
|
||||||
|
shader_location: 4,
|
||||||
|
format: wgpu::VertexFormat::Float32x2,
|
||||||
|
},
|
||||||
|
wgpu::VertexAttribute {
|
||||||
|
offset: mem::size_of::<[f32; 6]>() as wgpu::BufferAddress,
|
||||||
|
shader_location: 5,
|
||||||
|
format: wgpu::VertexFormat::Float32x2,
|
||||||
},
|
},
|
||||||
// Size
|
// Size
|
||||||
wgpu::VertexAttribute {
|
wgpu::VertexAttribute {
|
||||||
offset: mem::size_of::<[f32; 3]>() as wgpu::BufferAddress,
|
offset: mem::size_of::<[f32; 8]>() as wgpu::BufferAddress,
|
||||||
shader_location: 3,
|
shader_location: 6,
|
||||||
|
format: wgpu::VertexFormat::Float32,
|
||||||
|
},
|
||||||
|
// Created
|
||||||
|
wgpu::VertexAttribute {
|
||||||
|
offset: mem::size_of::<[f32; 9]>() as wgpu::BufferAddress,
|
||||||
|
shader_location: 7,
|
||||||
format: wgpu::VertexFormat::Float32,
|
format: wgpu::VertexFormat::Float32,
|
||||||
},
|
},
|
||||||
// Expires
|
// Expires
|
||||||
wgpu::VertexAttribute {
|
wgpu::VertexAttribute {
|
||||||
offset: mem::size_of::<[f32; 4]>() as wgpu::BufferAddress,
|
offset: mem::size_of::<[f32; 10]>() as wgpu::BufferAddress,
|
||||||
shader_location: 4,
|
shader_location: 8,
|
||||||
format: wgpu::VertexFormat::Float32,
|
format: wgpu::VertexFormat::Float32,
|
||||||
},
|
},
|
||||||
// Texture
|
// Texture index / len / repeat
|
||||||
wgpu::VertexAttribute {
|
wgpu::VertexAttribute {
|
||||||
offset: mem::size_of::<[f32; 5]>() as wgpu::BufferAddress,
|
offset: mem::size_of::<[f32; 11]>() as wgpu::BufferAddress,
|
||||||
shader_location: 5,
|
shader_location: 9,
|
||||||
format: wgpu::VertexFormat::Uint32,
|
format: wgpu::VertexFormat::Uint32x3,
|
||||||
|
},
|
||||||
|
// Texture aspect / fps
|
||||||
|
wgpu::VertexAttribute {
|
||||||
|
offset: mem::size_of::<[f32; 14]>() as wgpu::BufferAddress,
|
||||||
|
shader_location: 10,
|
||||||
|
format: wgpu::VertexFormat::Float32x2,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,10 +4,10 @@ use galactica_gameobject as object;
|
||||||
use galactica_render::{AnchoredUiPosition, UiSprite};
|
use galactica_render::{AnchoredUiPosition, UiSprite};
|
||||||
use galactica_world::{util, ShipPhysicsHandle, World};
|
use galactica_world::{util, ShipPhysicsHandle, World};
|
||||||
|
|
||||||
// TODO: camera as one unit
|
// TODO: args as one unit
|
||||||
pub fn build_radar(
|
pub fn build_radar(
|
||||||
ct: &content::Content,
|
ct: &content::Content,
|
||||||
player: &ShipPhysicsHandle,
|
player: ShipPhysicsHandle,
|
||||||
physics: &World,
|
physics: &World,
|
||||||
system: &object::System,
|
system: &object::System,
|
||||||
camera_zoom: f32,
|
camera_zoom: f32,
|
||||||
|
|
|
@ -27,7 +27,6 @@ impl ShipControls {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A ship instance in the physics system
|
/// A ship instance in the physics system
|
||||||
/// TODO: Decouple ship data from physics
|
|
||||||
pub struct ShipWorldObject {
|
pub struct ShipWorldObject {
|
||||||
/// TODO
|
/// TODO
|
||||||
pub physics_handle: ShipPhysicsHandle,
|
pub physics_handle: ShipPhysicsHandle,
|
||||||
|
@ -56,17 +55,18 @@ impl ShipWorldObject {
|
||||||
|
|
||||||
if self.controls.thrust {
|
if self.controls.thrust {
|
||||||
r.apply_impulse(
|
r.apply_impulse(
|
||||||
vector![engine_force.x, engine_force.y] * self.ship.outfits.stats.engine_thrust,
|
vector![engine_force.x, engine_force.y]
|
||||||
|
* self.ship.outfits.stat_sum().engine_thrust,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.controls.right {
|
if self.controls.right {
|
||||||
r.apply_torque_impulse(self.ship.outfits.stats.steer_power * -100.0 * t, true);
|
r.apply_torque_impulse(self.ship.outfits.stat_sum().steer_power * -100.0 * t, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.controls.left {
|
if self.controls.left {
|
||||||
r.apply_torque_impulse(self.ship.outfits.stats.steer_power * 100.0 * t, true);
|
r.apply_torque_impulse(self.ship.outfits.stat_sum().steer_power * 100.0 * t, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
for i in self.ship.outfits.iter_guns() {
|
for i in self.ship.outfits.iter_guns() {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use cgmath::{Deg, EuclideanSpace, InnerSpace, Matrix2, Point2, Point3, Rad, Vector2};
|
use cgmath::{Deg, EuclideanSpace, InnerSpace, Matrix2, Point2, Rad, Vector2};
|
||||||
use crossbeam::channel::Receiver;
|
use crossbeam::channel::Receiver;
|
||||||
use nalgebra::vector;
|
use nalgebra::vector;
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
|
@ -57,12 +57,12 @@ impl<'a> World {
|
||||||
/// Add a projectile fired from a ship
|
/// Add a projectile fired from a ship
|
||||||
fn add_projectiles(
|
fn add_projectiles(
|
||||||
&mut self,
|
&mut self,
|
||||||
s: &ShipPhysicsHandle,
|
s: ShipPhysicsHandle,
|
||||||
p: Vec<(object::Projectile, content::GunPoint)>,
|
p: Vec<(object::Projectile, content::GunPoint)>,
|
||||||
) {
|
) {
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
for (projectile, point) in p {
|
for (projectile, point) in p {
|
||||||
let r = self.get_ship_body(&s).unwrap().1;
|
let r = self.get_ship_body(s).unwrap().1;
|
||||||
|
|
||||||
let ship_pos = util::rigidbody_position(r);
|
let ship_pos = util::rigidbody_position(r);
|
||||||
let ship_rot = util::rigidbody_rotation(r);
|
let ship_rot = util::rigidbody_rotation(r);
|
||||||
|
@ -187,7 +187,7 @@ impl<'a> World {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (s, p) in projectiles {
|
for (s, p) in projectiles {
|
||||||
self.add_projectiles(&s, p);
|
self.add_projectiles(s, p);
|
||||||
}
|
}
|
||||||
for s in to_remove {
|
for s in to_remove {
|
||||||
self.remove_ship(s);
|
self.remove_ship(s);
|
||||||
|
@ -211,20 +211,40 @@ impl<'a> World {
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(p) = self.projectiles.get(a) {
|
if let Some(p) = self.projectiles.get(a) {
|
||||||
if let Some(s) = self.ships.get_mut(b) {
|
let hit = if let Some(s) = self.ships.get_mut(b) {
|
||||||
let hit = s.ship.handle_projectile_collision(ct, &p.projectile);
|
s.ship.handle_projectile_collision(ct, &p.projectile)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
// Stupid re-borrow trick.
|
||||||
|
// We can't have s be mutable inside this block.
|
||||||
|
if let Some(s) = self.ships.get(b) {
|
||||||
if hit {
|
if hit {
|
||||||
let r = self.get_rigid_body(p.rigid_body);
|
let pr = self.get_rigid_body(p.rigid_body);
|
||||||
let pos = util::rigidbody_position(r);
|
let pos = util::rigidbody_position(pr);
|
||||||
|
let angle: Deg<f32> = util::rigidbody_rotation(pr)
|
||||||
|
.angle(Vector2 { x: 1.0, y: 0.0 })
|
||||||
|
.into();
|
||||||
|
|
||||||
|
// Match target ship velocity, so impact particles are "sticky".
|
||||||
|
// Particles will fly off if the ship is spinning fast, but I
|
||||||
|
// haven't found a good way to fix that.
|
||||||
|
let (_, sr) = self.get_ship_body(s.physics_handle).unwrap();
|
||||||
|
let velocity =
|
||||||
|
sr.velocity_at_point(&nalgebra::Point2::new(pos.x, pos.y));
|
||||||
|
let velocity = Vector2 {
|
||||||
|
x: velocity.x,
|
||||||
|
y: velocity.y,
|
||||||
|
};
|
||||||
|
|
||||||
particles.push(ParticleBuilder {
|
particles.push(ParticleBuilder {
|
||||||
texture: ct.get_texture_handle("particle::blaster"),
|
texture: ct.get_texture_handle("particle::blaster"),
|
||||||
pos: Point3 {
|
pos: Point2 { x: pos.x, y: pos.y },
|
||||||
x: pos.x,
|
velocity,
|
||||||
y: pos.y,
|
angle: -angle,
|
||||||
z: 1.0, // TODO:remove z coordinate
|
lifetime: 0.15,
|
||||||
},
|
size: 5.0,
|
||||||
lifetime: 0.1,
|
|
||||||
size: 10.0,
|
|
||||||
});
|
});
|
||||||
self.remove_projectile(*a);
|
self.remove_projectile(*a);
|
||||||
}
|
}
|
||||||
|
@ -259,7 +279,7 @@ impl<'a> World {
|
||||||
/// Get a ship and its rigidbody from a handle
|
/// Get a ship and its rigidbody from a handle
|
||||||
pub fn get_ship_body(
|
pub fn get_ship_body(
|
||||||
&self,
|
&self,
|
||||||
s: &ShipPhysicsHandle,
|
s: ShipPhysicsHandle,
|
||||||
) -> Option<(&objects::ShipWorldObject, &RigidBody)> {
|
) -> Option<(&objects::ShipWorldObject, &RigidBody)> {
|
||||||
// TODO: handle dead handles
|
// TODO: handle dead handles
|
||||||
Some((self.ships.get(&s.1)?, self.wrapper.rigid_body_set.get(s.0)?))
|
Some((self.ships.get(&s.1)?, self.wrapper.rigid_body_set.get(s.0)?))
|
||||||
|
|
Loading…
Reference in New Issue