Compare commits

...

2 Commits

Author SHA1 Message Date
Mark 2a7d4a7688
Reorganized TODO 2024-01-14 12:57:21 -08:00
Mark 5815bb9f9f
Minor fixes 2024-01-14 11:50:19 -08:00
8 changed files with 225 additions and 180 deletions

View File

@ -6,4 +6,8 @@ indent_size = 4
end_of_line = lf end_of_line = lf
charset = utf-8 charset = utf-8
trim_trailing_whitespace = false trim_trailing_whitespace = false
insert_final_newline = false insert_final_newline = false
[*.md]
indent_style = space
indent_size = 2

282
TODO.md
View File

@ -1,7 +1,129 @@
# Specific projects
## Currently working on:
- player selection
- planet outfitter
## Small jobs
- Procedural suns
- 🌟 Back arrow -> reverse
- No wobble for ai ships & autopilot
- 🌟 User-configurable outfit space types
- 🌟 Sticky radar
- Configurable radar
- 🌟 Ship damage events, unify spawners
- Better landing animation (slow down)
- Land from farther away
- Ship collapse: damage + force events
- Redesign UI elements
- Background haze: 3d perlin?
- nova dust parallax
- Motion blur
- 🌟 Generate outlines and colliders
- Then: outline self in status
- Reverse engines + flares
- Turn flares (physics by location?)
- Angled engines & guns
## Misc fixes & Optimizations
- 🌟 Better errors when content/asset dirs don't exist
- Clear `// TODO:` comments
- Correct drawing order (player on top, landing ships)
- Faster handles (better than a hashmap?)
- Check for handle leaks
- 🌟 Log/warning system
- Clean up & faster frame timings
- 🌟 Handle lost focus
- User config file
- 🌟 Document content
- 🌟 Clean up content: one ship per file, autodetect
- CLI: pack sprites(?), check content, content location, logs, etc
- Better WGSL preprocessor?
- Projectile performance
- Starfield clusters, shader instead of an array?
- Collider groups for factions? (projectile optimization)
## 🌟 Player selection
- Planet name, ring, and distance
- Selection UI (around game object)
- Selection UI (radar arrow)
- Selection keys
- Select ships
- Selection UI (HUD)
- attack selected ship, even if friendly
## 🌟 Planet outfitter
- UI, show ship on land
- Money
- Display name != key
- then: non-removable outfits
- then: cargo space
- then: collectible flotsam
- then: mass from cargo and outfits
- then: space conversion
- then: heat + energy stats
- then: damage type (heat, energy, disrupt, etc)
- then: debuffs
- then: assign guns to points
- then: guns with consumable ammunition
- then: enable/disable weapons
## Ship Spawning
- requires: Basic ship AI (disable collision for now?)
- requires: Fleets
- Spawn ships from planets
- Land ships on planets (destroy)
- Start with player on planet, respawn
## Debris (requires: flotsam)
- Destroyed ships leave debris
- Destructible & floatingdebris
- Orbiting debris: asteroids
- ES-style asteroid field (how should we do this?)
## Camera physics
- lookahead
- damping
- Don't jump when landing/taking off
- wobble / ship thrown when hit?
- UI/effects when damaged?
- Focus important objects
## Sprite reels
- Sprite reels in content
- Trigger sprite reel during collapse sequence
- Trigger sprite reel when firing (requires: non-removable outfits)
- then: ship tint
- then: frame timing randomness
- then: leaks during collapse (needs particle physics)
- then: passive engine glow, ease in/out
- Sprite color variation
## Texture loading
- loading screen & menu
- GPU limits?
- Cache direcory
- How to pack?
## Particle physics
- Particles should stick to their ships. How?
- Particle compute shader
- Land and unland particles
- Effect on fire gun
- Particle / sprite color variation
## Sound effects
- Sound effects
- User-configurable music (?, game config or user config?)
## Specific Jobs ## Specific Jobs
- Fix angles (point, land) - Correct depth order
- Land from farther away - Land from farther away
- Take off
- Prevent collisions on unlanding - Prevent collisions on unlanding
- UI: text arranger - UI: text arranger
- loading screen, menus - loading screen, menus
@ -27,122 +149,50 @@
- Arbitrary size resource names - Arbitrary size resource names
----------------------------------
----------------------------------------------------
----------------------------------------------------
# Ideas and long-term goals
## Features (not soon)
- Multiplayer
- Configure key bindings, controller support
- Save game state
- Dynamic relationships (get angry when attacked)
- Jump between systems
- Story: how?
- Conversations, trade, missions
- Lightweight whole-universe simulation
- Documentation site & front page
- Dynamic lighting (planets & ships)
- Lens flare?
- Active abilities
- Galaxy date system, slowly orbiting planets
- Parallelize frame computations
- Advanced AI: avoid collisions
## Game & Story ## Game & Story
- How to keep player in system bounds, what to do if they fly far away - How to keep player in system bounds, what to do if they fly far away
- Max chase distance, physics-enforced area? - Max chase distance, physics-enforced area?
- (Soft limits, you shouldn't go too far unless you decide to.) - (Soft limits, you shouldn't go too far unless you decide to.)
- Enforce: silently teleport to the other end - Enforce: silently teleport to the other end
- How to handle death -- save scum should not be the norm (hollow knight?) - How to handle death -- save scum should not be the norm (hollow knight?)
- Jumping between systems -- how? - Locks and keys -> variation?
- Locks and keys -> variation? - Hard to get outfits you've lost (hollow knight's charms do this very well)
- Active abilities - Missions: hunt scary targets
- Hard to get outfits you've lost (hollow knight's charms do this very well) - The world exists outside of the system you're in
- Missions: hunt scary targets - (large-scale dynamics)
- The world exists outside of the system you're in - How does information travel?
- (large-scale dynamics) - Communication timer: destroy a ship fast, nobody needs to know
- How does information travel? - Do we want to pay crew?
- Communication timer: destroy a ship fast, nobody needs to know - How should ship capturing work?
- Do we want to pay crew? - Enemy motivations
- How should ship capturing work? - Where does money come from?
- Enemy motivations - Changing map paths? How?
- Where does money come from? - Actions against one faction affect another, dynamic relationships
- Changing map paths? How?
## Features
- Music (user-configurable)
- Sound effects
- Debris (ship death, destructible, physics)
- Orbiting debris (asteroids)
- Collectibles (flotsam)
- Back arrow: reverse thruster or reverse ship
- Multiplayer? (how does that fit into gameplay?)
- On-screen text
- Controller input & key bindings
- Save games
- Date system -> planet position
- AI fleets
- More ship behaviors, improved ai
- Collision avoidance
- Jump between systems
- Government color, ship tint
- Different kinds of ship behaviors:
- who to target
- how to target them
- where to go
- etc, extra flags
- Advanced particle physics (must move to cpu. Maybe both?)
- Background simulation (two modes)
## Faction interaction
- Targeting overrides hitscan rules (only for player)
- Static and dynamic faction relationships (change with player actions/game events?)
- Dynamic relationships only for player? Other governments may be hard-coded
- Opinion towards player -> how to handle well?
- Actions against one faction affect another
## Internal
- Logging/warning system
- Only compute timings when necessary
- Elegantly handle lost focus
- Pause game
- Clear all `// TODO:` comments littered in the source
- CLI options (debug, save location, content location, check content)
- Config file and compile options, remove all those consts.
- Better error when run outside of directory
- Documentation site & front page
- Random animation delay/fps?
- Better WGSL preprocessor
- Depth buffer (z-axis when landing!)
- Compute shader for particles
- Better performance for projectiles
## Content
- Angled engines
- Angled guns
- Turn engine flares
- Turn engine physics?
- Reverse engine & flares
- Better ship colliders (need a tool or an algorithm)
- Turrets
- Weapons with ammunition
- Enable/disable weapons
- Cargo space
- Display name != internal key
- Conversations
- Trade
- Missions
- Procedural suns
- Heat and energy
- Non-removable outfits
- Space-converting outfits
- Damage struct and debuffs
## Camera
- Shake/wobble on heavy hits?
- Camera effects on low health?
- (or are ship particles better?)
- Lookahead -> position or direction?
- Damping?
- Important objects affect camera
## Visuals
- Dynamic lighting (planets & ships)
- Motion blur
- Zoom parallax (?)
- Background haze
- Nova dust parallax
- Engine flare ease in/out
- Lens flare
- Clustered starfield
- Redesign UI
- Passive engine glow
- Landing atmosphere burn
## Write and Document ## Write and Document
- Parallax - Parallax

