Compare commits

...

3 Commits

Author SHA1 Message Date
Mark 54a1a26a2c
More cleanup 2023-12-29 16:01:15 -08:00
Mark b40adb1685
Documentation 2023-12-29 15:46:09 -08:00
Mark da1b38cd33
Minor reorganization 2023-12-29 15:14:04 -08:00
23 changed files with 264 additions and 115 deletions

12
Cargo.lock generated
View File

@ -573,6 +573,17 @@ version = "0.3.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817"
[[package]]
name = "galactica-content"
version = "0.0.0"
dependencies = [
"anyhow",
"cgmath",
"serde",
"toml",
"walkdir",
]
[[package]]
name = "game"
version = "0.1.0"
@ -581,6 +592,7 @@ dependencies = [
"bytemuck",
"cgmath",
"crossbeam",
"galactica-content",
"image",
"nalgebra",
"pollster",

View File

@ -26,7 +26,15 @@ panic = "abort"
incremental = false
rpath = false
[workspace]
members = ["crates/content"]
[dependencies]
# Internal crates
galactica-content = { path = "crates/content" }
# Files
image = { version = "0.24", features = ["png"] }
toml = "0.8.8"
@ -35,12 +43,13 @@ serde = { version = "1.0.193", features = ["derive"] }
winit = "0.28"
wgpu = "0.18"
bytemuck = { version = "1.12", features = ["derive"] }
# Physics
rapier2d = "0.17.2"
nalgebra = "0.32.3"
crossbeam = "0.8.3"
# Misc helpers
pollster = "0.3"
anyhow = "1.0"
cgmath = "0.18.0"
rand = "0.8.5"
walkdir = "2.4.0"
rapier2d = "0.17.2"
nalgebra = "0.32.3"
crossbeam = "0.8.3"

11
crates/content/Cargo.toml Normal file
View File

@ -0,0 +1,11 @@
[package]
name = "galactica-content"
version = "0.0.0"
edition = "2021"
[dependencies]
toml = "0.8.8"
serde = { version = "1.0.193", features = ["derive"] }
anyhow = "1.0"
cgmath = "0.18.0"
walkdir = "2.4.0"

View File

@ -17,10 +17,18 @@ pub(super) mod syntax {
}
}
/// Represents an (foward) engine outfit that may be attached to a ship.
#[derive(Debug, Clone)]
pub struct Engine {
/// The name of this outfit
pub name: String,
/// How much thrust this engine produces
pub thrust: f32,
/// The flare sprite this engine creates.
/// Its location and size is determined by a ship's
/// engine points.
pub flare_sprite: String,
}

View File

@ -27,24 +27,54 @@ pub(super) mod syntax {
}
}
/// Represents a gun outfit.
#[derive(Debug, Clone)]
pub struct Gun {
/// The name of this gun
pub name: String,
/// The projectile this gun produces
pub projectile: Projectile,
/// The accuracy of this gun.
/// Projectiles can be off center up to
/// `spread / 2` degrees in both directions.
///
/// (Forming a "fire cone" of `spread` degrees)
pub spread: Deg<f32>,
/// Average delay between projectiles, in seconds.
pub rate: f32,
/// Random variation of projectile delay, in seconds.
/// Each shot waits (rate += rate_rng).
pub rate_rng: f32,
}
/// Represents a projectile that a [`Gun`] produces.
#[derive(Debug, Clone)]
pub struct Projectile {
/// The projectile sprite
pub sprite: String,
/// The average size of this projectile
/// (height in game units)
pub size: f32,
/// Random size variation
pub size_rng: f32,
/// The speed of this projectile, in game units / second
pub speed: f32,
/// Random speed variation
pub speed_rng: f32,
/// The lifespan of this projectile.
/// It will vanish if it lives this long without hitting anything.
pub lifetime: f32,
/// Random lifetime variation
pub lifetime_rng: f32,
/// The damage this projectile does
pub damage: f32,
}

View File

