Compare commits

...

4 Commits

Author SHA1 Message Date
Mark 389803eae9
Added basic ship guns 2023-12-27 20:14:53 -08:00
Mark 3f92e62724
Increased sprite limit 2023-12-27 20:14:42 -08:00
Mark c09974ba67
Added gun content 2023-12-27 20:13:39 -08:00
Mark aff7b3801f
Reworked content parser 2023-12-27 19:51:58 -08:00
18 changed files with 471 additions and 257 deletions

2
assets

@ -1 +1 @@
Subproject commit e4972a4daff7bac077fb2d32c298e3d6af46da91 Subproject commit 5a444ca5c63aac4b4aca4866684859c08972f9e7

5
content/guns.toml Normal file
View File

@ -0,0 +1,5 @@
[gun."blaster"]
projectile.sprite = "projectile::blaster"
projectile.size = 100
projectile.speed = 300
projectile.lifetime = 2.0

View File

@ -1,7 +1,6 @@
# content type: ship [ship."Gypsum"]
[ship]
name = "Gypsum"
sprite = "ship::gypsum" 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 }]

View File

@ -1,27 +1,20 @@
# content type: system [system."12 Autumn Above"]
[system]
name = "12 Autumn above"
[object.star] object.star.sprite = "star::star"
sprite = "star::star" object.star.position = [0.0, 0.0, 30.0]
position = [0.0, 0.0, 30.0] object.star.size = 2000
size = 2000
object.earth.sprite = "planet::earth"
object.earth.position.center = "star"
object.earth.position.radius = 4000
object.earth.position.angle = 0
object.earth.position.z = 10.0
object.earth.size = 1000
[object.earth] object.luna.sprite = "planet::luna"
sprite = "planet::earth" object.luna.position.center = "earth"
position.center = "star" object.luna.position.radius = 1600
position.radius = 4000 object.luna.position.angle = 135
position.angle = 0 object.luna.position.z = 7.8
position.z = 10.0 object.luna.size = 500
size = 1000 object.luna.angle = -45
[object.luna]
sprite = "planet::luna"
position.center = "earth"
position.radius = 1600
position.angle = 135
position.z = 7.8
size = 500
angle = -45

View File

@ -1,34 +0,0 @@
use anyhow::{Context, Result};
use std::path::PathBuf;
use super::{syntax, ContentType};
#[derive(Debug)]
pub struct Content {
pub systems: Vec<syntax::system::System>,
pub ships: Vec<syntax::ship::Ship>,
}
impl Content {
pub fn new(cv: Vec<(PathBuf, ContentType)>) -> Result<Self> {
let mut systems = Vec::new();
let mut ships = Vec::new();
// These methods check intra-file consistency
for (p, c) in cv {
match c {
ContentType::System(v) => systems.push(
syntax::system::System::parse(v)
.with_context(|| format!("Could not parse {}", p.display()))?,
),
ContentType::Ship(v) => ships.push(
syntax::ship::Ship::parse(v)
.with_context(|| format!("Could not parse {}", p.display()))?,
),
}
}
return Ok(Self { systems, ships });
}
}

View File

@ -1,41 +0,0 @@
use anyhow::{bail, Result};
use std::{fs::File, io::Read, path::Path};
use super::syntax;
#[derive(Debug)]
pub enum ContentType {
System(syntax::system::toml::SystemRoot),
Ship(syntax::ship::toml::ShipRoot),
}
// TODO: check content without loading game
impl ContentType {
pub fn try_parse(file_string: &str) -> Result<Option<Self>> {
// TODO: More advanced parsing, read the whole top comment
let first = match file_string.split_once("\n") {
None => bail!("This file is empty."),
Some((first, _)) => first,
};
let type_spec = first[1..].trim(); // Remove hash
let type_spec = if type_spec.starts_with("content type: ") {
type_spec[14..].to_owned()
} else {
bail!("No content type specified")
};
return Ok(match &type_spec[..] {
"system" => Some(Self::System(toml::from_str(&file_string)?)),
"ship" => Some(Self::Ship(toml::from_str(&file_string)?)),
_ => bail!("Invalid content type `{}`", type_spec),
});
}
pub fn from_path(path: &Path) -> Result<Option<Self>> {
let mut file_string = String::new();
let _ = File::open(path)?.read_to_string(&mut file_string);
let file_string = file_string.trim();
return Self::try_parse(&file_string);
}
}

