Compare commits

..

No commits in common. "ece82a8c1b0d86e3caede0f5314999d77718012e" and "389803eae9254d8c29b1cad8208fcceabb83c9e7" have entirely different histories.

14 changed files with 164 additions and 410 deletions

View File

@ -1,4 +0,0 @@
[engine."plasma"]
thrust = 50
flare.sprite = "flare::ion"

View File

@ -1,22 +1,5 @@
[gun."blaster"] [gun."blaster"]
# Angle of fire cone
# Smaller angle = more accurate
spread = 2
# Average delay between shots
rate = 0.2
# Random rate variation (+- this in both directions)
rate_rng = 0.1
projectile.sprite = "projectile::blaster" projectile.sprite = "projectile::blaster"
# Height of projectile in game units projectile.size = 100
projectile.size = 10
projectile.size_rng = 0.0
# Speed of projectile, in game units/second
projectile.speed = 300 projectile.speed = 300
projectile.speed_rng = 10.0
# Lifetime of projectile, in seconds
projectile.lifetime = 2.0 projectile.lifetime = 2.0
projectile.lifetime_rng = 0.2

View File

@ -3,4 +3,4 @@ sprite = "ship::gypsum"
size = 100 size = 100
engines = [{ x = 0.0, y = -105, size = 50.0 }] engines = [{ x = 0.0, y = -105, size = 50.0 }]
guns = [{ x = 0.0, y = 100 }, { x = 10.0, y = 80 }, { x = -10.0, y = 80 }] guns = [{ x = 0.0, y = 100 }]

View File

@ -1,46 +0,0 @@
use anyhow::Result;
pub(super) mod syntax {
use serde::Deserialize;
// Raw serde syntax structs.
// These are never seen by code outside this crate.
#[derive(Debug, Deserialize)]
pub struct Engine {
pub thrust: f32,
pub flare: Flare,
}
#[derive(Debug, Deserialize)]
pub struct Flare {
pub sprite: String,
}
}
#[derive(Debug, Clone)]
pub struct Engine {
pub name: String,
pub thrust: f32,
pub flare_sprite: String,
}
impl super::Build for Engine {
fn build(root: &super::syntax::Root) -> Result<Vec<Self>> {
let engine = if let Some(engine) = &root.engine {
engine
} else {
return Ok(vec![]);
};
let mut out = Vec::new();
for (engine_name, engine) in engine {
out.push(Self {
name: engine_name.to_owned(),
thrust: engine.thrust,
flare_sprite: engine.flare.sprite.clone(),
});
}
return Ok(out);
}
}

View File