@ -1,8 +1,13 @@
#![allow(dead_code)]
#![warn(missing_docs)]
//! This subcrate is responsible for loading, parsing, validating game content,
//! which is usually stored in `./content`.
mod engine;
mod gun;
mod ship;
mod system;
mod util;
pub use engine::Engine;
pub use gun::{Gun, Projectile};
@ -39,9 +44,16 @@ trait Build {
/// Represents generic game content, not connected to any game objects.
#[derive(Debug)]
pub struct Content {
/// Star systems
pub systems: Vec<system::System>,
/// Ship bodies
pub ships: Vec<ship::Ship>,
/// Gun outfits
pub guns: Vec<gun::Gun>,
/// Engine outfits
pub engines: Vec<engine::Engine>,
}
@ -79,6 +91,7 @@ impl Content {
return Ok(());
}
/// Load content from a directory.
pub fn load_dir(path: &str) -> Result<Self> {
let mut content = Self {
systems: Vec::new(),

View File

@ -32,24 +32,50 @@ pub(super) mod syntax {
// Processed data structs.
// These are exported.
/// Represents a ship chassis.
#[derive(Debug, Clone)]
pub struct Ship {
/// This ship's name
pub name: String,
/// This ship's sprite
pub sprite: String,
/// The size of this ship.
/// Measured as unrotated height,
/// in terms of game units.
pub size: f32,
/// Engine points on this ship.
/// This is where engine flares are drawn.
pub engines: Vec<EnginePoint>,
/// Gun points on this ship.
/// A gun outfit can be mounted on each.
pub guns: Vec<GunPoint>,
/// This ship's hull strength
pub hull: f32,
}
/// An engine point on a ship.
/// This is where flares are drawn.
#[derive(Debug, Clone)]
pub struct EnginePoint {
/// This engine point's position, in game units,
/// relative to the ship's center.
pub pos: Point2<f32>,
/// The size of the flare that should be drawn
/// at this point, measured as height in game units.
pub size: f32,
}
/// A gun point on a ship.
#[derive(Debug, Clone)]
pub struct GunPoint {
/// This gun point's position, in game units,
/// relative to the ship's center.
pub pos: Point2<f32>,
}

View File

@ -77,17 +77,36 @@ pub(super) mod syntax {
// Processed data structs.
// These are exported.
/// Represents a star system
#[derive(Debug)]
pub struct System {
/// This star system's name
pub name: String,
/// Objects in this system
pub objects: Vec<Object>,
}
/// Represents an orbiting body in a star system
/// (A star, planet, moon, satellite, etc)
/// These may be landable and may be decorative.
/// System objects to not interact with the physics engine.
#[derive(Debug)]
pub struct Object {
/// This object's sprite
pub sprite: String,
pub position: Point3<f32>,
/// This object's size.
/// Measured as height in game units.
/// This value is scaled for distance
/// (i.e, the z-value of position)
pub size: f32,
/// This object's position, in game coordinates,
/// relative to the system's center (0, 0).
pub position: Point3<f32>,
/// This object's sprite's angle, in degrees.
pub angle: Deg<f32>,
}

View File

@ -1,14 +1,15 @@
use cgmath::Point2;
use std::time::Instant;
use winit::event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase, VirtualKeyCode};
use super::{
camera::Camera,
system::System,
util,
world::{ShipGun, ShipHandle, ShipOutfit, World},
InputStatus,
use super::{camera::Camera, outfits, system::System};
use crate::{
consts,
content::Content,
inputstatus::InputStatus,
physics::{util, Physics, ShipHandle},
render::Sprite,
};
use crate::{consts, content::Content, render::Sprite};
pub struct Game {
pub input: InputStatus,
@ -19,23 +20,32 @@ pub struct Game {
paused: bool,
pub time_scale: f32,
world: World,
world: Physics,
}
impl Game {
pub fn new(ct: Content) -> Self {
let mut world = World::new();
let mut world = Physics::new();
let c = world.add_ship(
&ct.ships[0],
vec![
ShipOutfit::Gun(ShipGun::new(ct.guns[0].clone(), 1)),
ShipOutfit::Gun(ShipGun::new(ct.guns[0].clone(), 2)),
ShipOutfit::Engine(ct.engines[0].clone()),
outfits::ShipOutfit::Gun(outfits::ShipGun::new(ct.guns[0].clone(), 1)),
outfits::ShipOutfit::Gun(outfits::ShipGun::new(ct.guns[0].clone(), 2)),
outfits::ShipOutfit::Engine(ct.engines[0].clone()),
],
Point2 { x: 0.0, y: 0.0 },
);
world.add_ship(&ct.ships[0], vec![]);
world.add_ship(&ct.ships[0], vec![], Point2 { x: 300.0, y: 300.0 });
world.add_ship(
&ct.ships[0],
vec![],
Point2 {
x: -300.0,
y: 300.0,
},
);
Game {
last_update: Instant::now(),

View File

@ -1,11 +1,10 @@
//! This module contains high-level game control routines.
mod camera;
mod game;
mod inputstatus;
pub mod outfits;
mod system;
mod systemobject;
mod util;
mod world;
pub use game::Game;
pub use inputstatus::InputStatus;
pub use systemobject::SystemObject;

View File

@ -1,13 +0,0 @@
mod physicswrapper;
mod projectile;
mod ship;
mod world;
pub use projectile::{Projectile, ProjectileBuilder};
pub use ship::{Ship, ShipControls, ShipGun, ShipOutfit, ShipOutfits, ShipTickResult};
pub use world::World;
use rapier2d::{dynamics::RigidBodyHandle, geometry::ColliderHandle};
#[derive(Debug, Copy, Clone)]
pub struct ShipHandle(pub(in super::super) RigidBodyHandle, ColliderHandle);

View File

@ -1,29 +0,0 @@
mod outfits;
mod ship;
pub use outfits::{ShipGun, ShipOutfit, ShipOutfits};
pub use ship::Ship;
use super::{super::InputStatus, ProjectileBuilder};
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<ProjectileBuilder>,
}

View File

@ -1,10 +1,12 @@
mod consts;
mod content;
mod game;
mod inputstatus;
mod objects;
mod physics;
mod render;
mod util;
use anyhow::Result;
use galactica_content as content;
use winit::{
event::{Event, KeyboardInput, WindowEvent},
event_loop::{ControlFlow, EventLoop},

7
src/objects/mod.rs Normal file
View File

@ -0,0 +1,7 @@
//! This module contains game objects that may interact with the physics engine.
mod projectile;
mod ship;
pub use projectile::{Projectile, ProjectileBuilder};
pub use ship::Ship;

View File

@ -4,8 +4,10 @@ use rapier2d::{
geometry::{ColliderBuilder, ColliderHandle},
};
use super::super::util;
use crate::render::{Sprite, SpriteTexture};
use crate::{
physics::util,
render::{Sprite, SpriteTexture},
};
pub struct ProjectileBuilder {
pub rigid_body: RigidBodyBuilder,

View File

@ -1,21 +1,46 @@
use cgmath::{Deg, EuclideanSpace, Matrix2, Rad, Vector2};
use nalgebra::vector;
use rand::Rng;
use rapier2d::dynamics::{RigidBody, RigidBodyBuilder};
use rapier2d::geometry::ColliderBuilder;
use rapier2d::pipeline::ActiveEvents;
use rapier2d::{
dynamics::{RigidBody, RigidBodyBuilder},
geometry::ColliderBuilder,
pipeline::ActiveEvents,
};
use super::ProjectileBuilder;
use super::{outfits::ShipOutfits, InputStatus, ShipControls, ShipOutfit, ShipTickResult};
use crate::game::{util, world::ShipHandle};
use crate::{
content,
game::outfits,
inputstatus::InputStatus,
physics::{util, ShipHandle},
render::{Sprite, SpriteTexture},
};
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<ProjectileBuilder>,
}
pub struct Ship {
pub physics_handle: ShipHandle,
outfits: ShipOutfits,
outfits: outfits::ShipOutfits,
sprite: SpriteTexture,
size: f32,
@ -26,8 +51,12 @@ pub struct Ship {
}
impl Ship {
pub fn new(c: &content::Ship, outfits: Vec<ShipOutfit>, physics_handle: ShipHandle) -> Self {
let mut o = ShipOutfits::new(c);
pub fn new(
c: &content::Ship,
outfits: Vec<outfits::ShipOutfit>,
physics_handle: ShipHandle,
) -> Self {
let mut o = outfits::ShipOutfits::new(c);
for x in outfits.into_iter() {
o.add(x)
}

10
src/physics/mod.rs Normal file
View File

@ -0,0 +1,10 @@
mod physics;
pub mod util;
mod wrapper;
pub use physics::Physics;
use rapier2d::{dynamics::RigidBodyHandle, geometry::ColliderHandle};
#[derive(Debug, Copy, Clone)]
pub struct ShipHandle(pub(super) RigidBodyHandle, ColliderHandle);

View File

@ -1,3 +1,4 @@
use cgmath::Point2;
use crossbeam::channel::Receiver;
use nalgebra::vector;
use rapier2d::{
@ -7,57 +8,55 @@ use rapier2d::{
};
use std::collections::HashMap;
use super::{
physicswrapper::PhysicsWrapper, Projectile, ProjectileBuilder, Ship, ShipHandle, ShipOutfit,
};
use crate::{content, render::Sprite};
use super::{wrapper::Wrapper, ShipHandle};
use crate::{content, game::outfits, objects, render::Sprite};
/// Keeps track of all objects in the world that we can interact with.
/// Also wraps our physics engine
pub struct World {
physics: PhysicsWrapper,
projectiles: HashMap<ColliderHandle, Projectile>,
ships: HashMap<ColliderHandle, Ship>,
pub struct Physics {
wrapper: Wrapper,
projectiles: HashMap<ColliderHandle, objects::Projectile>,
ships: HashMap<ColliderHandle, objects::Ship>,
collision_handler: ChannelEventCollector,
collision_queue: Receiver<CollisionEvent>,
}
// Private methods
impl World {
impl Physics {
fn remove_projectile(&mut self, c: ColliderHandle) {
let p = match self.projectiles.remove(&c) {
Some(p) => p,
None => return,
};
self.physics.rigid_body_set.remove(
self.wrapper.rigid_body_set.remove(
p.rigid_body,
&mut self.physics.im,
&mut self.physics.collider_set,
&mut self.physics.ij,
&mut self.physics.mj,
&mut self.wrapper.im,
&mut self.wrapper.collider_set,
&mut self.wrapper.ij,
&mut self.wrapper.mj,
true,
);
}
fn remove_ship(&mut self, h: ShipHandle) {
self.physics.rigid_body_set.remove(
self.wrapper.rigid_body_set.remove(
h.0,
&mut self.physics.im,
&mut self.physics.collider_set,
&mut self.physics.ij,
&mut self.physics.mj,
&mut self.wrapper.im,
&mut self.wrapper.collider_set,
&mut self.wrapper.ij,
&mut self.wrapper.mj,
true,
);
self.ships.remove(&h.1);
}
fn add_projectile(&mut self, pb: ProjectileBuilder) -> ColliderHandle {
let r = self.physics.rigid_body_set.insert(pb.rigid_body.build());
let c = self.physics.collider_set.insert_with_parent(
fn add_projectile(&mut self, pb: objects::ProjectileBuilder) -> ColliderHandle {
let r = self.wrapper.rigid_body_set.insert(pb.rigid_body.build());
let c = self.wrapper.collider_set.insert_with_parent(
pb.collider.build(),
r,
&mut self.physics.rigid_body_set,
&mut self.wrapper.rigid_body_set,
);
self.projectiles.insert(c, pb.build(r, c));
return c;
@ -65,13 +64,13 @@ impl World {
}
// Public methods
impl World {
impl Physics {
pub fn new() -> Self {
let (collision_send, collision_queue) = crossbeam::channel::unbounded();
let (contact_force_send, _) = crossbeam::channel::unbounded();
Self {
physics: PhysicsWrapper::new(),
wrapper: Wrapper::new(),
projectiles: HashMap::new(),
ships: HashMap::new(),
collision_handler: ChannelEventCollector::new(collision_send, contact_force_send),
@ -79,21 +78,26 @@ impl World {
}
}
pub fn add_ship(&mut self, ct: &content::Ship, outfits: Vec<ShipOutfit>) -> ShipHandle {
pub fn add_ship(
&mut self,
ct: &content::Ship,
outfits: Vec<outfits::ShipOutfit>,
position: Point2<f32>,
) -> ShipHandle {
let rb = RigidBodyBuilder::dynamic()
.translation(vector![0.0, 0.0])
.translation(vector![position.x, position.y])
.can_sleep(false);
let cl = ColliderBuilder::ball(50.0).restitution(0.7).mass(1.0);
let r = self.physics.rigid_body_set.insert(rb.build());
let c = self.physics.collider_set.insert_with_parent(
let r = self.wrapper.rigid_body_set.insert(rb.build());
let c = self.wrapper.collider_set.insert_with_parent(
cl.build(),
r,
&mut self.physics.rigid_body_set,
&mut self.wrapper.rigid_body_set,
);
let h = ShipHandle(r, c);
self.ships.insert(c, Ship::new(ct, outfits, h));
self.ships.insert(c, objects::Ship::new(ct, outfits, h));
return h;
}
@ -106,7 +110,7 @@ impl World {
to_remove.push(s.physics_handle);
continue;
}
let r = &mut self.physics.rigid_body_set[s.physics_handle.0];
let r = &mut self.wrapper.rigid_body_set[s.physics_handle.0];
res.push(s.tick(r, t));
}
for r in to_remove {
@ -119,7 +123,7 @@ impl World {
}
// Update physics
self.physics.step(t, &self.collision_handler);
self.wrapper.step(t, &self.collision_handler);
// Handle collision events
while let Ok(event) = &self.collision_queue.try_recv() {
@ -159,22 +163,22 @@ impl World {
}
pub fn get_rigid_body(&self, r: RigidBodyHandle) -> &RigidBody {
&self.physics.rigid_body_set[r]
&self.wrapper.rigid_body_set[r]
}
pub fn get_ship_mut(&mut self, s: &ShipHandle) -> &mut Ship {
pub fn get_ship_mut(&mut self, s: &ShipHandle) -> &mut objects::Ship {
self.ships.get_mut(&s.1).unwrap()
}
pub fn get_ship_sprites(&self) -> impl Iterator<Item = Sprite> + '_ {
self.ships
.values()
.map(|x| x.get_sprite(&self.physics.rigid_body_set[x.physics_handle.0]))
.map(|x| x.get_sprite(&self.wrapper.rigid_body_set[x.physics_handle.0]))
}
pub fn get_projectile_sprites(&self) -> impl Iterator<Item = Sprite> + '_ {
self.projectiles
.values()
.map(|x| x.get_sprite(&self.physics.rigid_body_set[x.rigid_body]))
.map(|x| x.get_sprite(&self.wrapper.rigid_body_set[x.rigid_body]))
}
}

View File

@ -8,7 +8,7 @@ use rapier2d::{
pipeline::{EventHandler, PhysicsPipeline},
};
pub(super) struct PhysicsWrapper {
pub(super) struct Wrapper {
pub rigid_body_set: RigidBodySet,
pub collider_set: ColliderSet,
@ -22,7 +22,7 @@ pub(super) struct PhysicsWrapper {
pub ccd: CCDSolver,
}
impl PhysicsWrapper {
impl Wrapper {
pub fn new() -> Self {
Self {
rigid_body_set: RigidBodySet::new(),