59
src/content/gun.rs Normal file
View File

@ -0,0 +1,59 @@
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 Gun {
pub projectile: Projectile,
}
#[derive(Debug, Deserialize)]
pub struct Projectile {
pub sprite: String,
pub size: f32,
pub speed: f32,
pub lifetime: f32,
}
}
#[derive(Debug, Clone)]
pub struct Gun {
pub name: String,
pub projectile: Projectile,
}
#[derive(Debug, Clone)]
pub struct Projectile {
pub sprite: String,
pub size: f32,
pub speed: f32,
pub lifetime: f32,
}
impl super::Build for Gun {
fn build(root: &super::syntax::Root) -> Result<Vec<Self>> {
let gun = if let Some(gun) = &root.gun {
gun
} else {
return Ok(vec![]);
};
let mut out = Vec::new();
for (gun_name, gun) in gun {
out.push(Self {
name: gun_name.to_owned(),
projectile: Projectile {
sprite: gun.projectile.sprite.to_owned(),
size: gun.projectile.size,
speed: gun.projectile.speed,
lifetime: gun.projectile.lifetime,
},
});
}
return Ok(out);
}
}

View File

@ -1,17 +1,85 @@
mod content; #![allow(dead_code)]
mod contenttype; mod gun;
mod syntax; mod ship;
mod system;
pub use content::Content; pub use gun::{Gun, Projectile};
pub use contenttype::ContentType; pub use ship::{Engine, Ship, ShipGun};
pub use syntax::ship; pub use system::{Object, System};
pub use syntax::system;
use anyhow::{Context, Result}; use anyhow::{bail, Context, Result};
use std::collections::HashMap;
use std::{fs::File, io::Read, path::Path};
use toml;
use walkdir::WalkDir; use walkdir::WalkDir;
pub fn load_content_dir(path: &str) -> Result<Content> { mod syntax {
let mut raw_content = Vec::new(); use super::HashMap;
use super::{gun, ship, system};
use serde::Deserialize;
#[derive(Debug, Deserialize)]
pub struct Root {
pub gun: Option<HashMap<String, gun::syntax::Gun>>,
pub ship: Option<HashMap<String, ship::syntax::Ship>>,
pub system: Option<HashMap<String, system::syntax::System>>,
}
}
trait Build {
/// Build a processed System struct from raw serde data
fn build(root: &syntax::Root) -> Result<Vec<Self>>
where
Self: Sized;
}
#[derive(Debug)]
pub struct Content {
pub systems: Vec<system::System>,
pub ships: Vec<ship::Ship>,
pub guns: Vec<gun::Gun>,
}
macro_rules! quick_name_dup_check {
($array:expr, $root:ident, $build:expr) => {{
let mut p = $build(&$root)?;
for s in &$array {
for o in &p {
if s.name == o.name {
bail!(
"Error parsing content: duplicate ship names `{}` and `{}`",
s.name,
o.name
)
}
}
}
$array.append(&mut p);
}};
}
impl Content {
fn try_parse(path: &Path) -> Result<syntax::Root> {
let mut file_string = String::new();
let _ = File::open(path)?.read_to_string(&mut file_string);
let file_string = file_string.trim();
return Ok(toml::from_str(&file_string)?);
}
fn add_root(&mut self, root: syntax::Root) -> Result<()> {
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.ships, root, ship::Ship::build);
return Ok(());
}
pub fn load_dir(path: &str) -> Result<Self> {
let mut content = Self {
systems: Vec::new(),
ships: Vec::new(),
guns: 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()) {
if e.metadata().unwrap().is_file() { if e.metadata().unwrap().is_file() {
// TODO: better warnings // TODO: better warnings
@ -28,15 +96,15 @@ pub fn load_content_dir(path: &str) -> Result<Content> {
} }
} }
let c = crate::content::ContentType::from_path(e.path()) let path = e.path();
let root = Self::try_parse(path)
.with_context(|| format!("Could not load {:#?}", e.path()))?; .with_context(|| format!("Could not load {:#?}", e.path()))?;
content
match c { .add_root(root)
Some(c) => raw_content.push((e.path().to_path_buf(), c)), .with_context(|| format!("Could not parse {}", path.display()))?;
None => continue,
}
} }
} }
return crate::content::Content::new(raw_content); return Ok(content);
}
} }