View File

@ -38,13 +38,7 @@ pub enum ShipState {
}, },
/// This ship has been destroyed, and is playing its collapse sequence. /// This ship has been destroyed, and is playing its collapse sequence.
Collapsing { Collapsing,
/// Total collapse sequence length, in seconds
total: f32,
/// How many seconds of the collapse sequence we've played
elapsed: f32,
},
/// This ship is landed on a planet /// This ship is landed on a planet
Landed { Landed {
@ -84,18 +78,6 @@ impl ShipState {
_ => None, _ => None,
} }
} }
/// If this ship is collapsing, return total collapse time and remaining collapse time.
/// Otherwise, return None
pub fn collapse_state(&self) -> Option<(f32, f32)> {
match self {
Self::Collapsing {
total,
elapsed: remaining,
} => Some((*total, *remaining)),
_ => None,
}
}
} }
/// Represents all attributes of a single ship /// Represents all attributes of a single ship
@ -257,6 +239,18 @@ impl ShipData {
} }
}; };
} }
/// Called when collapse sequence is finished.
/// Will panic if we're not collapsing
pub fn finish_collapse(&mut self) {
match self.state {
ShipState::Collapsing => self.state = ShipState::Dead,
_ => {
unreachable!("Called `finish_collapse` on a ship that isn't collapsing!")
}
};
}
/// Add an outfit to this ship /// Add an outfit to this ship
pub fn add_outfit(&mut self, o: &Outfit) -> super::OutfitAddResult { pub fn add_outfit(&mut self, o: &Outfit) -> super::OutfitAddResult {
let r = self.outfits.add(o); let r = self.outfits.add(o);
@ -292,7 +286,7 @@ impl ShipData {
} }
/// Hit this ship with the given amount of damage /// Hit this ship with the given amount of damage
pub(crate) fn apply_damage(&mut self, ct: &Content, mut d: f32) { pub(crate) fn apply_damage(&mut self, mut d: f32) {
match self.state { match self.state {
ShipState::Flying { .. } => {} ShipState::Flying { .. } => {}
_ => { _ => {
@ -311,10 +305,7 @@ impl ShipData {
if self.hull <= 0.0 { if self.hull <= 0.0 {
// This ship has been destroyed, update state // This ship has been destroyed, update state
self.state = ShipState::Collapsing { self.state = ShipState::Collapsing
total: ct.get_ship(self.ct_handle).collapse.length,
elapsed: 0.0,
}
} }
} }
@ -360,17 +351,7 @@ impl ShipData {
} }
} }
ShipState::Collapsing { ShipState::Collapsing {} | ShipState::Dead => {}
ref mut elapsed,
total,
} => {
*elapsed += t;
if *elapsed >= total {
self.state = ShipState::Dead
}
}
ShipState::Dead => {}
} }
} }
} }

