Compare commits
5 Commits
966ad4e5a4
...
35c6676e95
Author | SHA1 | Date |
---|---|---|
|
35c6676e95 | |
|
901f407068 | |
|
3c0d6786bc | |
|
85da817ed6 | |
|
8ec3ece500 |
|
@ -579,7 +579,6 @@ version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"cgmath",
|
"cgmath",
|
||||||
"galactica-behavior",
|
|
||||||
"galactica-constants",
|
"galactica-constants",
|
||||||
"galactica-content",
|
"galactica-content",
|
||||||
"galactica-gameobject",
|
"galactica-gameobject",
|
||||||
|
@ -590,15 +589,6 @@ dependencies = [
|
||||||
"winit",
|
"winit",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "galactica-behavior"
|
|
||||||
version = "0.0.0"
|
|
||||||
dependencies = [
|
|
||||||
"cgmath",
|
|
||||||
"galactica-content",
|
|
||||||
"galactica-world",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "galactica-constants"
|
name = "galactica-constants"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
|
@ -647,6 +637,7 @@ dependencies = [
|
||||||
"cgmath",
|
"cgmath",
|
||||||
"galactica-constants",
|
"galactica-constants",
|
||||||
"galactica-content",
|
"galactica-content",
|
||||||
|
"galactica-gameobject",
|
||||||
"galactica-packer",
|
"galactica-packer",
|
||||||
"galactica-world",
|
"galactica-world",
|
||||||
"image",
|
"image",
|
||||||
|
|
|
@ -46,7 +46,6 @@ galactica-constants = { path = "crates/constants" }
|
||||||
galactica-content = { path = "crates/content" }
|
galactica-content = { path = "crates/content" }
|
||||||
galactica-render = { path = "crates/render" }
|
galactica-render = { path = "crates/render" }
|
||||||
galactica-world = { path = "crates/world" }
|
galactica-world = { path = "crates/world" }
|
||||||
galactica-behavior = { path = "crates/behavior" }
|
|
||||||
galactica-gameobject = { path = "crates/gameobject" }
|
galactica-gameobject = { path = "crates/gameobject" }
|
||||||
galactica-packer = { path = "crates/packer" }
|
galactica-packer = { path = "crates/packer" }
|
||||||
galactica = { path = "crates/galactica" }
|
galactica = { path = "crates/galactica" }
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "galactica-behavior"
|
|
||||||
description = "AI behaviors for Galactica"
|
|
||||||
categories = { workspace = true }
|
|
||||||
keywords = { workspace = true }
|
|
||||||
version = { workspace = true }
|
|
||||||
rust-version = { workspace = true }
|
|
||||||
authors = { workspace = true }
|
|
||||||
edition = { workspace = true }
|
|
||||||
homepage = { workspace = true }
|
|
||||||
repository = { workspace = true }
|
|
||||||
license = { workspace = true }
|
|
||||||
documentation = { workspace = true }
|
|
||||||
readme = { workspace = true }
|
|
||||||
|
|
||||||
[lints]
|
|
||||||
workspace = true
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
galactica-content = { workspace = true }
|
|
||||||
galactica-world = { workspace = true }
|
|
||||||
cgmath = { workspace = true }
|
|
|
@ -1,23 +0,0 @@
|
||||||
use crate::ShipBehavior;
|
|
||||||
use galactica_content as content;
|
|
||||||
use galactica_world::{ShipPhysicsHandle, World};
|
|
||||||
|
|
||||||
/// Dummy ship behavior.
|
|
||||||
/// Does nothing.
|
|
||||||
pub struct Dummy {
|
|
||||||
_handle: ShipPhysicsHandle,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Dummy {
|
|
||||||
/// Create a new ship controller
|
|
||||||
pub fn new(handle: ShipPhysicsHandle) -> Self {
|
|
||||||
Self { _handle: handle }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ShipBehavior for Dummy {
|
|
||||||
fn update_controls(&mut self, _physics: &mut World, _content: &content::Content) {}
|
|
||||||
fn get_handle(&self) -> ShipPhysicsHandle {
|
|
||||||
return self._handle;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
//! Various implementations of [`crate::ShipBehavior`]
|
|
||||||
|
|
||||||
mod dummy;
|
|
||||||
mod player;
|
|
||||||
mod point;
|
|
||||||
|
|
||||||
pub use dummy::Dummy;
|
|
||||||
pub use player::Player;
|
|
||||||
pub use point::Point;
|
|
|
@ -1,48 +0,0 @@
|
||||||
use crate::ShipBehavior;
|
|
||||||
use galactica_content as content;
|
|
||||||
use galactica_world::{ShipPhysicsHandle, World};
|
|
||||||
|
|
||||||
/// Player ship behavior.
|
|
||||||
/// Controls a ship using controller input
|
|
||||||
pub struct Player {
|
|
||||||
handle: ShipPhysicsHandle,
|
|
||||||
|
|
||||||
/// Turn left
|
|
||||||
pub key_left: bool,
|
|
||||||
|
|
||||||
/// Turn right
|
|
||||||
pub key_right: bool,
|
|
||||||
|
|
||||||
/// Fire guns
|
|
||||||
pub key_guns: bool,
|
|
||||||
|
|
||||||
/// Foward thrust
|
|
||||||
pub key_thrust: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Player {
|
|
||||||
/// Make a new ship controller
|
|
||||||
pub fn new(handle: ShipPhysicsHandle) -> Self {
|
|
||||||
Self {
|
|
||||||
handle,
|
|
||||||
key_left: false,
|
|
||||||
key_right: false,
|
|
||||||
key_guns: false,
|
|
||||||
key_thrust: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ShipBehavior for Player {
|
|
||||||
fn update_controls(&mut self, physics: &mut World, _content: &content::Content) {
|
|
||||||
let s = physics.get_ship_mut(&self.handle).unwrap();
|
|
||||||
s.controls.left = self.key_left;
|
|
||||||
s.controls.right = self.key_right;
|
|
||||||
s.controls.guns = self.key_guns;
|
|
||||||
s.controls.thrust = self.key_thrust;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_handle(&self) -> ShipPhysicsHandle {
|
|
||||||
return self.handle;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
#![warn(missing_docs)]
|
|
||||||
|
|
||||||
//! Computer-controlled ship behaviors
|
|
||||||
|
|
||||||
pub mod behavior;
|
|
||||||
|
|
||||||
use galactica_content as content;
|
|
||||||
use galactica_world::{ShipPhysicsHandle, World};
|
|
||||||
|
|
||||||
/// Main behavior trait. Any struct that implements this
|
|
||||||
/// may be used to control a ship.
|
|
||||||
pub trait ShipBehavior
|
|
||||||
where
|
|
||||||
Self: Send,
|
|
||||||
{
|
|
||||||
/// Update a ship's controls based on world state
|
|
||||||
fn update_controls(&mut self, physics: &mut World, content: &content::Content);
|
|
||||||
|
|
||||||
/// Get the ship this behavior is attached to
|
|
||||||
fn get_handle(&self) -> ShipPhysicsHandle;
|
|
||||||
}
|
|
|
@ -272,6 +272,11 @@ impl Content {
|
||||||
|
|
||||||
// Access methods
|
// Access methods
|
||||||
impl Content {
|
impl Content {
|
||||||
|
/// Iterate over all valid system handles
|
||||||
|
pub fn iter_systems(&self) -> impl Iterator<Item = SystemHandle> {
|
||||||
|
(0..self.systems.len()).map(|x| SystemHandle { index: x })
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the handle for the starfield sprite
|
/// Get the handle for the starfield sprite
|
||||||
pub fn get_starfield_handle(&self) -> SpriteHandle {
|
pub fn get_starfield_handle(&self) -> SpriteHandle {
|
||||||
match self.starfield_handle {
|
match self.starfield_handle {
|
||||||
|
|
|
@ -25,7 +25,6 @@ galactica-content = { workspace = true }
|
||||||
galactica-render = { workspace = true }
|
galactica-render = { workspace = true }
|
||||||
galactica-constants = { workspace = true }
|
galactica-constants = { workspace = true }
|
||||||
galactica-world = { workspace = true }
|
galactica-world = { workspace = true }
|
||||||
galactica-behavior = { workspace = true }
|
|
||||||
galactica-gameobject = { workspace = true }
|
galactica-gameobject = { workspace = true }
|
||||||
|
|
||||||
winit = { workspace = true }
|
winit = { workspace = true }
|
||||||
|
|
|
@ -1,23 +1,23 @@
|
||||||
use cgmath::Point2;
|
use object::{
|
||||||
use content::Ship;
|
ship::{OutfitSet, ShipPersonality},
|
||||||
use object::{ship::ShipPersonality, GameData};
|
GameData, GameShipHandle,
|
||||||
|
};
|
||||||
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 crate::camera::Camera;
|
use crate::camera::Camera;
|
||||||
use crate::inputstatus::InputStatus;
|
use crate::inputstatus::InputStatus;
|
||||||
|
|
||||||
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::RenderState;
|
use galactica_render::RenderState;
|
||||||
use galactica_world::{util, ParticleBuilder, ShipPhysicsHandle, World};
|
use galactica_world::{objects::ShipControls, ParticleBuilder, StepResources, World};
|
||||||
|
|
||||||
pub struct Game {
|
pub struct Game {
|
||||||
input: InputStatus,
|
input: InputStatus,
|
||||||
last_update: Instant,
|
last_update: Instant,
|
||||||
player: ShipPhysicsHandle,
|
player: GameShipHandle,
|
||||||
paused: bool,
|
paused: bool,
|
||||||
time_scale: f32,
|
time_scale: f32,
|
||||||
start_instant: Instant,
|
start_instant: Instant,
|
||||||
|
@ -25,9 +25,6 @@ pub struct Game {
|
||||||
|
|
||||||
// TODO: include system in world
|
// TODO: include system in world
|
||||||
//system: object::System,
|
//system: object::System,
|
||||||
shipbehaviors: Vec<Box<dyn ShipBehavior>>,
|
|
||||||
playerbehavior: behavior::Player,
|
|
||||||
|
|
||||||
gamedata: GameData,
|
gamedata: GameData,
|
||||||
|
|
||||||
content: content::Content,
|
content: content::Content,
|
||||||
|
@ -37,28 +34,28 @@ pub struct Game {
|
||||||
|
|
||||||
impl Game {
|
impl Game {
|
||||||
pub fn new(ct: content::Content) -> Self {
|
pub fn new(ct: content::Content) -> Self {
|
||||||
let mut physics = World::new();
|
let mut gamedata = GameData::new(&ct);
|
||||||
let mut gamedata = GameData::new();
|
|
||||||
|
|
||||||
let ss = ct.get_ship(content::ShipHandle { index: 0 });
|
let ss = ct.get_ship(content::ShipHandle { index: 0 });
|
||||||
|
|
||||||
let mut o1 = object::OutfitSet::new(ss);
|
let mut o1 = OutfitSet::new(ss.space, &[]);
|
||||||
o1.add(&ct, content::OutfitHandle { index: 0 });
|
o1.add(&ct.get_outfit(content::OutfitHandle { index: 0 }));
|
||||||
o1.add(&ct, content::OutfitHandle { index: 1 });
|
o1.add(&ct.get_outfit(content::OutfitHandle { index: 1 }));
|
||||||
o1.add_gun(&ct, content::GunHandle { index: 0 });
|
//o1.add_gun(&ct, content::GunHandle { index: 0 });
|
||||||
o1.add_gun(&ct, content::GunHandle { index: 0 });
|
//o1.add_gun(&ct, content::GunHandle { index: 0 });
|
||||||
o1.add_gun(&ct, content::GunHandle { index: 0 });
|
//o1.add_gun(&ct, content::GunHandle { index: 0 });
|
||||||
|
|
||||||
let mut o1 = object::OutfitSet::new(ss);
|
let mut o1 = OutfitSet::new(ss.space, &[]);
|
||||||
o1.add(&ct, content::OutfitHandle { index: 0 });
|
o1.add(&ct.get_outfit(content::OutfitHandle { index: 0 }));
|
||||||
o1.add_gun(&ct, content::GunHandle { index: 0 });
|
//o1.add_gun(&ct, content::GunHandle { index: 0 });
|
||||||
|
|
||||||
gamedata.create_ship(
|
let player = gamedata.create_ship(
|
||||||
&ct,
|
&ct,
|
||||||
content::ShipHandle { index: 0 },
|
content::ShipHandle { index: 0 },
|
||||||
content::FactionHandle { index: 0 },
|
content::FactionHandle { index: 0 },
|
||||||
ShipPersonality::Player,
|
ShipPersonality::Player,
|
||||||
o1,
|
o1.clone(),
|
||||||
|
&content::SystemHandle { index: 0 },
|
||||||
);
|
);
|
||||||
|
|
||||||
gamedata.create_ship(
|
gamedata.create_ship(
|
||||||
|
@ -66,7 +63,8 @@ impl Game {
|
||||||
content::ShipHandle { index: 0 },
|
content::ShipHandle { index: 0 },
|
||||||
content::FactionHandle { index: 1 },
|
content::FactionHandle { index: 1 },
|
||||||
ShipPersonality::Dummy,
|
ShipPersonality::Dummy,
|
||||||
object::OutfitSet::new(ss),
|
OutfitSet::new(ss.space, &[]),
|
||||||
|
&content::SystemHandle { index: 0 },
|
||||||
);
|
);
|
||||||
|
|
||||||
gamedata.create_ship(
|
gamedata.create_ship(
|
||||||
|
@ -75,16 +73,15 @@ impl Game {
|
||||||
content::FactionHandle { index: 1 },
|
content::FactionHandle { index: 1 },
|
||||||
ShipPersonality::Point,
|
ShipPersonality::Point,
|
||||||
o1,
|
o1,
|
||||||
|
&content::SystemHandle { index: 0 },
|
||||||
);
|
);
|
||||||
|
|
||||||
//let mut shipbehaviors: Vec<Box<dyn ShipBehavior>> = Vec::new();
|
let physics = World::new(&ct, &gamedata, content::SystemHandle { index: 0 });
|
||||||
//shipbehaviors.push(Box::new(behavior::Dummy::new(h2)));
|
|
||||||
//shipbehaviors.push(Box::new(behavior::Point::new(h3)));
|
|
||||||
|
|
||||||
Game {
|
Game {
|
||||||
last_update: Instant::now(),
|
last_update: Instant::now(),
|
||||||
input: InputStatus::new(),
|
input: InputStatus::new(),
|
||||||
player: h1,
|
player,
|
||||||
start_instant: Instant::now(),
|
start_instant: Instant::now(),
|
||||||
|
|
||||||
camera: Camera {
|
camera: Camera {
|
||||||
|
@ -96,10 +93,8 @@ impl Game {
|
||||||
paused: false,
|
paused: false,
|
||||||
time_scale: 1.0,
|
time_scale: 1.0,
|
||||||
world: physics,
|
world: physics,
|
||||||
shipbehaviors,
|
|
||||||
gamedata,
|
gamedata,
|
||||||
content: ct,
|
content: ct,
|
||||||
playerbehavior: behavior::Player::new(h1),
|
|
||||||
new_particles: Vec::new(),
|
new_particles: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -132,39 +127,27 @@ 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.playerbehavior.key_guns = self.input.key_guns;
|
let world_output = self.world.step(StepResources {
|
||||||
self.playerbehavior.key_thrust = self.input.key_thrust;
|
player: self.player,
|
||||||
self.playerbehavior.key_right = self.input.key_right;
|
player_controls: ShipControls {
|
||||||
self.playerbehavior.key_left = self.input.key_left;
|
left: self.input.key_left,
|
||||||
self.playerbehavior
|
right: self.input.key_right,
|
||||||
.update_controls(&mut self.world, &self.content);
|
thrust: self.input.key_thrust,
|
||||||
|
guns: self.input.key_guns,
|
||||||
self.shipbehaviors.retain_mut(|b| {
|
},
|
||||||
// Remove shipbehaviors of destroyed ships
|
ct: &self.content,
|
||||||
if self.world.get_ship_mut(&b.get_handle()).is_none() {
|
dt: &mut self.gamedata,
|
||||||
false
|
particles: &mut self.new_particles,
|
||||||
} else {
|
t,
|
||||||
b.update_controls(&mut self.world, &self.content);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
self.world.step(t, &self.content, &mut self.new_particles);
|
|
||||||
|
|
||||||
if self.input.v_scroll != 0.0 {
|
if self.input.v_scroll != 0.0 {
|
||||||
self.camera.zoom = (self.camera.zoom + self.input.v_scroll)
|
self.camera.zoom = (self.camera.zoom + self.input.v_scroll)
|
||||||
.clamp(galactica_constants::ZOOM_MIN, galactica_constants::ZOOM_MAX);
|
.clamp(galactica_constants::ZOOM_MIN, galactica_constants::ZOOM_MAX);
|
||||||
self.input.v_scroll = 0.0;
|
self.input.v_scroll = 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
let r = self
|
self.camera.pos = world_output.player_position;
|
||||||
.world
|
|
||||||
.get_ship_mut(&self.player)
|
|
||||||
.unwrap()
|
|
||||||
.physics_handle;
|
|
||||||
let r = self.world.get_rigid_body(r.0).unwrap(); // TODO: r.0 shouldn't be public
|
|
||||||
let ship_pos = util::rigidbody_position(r);
|
|
||||||
self.camera.pos = ship_pos;
|
|
||||||
self.last_update = Instant::now();
|
self.last_update = Instant::now();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,7 +159,8 @@ impl Game {
|
||||||
content: &self.content,
|
content: &self.content,
|
||||||
world: &self.world,
|
world: &self.world,
|
||||||
particles: &mut self.new_particles,
|
particles: &mut self.new_particles,
|
||||||
player: &self.player,
|
player_data: self.player,
|
||||||
|
data: &self.gamedata,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,21 +5,33 @@ use crate::{
|
||||||
ship::Ship,
|
ship::Ship,
|
||||||
ship::{OutfitSet, ShipPersonality},
|
ship::{OutfitSet, ShipPersonality},
|
||||||
};
|
};
|
||||||
use content::ShipHandle;
|
|
||||||
use galactica_content as content;
|
use galactica_content as content;
|
||||||
|
|
||||||
/// Keeps track of all objects in the galaxy.
|
/// Keeps track of all objects in the galaxy.
|
||||||
/// This struct does NO physics, it keeps track of data exclusively.
|
/// This struct does NO physics, it keeps track of data exclusively.
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct GameData {
|
pub struct GameData {
|
||||||
// Universal counter.
|
/// Universal counter.
|
||||||
// Used to create unique handles for game objects.
|
/// Used to create unique handles for game objects.
|
||||||
index: u64,
|
index: u64,
|
||||||
|
|
||||||
|
/// All ships in the galaxy
|
||||||
ships: HashMap<GameShipHandle, Ship>,
|
ships: HashMap<GameShipHandle, Ship>,
|
||||||
|
|
||||||
|
/// Ships indexed by the system they're in.
|
||||||
|
/// A ship must always be in exactly one system.
|
||||||
|
system_ship_table: HashMap<content::SystemHandle, Vec<GameShipHandle>>,
|
||||||
|
|
||||||
|
/// Systems indexed by which ships they contain.
|
||||||
|
/// A ship must always be in exactly one system.
|
||||||
|
ship_system_table: HashMap<GameShipHandle, content::SystemHandle>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GameData {
|
impl GameData {
|
||||||
pub fn new() -> Self {
|
pub fn new(ct: &content::Content) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
system_ship_table: ct.iter_systems().map(|s| (s, Vec::new())).collect(),
|
||||||
|
ship_system_table: HashMap::new(),
|
||||||
index: 0,
|
index: 0,
|
||||||
ships: HashMap::new(),
|
ships: HashMap::new(),
|
||||||
}
|
}
|
||||||
|
@ -29,19 +41,40 @@ impl GameData {
|
||||||
pub fn create_ship(
|
pub fn create_ship(
|
||||||
&mut self,
|
&mut self,
|
||||||
ct: &content::Content,
|
ct: &content::Content,
|
||||||
ship: ShipHandle,
|
ship: content::ShipHandle,
|
||||||
faction: content::FactionHandle,
|
faction: content::FactionHandle,
|
||||||
personality: ShipPersonality,
|
personality: ShipPersonality,
|
||||||
outfits: OutfitSet,
|
outfits: OutfitSet,
|
||||||
|
system: &content::SystemHandle,
|
||||||
) -> GameShipHandle {
|
) -> GameShipHandle {
|
||||||
let handle = GameShipHandle { index: self.index };
|
let handle = GameShipHandle {
|
||||||
|
index: self.index,
|
||||||
|
content: ship,
|
||||||
|
};
|
||||||
self.index += 1;
|
self.index += 1;
|
||||||
self.ships
|
|
||||||
.insert(handle, Ship::new(ct, ship, faction, personality, outfits));
|
self.ships.insert(
|
||||||
|
handle,
|
||||||
|
Ship::new(ct, handle, ship, faction, personality, outfits),
|
||||||
|
);
|
||||||
|
self.system_ship_table.get_mut(system).unwrap().push(handle);
|
||||||
|
self.ship_system_table.insert(handle, *system);
|
||||||
|
|
||||||
return handle;
|
return handle;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Public getters
|
||||||
|
impl GameData {
|
||||||
pub fn get_ship(&self, handle: GameShipHandle) -> Option<&Ship> {
|
pub fn get_ship(&self, handle: GameShipHandle) -> Option<&Ship> {
|
||||||
self.ships.get(&handle)
|
self.ships.get(&handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_ship_mut(&mut self, handle: GameShipHandle) -> Option<&mut Ship> {
|
||||||
|
self.ships.get_mut(&handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter_ships(&self) -> impl Iterator<Item = &Ship> {
|
||||||
|
self.ships.values()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,33 @@
|
||||||
|
use std::hash::Hash;
|
||||||
|
|
||||||
|
use galactica_content::ShipHandle;
|
||||||
|
|
||||||
/// A lightweight representation of a ship in the galaxy
|
/// A lightweight representation of a ship in the galaxy
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct GameShipHandle {
|
pub struct GameShipHandle {
|
||||||
|
/// This ship's unique index
|
||||||
pub(crate) index: u64,
|
pub(crate) index: u64,
|
||||||
|
|
||||||
|
/// This ship's content handle
|
||||||
|
/// (technically redundant, but this greatly simplifies code)
|
||||||
|
pub(crate) content: ShipHandle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GameShipHandle {
|
||||||
|
pub fn content_handle(&self) -> ShipHandle {
|
||||||
|
self.content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Hash for GameShipHandle {
|
||||||
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
self.index.hash(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for GameShipHandle {}
|
||||||
|
impl PartialEq for GameShipHandle {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.index.eq(&other.index)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
use galactica_content as content;
|
|
||||||
|
|
||||||
// TODO: remove. projectiles only exist in physics
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Projectile {
|
|
||||||
pub content: content::Projectile,
|
|
||||||
pub lifetime: f32,
|
|
||||||
pub faction: content::FactionHandle,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Projectile {
|
|
||||||
pub fn tick(&mut self, t: f32) {
|
|
||||||
self.lifetime -= t;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_expired(&self) -> bool {
|
|
||||||
return self.lifetime < 0.0;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -29,7 +29,7 @@ pub enum OutfitRemoveResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A simple data class, used to keep track of delayed shield generators
|
/// A simple data class, used to keep track of delayed shield generators
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub(crate) struct ShieldGenerator {
|
pub(crate) struct ShieldGenerator {
|
||||||
pub outfit: OutfitHandle,
|
pub outfit: OutfitHandle,
|
||||||
pub delay: f32,
|
pub delay: f32,
|
||||||
|
@ -37,7 +37,7 @@ pub(crate) struct ShieldGenerator {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This struct keeps track of a ship's outfit loadout.
|
/// This struct keeps track of a ship's outfit loadout.
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct OutfitSet {
|
pub struct OutfitSet {
|
||||||
/// What outfits does this statsum contain?
|
/// What outfits does this statsum contain?
|
||||||
outfits: HashMap<OutfitHandle, u32>,
|
outfits: HashMap<OutfitHandle, u32>,
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
|
use crate::GameShipHandle;
|
||||||
|
|
||||||
use super::{OutfitSet, ShipPersonality};
|
use super::{OutfitSet, ShipPersonality};
|
||||||
use galactica_content as content;
|
use galactica_content as content;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Ship {
|
pub struct Ship {
|
||||||
// Metadata values
|
// Metadata values
|
||||||
|
handle: GameShipHandle,
|
||||||
ct_handle: content::ShipHandle,
|
ct_handle: content::ShipHandle,
|
||||||
faction: content::FactionHandle,
|
faction: content::FactionHandle,
|
||||||
outfits: OutfitSet,
|
outfits: OutfitSet,
|
||||||
|
@ -25,6 +28,7 @@ pub struct Ship {
|
||||||
impl Ship {
|
impl Ship {
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
ct: &content::Content,
|
ct: &content::Content,
|
||||||
|
handle: GameShipHandle,
|
||||||
ct_handle: content::ShipHandle,
|
ct_handle: content::ShipHandle,
|
||||||
faction: content::FactionHandle,
|
faction: content::FactionHandle,
|
||||||
personality: ShipPersonality,
|
personality: ShipPersonality,
|
||||||
|
@ -33,6 +37,7 @@ impl Ship {
|
||||||
let s = ct.get_ship(ct_handle);
|
let s = ct.get_ship(ct_handle);
|
||||||
let shields = outfits.get_shield_strength();
|
let shields = outfits.get_shield_strength();
|
||||||
Ship {
|
Ship {
|
||||||
|
handle,
|
||||||
ct_handle,
|
ct_handle,
|
||||||
faction,
|
faction,
|
||||||
outfits,
|
outfits,
|
||||||
|
@ -84,10 +89,20 @@ impl Ship {
|
||||||
|
|
||||||
// Misc getters, so internal state is untouchable
|
// Misc getters, so internal state is untouchable
|
||||||
impl Ship {
|
impl Ship {
|
||||||
|
/// Get a handle to this ship game object
|
||||||
|
pub fn get_handle(&self) -> GameShipHandle {
|
||||||
|
self.handle
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a handle to this ship's content
|
||||||
|
pub fn get_content(&self) -> content::ShipHandle {
|
||||||
|
self.ct_handle
|
||||||
|
}
|
||||||
|
|
||||||
/// Get this ship's current hull.
|
/// Get this ship's current hull.
|
||||||
/// Use content handle to get maximum hull
|
/// Use content handle to get maximum hull
|
||||||
pub fn get_hull(&self) -> f32 {
|
pub fn get_hull(&self) -> f32 {
|
||||||
return self.hull;
|
self.hull
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get this ship's current shields.
|
/// Get this ship's current shields.
|
||||||
|
|
|
@ -21,6 +21,7 @@ galactica-content = { workspace = true }
|
||||||
galactica-constants = { workspace = true }
|
galactica-constants = { workspace = true }
|
||||||
galactica-packer = { workspace = true }
|
galactica-packer = { workspace = true }
|
||||||
galactica-world = { workspace = true }
|
galactica-world = { workspace = true }
|
||||||
|
galactica-gameobject = { workspace = true }
|
||||||
|
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
cgmath = { workspace = true }
|
cgmath = { workspace = true }
|
||||||
|
|
|
@ -22,7 +22,12 @@ impl GPUState {
|
||||||
//let system_object_scale = 1.0 / 600.0;
|
//let system_object_scale = 1.0 / 600.0;
|
||||||
let ship_scale = 1.0 / 10.0;
|
let ship_scale = 1.0 / 10.0;
|
||||||
|
|
||||||
let (_, player_body) = state.world.get_ship_body(*state.player).unwrap();
|
let player_world_object = state.world.get_ship(state.player_data).unwrap();
|
||||||
|
let player_body = state
|
||||||
|
.world
|
||||||
|
.get_rigid_body(player_world_object.rigid_body)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let player_position = util::rigidbody_position(player_body);
|
let player_position = util::rigidbody_position(player_body);
|
||||||
//let planet_sprite = state.content.get_sprite_handle("ui::planetblip");
|
//let planet_sprite = state.content.get_sprite_handle("ui::planetblip");
|
||||||
let ship_sprite = state.content.get_sprite_handle("ui::shipblip");
|
let ship_sprite = state.content.get_sprite_handle("ui::shipblip");
|
||||||
|
@ -88,7 +93,8 @@ impl GPUState {
|
||||||
|
|
||||||
// Draw ships
|
// Draw ships
|
||||||
for (s, r) in state.world.iter_ship_body() {
|
for (s, r) in state.world.iter_ship_body() {
|
||||||
let ship = state.content.get_ship(s.ship.handle);
|
let data = state.data.get_ship(s.data_handle).unwrap();
|
||||||
|
let ship = state.content.get_ship(s.data_handle.content_handle());
|
||||||
let size = (ship.size * ship.sprite.aspect) * ship_scale;
|
let size = (ship.size * ship.sprite.aspect) * ship_scale;
|
||||||
let p = util::rigidbody_position(r);
|
let p = util::rigidbody_position(r);
|
||||||
let d = (p - player_position) / radar_range;
|
let d = (p - player_position) / radar_range;
|
||||||
|
@ -99,7 +105,7 @@ impl GPUState {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let angle = util::rigidbody_rotation(r).angle(Vector2 { x: 0.0, y: 1.0 });
|
let angle = util::rigidbody_rotation(r).angle(Vector2 { x: 0.0, y: 1.0 });
|
||||||
let f = state.content.get_faction(s.ship.faction).color;
|
let f = state.content.get_faction(data.get_faction()).color;
|
||||||
let f = [f[0], f[1], f[2], 1.0];
|
let f = [f[0], f[1], f[2], 1.0];
|
||||||
|
|
||||||
let position = Point2 {
|
let position = Point2 {
|
||||||
|
@ -264,11 +270,16 @@ impl GPUState {
|
||||||
panic!("UI limit exceeded!")
|
panic!("UI limit exceeded!")
|
||||||
}
|
}
|
||||||
|
|
||||||
let (s, _) = state.world.get_ship_body(*state.player).unwrap();
|
let player_world_object = state.world.get_ship(state.player_data).unwrap();
|
||||||
let max_shields = s.ship.outfits.stat_sum().shield_strength;
|
|
||||||
let current_shields = s.ship.shields;
|
let data = state
|
||||||
let current_hull = s.ship.hull;
|
.data
|
||||||
let max_hull = state.content.get_ship(s.ship.handle).hull;
|
.get_ship(player_world_object.data_handle)
|
||||||
|
.unwrap();
|
||||||
|
let max_shields = data.get_outfits().get_shield_strength();
|
||||||
|
let current_shields = data.get_shields();
|
||||||
|
let current_hull = data.get_hull();
|
||||||
|
let max_hull = state.content.get_ship(data.get_content()).hull;
|
||||||
|
|
||||||
self.queue.write_buffer(
|
self.queue.write_buffer(
|
||||||
&self.vertex_buffers.ui.instances,
|
&self.vertex_buffers.ui.instances,
|
||||||
|
|
|
@ -21,11 +21,12 @@ impl GPUState {
|
||||||
screen_clip: (Point2<f32>, Point2<f32>),
|
screen_clip: (Point2<f32>, Point2<f32>),
|
||||||
s: &ShipWorldObject,
|
s: &ShipWorldObject,
|
||||||
) {
|
) {
|
||||||
let (_, r) = state.world.get_ship_body(s.physics_handle).unwrap();
|
let r = state.world.get_rigid_body(s.rigid_body).unwrap();
|
||||||
|
let ship = state.data.get_ship(s.data_handle);
|
||||||
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);
|
||||||
let ship_ang = -ship_rot.angle(Vector2 { x: 0.0, y: 1.0 }); // TODO: inconsistent angles. Fix!
|
let ship_ang = -ship_rot.angle(Vector2 { x: 0.0, y: 1.0 }); // TODO: inconsistent angles. Fix!
|
||||||
let ship_cnt = state.content.get_ship(s.ship.handle);
|
let ship_cnt = state.content.get_ship(s.data_handle.content_handle());
|
||||||
|
|
||||||
// Position adjusted for parallax
|
// Position adjusted for parallax
|
||||||
// TODO: adjust parallax for zoom?
|
// TODO: adjust parallax for zoom?
|
||||||
|
@ -84,8 +85,10 @@ impl GPUState {
|
||||||
);
|
);
|
||||||
self.vertex_buffers.object_counter += 1;
|
self.vertex_buffers.object_counter += 1;
|
||||||
|
|
||||||
|
/*
|
||||||
// Draw engine flares if necessary
|
// Draw engine flares if necessary
|
||||||
if s.controls.thrust && !s.ship.is_dead() {
|
//if s.controls.thrust && !s.ship.is_dead() {
|
||||||
|
if s.controls.thrust {
|
||||||
for f in s.ship.outfits.iter_enginepoints() {
|
for f in s.ship.outfits.iter_enginepoints() {
|
||||||
let flare = match s.ship.outfits.get_flare_sprite() {
|
let flare = match s.ship.outfits.get_flare_sprite() {
|
||||||
None => continue,
|
None => continue,
|
||||||
|
@ -126,6 +129,7 @@ impl GPUState {
|
||||||
self.vertex_buffers.object_counter += 1;
|
self.vertex_buffers.object_counter += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn world_push_projectile(
|
pub(super) fn world_push_projectile(
|
||||||
|
@ -139,7 +143,7 @@ impl GPUState {
|
||||||
let proj_pos = util::rigidbody_position(&r);
|
let proj_pos = util::rigidbody_position(&r);
|
||||||
let proj_rot = util::rigidbody_rotation(r);
|
let proj_rot = util::rigidbody_rotation(r);
|
||||||
let proj_ang = -proj_rot.angle(Vector2 { x: 1.0, y: 0.0 });
|
let proj_ang = -proj_rot.angle(Vector2 { x: 1.0, y: 0.0 });
|
||||||
let proj_cnt = &p.projectile.content; // TODO: don't clone this?
|
let proj_cnt = &p.content; // TODO: don't clone this?
|
||||||
|
|
||||||
// Position adjusted for parallax
|
// Position adjusted for parallax
|
||||||
// TODO: adjust parallax for zoom?
|
// TODO: adjust parallax for zoom?
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
use cgmath::Point2;
|
use cgmath::Point2;
|
||||||
use galactica_content::Content;
|
use galactica_content::Content;
|
||||||
use galactica_world::{ParticleBuilder, ShipPhysicsHandle, World};
|
use galactica_gameobject::{GameData, GameShipHandle};
|
||||||
|
use galactica_world::{ParticleBuilder, World};
|
||||||
|
|
||||||
/// Bundles parameters passed to a single call to GPUState::render
|
/// Bundles parameters passed to a single call to GPUState::render
|
||||||
pub struct RenderState<'a> {
|
pub struct RenderState<'a> {
|
||||||
/// Camera position, in world units
|
/// Camera position, in world units
|
||||||
pub camera_pos: Point2<f32>,
|
pub camera_pos: Point2<f32>,
|
||||||
|
|
||||||
/// Player ship
|
/// Player ship data
|
||||||
pub player: &'a ShipPhysicsHandle,
|
pub player_data: GameShipHandle,
|
||||||
|
|
||||||
/// Height of screen, in world units
|
/// Height of screen, in world units
|
||||||
pub camera_zoom: f32,
|
pub camera_zoom: f32,
|
||||||
|
@ -23,6 +24,9 @@ pub struct RenderState<'a> {
|
||||||
/// Game content
|
/// Game content
|
||||||
pub content: &'a Content,
|
pub content: &'a Content,
|
||||||
|
|
||||||
|
/// Game data
|
||||||
|
pub data: &'a GameData,
|
||||||
|
|
||||||
/// Particles to spawn during this frame
|
/// Particles to spawn during this frame
|
||||||
pub particles: &'a mut Vec<ParticleBuilder>,
|
pub particles: &'a mut Vec<ParticleBuilder>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
//! Various implementations of [`crate::ShipBehavior`]
|
||||||
|
|
||||||
|
mod null;
|
||||||
|
//mod point;
|
||||||
|
|
||||||
|
pub use null::*;
|
||||||
|
//pub use point::Point;
|
||||||
|
|
||||||
|
use crate::{objects::ShipControls, StepResources};
|
||||||
|
|
||||||
|
/// Main behavior trait. Any struct that implements this
|
||||||
|
/// may be used to control a ship.
|
||||||
|
pub trait ShipBehavior {
|
||||||
|
/// Update a ship's controls based on world state.
|
||||||
|
/// This method does not return anything, it modifies
|
||||||
|
/// the ship's controls in-place.
|
||||||
|
fn update_controls(&mut self, res: &StepResources) -> ShipControls;
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
use super::ShipBehavior;
|
||||||
|
use crate::{objects::ShipControls, StepResources};
|
||||||
|
|
||||||
|
/// The Null behaviors is assigned to objects that are not controlled by the computer.
|
||||||
|
/// Most notably, the player's ship has a Null behavior.
|
||||||
|
pub struct Null {}
|
||||||
|
|
||||||
|
impl Null {
|
||||||
|
/// Create a new ship controller
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ShipBehavior for Null {
|
||||||
|
fn update_controls(&mut self, _res: &StepResources) -> ShipControls {
|
||||||
|
ShipControls::new()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,24 +1,30 @@
|
||||||
use cgmath::{Deg, InnerSpace};
|
use cgmath::{Deg, InnerSpace};
|
||||||
|
use galactica_gameobject::GameData;
|
||||||
|
|
||||||
use crate::ShipBehavior;
|
|
||||||
use galactica_content as content;
|
use galactica_content as content;
|
||||||
use galactica_world::{util, ShipPhysicsHandle, World};
|
|
||||||
|
use crate::World;
|
||||||
|
|
||||||
|
use super::ShipBehavior;
|
||||||
|
|
||||||
/// "Point" ship behavior.
|
/// "Point" ship behavior.
|
||||||
/// Point and shoot towards the nearest enemy.
|
/// Point and shoot towards the nearest enemy.
|
||||||
pub struct Point {
|
pub struct Point {}
|
||||||
handle: ShipPhysicsHandle,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Point {
|
impl Point {
|
||||||
/// Create a new ship controller
|
/// Create a new ship controller
|
||||||
pub fn new(handle: ShipPhysicsHandle) -> Self {
|
pub fn new() -> Self {
|
||||||
Self { handle }
|
Self {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ShipBehavior for Point {
|
impl ShipBehavior for Point {
|
||||||
fn update_controls(&mut self, physics: &mut World, content: &content::Content) {
|
fn update_controls(
|
||||||
|
&mut self,
|
||||||
|
physics: &mut World,
|
||||||
|
content: &content::Content,
|
||||||
|
data: &GameData,
|
||||||
|
) {
|
||||||
// Turn off all controls
|
// Turn off all controls
|
||||||
let s = physics.get_ship_mut(&self.handle).unwrap();
|
let s = physics.get_ship_mut(&self.handle).unwrap();
|
||||||
s.controls.left = false;
|
s.controls.left = false;
|
||||||
|
@ -27,20 +33,22 @@ impl ShipBehavior for Point {
|
||||||
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_data = data.get_ship(my_s.data_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();
|
||||||
let my_faction = content.get_faction(my_s.ship.faction);
|
let my_faction = content.get_faction(my_data.get_faction());
|
||||||
|
|
||||||
// Iterate all possible targets
|
// Iterate all possible targets
|
||||||
let mut it = physics
|
let mut it = physics
|
||||||
.iter_ship_body()
|
.iter_ship_body()
|
||||||
.filter(
|
.filter(|(s, _)| {
|
||||||
|(s, _)| match my_faction.relationships.get(&s.ship.faction).unwrap() {
|
let data = data.get_ship(s.data_handle).unwrap();
|
||||||
|
match my_faction.relationships.get(&data.get_faction()).unwrap() {
|
||||||
content::Relationship::Hostile => true,
|
content::Relationship::Hostile => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
},
|
}
|
||||||
)
|
})
|
||||||
.map(|(_, r)| r);
|
.map(|(_, r)| r);
|
||||||
|
|
||||||
// Find the closest target
|
// Find the closest target
|
||||||
|
@ -75,8 +83,4 @@ impl ShipBehavior for Point {
|
||||||
s.controls.guns = true;
|
s.controls.guns = true;
|
||||||
s.controls.thrust = false;
|
s.controls.thrust = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_handle(&self) -> ShipPhysicsHandle {
|
|
||||||
return self.handle;
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -3,102 +3,14 @@
|
||||||
//! This module keeps track of the visible world.
|
//! This module keeps track of the visible world.
|
||||||
//! Ships, projectiles, collisions, etc.
|
//! Ships, projectiles, collisions, etc.
|
||||||
|
|
||||||
|
pub mod behavior;
|
||||||
pub mod objects;
|
pub mod objects;
|
||||||
|
mod particlebuilder;
|
||||||
|
mod stepresources;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
mod world;
|
mod world;
|
||||||
mod wrapper;
|
mod wrapper;
|
||||||
|
|
||||||
use cgmath::{Matrix2, Point2, Rad, Vector2};
|
pub use particlebuilder::*;
|
||||||
use galactica_content as content;
|
pub use stepresources::*;
|
||||||
use rand::Rng;
|
|
||||||
pub use world::World;
|
pub use world::World;
|
||||||
|
|
||||||
use rapier2d::{dynamics::RigidBodyHandle, geometry::ColliderHandle};
|
|
||||||
|
|
||||||
/// A lightweight handle for a specific ship in our physics system
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
||||||
pub struct ShipPhysicsHandle(pub RigidBodyHandle, ColliderHandle);
|
|
||||||
|
|
||||||
/// Instructions to create a new particle
|
|
||||||
pub struct ParticleBuilder {
|
|
||||||
/// The sprite to use for this particle
|
|
||||||
pub sprite: content::SpriteHandle,
|
|
||||||
|
|
||||||
/// This object's center, in world coordinates.
|
|
||||||
pub pos: Point2<f32>,
|
|
||||||
|
|
||||||
/// This particle's velocity, in world coordinates
|
|
||||||
pub velocity: Vector2<f32>,
|
|
||||||
|
|
||||||
/// This particle's angle
|
|
||||||
pub angle: Rad<f32>,
|
|
||||||
|
|
||||||
/// This particle's angular velocity (rad/sec)
|
|
||||||
pub angvel: Rad<f32>,
|
|
||||||
|
|
||||||
/// This particle's lifetime, in seconds
|
|
||||||
pub lifetime: f32,
|
|
||||||
|
|
||||||
/// The size of this particle,
|
|
||||||
/// given as height in world units.
|
|
||||||
pub size: f32,
|
|
||||||
|
|
||||||
/// Fade this particle out over this many seconds as it expires
|
|
||||||
pub fade: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ParticleBuilder {
|
|
||||||
/// Create a ParticleBuilder from an Effect
|
|
||||||
pub fn from_content(
|
|
||||||
effect: &content::Effect,
|
|
||||||
pos: Point2<f32>,
|
|
||||||
parent_angle: Rad<f32>,
|
|
||||||
parent_velocity: Vector2<f32>,
|
|
||||||
target_velocity: Vector2<f32>,
|
|
||||||
) -> Self {
|
|
||||||
let mut rng = rand::thread_rng();
|
|
||||||
|
|
||||||
let velocity = {
|
|
||||||
let a =
|
|
||||||
rng.gen_range(-effect.velocity_scale_parent_rng..=effect.velocity_scale_parent_rng);
|
|
||||||
let b =
|
|
||||||
rng.gen_range(-effect.velocity_scale_target_rng..=effect.velocity_scale_target_rng);
|
|
||||||
|
|
||||||
let velocity = ((effect.velocity_scale_parent + a) * parent_velocity)
|
|
||||||
+ ((effect.velocity_scale_target + b) * target_velocity);
|
|
||||||
|
|
||||||
Matrix2::from_angle(Rad(
|
|
||||||
rng.gen_range(-effect.direction_rng..=effect.direction_rng)
|
|
||||||
)) * velocity
|
|
||||||
};
|
|
||||||
|
|
||||||
// Rad has odd behavior when its angle is zero, so we need extra checks here
|
|
||||||
let angvel = if effect.angvel_rng == 0.0 {
|
|
||||||
effect.angvel
|
|
||||||
} else {
|
|
||||||
Rad(effect.angvel.0 + rng.gen_range(-effect.angvel_rng..=effect.angvel_rng))
|
|
||||||
};
|
|
||||||
let angle = if effect.angle_rng == 0.0 {
|
|
||||||
parent_angle + effect.angle
|
|
||||||
} else {
|
|
||||||
parent_angle + effect.angle + Rad(rng.gen_range(-effect.angle_rng..=effect.angle_rng))
|
|
||||||
};
|
|
||||||
|
|
||||||
ParticleBuilder {
|
|
||||||
sprite: effect.sprite,
|
|
||||||
pos,
|
|
||||||
velocity,
|
|
||||||
|
|
||||||
angle,
|
|
||||||
angvel,
|
|
||||||
|
|
||||||
lifetime: 0f32
|
|
||||||
.max(effect.lifetime + rng.gen_range(-effect.lifetime_rng..=effect.lifetime_rng)),
|
|
||||||
|
|
||||||
// Make sure size isn't negative. This check should be on EVERY rng!
|
|
||||||
size: 0f32.max(effect.size + rng.gen_range(-effect.size_rng..=effect.size_rng)),
|
|
||||||
|
|
||||||
fade: 0f32.max(effect.fade + rng.gen_range(-effect.fade_rng..=effect.fade_rng)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -3,4 +3,4 @@ mod projectile;
|
||||||
mod ship;
|
mod ship;
|
||||||
|
|
||||||
pub use projectile::ProjectileWorldObject;
|
pub use projectile::ProjectileWorldObject;
|
||||||
pub use ship::ShipWorldObject;
|
pub use ship::{ShipControls, ShipWorldObject};
|
||||||
|
|
|
@ -1,13 +1,19 @@
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use rapier2d::{dynamics::RigidBodyHandle, geometry::ColliderHandle};
|
use rapier2d::{dynamics::RigidBodyHandle, geometry::ColliderHandle};
|
||||||
|
|
||||||
use galactica_gameobject as object;
|
use galactica_content as content;
|
||||||
|
|
||||||
/// A single projectile in the world
|
/// A single projectile in the world
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ProjectileWorldObject {
|
pub struct ProjectileWorldObject {
|
||||||
/// This projectile's game data
|
/// This projectile's game data
|
||||||
pub projectile: object::Projectile,
|
pub content: content::Projectile,
|
||||||
|
|
||||||
|
/// The remaining lifetime of this projectile, in seconds
|
||||||
|
pub lifetime: f32,
|
||||||
|
|
||||||
|
/// The faction this projectile belongs to
|
||||||
|
pub faction: content::FactionHandle,
|
||||||
|
|
||||||
/// This projectile's rigidbody
|
/// This projectile's rigidbody
|
||||||
pub rigid_body: RigidBodyHandle,
|
pub rigid_body: RigidBodyHandle,
|
||||||
|
@ -20,19 +26,33 @@ pub struct ProjectileWorldObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProjectileWorldObject {
|
impl ProjectileWorldObject {
|
||||||
/// Make a new projectile
|
/// Create a new projectile
|
||||||
pub fn new(
|
pub fn new(
|
||||||
projectile: object::Projectile,
|
content: content::Projectile, // TODO: use a handle
|
||||||
rigid_body: RigidBodyHandle,
|
rigid_body: RigidBodyHandle,
|
||||||
|
faction: content::FactionHandle,
|
||||||
collider: ColliderHandle,
|
collider: ColliderHandle,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
let size_rng = projectile.content.size_rng;
|
let size_rng = content.size_rng;
|
||||||
|
let lifetime = content.lifetime;
|
||||||
ProjectileWorldObject {
|
ProjectileWorldObject {
|
||||||
rigid_body,
|
rigid_body,
|
||||||
collider,
|
collider,
|
||||||
projectile,
|
content,
|
||||||
|
lifetime,
|
||||||
|
faction,
|
||||||
size_rng: rng.gen_range(-size_rng..=size_rng),
|
size_rng: rng.gen_range(-size_rng..=size_rng),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Process this projectile's state after `t` seconds
|
||||||
|
pub fn tick(&mut self, t: f32) {
|
||||||
|
self.lifetime -= t;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Has this projectile expired?
|
||||||
|
pub fn is_expired(&self) -> bool {
|
||||||
|
return self.lifetime < 0.0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,30 @@
|
||||||
use cgmath::{Deg, EuclideanSpace, InnerSpace, Matrix2, Rad, Vector2, Zero};
|
use cgmath::{Deg, EuclideanSpace, InnerSpace, Matrix2, Rad, Vector2, Zero};
|
||||||
use nalgebra::{point, vector};
|
use nalgebra::{point, vector};
|
||||||
|
|
||||||
|
use object::GameShipHandle;
|
||||||
use rand::{rngs::ThreadRng, Rng};
|
use rand::{rngs::ThreadRng, Rng};
|
||||||
use rapier2d::{dynamics::RigidBody, geometry::Collider};
|
use rapier2d::{
|
||||||
|
dynamics::{RigidBody, RigidBodyHandle},
|
||||||
|
geometry::{Collider, ColliderHandle},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{util, ParticleBuilder, ShipPhysicsHandle};
|
use crate::{behavior::ShipBehavior, util, ParticleBuilder, StepResources};
|
||||||
use galactica_content as content;
|
use galactica_content as content;
|
||||||
use galactica_gameobject as object;
|
use galactica_gameobject as object;
|
||||||
|
|
||||||
|
/// A ship's controls
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct ShipControls {
|
pub struct ShipControls {
|
||||||
|
/// True if turning left
|
||||||
pub left: bool,
|
pub left: bool,
|
||||||
|
|
||||||
|
/// True if turning right
|
||||||
pub right: bool,
|
pub right: bool,
|
||||||
|
|
||||||
|
/// True if foward thrust
|
||||||
pub thrust: bool,
|
pub thrust: bool,
|
||||||
|
|
||||||
|
/// True if firing guns
|
||||||
pub guns: bool,
|
pub guns: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,22 +48,23 @@ struct ShipCollapseSequence {
|
||||||
impl ShipCollapseSequence {
|
impl ShipCollapseSequence {
|
||||||
fn new(total_length: f32) -> Self {
|
fn new(total_length: f32) -> Self {
|
||||||
Self {
|
Self {
|
||||||
total_length: total_length,
|
total_length,
|
||||||
time_elapsed: 0.0,
|
time_elapsed: 0.0,
|
||||||
rng: rand::thread_rng(),
|
rng: rand::thread_rng(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Has this sequence been fully played out?
|
||||||
fn is_done(&self) -> bool {
|
fn is_done(&self) -> bool {
|
||||||
self.time_elapsed >= self.total_length
|
self.time_elapsed >= self.total_length
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Pick a random points inside a ship's collider
|
||||||
fn random_in_ship(
|
fn random_in_ship(
|
||||||
&mut self,
|
&mut self,
|
||||||
ship_content: &content::Ship,
|
ship_content: &content::Ship,
|
||||||
collider: &Collider,
|
collider: &Collider,
|
||||||
) -> Vector2<f32> {
|
) -> Vector2<f32> {
|
||||||
// Pick a random point inside this ship's collider
|
|
||||||
let mut y = 0.0;
|
let mut y = 0.0;
|
||||||
let mut x = 0.0;
|
let mut x = 0.0;
|
||||||
let mut a = false;
|
let mut a = false;
|
||||||
|
@ -63,17 +77,16 @@ impl ShipCollapseSequence {
|
||||||
Vector2 { x, y }
|
Vector2 { x, y }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Step this sequence `t` seconds
|
||||||
fn step(
|
fn step(
|
||||||
&mut self,
|
&mut self,
|
||||||
ship: &object::Ship,
|
res: &mut StepResources,
|
||||||
ct: &content::Content,
|
ship: GameShipHandle,
|
||||||
particles: &mut Vec<ParticleBuilder>,
|
|
||||||
rigid_body: &mut RigidBody,
|
rigid_body: &mut RigidBody,
|
||||||
collider: &mut Collider,
|
collider: &mut Collider,
|
||||||
t: f32,
|
|
||||||
) {
|
) {
|
||||||
let h = ship.handle;
|
let h = ship.content_handle();
|
||||||
let ship_content = ct.get_ship(h);
|
let ship_content = res.ct.get_ship(h);
|
||||||
let ship_pos = util::rigidbody_position(rigid_body);
|
let ship_pos = util::rigidbody_position(rigid_body);
|
||||||
let ship_rot = util::rigidbody_rotation(rigid_body);
|
let ship_rot = util::rigidbody_rotation(rigid_body);
|
||||||
let ship_ang = ship_rot.angle(Vector2 { x: 1.0, y: 0.0 });
|
let ship_ang = ship_rot.angle(Vector2 { x: 1.0, y: 0.0 });
|
||||||
|
@ -85,11 +98,11 @@ impl ShipCollapseSequence {
|
||||||
for event in &ship_content.collapse.events {
|
for event in &ship_content.collapse.events {
|
||||||
match event {
|
match event {
|
||||||
content::CollapseEvent::Effect(event) => {
|
content::CollapseEvent::Effect(event) => {
|
||||||
if (event.time > self.time_elapsed && event.time <= self.time_elapsed + t)
|
if (event.time > self.time_elapsed && event.time <= self.time_elapsed + res.t)
|
||||||
|| (event.time == 0.0 && self.time_elapsed == 0.0)
|
|| (event.time == 0.0 && self.time_elapsed == 0.0)
|
||||||
{
|
{
|
||||||
for spawner in &event.effects {
|
for spawner in &event.effects {
|
||||||
let effect = ct.get_effect(spawner.effect);
|
let effect = res.ct.get_effect(spawner.effect);
|
||||||
|
|
||||||
for _ in 0..spawner.count as usize {
|
for _ in 0..spawner.count as usize {
|
||||||
let pos = if let Some(pos) = spawner.pos {
|
let pos = if let Some(pos) = spawner.pos {
|
||||||
|
@ -102,7 +115,7 @@ impl ShipCollapseSequence {
|
||||||
|
|
||||||
let velocity = rigid_body.velocity_at_point(&point![pos.x, pos.y]);
|
let velocity = rigid_body.velocity_at_point(&point![pos.x, pos.y]);
|
||||||
|
|
||||||
particles.push(ParticleBuilder::from_content(
|
res.particles.push(ParticleBuilder::from_content(
|
||||||
effect,
|
effect,
|
||||||
pos,
|
pos,
|
||||||
Rad::zero(),
|
Rad::zero(),
|
||||||
|
@ -121,7 +134,7 @@ impl ShipCollapseSequence {
|
||||||
|
|
||||||
// Create collapse effects
|
// Create collapse effects
|
||||||
for spawner in &ship_content.collapse.effects {
|
for spawner in &ship_content.collapse.effects {
|
||||||
let effect = ct.get_effect(spawner.effect);
|
let effect = res.ct.get_effect(spawner.effect);
|
||||||
|
|
||||||
// Probability of adding a particle this frame.
|
// Probability of adding a particle this frame.
|
||||||
// The area of this function over [0, 1] should be 1.
|
// The area of this function over [0, 1] should be 1.
|
||||||
|
@ -136,7 +149,7 @@ impl ShipCollapseSequence {
|
||||||
return y;
|
return y;
|
||||||
};
|
};
|
||||||
|
|
||||||
let p_add = (t / self.total_length) * pdf(frac_done) * spawner.count;
|
let p_add = (res.t / self.total_length) * pdf(frac_done) * spawner.count;
|
||||||
|
|
||||||
if self.rng.gen_range(0.0..=1.0) <= p_add {
|
if self.rng.gen_range(0.0..=1.0) <= p_add {
|
||||||
let pos = if let Some(pos) = spawner.pos {
|
let pos = if let Some(pos) = spawner.pos {
|
||||||
|
@ -149,7 +162,7 @@ impl ShipCollapseSequence {
|
||||||
let pos = ship_pos + Matrix2::from_angle(-ship_ang - Rad::from(Deg(90.0))) * pos;
|
let pos = ship_pos + Matrix2::from_angle(-ship_ang - Rad::from(Deg(90.0))) * pos;
|
||||||
let vel = rigid_body.velocity_at_point(&point![pos.x, pos.y]);
|
let vel = rigid_body.velocity_at_point(&point![pos.x, pos.y]);
|
||||||
|
|
||||||
particles.push(ParticleBuilder {
|
res.particles.push(ParticleBuilder {
|
||||||
sprite: effect.sprite,
|
sprite: effect.sprite,
|
||||||
pos,
|
pos,
|
||||||
velocity: Vector2 { x: vel.x, y: vel.y },
|
velocity: Vector2 { x: vel.x, y: vel.y },
|
||||||
|
@ -162,21 +175,28 @@ impl ShipCollapseSequence {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.time_elapsed += t;
|
self.time_elapsed += res.t;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A ship instance in the physics system
|
/// A ship instance in the physics system
|
||||||
pub struct ShipWorldObject {
|
pub struct ShipWorldObject {
|
||||||
/// This ship's physics handle
|
/// This ship's physics handle
|
||||||
pub physics_handle: ShipPhysicsHandle,
|
pub rigid_body: RigidBodyHandle,
|
||||||
|
|
||||||
|
/// This ship's collider
|
||||||
|
pub collider: ColliderHandle,
|
||||||
|
|
||||||
/// This ship's game data
|
/// This ship's game data
|
||||||
pub ship: object::Ship,
|
pub data_handle: GameShipHandle,
|
||||||
|
|
||||||
/// This ship's controls
|
/// This ship's controls
|
||||||
pub controls: ShipControls,
|
pub(crate) controls: ShipControls,
|
||||||
|
|
||||||
|
/// This ship's behavior
|
||||||
|
behavior: Box<dyn ShipBehavior>,
|
||||||
|
|
||||||
|
/// This ship's collapse sequence
|
||||||
collapse_sequence: ShipCollapseSequence,
|
collapse_sequence: ShipCollapseSequence,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,51 +204,45 @@ impl ShipWorldObject {
|
||||||
/// Make a new ship
|
/// Make a new ship
|
||||||
pub fn new(
|
pub fn new(
|
||||||
ct: &content::Content,
|
ct: &content::Content,
|
||||||
ship: object::Ship,
|
data_handle: GameShipHandle,
|
||||||
physics_handle: ShipPhysicsHandle,
|
behavior: Box<dyn ShipBehavior>,
|
||||||
|
rigid_body: RigidBodyHandle,
|
||||||
|
collider: ColliderHandle,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let ship_content = ct.get_ship(ship.handle);
|
let ship_content = ct.get_ship(data_handle.content_handle());
|
||||||
ShipWorldObject {
|
ShipWorldObject {
|
||||||
physics_handle,
|
rigid_body,
|
||||||
ship,
|
collider,
|
||||||
|
data_handle,
|
||||||
|
behavior,
|
||||||
controls: ShipControls::new(),
|
controls: ShipControls::new(),
|
||||||
collapse_sequence: ShipCollapseSequence::new(ship_content.collapse.length),
|
collapse_sequence: ShipCollapseSequence::new(ship_content.collapse.length),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Should this ship should be removed from the world?
|
/// Compute this ship's controls using its behavior
|
||||||
pub fn remove_from_world(&self) -> bool {
|
pub fn update_controls(&mut self, res: &StepResources) {
|
||||||
return self.ship.is_dead() && self.collapse_sequence.is_done();
|
self.controls = self.behavior.update_controls(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Step this ship's state by t seconds
|
/// Step this ship's state by t seconds
|
||||||
pub fn step(
|
pub fn step(
|
||||||
&mut self,
|
&mut self,
|
||||||
ct: &content::Content,
|
res: &mut StepResources,
|
||||||
particles: &mut Vec<ParticleBuilder>,
|
|
||||||
rigid_body: &mut RigidBody,
|
rigid_body: &mut RigidBody,
|
||||||
collider: &mut Collider,
|
collider: &mut Collider,
|
||||||
t: f32,
|
|
||||||
) {
|
) {
|
||||||
if self.ship.is_dead() {
|
let ship = res.dt.get_ship(self.data_handle).unwrap();
|
||||||
return self
|
let ship_content = res.ct.get_ship(self.data_handle.content_handle());
|
||||||
.collapse_sequence
|
|
||||||
.step(&self.ship, ct, particles, rigid_body, collider, t);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.ship.step(t);
|
|
||||||
|
|
||||||
let ship_content = ct.get_ship(self.ship.handle);
|
|
||||||
let ship_pos = util::rigidbody_position(&rigid_body);
|
let ship_pos = util::rigidbody_position(&rigid_body);
|
||||||
let ship_rot = util::rigidbody_rotation(rigid_body);
|
let ship_rot = util::rigidbody_rotation(rigid_body);
|
||||||
let ship_ang = ship_rot.angle(Vector2 { x: 1.0, y: 0.0 });
|
let ship_ang = ship_rot.angle(Vector2 { x: 1.0, y: 0.0 });
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
if self.ship.hull <= ship_content.damage.hull {
|
if ship.get_hull() <= ship_content.damage.hull {
|
||||||
for e in &ship_content.damage.effects {
|
for e in &ship_content.damage.effects {
|
||||||
if rng.gen_range(0.0..=1.0) <= t / e.frequency {
|
if rng.gen_range(0.0..=1.0) <= res.t / e.frequency {
|
||||||
let effect = ct.get_effect(e.effect);
|
let effect = res.ct.get_effect(e.effect);
|
||||||
let ship_content = ct.get_ship(self.ship.handle);
|
|
||||||
|
|
||||||
let pos = if let Some(pos) = e.pos {
|
let pos = if let Some(pos) = e.pos {
|
||||||
pos.to_vec()
|
pos.to_vec()
|
||||||
|
@ -252,7 +266,7 @@ impl ShipWorldObject {
|
||||||
|
|
||||||
let velocity = rigid_body.velocity_at_point(&point![pos.x, pos.y]);
|
let velocity = rigid_body.velocity_at_point(&point![pos.x, pos.y]);
|
||||||
|
|
||||||
particles.push(ParticleBuilder::from_content(
|
res.particles.push(ParticleBuilder::from_content(
|
||||||
effect,
|
effect,
|
||||||
pos,
|
pos,
|
||||||
Rad::zero(),
|
Rad::zero(),
|
||||||
|
@ -266,28 +280,27 @@ impl ShipWorldObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let engine_force = ship_rot * t;
|
let engine_force = ship_rot * res.t;
|
||||||
|
|
||||||
if self.controls.thrust {
|
if self.controls.thrust {
|
||||||
rigid_body.apply_impulse(
|
rigid_body.apply_impulse(
|
||||||
vector![engine_force.x, engine_force.y]
|
vector![engine_force.x, engine_force.y] * ship.get_outfits().get_engine_thrust(),
|
||||||
* self.ship.outfits.stat_sum().engine_thrust,
|
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.controls.right {
|
if self.controls.right {
|
||||||
rigid_body
|
rigid_body
|
||||||
.apply_torque_impulse(self.ship.outfits.stat_sum().steer_power * -100.0 * t, true);
|
.apply_torque_impulse(ship.get_outfits().get_steer_power() * -100.0 * res.t, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.controls.left {
|
if self.controls.left {
|
||||||
rigid_body
|
rigid_body
|
||||||
.apply_torque_impulse(self.ship.outfits.stat_sum().steer_power * 100.0 * t, true);
|
.apply_torque_impulse(ship.get_outfits().get_steer_power() * 100.0 * res.t, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
for i in self.ship.outfits.iter_guns() {
|
//for i in self.ship.outfits.iter_guns() {
|
||||||
i.cooldown -= t;
|
// i.cooldown -= t;
|
||||||
}
|
//}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,88 @@
|
||||||
|
use cgmath::{Matrix2, Point2, Rad, Vector2};
|
||||||
|
use galactica_content as content;
|
||||||
|
use rand::Rng;
|
||||||
|
|
||||||
|
/// Instructions to create a new particle
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ParticleBuilder {
|
||||||
|
/// The sprite to use for this particle
|
||||||
|
pub sprite: content::SpriteHandle,
|
||||||
|
|
||||||
|
/// This object's center, in world coordinates.
|
||||||
|
pub pos: Point2<f32>,
|
||||||
|
|
||||||
|
/// This particle's velocity, in world coordinates
|
||||||
|
pub velocity: Vector2<f32>,
|
||||||
|
|
||||||
|
/// This particle's angle
|
||||||
|
pub angle: Rad<f32>,
|
||||||
|
|
||||||
|
/// This particle's angular velocity (rad/sec)
|
||||||
|
pub angvel: Rad<f32>,
|
||||||
|
|
||||||
|
/// This particle's lifetime, in seconds
|
||||||
|
pub lifetime: f32,
|
||||||
|
|
||||||
|
/// The size of this particle,
|
||||||
|
/// given as height in world units.
|
||||||
|
pub size: f32,
|
||||||
|
|
||||||
|
/// Fade this particle out over this many seconds as it expires
|
||||||
|
pub fade: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ParticleBuilder {
|
||||||
|
/// Create a ParticleBuilder from an Effect
|
||||||
|
pub fn from_content(
|
||||||
|
effect: &content::Effect,
|
||||||
|
pos: Point2<f32>,
|
||||||
|
parent_angle: Rad<f32>,
|
||||||
|
parent_velocity: Vector2<f32>,
|
||||||
|
target_velocity: Vector2<f32>,
|
||||||
|
) -> Self {
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
|
let velocity = {
|
||||||
|
let a =
|
||||||
|
rng.gen_range(-effect.velocity_scale_parent_rng..=effect.velocity_scale_parent_rng);
|
||||||
|
let b =
|
||||||
|
rng.gen_range(-effect.velocity_scale_target_rng..=effect.velocity_scale_target_rng);
|
||||||
|
|
||||||
|
let velocity = ((effect.velocity_scale_parent + a) * parent_velocity)
|
||||||
|
+ ((effect.velocity_scale_target + b) * target_velocity);
|
||||||
|
|
||||||
|
Matrix2::from_angle(Rad(
|
||||||
|
rng.gen_range(-effect.direction_rng..=effect.direction_rng)
|
||||||
|
)) * velocity
|
||||||
|
};
|
||||||
|
|
||||||
|
// Rad has odd behavior when its angle is zero, so we need extra checks here
|
||||||
|
let angvel = if effect.angvel_rng == 0.0 {
|
||||||
|
effect.angvel
|
||||||
|
} else {
|
||||||
|
Rad(effect.angvel.0 + rng.gen_range(-effect.angvel_rng..=effect.angvel_rng))
|
||||||
|
};
|
||||||
|
let angle = if effect.angle_rng == 0.0 {
|
||||||
|
parent_angle + effect.angle
|
||||||
|
} else {
|
||||||
|
parent_angle + effect.angle + Rad(rng.gen_range(-effect.angle_rng..=effect.angle_rng))
|
||||||
|
};
|
||||||
|
|
||||||
|
ParticleBuilder {
|
||||||
|
sprite: effect.sprite,
|
||||||
|
pos,
|
||||||
|
velocity,
|
||||||
|
|
||||||
|
angle,
|
||||||
|
angvel,
|
||||||
|
|
||||||
|
lifetime: 0f32
|
||||||
|
.max(effect.lifetime + rng.gen_range(-effect.lifetime_rng..=effect.lifetime_rng)),
|
||||||
|
|
||||||
|
// Make sure size isn't negative. This check should be on EVERY rng!
|
||||||
|
size: 0f32.max(effect.size + rng.gen_range(-effect.size_rng..=effect.size_rng)),
|
||||||
|
|
||||||
|
fade: 0f32.max(effect.fade + rng.gen_range(-effect.fade_rng..=effect.fade_rng)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
use crate::{objects::ShipControls, ParticleBuilder};
|
||||||
|
use cgmath::Point2;
|
||||||
|
use galactica_content::Content;
|
||||||
|
use galactica_gameobject::{GameData, GameShipHandle};
|
||||||
|
|
||||||
|
/// External resources we need to compute time steps
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct StepResources<'a> {
|
||||||
|
/// Game content
|
||||||
|
pub ct: &'a Content,
|
||||||
|
|
||||||
|
/// Game data
|
||||||
|
pub dt: &'a mut GameData,
|
||||||
|
|
||||||
|
/// Length of time step
|
||||||
|
pub t: f32,
|
||||||
|
|
||||||
|
/// Particles to create
|
||||||
|
pub particles: &'a mut Vec<ParticleBuilder>,
|
||||||
|
|
||||||
|
/// Player inputs
|
||||||
|
pub player_controls: ShipControls,
|
||||||
|
|
||||||
|
/// The ship that the player controls
|
||||||
|
pub player: GameShipHandle,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return values after computing time steps
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct StepOutput {
|
||||||
|
/// The player's position in world coordinates
|
||||||
|
pub player_position: Point2<f32>,
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
use cgmath::{Deg, EuclideanSpace, InnerSpace, Matrix2, Point2, Rad, Vector2, Zero};
|
use cgmath::{Deg, EuclideanSpace, InnerSpace, Matrix2, Point2, Rad, Vector2, Zero};
|
||||||
use crossbeam::channel::Receiver;
|
use crossbeam::channel::Receiver;
|
||||||
use nalgebra::{point, vector};
|
use nalgebra::{point, vector};
|
||||||
|
use object::{ship::Ship, GameData, GameShipHandle};
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use rapier2d::{
|
use rapier2d::{
|
||||||
dynamics::{RigidBody, RigidBodyBuilder, RigidBodyHandle},
|
dynamics::{RigidBody, RigidBodyBuilder, RigidBodyHandle},
|
||||||
|
@ -10,21 +11,24 @@ use rapier2d::{
|
||||||
use std::{collections::HashMap, f32::consts::PI};
|
use std::{collections::HashMap, f32::consts::PI};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
objects,
|
behavior, objects,
|
||||||
objects::{ProjectileWorldObject, ShipWorldObject},
|
objects::{ProjectileWorldObject, ShipWorldObject},
|
||||||
util,
|
util,
|
||||||
wrapper::Wrapper,
|
wrapper::Wrapper,
|
||||||
ParticleBuilder, ShipPhysicsHandle,
|
ParticleBuilder, StepOutput, StepResources,
|
||||||
};
|
};
|
||||||
use galactica_content as content;
|
use galactica_content as content;
|
||||||
use galactica_gameobject as object;
|
use galactica_gameobject as object;
|
||||||
|
|
||||||
/// Keeps track of all objects in the world that we can interact with.
|
/// Manages the physics state of one system
|
||||||
/// Also wraps our physics engine
|
|
||||||
pub struct World {
|
pub struct World {
|
||||||
|
/// The system this world is attached to
|
||||||
|
system: content::SystemHandle,
|
||||||
|
|
||||||
wrapper: Wrapper,
|
wrapper: Wrapper,
|
||||||
projectiles: HashMap<ColliderHandle, objects::ProjectileWorldObject>,
|
projectiles: HashMap<ColliderHandle, objects::ProjectileWorldObject>,
|
||||||
ships: HashMap<ColliderHandle, objects::ShipWorldObject>,
|
ships: HashMap<GameShipHandle, objects::ShipWorldObject>,
|
||||||
|
collider_ship_table: HashMap<ColliderHandle, GameShipHandle>,
|
||||||
|
|
||||||
collision_handler: ChannelEventCollector,
|
collision_handler: ChannelEventCollector,
|
||||||
collision_queue: Receiver<CollisionEvent>,
|
collision_queue: Receiver<CollisionEvent>,
|
||||||
|
@ -56,23 +60,25 @@ impl<'a> World {
|
||||||
return Some((r, p));
|
return Some((r, p));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_ship(&mut self, h: ShipPhysicsHandle) {
|
fn remove_ship(&mut self, s: &ShipWorldObject) {
|
||||||
self.wrapper.rigid_body_set.remove(
|
self.wrapper.rigid_body_set.remove(
|
||||||
h.0,
|
s.rigid_body,
|
||||||
&mut self.wrapper.im,
|
&mut self.wrapper.im,
|
||||||
&mut self.wrapper.collider_set,
|
&mut self.wrapper.collider_set,
|
||||||
&mut self.wrapper.ij,
|
&mut self.wrapper.ij,
|
||||||
&mut self.wrapper.mj,
|
&mut self.wrapper.mj,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
self.ships.remove(&h.1);
|
let h = self.collider_ship_table.remove(&s.collider).unwrap();
|
||||||
|
self.ships.remove(&h);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
/// 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<(ProjectileWorldObject, content::GunPoint)>,
|
||||||
) {
|
) {
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
for (projectile, point) in p {
|
for (projectile, point) in p {
|
||||||
|
@ -126,37 +132,44 @@ impl<'a> World {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
fn collide_projectile_ship(
|
fn collide_projectile_ship(
|
||||||
&mut self,
|
&mut self,
|
||||||
ct: &content::Content,
|
res: &mut StepResources,
|
||||||
particles: &mut Vec<ParticleBuilder>,
|
|
||||||
projectile_h: ColliderHandle,
|
projectile_h: ColliderHandle,
|
||||||
ship_h: ColliderHandle,
|
ship_h: ColliderHandle,
|
||||||
) {
|
) {
|
||||||
let projectile = self.projectiles.get(&projectile_h);
|
let projectile = self.projectiles.get(&projectile_h);
|
||||||
let ship = self.ships.get_mut(&ship_h);
|
let ship = self
|
||||||
|
.ships
|
||||||
|
.get_mut(self.collider_ship_table.get(&ship_h).unwrap());
|
||||||
if projectile.is_none() || ship.is_none() {
|
if projectile.is_none() || ship.is_none() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let projectile = projectile.unwrap();
|
let projectile = projectile.unwrap();
|
||||||
let ship = ship.unwrap();
|
let ship = ship.unwrap();
|
||||||
|
|
||||||
let hit = ship
|
let ship_d = res.dt.get_ship_mut(ship.data_handle).unwrap();
|
||||||
.ship
|
|
||||||
.handle_projectile_collision(ct, &projectile.projectile);
|
// TODO: check faction
|
||||||
let s = ship.physics_handle;
|
ship_d.apply_damage(projectile.content.damage);
|
||||||
if hit {
|
|
||||||
|
if true {
|
||||||
let pr = self
|
let pr = self
|
||||||
.wrapper
|
.wrapper
|
||||||
.rigid_body_set
|
.rigid_body_set
|
||||||
.get(projectile.rigid_body)
|
.get(projectile.rigid_body)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let v = util::rigidbody_velocity(pr).normalize() * projectile.projectile.content.force;
|
let v = util::rigidbody_velocity(pr).normalize() * projectile.content.force;
|
||||||
let pos = util::rigidbody_position(pr);
|
let pos = util::rigidbody_position(pr);
|
||||||
let _ = pr;
|
let _ = pr;
|
||||||
|
|
||||||
let r = self.wrapper.rigid_body_set.get_mut(s.0).unwrap();
|
let r = self
|
||||||
|
.wrapper
|
||||||
|
.rigid_body_set
|
||||||
|
.get_mut(ship.rigid_body)
|
||||||
|
.unwrap();
|
||||||
r.apply_impulse_at_point(vector![v.x, v.y], point![pos.x, pos.y], true);
|
r.apply_impulse_at_point(vector![v.x, v.y], point![pos.x, pos.y], true);
|
||||||
|
|
||||||
// Borrow again, we can only have one at a time
|
// Borrow again, we can only have one at a time
|
||||||
|
@ -168,16 +181,17 @@ impl<'a> World {
|
||||||
let pos = util::rigidbody_position(pr);
|
let pos = util::rigidbody_position(pr);
|
||||||
let angle = util::rigidbody_rotation(pr).angle(Vector2 { x: 1.0, y: 0.0 });
|
let angle = util::rigidbody_rotation(pr).angle(Vector2 { x: 1.0, y: 0.0 });
|
||||||
|
|
||||||
match &projectile.projectile.content.impact_effect {
|
match &projectile.content.impact_effect {
|
||||||
None => {}
|
None => {}
|
||||||
Some(x) => {
|
Some(x) => {
|
||||||
let effect = ct.get_effect(*x);
|
let effect = res.ct.get_effect(*x);
|
||||||
let (_, sr) = self.get_ship_body(s).unwrap();
|
let r = ship.rigid_body;
|
||||||
|
let sr = self.get_rigid_body(r).unwrap();
|
||||||
let parent_velocity = util::rigidbody_velocity(pr);
|
let parent_velocity = util::rigidbody_velocity(pr);
|
||||||
let target_velocity =
|
let target_velocity =
|
||||||
sr.velocity_at_point(&nalgebra::Point2::new(pos.x, pos.y));
|
sr.velocity_at_point(&nalgebra::Point2::new(pos.x, pos.y));
|
||||||
|
|
||||||
particles.push(ParticleBuilder::from_content(
|
res.particles.push(ParticleBuilder::from_content(
|
||||||
effect,
|
effect,
|
||||||
pos,
|
pos,
|
||||||
-angle,
|
-angle,
|
||||||
|
@ -198,27 +212,41 @@ impl<'a> World {
|
||||||
// Public methods
|
// Public methods
|
||||||
impl<'a> World {
|
impl<'a> World {
|
||||||
/// Create a new physics system
|
/// Create a new physics system
|
||||||
pub fn new() -> Self {
|
pub fn new(ct: &content::Content, dt: &GameData, system: content::SystemHandle) -> Self {
|
||||||
let (collision_send, collision_queue) = crossbeam::channel::unbounded();
|
let (collision_send, collision_queue) = crossbeam::channel::unbounded();
|
||||||
let (contact_force_send, _) = crossbeam::channel::unbounded();
|
let (contact_force_send, _) = crossbeam::channel::unbounded();
|
||||||
|
|
||||||
Self {
|
let mut w = Self {
|
||||||
|
system,
|
||||||
wrapper: Wrapper::new(),
|
wrapper: Wrapper::new(),
|
||||||
projectiles: HashMap::new(),
|
projectiles: HashMap::new(),
|
||||||
ships: HashMap::new(),
|
ships: HashMap::new(),
|
||||||
|
collider_ship_table: HashMap::new(),
|
||||||
collision_handler: ChannelEventCollector::new(collision_send, contact_force_send),
|
collision_handler: ChannelEventCollector::new(collision_send, contact_force_send),
|
||||||
collision_queue,
|
collision_queue,
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: guarantee not touching
|
||||||
|
// TODO: add, remove ships each tick
|
||||||
|
// Maybe store position in gamedata?
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
for s in dt.iter_ships() {
|
||||||
|
w.add_ship(
|
||||||
|
ct,
|
||||||
|
s,
|
||||||
|
Point2 {
|
||||||
|
x: rng.gen_range(-500.0..=500.0),
|
||||||
|
y: rng.gen_range(-500.0..=500.0),
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return w;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a ship to this physics system
|
/// Add a ship to this physics system
|
||||||
pub fn add_ship(
|
pub fn add_ship(&mut self, ct: &content::Content, ship: &Ship, position: Point2<f32>) {
|
||||||
&mut self,
|
let ship_content = ct.get_ship(ship.get_content());
|
||||||
ct: &content::Content,
|
|
||||||
ship: object::Ship,
|
|
||||||
position: Point2<f32>,
|
|
||||||
) -> ShipPhysicsHandle {
|
|
||||||
let ship_content = ct.get_ship(ship.handle);
|
|
||||||
let cl = ColliderBuilder::convex_decomposition(
|
let cl = ColliderBuilder::convex_decomposition(
|
||||||
&ship_content.collision.points[..],
|
&ship_content.collision.points[..],
|
||||||
&ship_content.collision.indices[..],
|
&ship_content.collision.indices[..],
|
||||||
|
@ -242,42 +270,60 @@ impl<'a> World {
|
||||||
&mut self.wrapper.rigid_body_set,
|
&mut self.wrapper.rigid_body_set,
|
||||||
);
|
);
|
||||||
|
|
||||||
let h = ShipPhysicsHandle(r, c);
|
self.collider_ship_table.insert(c, ship.get_handle());
|
||||||
self.ships
|
self.ships.insert(
|
||||||
.insert(c, objects::ShipWorldObject::new(ct, ship, h));
|
ship.get_handle(),
|
||||||
return h;
|
objects::ShipWorldObject::new(
|
||||||
|
ct,
|
||||||
|
ship.get_handle(),
|
||||||
|
Box::new(behavior::Null::new()),
|
||||||
|
r,
|
||||||
|
c,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Step this physics system by `t` seconds
|
/// Step this physics system by `t` seconds
|
||||||
pub fn step(&mut self, t: f32, ct: &content::Content, particles: &mut Vec<ParticleBuilder>) {
|
pub fn step(&mut self, mut res: StepResources) -> StepOutput {
|
||||||
|
let mut output = StepOutput {
|
||||||
|
player_position: Point2 { x: 0.0, y: 0.0 },
|
||||||
|
};
|
||||||
|
|
||||||
// Run ship updates
|
// Run ship updates
|
||||||
// TODO: maybe reorganize projectile creation?
|
// TODO: maybe reorganize projectile creation?
|
||||||
let mut projectiles = Vec::new();
|
//let mut projectiles = Vec::new();
|
||||||
let mut to_remove = Vec::new();
|
let mut to_remove = Vec::new();
|
||||||
for (_, s) in &mut self.ships {
|
for (_, s) in &mut self.ships {
|
||||||
let r = &mut self.wrapper.rigid_body_set[s.physics_handle.0];
|
let r = &mut self.wrapper.rigid_body_set[s.rigid_body];
|
||||||
let c = &mut self.wrapper.collider_set[s.physics_handle.1];
|
let c = &mut self.wrapper.collider_set[s.collider];
|
||||||
|
|
||||||
|
if s.data_handle == res.player {
|
||||||
|
s.controls = res.player_controls.clone();
|
||||||
|
output.player_position = util::rigidbody_position(r);
|
||||||
|
} else {
|
||||||
|
s.update_controls(&res);
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: unified step info struct
|
// TODO: unified step info struct
|
||||||
s.step(ct, particles, r, c, t);
|
s.step(&mut res, r, c);
|
||||||
if s.controls.guns {
|
//if s.controls.guns {
|
||||||
projectiles.push((s.physics_handle, s.ship.fire_guns()));
|
// projectiles.push((s.physics_handle, s.ship.fire_guns()));
|
||||||
}
|
//}
|
||||||
|
|
||||||
if s.remove_from_world() {
|
//if s.remove_from_world() {
|
||||||
to_remove.push(s.physics_handle);
|
// to_remove.push(s.physics_handle);
|
||||||
continue;
|
// continue;
|
||||||
}
|
//}
|
||||||
}
|
|
||||||
for (s, p) in projectiles {
|
|
||||||
self.add_projectiles(s, p);
|
|
||||||
}
|
}
|
||||||
|
//for (s, p) in projectiles {
|
||||||
|
// self.add_projectiles(s, p);
|
||||||
|
//}
|
||||||
for s in to_remove {
|
for s in to_remove {
|
||||||
self.remove_ship(s);
|
self.remove_ship(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update physics
|
// Update physics
|
||||||
self.wrapper.step(t, &self.collision_handler);
|
self.wrapper.step(res.t, &self.collision_handler);
|
||||||
|
|
||||||
// Handle collision events
|
// Handle collision events
|
||||||
while let Ok(event) = &self.collision_queue.try_recv() {
|
while let Ok(event) = &self.collision_queue.try_recv() {
|
||||||
|
@ -294,19 +340,21 @@ impl<'a> World {
|
||||||
};
|
};
|
||||||
|
|
||||||
let p = self.projectiles.get(&a);
|
let p = self.projectiles.get(&a);
|
||||||
let s = self.ships.get_mut(&b);
|
let s = self
|
||||||
|
.ships
|
||||||
|
.get_mut(self.collider_ship_table.get(&b).unwrap());
|
||||||
if p.is_none() || s.is_none() {
|
if p.is_none() || s.is_none() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
self.collide_projectile_ship(ct, particles, a, b);
|
self.collide_projectile_ship(&mut res, a, b);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete projectiles
|
// Delete projectiles
|
||||||
let mut to_remove = Vec::new();
|
let mut to_remove = Vec::new();
|
||||||
for (c, p) in &mut self.projectiles {
|
for (c, p) in &mut self.projectiles {
|
||||||
p.projectile.tick(t);
|
p.tick(res.t);
|
||||||
if p.projectile.is_expired() {
|
if p.is_expired() {
|
||||||
to_remove.push(*c);
|
to_remove.push(*c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -315,10 +363,10 @@ impl<'a> World {
|
||||||
for c in to_remove {
|
for c in to_remove {
|
||||||
let (pr, p) = self.remove_projectile(c).unwrap();
|
let (pr, p) = self.remove_projectile(c).unwrap();
|
||||||
|
|
||||||
match &p.projectile.content.expire_effect {
|
match &p.content.expire_effect {
|
||||||
None => {}
|
None => {}
|
||||||
Some(x) => {
|
Some(x) => {
|
||||||
let x = ct.get_effect(*x);
|
let x = res.ct.get_effect(*x);
|
||||||
let pos = util::rigidbody_position(&pr);
|
let pos = util::rigidbody_position(&pr);
|
||||||
let vel = util::rigidbody_velocity(&pr);
|
let vel = util::rigidbody_velocity(&pr);
|
||||||
let angle = util::rigidbody_rotation(&pr).angle(Vector2 { x: 1.0, y: 0.0 });
|
let angle = util::rigidbody_rotation(&pr).angle(Vector2 { x: 1.0, y: 0.0 });
|
||||||
|
@ -332,7 +380,7 @@ impl<'a> World {
|
||||||
velocity
|
velocity
|
||||||
};
|
};
|
||||||
|
|
||||||
particles.push(ParticleBuilder::from_content(
|
res.particles.push(ParticleBuilder::from_content(
|
||||||
x,
|
x,
|
||||||
pos,
|
pos,
|
||||||
-angle,
|
-angle,
|
||||||
|
@ -342,6 +390,12 @@ impl<'a> World {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_ship(&self, ship: GameShipHandle) -> Option<&ShipWorldObject> {
|
||||||
|
self.ships.get(&ship)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a rigid body from a handle
|
/// Get a rigid body from a handle
|
||||||
|
@ -354,29 +408,13 @@ impl<'a> World {
|
||||||
self.wrapper.rigid_body_set.get_mut(r)
|
self.wrapper.rigid_body_set.get_mut(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a ship from a handle
|
|
||||||
pub fn get_ship_mut(&mut self, s: &ShipPhysicsHandle) -> Option<&mut objects::ShipWorldObject> {
|
|
||||||
self.ships.get_mut(&s.1)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a ship and its rigidbody from a handle
|
|
||||||
pub fn get_ship_body(
|
|
||||||
&self,
|
|
||||||
s: ShipPhysicsHandle,
|
|
||||||
) -> Option<(&objects::ShipWorldObject, &RigidBody)> {
|
|
||||||
Some((self.ships.get(&s.1)?, self.wrapper.rigid_body_set.get(s.0)?))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iterate over all ships in this physics system
|
/// Iterate over all ships in this physics system
|
||||||
pub fn iter_ship_body(
|
pub fn iter_ship_body(
|
||||||
&self,
|
&self,
|
||||||
) -> impl Iterator<Item = (&objects::ShipWorldObject, &RigidBody)> + '_ {
|
) -> impl Iterator<Item = (&objects::ShipWorldObject, &RigidBody)> + '_ {
|
||||||
self.ships.values().map(|x| {
|
self.ships
|
||||||
(
|
.values()
|
||||||
x,
|
.map(|x| (x, self.wrapper.rigid_body_set.get(x.rigid_body).unwrap()))
|
||||||
self.wrapper.rigid_body_set.get(x.physics_handle.0).unwrap(),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Iterate over all ships in this physics system
|
/// Iterate over all ships in this physics system
|
||||||
|
|
Loading…
Reference in New Issue