92
src/content/ship.rs Normal file
View File

@ -0,0 +1,92 @@
use anyhow::Result;
use cgmath::Point2;
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 Ship {
pub sprite: String,
pub size: f32,
pub engines: Vec<Engine>,
pub guns: Vec<Gun>,
}
#[derive(Debug, Deserialize)]
pub struct Engine {
pub x: f32,
pub y: f32,
pub size: f32,
}
#[derive(Debug, Deserialize)]
pub struct Gun {
pub x: f32,
pub y: f32,
}
}
// Processed data structs.
// These are exported.
#[derive(Debug, Clone)]
pub struct Ship {
pub name: String,
pub sprite: String,
pub size: f32,
pub engines: Vec<Engine>,
pub guns: Vec<ShipGun>,
}
#[derive(Debug, Clone)]
pub struct Engine {
pub pos: Point2<f32>,
pub size: f32,
}
#[derive(Debug, Clone)]
pub struct ShipGun {
pub pos: Point2<f32>,
pub cooldown: f32,
pub active_cooldown: f32,
}
impl super::Build for Ship {
fn build(root: &super::syntax::Root) -> Result<Vec<Self>> {
let ship = if let Some(ship) = &root.ship {
ship
} else {
return Ok(vec![]);
};
let mut out = Vec::new();
for (ship_name, ship) in ship {
out.push(Self {
name: ship_name.to_owned(),
sprite: ship.sprite.to_owned(),
size: ship.size,
engines: ship
.engines
.iter()
.map(|e| Engine {
pos: Point2 { x: e.x, y: e.y },
size: e.size,
})
.collect(),
guns: ship
.guns
.iter()
.map(|e| ShipGun {
pos: Point2 { x: e.x, y: e.y },
cooldown: 0.2,
active_cooldown: 0.0,
})
.collect(),
});
}
return Ok(out);
}
}

View File

@ -1,3 +0,0 @@
#![allow(dead_code)]
pub mod ship;
pub mod system;

View File

@ -1,60 +0,0 @@
use anyhow::Result;
use cgmath::Point2;
/// Toml file syntax
pub(in crate::content) mod toml {
use serde::Deserialize;
#[derive(Debug, Deserialize)]
pub struct ShipRoot {
pub ship: Ship,
}
#[derive(Debug, Deserialize)]
pub struct Ship {
pub name: String,
pub sprite: String,
pub size: f32,
pub engines: Vec<Engine>,
}
#[derive(Debug, Deserialize)]
pub struct Engine {
pub x: f32,
pub y: f32,
pub size: f32,
}
}
#[derive(Debug, Clone)]
pub struct Ship {
pub name: String,
pub sprite: String,
pub size: f32,
pub engines: Vec<Engine>,
}
#[derive(Debug, Clone)]
pub struct Engine {
pub pos: Point2<f32>,
pub size: f32,
}
impl Ship {
pub fn parse(value: toml::ShipRoot) -> Result<Self> {
return Ok(Self {
name: value.ship.name,
sprite: value.ship.sprite,
size: value.ship.size,
engines: value
.ship
.engines
.iter()
.map(|e| Engine {
pos: Point2 { x: e.x, y: e.y },
size: e.size,
})
.collect(),
});
}
}

View File