View File

@ -1,5 +1,6 @@
use galactica_content::Relationship; use galactica_content::Relationship;
use nalgebra::{Rotation2, Vector2}; use galactica_util::clockwise_angle;
use nalgebra::Vector2;
use rapier2d::{dynamics::RigidBodySet, geometry::ColliderHandle}; use rapier2d::{dynamics::RigidBodySet, geometry::ColliderHandle};
use std::collections::HashMap; use std::collections::HashMap;
@ -73,12 +74,14 @@ impl ShipControllerStruct for PointShipController {
} }
} }
let angle = (closest_enemy_position - my_position).angle(&Vector2::new(1.0, 0.0)); let angle = clockwise_angle(
let angle_delta = my_rotation.angle_to(&Rotation2::new(angle).into()); &(my_rotation * Vector2::new(1.0, 0.0)),
&(closest_enemy_position - my_position),
);
if angle_delta < 0.0 && my_angvel > -0.3 { if angle < 0.0 && my_angvel > -0.3 {
controls.right = true; controls.right = true;
} else if angle_delta > 0.0 && my_angvel < 0.3 { } else if angle > 0.0 && my_angvel < 0.3 {
controls.left = true; controls.left = true;
} }

View File

@ -10,27 +10,35 @@ use crate::data::ShipData;
pub(super) struct ShipCollapseSequence { pub(super) struct ShipCollapseSequence {
rng: ThreadRng, rng: ThreadRng,
/// The elapsed collapse duration when step() /// The total length of this collapse sequence
/// was last called total_length: f32,
last_call: f32,
/// How many seconds we've spent playing this sequence
elapsed: f32,
} }
impl ShipCollapseSequence { impl ShipCollapseSequence {
pub(super) fn new() -> Self { pub(super) fn new(total_length: f32) -> Self {
Self { Self {
rng: rand::thread_rng(), rng: rand::thread_rng(),
last_call: 0.0, total_length,
elapsed: 0.0,
} }
} }
/// Has this collapse sequence fully played out?
pub fn is_done(&self) -> bool {
self.elapsed >= self.total_length
}
/// Pick a random points inside a ship's collider /// Pick a random points inside a ship's collider
fn random_in_ship(&mut self, ship: &Ship, collider: &Collider) -> Vector2<f32> { fn random_in_ship(&mut self, ship: &Ship, collider: &Collider) -> Vector2<f32> {
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;
while !a { while !a {
y = self.rng.gen_range(-1.0..=1.0) * ship.size / 2.0; x = self.rng.gen_range(-1.0..=1.0) * ship.size / 2.0;
x = self.rng.gen_range(-1.0..=1.0) * ship.size * ship.sprite.aspect / 2.0; y = self.rng.gen_range(-1.0..=1.0) * ship.size * ship.sprite.aspect / 2.0;
a = collider.shape().contains_local_point(&Point2::new(x, y)); a = collider.shape().contains_local_point(&Point2::new(x, y));
} }
Vector2::new(x, y) Vector2::new(x, y)
@ -48,12 +56,8 @@ impl ShipCollapseSequence {
let ship_pos = rigid_body.translation(); let ship_pos = rigid_body.translation();
let ship_rot = rigid_body.rotation(); let ship_rot = rigid_body.rotation();
let (total, elapsed) = ship_data.get_state().collapse_state().unwrap();
// How much time has passed since step() was last called
let delta = elapsed - self.last_call;
// The fraction of this collapse sequence that has been played // The fraction of this collapse sequence that has been played
let frac_done = elapsed / total; let frac_done = self.elapsed / self.total_length;
// TODO: slight random offset for event particles // TODO: slight random offset for event particles
@ -61,8 +65,8 @@ impl ShipCollapseSequence {
for event in &ship_content.collapse.events { for event in &ship_content.collapse.events {
match event { match event {
CollapseEvent::Effect(event) => { CollapseEvent::Effect(event) => {
if (event.time > self.last_call && event.time <= elapsed) if (event.time > self.elapsed && event.time <= self.elapsed + res.t)
|| (event.time == 0.0 && self.last_call == 0.0) || (event.time == 0.0 && self.elapsed == 0.0)
// ^^ Don't miss events scheduled at the very start of the sequence! // ^^ Don't miss events scheduled at the very start of the sequence!
{ {
for spawner in &event.effects { for spawner in &event.effects {
@ -110,9 +114,7 @@ impl ShipCollapseSequence {
return y; return y;
}; };
// Notice that we don't use res.t here, since ship state is updated earlier let p_add = (res.t / self.total_length) * pdf(frac_done) * spawner.count;
// ( through self.data.step() )
let p_add = (delta / total) * 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 {
@ -138,6 +140,6 @@ impl ShipCollapseSequence {
} }
} }
self.last_call = elapsed; self.elapsed += res.t;
} }
} }

View File

@ -70,12 +70,13 @@ impl PhysSimShip {
rigid_body: RigidBodyHandle, rigid_body: RigidBodyHandle,
collider: ColliderHandle, collider: ColliderHandle,
) -> Self { ) -> Self {
let ship_ct = ct.get_ship(handle);
PhysSimShip { PhysSimShip {
rigid_body, rigid_body,
collider, collider,
data: ShipData::new(ct, handle, faction, personality), data: ShipData::new(ct, handle, faction, personality),
controls: ShipControls::new(), controls: ShipControls::new(),
collapse_sequence: Some(ShipCollapseSequence::new()), collapse_sequence: Some(ShipCollapseSequence::new(ship_ct.collapse.length)),
} }
} }
@ -94,6 +95,10 @@ impl PhysSimShip {
let mut seq = self.collapse_sequence.take().unwrap(); let mut seq = self.collapse_sequence.take().unwrap();
seq.step(res, &self.data, rigid_body, collider); seq.step(res, &self.data, rigid_body, collider);
self.collapse_sequence = Some(seq); self.collapse_sequence = Some(seq);
if self.collapse_sequence.as_ref().unwrap().is_done() {
self.data.finish_collapse();
}
} }
ShipState::Flying { .. } => { ShipState::Flying { .. } => {
self.step_physics(res, rigid_body, collider); self.step_physics(res, rigid_body, collider);
@ -167,8 +172,8 @@ impl PhysSimShip {
let mut x = 0.0; let mut x = 0.0;
let mut a = false; let mut a = false;
while !a { while !a {
y = rng.gen_range(-1.0..=1.0) * ship_content.size / 2.0; x = rng.gen_range(-1.0..=1.0) * ship_content.size / 2.0;
x = rng.gen_range(-1.0..=1.0) y = rng.gen_range(-1.0..=1.0)
* ship_content.size * ship_content.sprite.aspect * ship_content.size * ship_content.sprite.aspect
/ 2.0; / 2.0;
a = collider.shape().contains_local_point(&point![x, y]); a = collider.shape().contains_local_point(&point![x, y]);

View File

@ -162,7 +162,7 @@ impl PhysSim {
let destory_projectile = match r { let destory_projectile = match r {
Relationship::Hostile => match ship.data.get_state() { Relationship::Hostile => match ship.data.get_state() {
ShipState::Flying { .. } => { ShipState::Flying { .. } => {
ship.data.apply_damage(res.ct, projectile.content.damage); ship.data.apply_damage(projectile.content.damage);
true true
} }
ShipState::Collapsing { .. } => true, ShipState::Collapsing { .. } => true,

View File

@ -13,7 +13,7 @@ pub fn to_radians(degrees: f32) -> f32 {
} }
/// Compute the clockwise angle between two vectors /// Compute the clockwise angle between two vectors
/// Returns a value in [0, 2pi] /// Returns a value in [-pi, pi]
pub fn clockwise_angle(a: &Vector2<f32>, b: &Vector2<f32>) -> f32 { pub fn clockwise_angle(a: &Vector2<f32>, b: &Vector2<f32>) -> f32 {
(a.x * b.y - b.x * a.y).atan2(a.dot(&b)) (a.x * b.y - b.x * a.y).atan2(a.dot(&b))
} }