@ -1,5 +1,4 @@
use anyhow::Result; use anyhow::Result;
use cgmath::Deg;
pub(super) mod syntax { pub(super) mod syntax {
use serde::Deserialize; use serde::Deserialize;
@ -9,20 +8,14 @@ pub(super) mod syntax {
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct Gun { pub struct Gun {
pub projectile: Projectile, pub projectile: Projectile,
pub spread: f32,
pub rate: f32,
pub rate_rng: f32,
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct Projectile { pub struct Projectile {
pub sprite: String, pub sprite: String,
pub size: f32, pub size: f32,
pub size_rng: f32,
pub speed: f32, pub speed: f32,
pub speed_rng: f32,
pub lifetime: f32, pub lifetime: f32,
pub lifetime_rng: f32,
} }
} }
@ -30,20 +23,14 @@ pub(super) mod syntax {
pub struct Gun { pub struct Gun {
pub name: String, pub name: String,
pub projectile: Projectile, pub projectile: Projectile,
pub spread: Deg<f32>,
pub rate: f32,
pub rate_rng: f32,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Projectile { pub struct Projectile {
pub sprite: String, pub sprite: String,
pub size: f32, pub size: f32,
pub size_rng: f32,
pub speed: f32, pub speed: f32,
pub speed_rng: f32,
pub lifetime: f32, pub lifetime: f32,
pub lifetime_rng: f32,
} }
impl super::Build for Gun { impl super::Build for Gun {
@ -58,17 +45,11 @@ impl super::Build for Gun {
for (gun_name, gun) in gun { for (gun_name, gun) in gun {
out.push(Self { out.push(Self {
name: gun_name.to_owned(), name: gun_name.to_owned(),
spread: Deg(gun.spread),
rate: gun.rate,
rate_rng: gun.rate_rng,
projectile: Projectile { projectile: Projectile {
sprite: gun.projectile.sprite.to_owned(), sprite: gun.projectile.sprite.to_owned(),
size: gun.projectile.size, size: gun.projectile.size,
size_rng: gun.projectile.size_rng,
speed: gun.projectile.speed, speed: gun.projectile.speed,
speed_rng: gun.projectile.speed_rng,
lifetime: gun.projectile.lifetime, lifetime: gun.projectile.lifetime,
lifetime_rng: gun.projectile.lifetime_rng,
}, },
}); });
} }

View File

@ -1,12 +1,10 @@
#![allow(dead_code)] #![allow(dead_code)]
mod engine;
mod gun; mod gun;
mod ship; mod ship;
mod system; mod system;
pub use engine::Engine;
pub use gun::{Gun, Projectile}; pub use gun::{Gun, Projectile};
pub use ship::{EnginePoint, GunPoint, Ship}; pub use ship::{Engine, Ship, ShipGun};
pub use system::{Object, System}; pub use system::{Object, System};
use anyhow::{bail, Context, Result}; use anyhow::{bail, Context, Result};
@ -17,7 +15,7 @@ use walkdir::WalkDir;
mod syntax { mod syntax {
use super::HashMap; use super::HashMap;
use super::{engine, gun, ship, system}; use super::{gun, ship, system};
use serde::Deserialize; use serde::Deserialize;
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
@ -25,7 +23,6 @@ mod syntax {
pub gun: Option<HashMap<String, gun::syntax::Gun>>, pub gun: Option<HashMap<String, gun::syntax::Gun>>,
pub ship: Option<HashMap<String, ship::syntax::Ship>>, pub ship: Option<HashMap<String, ship::syntax::Ship>>,
pub system: Option<HashMap<String, system::syntax::System>>, pub system: Option<HashMap<String, system::syntax::System>>,
pub engine: Option<HashMap<String, engine::syntax::Engine>>,
} }
} }
@ -36,13 +33,11 @@ trait Build {
Self: Sized; Self: Sized;
} }
/// Represents generic game content, not connected to any game objects.
#[derive(Debug)] #[derive(Debug)]
pub struct Content { pub struct Content {
pub systems: Vec<system::System>, pub systems: Vec<system::System>,
pub ships: Vec<ship::Ship>, pub ships: Vec<ship::Ship>,
pub guns: Vec<gun::Gun>, pub guns: Vec<gun::Gun>,
pub engines: Vec<engine::Engine>,
} }
macro_rules! quick_name_dup_check { macro_rules! quick_name_dup_check {
@ -75,7 +70,6 @@ impl Content {
quick_name_dup_check!(self.systems, root, system::System::build); quick_name_dup_check!(self.systems, root, system::System::build);
quick_name_dup_check!(self.guns, root, gun::Gun::build); quick_name_dup_check!(self.guns, root, gun::Gun::build);
quick_name_dup_check!(self.ships, root, ship::Ship::build); quick_name_dup_check!(self.ships, root, ship::Ship::build);
quick_name_dup_check!(self.engines, root, engine::Engine::build);
return Ok(()); return Ok(());
} }
@ -84,7 +78,6 @@ impl Content {
systems: Vec::new(), systems: Vec::new(),
ships: Vec::new(), ships: Vec::new(),
guns: Vec::new(), guns: Vec::new(),
engines: Vec::new(),
}; };
for e in WalkDir::new(path).into_iter().filter_map(|e| e.ok()) { for e in WalkDir::new(path).into_iter().filter_map(|e| e.ok()) {

View File

@ -36,19 +36,21 @@ pub struct Ship {
pub name: String, pub name: String,
pub sprite: String, pub sprite: String,
pub size: f32, pub size: f32,
pub engines: Vec<EnginePoint>, pub engines: Vec<Engine>,
pub guns: Vec<GunPoint>, pub guns: Vec<ShipGun>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct EnginePoint { pub struct Engine {
pub pos: Point2<f32>, pub pos: Point2<f32>,
pub size: f32, pub size: f32,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct GunPoint { pub struct ShipGun {
pub pos: Point2<f32>, pub pos: Point2<f32>,
pub cooldown: f32,
pub active_cooldown: f32,
} }
impl super::Build for Ship { impl super::Build for Ship {
@ -68,7 +70,7 @@ impl super::Build for Ship {
engines: ship engines: ship
.engines .engines
.iter() .iter()
.map(|e| EnginePoint { .map(|e| Engine {
pos: Point2 { x: e.x, y: e.y }, pos: Point2 { x: e.x, y: e.y },
size: e.size, size: e.size,
}) })
@ -76,8 +78,10 @@ impl super::Build for Ship {
guns: ship guns: ship
.guns .guns
.iter() .iter()
.map(|e| GunPoint { .map(|e| ShipGun {
pos: Point2 { x: e.x, y: e.y }, pos: Point2 { x: e.x, y: e.y },
cooldown: 0.2,
active_cooldown: 0.0,
}) })
.collect(), .collect(),
}); });

View File

@ -2,7 +2,7 @@ use cgmath::{Deg, Point2, Point3, Vector2};
use std::time::Instant; use std::time::Instant;
use winit::event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase, VirtualKeyCode}; use winit::event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase, VirtualKeyCode};
use super::{ship, Camera, InputStatus, System}; use super::{Camera, InputStatus, Ship, System};
use crate::{ use crate::{
consts, consts,
content::Content, content::Content,
@ -16,7 +16,6 @@ pub struct Projectile {
pub sprite: SpriteTexture, pub sprite: SpriteTexture,
pub angle: Deg<f32>, pub angle: Deg<f32>,
pub lifetime: f32, pub lifetime: f32,
pub size: f32,
} }
impl Projectile { impl Projectile {
@ -33,8 +32,8 @@ impl Projectile {
pub struct Game { pub struct Game {
pub input: InputStatus, pub input: InputStatus,
pub last_update: Instant, pub last_update: Instant,
pub player: ship::Ship, pub player: Ship,
pub test: ship::Ship, pub test: Ship,
pub system: System, pub system: System,
pub camera: Camera, pub camera: Camera,
paused: bool, paused: bool,
@ -48,15 +47,7 @@ impl Game {
last_update: Instant::now(), last_update: Instant::now(),
input: InputStatus::new(), input: InputStatus::new(),
projectiles: Vec::new(), projectiles: Vec::new(),
player: ship::Ship::new( player: Ship::new(&ct.ships[0], (0.0, 0.0).into()),
&ct.ships[0],
vec![
ship::ShipOutfit::Gun(ship::ShipGun::new(ct.guns[0].clone(), 1)),
ship::ShipOutfit::Gun(ship::ShipGun::new(ct.guns[0].clone(), 2)),
ship::ShipOutfit::Engine(ct.engines[0].clone()),
],
(0.0, 0.0).into(),
),
camera: Camera { camera: Camera {
pos: (0.0, 0.0).into(), pos: (0.0, 0.0).into(),
zoom: 500.0, zoom: 500.0,
@ -65,7 +56,7 @@ impl Game {
paused: false, paused: false,
time_scale: 1.0, time_scale: 1.0,
test: ship::Ship::new(&ct.ships[0], vec![], (100.0, 100.0).into()), test: Ship::new(&ct.ships[0], (100.0, 100.0).into()),
} }
} }
@ -139,7 +130,7 @@ impl Game {
y: p.position.y, y: p.position.y,
z: 1.0, z: 1.0,
}, },
size: p.size, size: 10.0,
angle: p.angle, angle: p.angle,
children: None, children: None,
}) })

View File

@ -8,5 +8,6 @@ mod systemobject;
pub use camera::Camera; pub use camera::Camera;
pub use game::Game; pub use game::Game;
pub use inputstatus::InputStatus; pub use inputstatus::InputStatus;
pub use ship::Ship;
pub use system::System; pub use system::System;
pub use systemobject::SystemObject; pub use systemobject::SystemObject;

143
src/game/ship.rs Normal file
View File

@ -0,0 +1,143 @@
use cgmath::{Deg, EuclideanSpace, Matrix2, Point2, Point3, Vector2};
use crate::{
content,
physics::PhysicsBody,
render::{Sprite, SpriteTexture, Spriteable, SubSprite},
};
use super::{game::Projectile, InputStatus};
pub struct ShipControls {
pub left: bool,
pub right: bool,
pub thrust: bool,
pub guns: bool,
}
impl ShipControls {
pub fn new() -> Self {
ShipControls {
left: false,
right: false,
thrust: false,
guns: false,
}
}
}
pub struct ShipTickResult {
pub projectiles: Vec<Projectile>,
}
pub struct Ship {
pub physicsbody: PhysicsBody,
pub controls: ShipControls,
sprite: SpriteTexture,
size: f32,
engines: Vec<content::Engine>,
guns: Vec<content::ShipGun>,
}
impl Ship {
pub fn new(ct: &content::Ship, pos: Point2<f32>) -> Self {
Ship {
physicsbody: PhysicsBody::new(pos),
sprite: SpriteTexture(ct.sprite.clone()),
size: ct.size,
engines: ct.engines.clone(),
guns: ct.guns.clone(),
controls: ShipControls::new(),
}
}
pub fn update_controls(&mut self, input: &InputStatus) {
self.controls.thrust = input.key_thrust;
self.controls.right = input.key_right;
self.controls.left = input.key_left;
self.controls.guns = input.key_guns;
}
pub fn fire_guns(&mut self) -> Vec<Projectile> {
let mut out = Vec::new();
for i in &mut self.guns {
if i.active_cooldown > 0.0 {
continue;
}
i.active_cooldown = i.cooldown;
let p = self.physicsbody.pos
+ (Matrix2::from_angle(self.physicsbody.angle) * i.pos.to_vec());
out.push(Projectile {
position: p,
velocity: self.physicsbody.vel
+ (Matrix2::from_angle(self.physicsbody.angle) * Vector2 { x: 0.0, y: 400.0 }),
angle: self.physicsbody.angle,
sprite: SpriteTexture("projectile::blaster".into()),
lifetime: 5.0,
})
}
return out;
}
pub fn tick(&mut self, t: f32) -> ShipTickResult {
if self.controls.thrust {
self.physicsbody.thrust(50.0 * t);
}
if self.controls.right {
self.physicsbody.rot(Deg(35.0) * t);
}
if self.controls.left {
self.physicsbody.rot(Deg(-35.0) * t);
}
let p = if self.controls.guns {
self.fire_guns()
} else {
Vec::new()
};
self.physicsbody.tick(t);
for i in &mut self.guns {
i.active_cooldown -= t;
}
return ShipTickResult { projectiles: p };
}
}
impl Spriteable for Ship {
fn get_sprite(&self) -> Sprite {
let engines = if self.controls.thrust {
Some(
self.engines
.iter()
.map(|e| SubSprite {
pos: Point3 {
x: e.pos.x,
y: e.pos.y,
z: 1.0,
},
texture: SpriteTexture("flare::ion".to_owned()),
angle: Deg(0.0),
size: e.size,
})
.collect(),
)
} else {
None
};
Sprite {
pos: (self.physicsbody.pos.x, self.physicsbody.pos.y, 1.0).into(),
texture: self.sprite.clone(), // TODO: sprite texture should be easy to clone
angle: self.physicsbody.angle,
size: self.size,
children: engines,
}
}
}

View File

@ -1,29 +0,0 @@
mod outfits;
mod ship;
pub use outfits::{ShipGun, ShipOutfit, ShipOutfits};
pub use ship::Ship;
use super::{game::Projectile, InputStatus};
pub struct ShipControls {
pub left: bool,
pub right: bool,
pub thrust: bool,
pub guns: bool,
}
impl ShipControls {
pub fn new() -> Self {
ShipControls {
left: false,
right: false,
thrust: false,
guns: false,
}
}
}
pub struct ShipTickResult {
pub projectiles: Vec<Projectile>,
}

View File

@ -1,129 +0,0 @@
use cgmath::{Deg, Point3};
use crate::{
content::{self, EnginePoint, GunPoint},
render::{SpriteTexture, SubSprite},
};
/// Represents a gun attached to a specific ship at a certain gunpoint.
pub struct ShipGun {
pub kind: content::Gun,
pub cooldown: f32,
pub point: usize,
}
impl ShipGun {
pub fn new(kind: content::Gun, point: usize) -> Self {
Self {
kind,
point,
cooldown: 0.0,
}
}
}
/// Represents a specific outfit attached to a specific ship
pub enum ShipOutfit {
Gun(ShipGun),
Engine(content::Engine),
}
impl ShipOutfit {
pub fn gun(&mut self) -> Option<&mut ShipGun> {
match self {
Self::Gun(g) => Some(g),
_ => None,
}
}
pub fn engine(&self) -> Option<&content::Engine> {
match self {
Self::Engine(e) => Some(e),
_ => None,
}
}
}
pub struct ShipOutfits {
outfits: Vec<ShipOutfit>,
enginepoints: Vec<content::EnginePoint>,
gunpoints: Vec<content::GunPoint>,
// Minor performance optimization, since we
// rarely need to re-compute these.
engine_flare_sprites: Vec<SubSprite>,
}
impl<'a> ShipOutfits {
pub fn new(enginepoints: Vec<content::EnginePoint>, gunpoints: Vec<content::GunPoint>) -> Self {
Self {
outfits: Vec::new(),
enginepoints,
gunpoints,
engine_flare_sprites: vec![],
}
}
pub fn add(&mut self, o: ShipOutfit) {
self.outfits.push(o);
self.update_engine_flares();
}
pub fn iter_guns(&mut self) -> impl Iterator<Item = &mut ShipGun> {
self.outfits
.iter_mut()
.map(|x| x.gun())
.filter(|x| x.is_some())
.map(|x| x.unwrap())
}
pub fn iter_guns_points(&mut self) -> impl Iterator<Item = (&mut ShipGun, &GunPoint)> {
self.outfits
.iter_mut()
.map(|x| x.gun())
.filter(|x| x.is_some())
.map(|x| x.unwrap())
.map(|x| (&self.gunpoints[x.point], x))
.map(|(a, b)| (b, a))
}
pub fn iter_engines(&self) -> impl Iterator<Item = &content::Engine> {
self.outfits
.iter()
.map(|x| x.engine())
.filter(|x| x.is_some())
.map(|x| x.unwrap())
}
pub fn iter_enginepoints(&self) -> impl Iterator<Item = &EnginePoint> {
self.enginepoints.iter()
}
pub fn update_engine_flares(&mut self) {
// TODO: better way to pick flare texture
self.engine_flare_sprites.clear();
let t = if let Some(e) = self.iter_engines().next() {
SpriteTexture(e.flare_sprite.clone())
} else {
return;
};
self.engine_flare_sprites = self
.iter_enginepoints()
.map(|p| SubSprite {
pos: Point3 {
x: p.pos.x,
y: p.pos.y,
z: 1.0,
},
texture: t.clone(),
angle: Deg(0.0),
size: p.size,
})
.collect();
}
pub fn get_engine_flares(&self) -> Vec<SubSprite> {
return self.engine_flare_sprites.clone();
}
}

View File

@ -1,132 +0,0 @@
use cgmath::{Deg, EuclideanSpace, Matrix2, Point2, Vector2};
use rand::Rng;
use super::super::game::Projectile;
use super::ShipOutfit;
use super::{outfits::ShipOutfits, InputStatus, ShipControls, ShipTickResult};
use crate::{
content,
physics::PhysicsBody,
render::{Sprite, SpriteTexture, Spriteable},
};
pub struct Ship {
pub physicsbody: PhysicsBody,
pub controls: ShipControls,
outfits: ShipOutfits,
sprite: SpriteTexture,
size: f32,
}
impl Ship {
pub fn new(ct: &content::Ship, outfits: Vec<ShipOutfit>, pos: Point2<f32>) -> Self {
let mut o = ShipOutfits::new(ct.engines.clone(), ct.guns.clone());
for x in outfits.into_iter() {
o.add(x)
}
Ship {
physicsbody: PhysicsBody::new(pos),
controls: ShipControls::new(),
outfits: o,
sprite: SpriteTexture(ct.sprite.clone()),
size: ct.size,
}
}
pub fn update_controls(&mut self, input: &InputStatus) {
self.controls.thrust = input.key_thrust;
self.controls.right = input.key_right;
self.controls.left = input.key_left;
self.controls.guns = input.key_guns;
}
pub fn fire_guns(&mut self) -> Vec<Projectile> {
let mut rng = rand::thread_rng();
let mut out = Vec::new();
for (g, p) in self.outfits.iter_guns_points() {
if g.cooldown > 0.0 {
continue;
}
g.cooldown = g.kind.rate + rng.gen_range(-g.kind.rate_rng..=g.kind.rate_rng);
let pos = self.physicsbody.pos
+ (Matrix2::from_angle(self.physicsbody.angle) * p.pos.to_vec());
let vel = self.physicsbody.vel
+ (Matrix2::from_angle(
self.physicsbody.angle
+ Deg(rng.gen_range(-(g.kind.spread.0 / 2.0)..=g.kind.spread.0 / 2.0)),
) * Vector2 {
x: 0.0,
y: g.kind.projectile.speed
+ rng.gen_range(-g.kind.projectile.speed_rng..=g.kind.projectile.speed_rng),
});
out.push(Projectile {
position: pos,
velocity: vel,
angle: self.physicsbody.angle,
sprite: SpriteTexture(g.kind.projectile.sprite.clone()),
lifetime: g.kind.projectile.lifetime
+ rng.gen_range(
-g.kind.projectile.lifetime_rng..=g.kind.projectile.lifetime_rng,
),
size: g.kind.projectile.size
+ rng.gen_range(-g.kind.projectile.size_rng..=g.kind.projectile.size_rng),
})
}
return out;
}
pub fn tick(&mut self, t: f32) -> ShipTickResult {
if self.controls.thrust {
for e in self.outfits.iter_engines() {
self.physicsbody.thrust(e.thrust * t);
}
}
if self.controls.right {
self.physicsbody.rot(Deg(35.0) * t);
}
if self.controls.left {
self.physicsbody.rot(Deg(-35.0) * t);
}
let p = if self.controls.guns {
self.fire_guns()
} else {
Vec::new()
};
self.physicsbody.tick(t);
for i in self.outfits.iter_guns() {
i.cooldown -= t;
}
return ShipTickResult { projectiles: p };
}
}
impl Spriteable for Ship {
fn get_sprite(&self) -> Sprite {
Sprite {
pos: (self.physicsbody.pos.x, self.physicsbody.pos.y, 1.0).into(),
texture: self.sprite.clone(), // TODO: sprite texture should be easy to clone
angle: self.physicsbody.angle,
size: self.size,
children: if self.controls.thrust {
Some(self.outfits.get_engine_flares())
} else {
None
},
}
}
}

View File

@ -2,7 +2,6 @@ use cgmath::{Deg, Point3};
use super::SpriteTexture; use super::SpriteTexture;
#[derive(Debug, Clone)]
pub struct Sprite { pub struct Sprite {
/// The sprite texture to draw /// The sprite texture to draw
pub texture: SpriteTexture, pub texture: SpriteTexture,
@ -22,7 +21,6 @@ pub struct Sprite {
pub children: Option<Vec<SubSprite>>, pub children: Option<Vec<SubSprite>>,
} }
#[derive(Debug, Clone)]
pub struct SubSprite { pub struct SubSprite {
/// The sprite texture to draw /// The sprite texture to draw
pub texture: SpriteTexture, pub texture: SpriteTexture,