@ -4,20 +4,15 @@ use std::collections::{HashMap, HashSet};
use crate::physics::Polar; use crate::physics::Polar;
/// Toml file syntax pub(super) mod syntax {
pub(in crate::content) mod toml { use super::HashMap;
use serde::Deserialize; use serde::Deserialize;
use std::collections::HashMap; // Raw serde syntax structs.
// These are never seen by code outside this crate.
#[derive(Debug, Deserialize)]
pub struct SystemRoot {
pub system: System,
pub object: HashMap<String, Object>,
}
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct System { pub struct System {
pub name: String, pub object: HashMap<String, Object>,
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
@ -79,6 +74,9 @@ pub(in crate::content) mod toml {
} }
} }
// Processed data structs.
// These are exported.
#[derive(Debug)] #[derive(Debug)]
pub struct System { pub struct System {
pub name: String, pub name: String,
@ -93,15 +91,15 @@ pub struct Object {
pub angle: Deg<f32>, pub angle: Deg<f32>,
} }
// Helper function for resolve_position, never called on its own. /// Helper function for resolve_position, never called on its own.
fn resolve_coordinates( fn resolve_coordinates(
objects: &HashMap<String, toml::Object>, objects: &HashMap<String, syntax::Object>,
cor: &toml::CoordinatesThree, cor: &syntax::CoordinatesThree,
mut cycle_detector: HashSet<String>, mut cycle_detector: HashSet<String>,
) -> Result<Point3<f32>> { ) -> Result<Point3<f32>> {
match cor { match cor {
toml::CoordinatesThree::Coords(c) => Ok((*c).into()), syntax::CoordinatesThree::Coords(c) => Ok((*c).into()),
toml::CoordinatesThree::Label(l) => { syntax::CoordinatesThree::Label(l) => {
if cycle_detector.contains(l) { if cycle_detector.contains(l) {
bail!( bail!(
"Found coordinate cycle: `{}`", "Found coordinate cycle: `{}`",
@ -126,19 +124,19 @@ fn resolve_coordinates(
} }
} }
/// Given an object, resolve it's position as a Point3. /// Given an object, resolve its position as a Point3.
fn resolve_position( fn resolve_position(
objects: &HashMap<String, toml::Object>, objects: &HashMap<String, syntax::Object>,
obj: &toml::Object, obj: &syntax::Object,
cycle_detector: HashSet<String>, cycle_detector: HashSet<String>,
) -> Result<Point3<f32>> { ) -> Result<Point3<f32>> {
match &obj.position { match &obj.position {
toml::Position::Cartesian(c) => Ok(resolve_coordinates(objects, &c, cycle_detector)?), syntax::Position::Cartesian(c) => Ok(resolve_coordinates(objects, &c, cycle_detector)?),
toml::Position::Polar(p) => { syntax::Position::Polar(p) => {
let three = match &p.center { let three = match &p.center {
toml::CoordinatesTwo::Label(s) => toml::CoordinatesThree::Label(s.clone()), syntax::CoordinatesTwo::Label(s) => syntax::CoordinatesThree::Label(s.clone()),
toml::CoordinatesTwo::Coords(v) => { syntax::CoordinatesTwo::Coords(v) => {
toml::CoordinatesThree::Coords([v[0], v[1], f32::NAN]) syntax::CoordinatesThree::Coords([v[0], v[1], f32::NAN])
} }
}; };
let r = resolve_coordinates(&objects, &three, cycle_detector)?; let r = resolve_coordinates(&objects, &three, cycle_detector)?;
@ -157,26 +155,37 @@ fn resolve_position(
} }
} }
impl System { impl super::Build for System {
pub fn parse(value: toml::SystemRoot) -> Result<Self> { fn build(root: &super::syntax::Root) -> Result<Vec<Self>> {
let system = if let Some(system) = &root.system {
system
} else {
return Ok(vec![]);
};
let mut out = Vec::new();
for (system_name, system) in system {
let mut objects = Vec::new(); let mut objects = Vec::new();
for (label, obj) in &value.object { for (label, obj) in &system.object {
let mut cycle_detector = HashSet::new(); let mut cycle_detector = HashSet::new();
cycle_detector.insert(label.to_owned()); cycle_detector.insert(label.to_owned());
objects.push(Object { objects.push(Object {
sprite: obj.sprite.clone(), sprite: obj.sprite.clone(),
position: resolve_position(&value.object, &obj, cycle_detector) position: resolve_position(&system.object, &obj, cycle_detector)
.with_context(|| format!("In object {:#?}", label))?, .with_context(|| format!("In object {:#?}", label))?,
size: obj.size, size: obj.size,
angle: Deg(obj.angle.unwrap_or(0.0)), angle: Deg(obj.angle.unwrap_or(0.0)),
}); });
} }
return Ok(Self { out.push(Self {
name: value.system.name.clone(), name: system_name.to_owned(),
objects, objects,
}); });
} }
return Ok(out);
}
} }

View File

@ -1,9 +1,33 @@
use cgmath::Deg; 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::{Camera, InputStatus, Ship, System}; use super::{Camera, InputStatus, Ship, System};
use crate::{consts, content::Content, render::Sprite, render::Spriteable}; use crate::{
consts,
content::Content,
render::Spriteable,
render::{Sprite, SpriteTexture},
};
pub struct Projectile {
pub position: Point2<f32>,
pub velocity: Vector2<f32>,
pub sprite: SpriteTexture,
pub angle: Deg<f32>,
pub lifetime: f32,
}
impl Projectile {
pub fn tick(&mut self, t: f32) {
self.position += self.velocity * t;
self.lifetime -= t;
}
pub fn is_expired(&self) -> bool {
return self.lifetime < 0.0;
}
}
pub struct Game { pub struct Game {
pub input: InputStatus, pub input: InputStatus,
@ -13,6 +37,7 @@ pub struct Game {
pub system: System, pub system: System,
pub camera: Camera, pub camera: Camera,
paused: bool, paused: bool,
pub projectiles: Vec<Projectile>,
pub time_scale: f32, pub time_scale: f32,
} }
@ -21,12 +46,14 @@ impl Game {
Game { Game {
last_update: Instant::now(), last_update: Instant::now(),
input: InputStatus::new(), input: InputStatus::new(),
projectiles: Vec::new(),
player: Ship::new(&ct.ships[0], (0.0, 0.0).into()), player: Ship::new(&ct.ships[0], (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,
}, },
system: System::new(&ct.systems[0]), system: System::new(&ct.systems[0]),
paused: false, paused: false,
time_scale: 1.0, time_scale: 1.0,
test: Ship::new(&ct.ships[0], (100.0, 100.0).into()), test: Ship::new(&ct.ships[0], (100.0, 100.0).into()),
@ -57,17 +84,16 @@ impl Game {
pub fn update(&mut self) { pub fn update(&mut self) {
let t: f32 = self.last_update.elapsed().as_secs_f32() * self.time_scale; let t: f32 = self.last_update.elapsed().as_secs_f32() * self.time_scale;
self.player.engines_on = self.input.key_thrust; self.projectiles.retain_mut(|p| {
if self.input.key_thrust { p.tick(t);
self.player.physicsbody.thrust(50.0 * t); !p.is_expired()
} });
if self.input.key_right { // Update player and handle result
self.player.physicsbody.rot(Deg(35.0) * t); self.player.update_controls(&self.input);
} let mut p = self.player.tick(t);
if p.projectiles.len() != 0 {
if self.input.key_left { self.projectiles.append(&mut p.projectiles);
self.player.physicsbody.rot(Deg(-35.0) * t);
} }
if self.input.v_scroll != 0.0 { if self.input.v_scroll != 0.0 {
@ -76,7 +102,6 @@ impl Game {
self.input.v_scroll = 0.0; self.input.v_scroll = 0.0;
} }
self.player.physicsbody.tick(t);
self.camera.pos = self.player.physicsbody.pos; self.camera.pos = self.player.physicsbody.pos;
self.last_update = Instant::now(); self.last_update = Instant::now();
@ -96,6 +121,21 @@ impl Game {
// I've tried this, but it doesn't seem to work with transparent textures. // I've tried this, but it doesn't seem to work with transparent textures.
sprites.sort_by(|a, b| b.pos.z.total_cmp(&a.pos.z)); sprites.sort_by(|a, b| b.pos.z.total_cmp(&a.pos.z));
// Don't waste time sorting these, they should always be on top.
for p in &self.projectiles {
sprites.push(Sprite {
texture: p.sprite.clone(),
pos: Point3 {
x: p.position.x,
y: p.position.y,
z: 1.0,
},
size: 10.0,
angle: p.angle,
children: None,
})
}
return sprites; return sprites;
} }
} }

View File

@ -5,6 +5,7 @@ pub struct InputStatus {
pub key_left: bool, pub key_left: bool,
pub key_right: bool, pub key_right: bool,
pub key_thrust: bool, pub key_thrust: bool,
pub key_guns: bool,
pub v_scroll: f32, pub v_scroll: f32,
} }
@ -14,6 +15,7 @@ impl InputStatus {
key_left: false, key_left: false,
key_right: false, key_right: false,
key_thrust: false, key_thrust: false,
key_guns: false,
v_scroll: 0.0, v_scroll: 0.0,
scroll_speed: 10.0, scroll_speed: 10.0,
} }
@ -23,6 +25,7 @@ impl InputStatus {
self.key_left = false; self.key_left = false;
self.key_right = false; self.key_right = false;
self.key_thrust = false; self.key_thrust = false;
self.key_guns = false;
} }
pub fn process_key(&mut self, state: &ElementState, key: &VirtualKeyCode) { pub fn process_key(&mut self, state: &ElementState, key: &VirtualKeyCode) {
@ -31,6 +34,7 @@ impl InputStatus {
VirtualKeyCode::Left => self.key_left = down, VirtualKeyCode::Left => self.key_left = down,
VirtualKeyCode::Right => self.key_right = down, VirtualKeyCode::Right => self.key_right = down,
VirtualKeyCode::Up => self.key_thrust = down, VirtualKeyCode::Up => self.key_thrust = down,
VirtualKeyCode::Space => self.key_guns = down,
_ => {} _ => {}
} }
} }

View File

@ -1,35 +1,118 @@
use cgmath::{Deg, Point2, Point3}; use cgmath::{Deg, EuclideanSpace, Matrix2, Point2, Point3, Vector2};
use crate::{ use crate::{
content::{self, ship::Engine}, content,
physics::PhysicsBody, physics::PhysicsBody,
render::{Sprite, SpriteTexture, Spriteable, SubSprite}, 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 struct Ship {
pub physicsbody: PhysicsBody, pub physicsbody: PhysicsBody,
pub engines_on: bool, pub controls: ShipControls,
sprite: SpriteTexture, sprite: SpriteTexture,
size: f32, size: f32,
engines: Vec<Engine>, engines: Vec<content::Engine>,
guns: Vec<content::ShipGun>,
} }
impl Ship { impl Ship {
pub fn new(ct: &content::ship::Ship, pos: Point2<f32>) -> Self { pub fn new(ct: &content::Ship, pos: Point2<f32>) -> Self {
Ship { Ship {
physicsbody: PhysicsBody::new(pos), physicsbody: PhysicsBody::new(pos),
sprite: SpriteTexture(ct.sprite.clone()), sprite: SpriteTexture(ct.sprite.clone()),
size: ct.size, size: ct.size,
engines: ct.engines.clone(), engines: ct.engines.clone(),
engines_on: false, 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 { impl Spriteable for Ship {
fn get_sprite(&self) -> Sprite { fn get_sprite(&self) -> Sprite {
let engines = if self.engines_on { let engines = if self.controls.thrust {
Some( Some(
self.engines self.engines
.iter() .iter()

View File

@ -25,7 +25,7 @@ pub struct System {
} }
impl System { impl System {
pub fn new(ct: &content::system::System) -> Self { pub fn new(ct: &content::System) -> Self {
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
let sz = consts::STARFIELD_SIZE as f32 / 2.0; let sz = consts::STARFIELD_SIZE as f32 / 2.0;
let mut s = System { let mut s = System {

View File

@ -12,7 +12,7 @@ use winit::{
}; };
fn main() -> Result<()> { fn main() -> Result<()> {
let content = content::load_content_dir(consts::CONTENT_ROOT)?; let content = content::Content::load_dir(consts::CONTENT_ROOT)?;
let game = game::Game::new(content); let game = game::Game::new(content);
pollster::block_on(run(game))?; pollster::block_on(run(game))?;

View File

@ -3,7 +3,7 @@ use cgmath::Matrix4;
// We can draw at most this many sprites on the screen. // We can draw at most this many sprites on the screen.
// TODO: compile-time option // TODO: compile-time option
pub const SPRITE_INSTANCE_LIMIT: u64 = 100; pub const SPRITE_INSTANCE_LIMIT: u64 = 500;
// Must be small enough to fit in an i32 // Must be small enough to fit in an i32
pub const STARFIELD_INSTANCE_LIMIT: u64 = consts::STARFIELD_COUNT * 24; pub const STARFIELD_INSTANCE_LIMIT: u64 = consts::STARFIELD_COUNT * 24;