Compare commits

...

2 Commits

Author SHA1 Message Date
Mark 025a1df69e
Added basic unlanding animation 2024-01-12 18:16:20 -08:00
Mark 079ee461af
Added basic landing animation 2024-01-12 17:38:33 -08:00
8 changed files with 365 additions and 97 deletions

View File

@ -42,6 +42,7 @@ fn main() -> Result<()> {
let mut gpu = pollster::block_on(galactica_render::GPUState::new(window, &content))?; let mut gpu = pollster::block_on(galactica_render::GPUState::new(window, &content))?;
gpu.init(&content); gpu.init(&content);
// TODO: don't clone content
let mut game = game::Game::new(content.clone()); let mut game = game::Game::new(content.clone());
let p = game.make_player(); let p = game.make_player();
@ -96,6 +97,16 @@ fn main() -> Result<()> {
None None
} }
} }
ShipState::UnLanding { .. } => {
let pos =
o.data.get_state().unlanding_position(&content).unwrap();
Some(Point2 { x: pos.x, y: pos.y })
}
ShipState::Landing { .. } => {
let pos =
o.data.get_state().landing_position(&content).unwrap();
Some(Point2 { x: pos.x, y: pos.y })
}
ShipState::Landed { target } => { ShipState::Landed { target } => {
let b = content.get_system_object(*target); let b = content.get_system_object(*target);
Some(Point2 { Some(Point2 {
@ -103,6 +114,8 @@ fn main() -> Result<()> {
y: b.pos.y, y: b.pos.y,
}) })
} }
ShipState::Dead => None,
} }
} else { } else {
None None

View File

@ -1,7 +1,7 @@
//! GPUState routines for drawing items in a systemsim //! GPUState routines for drawing items in a systemsim
use bytemuck; use bytemuck;
use cgmath::{EuclideanSpace, InnerSpace, Point2, Vector2}; use cgmath::{EuclideanSpace, InnerSpace, Point2, Point3, Rad, Vector2};
use galactica_system::{data::ShipState, phys::util}; use galactica_system::{data::ShipState, phys::util};
use galactica_util::constants::OBJECT_SPRITE_INSTANCE_LIMIT; use galactica_util::constants::OBJECT_SPRITE_INSTANCE_LIMIT;
@ -19,28 +19,72 @@ impl GPUState {
screen_clip: (Point2<f32>, Point2<f32>), screen_clip: (Point2<f32>, Point2<f32>),
) { ) {
for ship in state.systemsim.iter_ships() { for ship in state.systemsim.iter_ships() {
match ship.data.get_state() { // TODO: move collapse sequence here?
ShipState::Collapsing { .. } | ShipState::Flying => {}
ShipState::Landed { .. } => continue,
}
let r = state.systemsim.get_rigid_body(ship.rigid_body).unwrap(); let ship_pos;
let ship_pos = util::rigidbody_position(&r); let ship_ang;
let ship_rot = util::rigidbody_rotation(r); let ship_cnt;
let ship_ang = -ship_rot.angle(Vector2 { x: 0.0, y: 1.0 }); // TODO: inconsistent angles. Fix! match ship.data.get_state() {
let ship_cnt = state.ct.get_ship(ship.data.get_content()); ShipState::Dead | ShipState::Landed { .. } => continue,
ShipState::Collapsing { .. } | ShipState::Flying => {
let r = state.systemsim.get_rigid_body(ship.rigid_body).unwrap();
let pos = util::rigidbody_position(&r);
ship_pos = Point3 {
x: pos.x,
y: pos.y,
z: 1.0,
};
let ship_rot = util::rigidbody_rotation(r);
ship_ang = -ship_rot.angle(Vector2 { x: 0.0, y: 1.0 }); // TODO: inconsistent angles. Fix!
ship_cnt = state.ct.get_ship(ship.data.get_content());
}
ShipState::Landing {
from_position,
from_angle,
target,
total: _total,
elapsed,
} => {
let target = state.ct.get_system_object(*target);
let diff = Point2 {
x: target.pos.x,
y: target.pos.y,
} - from_position;
ship_pos = ship.data.get_state().landing_position(state.ct).unwrap();
let target_angle = -diff.angle(Vector2 { x: 0.0, y: 1.0 });
ship_ang = from_angle + ((target_angle - from_angle) * 1f32.min(elapsed / 1.0));
ship_cnt = state.ct.get_ship(ship.data.get_content());
}
ShipState::UnLanding {
to_angle, elapsed, ..
} => {
ship_pos = ship.data.get_state().unlanding_position(state.ct).unwrap();
ship_ang = Rad(0.0) + ((to_angle - Rad(0.0)) * 1f32.min(elapsed / 1.0));
ship_cnt = state.ct.get_ship(ship.data.get_content());
}
}
// Position adjusted for parallax // Position adjusted for parallax
// TODO: adjust parallax for zoom? // TODO: adjust parallax for zoom?
// 1.0 is z-coordinate, which is constant for ships // 1.0 is z-coordinate, which is constant for ships
let pos: Point2<f32> = (ship_pos - state.camera_pos.to_vec()) / 1.0; let pos: Point2<f32> = (Point2 {
x: ship_pos.x,
y: ship_pos.y,
} - state.camera_pos.to_vec())
/ ship_pos.z;
// Game dimensions of this sprite post-scale. // Game dimensions of this sprite post-scale.
// Post-scale width or height, whichever is larger. // Post-scale width or height, whichever is larger.
// This is in game units. // This is in game units.
// //
// We take the maximum to account for rotated sprites. // We take the maximum to account for rotated sprites.
let m = (ship_cnt.size / 1.0) * ship_cnt.sprite.aspect.max(1.0); let m = (ship_cnt.size / ship_pos.z) * ship_cnt.sprite.aspect.max(1.0);
// Don't draw sprites that are off the screen // Don't draw sprites that are off the screen
if pos.x < screen_clip.0.x - m if pos.x < screen_clip.0.x - m
@ -59,7 +103,7 @@ impl GPUState {
bytemuck::cast_slice(&[ObjectData { bytemuck::cast_slice(&[ObjectData {
xpos: ship_pos.x, xpos: ship_pos.x,
ypos: ship_pos.y, ypos: ship_pos.y,
zpos: 1.0, zpos: ship_pos.z,
angle: ship_ang.0, angle: ship_ang.0,
size: ship_cnt.size, size: ship_cnt.size,
parent: 0, parent: 0,
@ -86,7 +130,13 @@ impl GPUState {
self.state.vertex_buffers.object_counter += 1; self.state.vertex_buffers.object_counter += 1;
let flare = ship.data.get_outfits().get_flare_sprite(state.ct); let flare = ship.data.get_outfits().get_flare_sprite(state.ct);
if ship.get_controls().thrust && flare.is_some() && ship.data.get_state().is_flying() { if {
let is_flying = match ship.data.get_state() {
ShipState::Flying => true,
_ => false,
};
ship.get_controls().thrust && flare.is_some() && is_flying
} {
for engine_point in &ship_cnt.engines { for engine_point in &ship_cnt.engines {
self.state.queue.write_buffer( self.state.queue.write_buffer(
&self.state.global_uniform.object_buffer, &self.state.global_uniform.object_buffer,

View File

@ -39,6 +39,26 @@ impl Radar {
.unwrap(); .unwrap();
match player_ship.data.get_state() { match player_ship.data.get_state() {
ShipState::Dead => {}
ShipState::Landing { .. } => {
let pos = player_ship
.data
.get_state()
.landing_position(&input.ct)
.unwrap();
self.last_player_position = Point2 { x: pos.x, y: pos.y }
}
ShipState::UnLanding { .. } => {
let pos = player_ship
.data
.get_state()
.unlanding_position(&input.ct)
.unwrap();
self.last_player_position = Point2 { x: pos.x, y: pos.y }
}
ShipState::Landed { target } => { ShipState::Landed { target } => {
let landed_body = input.ct.get_system_object(*target); let landed_body = input.ct.get_system_object(*target);
self.last_player_position = Point2 { self.last_player_position = Point2 {
@ -133,10 +153,14 @@ impl Radar {
// This will be None if this ship is dead. // This will be None if this ship is dead.
// Stays around while the physics system runs a collapse sequence // Stays around while the physics system runs a collapse sequence
let color = match s.data.get_state() { let color = match s.data.get_state() {
ShipState::Landed { .. } => { ShipState::Dead | ShipState::Landed { .. } => {
continue; continue;
} }
ShipState::Collapsing { .. } => {
// TODO: different color for landing?
ShipState::UnLanding { .. }
| ShipState::Landing { .. }
| ShipState::Collapsing { .. } => {
// TODO: configurable // TODO: configurable
[0.2, 0.2, 0.2, 1.0] [0.2, 0.2, 0.2, 1.0]
} }

View File

@ -37,10 +37,20 @@ impl Status {
.unwrap(); .unwrap();
match player_ship.data.get_state() { match player_ship.data.get_state() {
ShipState::Landed { .. } | ShipState::Collapsing { .. } | ShipState::Flying => { ShipState::Dead => {
current_shields = 0.0;
current_hull = 0.0;
max_shields = player_ship.data.get_outfits().get_shield_strength(); max_shields = player_ship.data.get_outfits().get_shield_strength();
max_hull = input.ct.get_ship(player_ship.data.get_content()).hull;
}
ShipState::UnLanding { .. }
| ShipState::Landing { .. }
| ShipState::Landed { .. }
| ShipState::Collapsing { .. }
| ShipState::Flying => {
current_shields = player_ship.data.get_shields(); current_shields = player_ship.data.get_shields();
current_hull = player_ship.data.get_hull(); current_hull = player_ship.data.get_hull();
max_shields = player_ship.data.get_outfits().get_shield_strength();
max_hull = input.ct.get_ship(player_ship.data.get_content()).hull; max_hull = input.ct.get_ship(player_ship.data.get_content()).hull;
} }
} }

View File

@ -1,6 +1,7 @@
use std::{collections::HashMap, time::Instant}; use std::{collections::HashMap, time::Instant};
use super::{OutfitSet, ShipPersonality}; use super::{OutfitSet, ShipPersonality};
use cgmath::{InnerSpace, Point2, Point3, Rad};
use galactica_content::{Content, FactionHandle, GunPoint, Outfit, ShipHandle, SystemObjectHandle}; use galactica_content::{Content, FactionHandle, GunPoint, Outfit, ShipHandle, SystemObjectHandle};
use rand::{rngs::ThreadRng, Rng}; use rand::{rngs::ThreadRng, Rng};
@ -10,6 +11,9 @@ use rand::{rngs::ThreadRng, Rng};
/// sequence fully plays out. /// sequence fully plays out.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum ShipState { pub enum ShipState {
/// This ship is dead, and should be removed from the game.
Dead,
/// This ship is alive and well in open space /// This ship is alive and well in open space
Flying, // TODO: system, position (also in collapse)? Flying, // TODO: system, position (also in collapse)?
@ -27,41 +31,47 @@ pub enum ShipState {
/// The planet this ship is landed on /// The planet this ship is landed on
target: SystemObjectHandle, target: SystemObjectHandle,
}, },
/// This ship is landing on a planet
/// (playing the animation)
Landing {
/// The point, in world coordinates, where we started
from_position: Point2<f32>,
/// The ship's angle when we started landing
from_angle: Rad<f32>,
/// The planet we're landing on
target: SystemObjectHandle,
/// The total amount of time, in seconds, we will spend landing
total: f32,
/// The amount of time we've already spent playing this landing sequence
elapsed: f32,
},
/// This ship is taking off from a planet
/// (playing the animation)
UnLanding {
/// The point, in world coordinates, to which we're going
to_position: Point2<f32>,
/// The angle we'll be at when we arrive
to_angle: Rad<f32>,
/// The planet we're taking off from
from: SystemObjectHandle,
/// The total amount of time, in seconds, we will spend taking off
total: f32,
/// The amount of time we've already spent playing this unlanding sequence
elapsed: f32,
},
} }
impl ShipState { impl ShipState {
/// Is this ship playing its collapse sequence?
pub fn is_collapsing(&self) -> bool {
match self {
Self::Collapsing { .. } => true,
_ => false,
}
}
/// Is this ship flying in open space?
pub fn is_flying(&self) -> bool {
match self {
Self::Flying => true,
_ => false,
}
}
/// Is this ship landed on a planet?
pub fn is_landed(&self) -> bool {
match self {
Self::Landed { .. } => true,
_ => false,
}
}
/// True if this ship has been destroyed and has finished it's collapse sequence.
/// Ships are deleted once this is true.
pub fn should_be_removed(&self) -> bool {
match self {
Self::Collapsing { elapsed, total } => elapsed >= total,
_ => false,
}
}
/// What planet is this ship landed on? /// What planet is this ship landed on?
pub fn landed_on(&self) -> Option<SystemObjectHandle> { pub fn landed_on(&self) -> Option<SystemObjectHandle> {
match self { match self {
@ -81,6 +91,86 @@ impl ShipState {
_ => None, _ => None,
} }
} }
/// Compute position of this ship's sprite during its landing sequence
pub fn landing_position(&self, ct: &Content) -> Option<Point3<f32>> {
match self {
Self::Landing {
from_position,
target,
total,
elapsed,
..
} => Some({
let target = ct.get_system_object(*target);
let diff = Point2 {
x: target.pos.x,
y: target.pos.y,
} - from_position;
let diff = diff - diff.normalize() * (target.size / 2.0) * 0.8;
// TODO: improve animation
// TODO: fade
// TODO: atmosphere burn
// TODO: land at random point
// TODO: don't jump camera
// TODO: time by distance
// TODO: keep momentum
let pos = from_position + (diff * (elapsed / total));
Point3 {
x: pos.x,
y: pos.y,
z: 1.0 + ((target.pos.z - 1.0) * (elapsed / total)),
}
}),
_ => None,
}
}
/// Compute position of this ship's sprite during its unlanding sequence
pub fn unlanding_position(&self, ct: &Content) -> Option<Point3<f32>> {
match self {
Self::UnLanding {
to_position,
from,
total,
elapsed,
..
} => Some({
let from = ct.get_system_object(*from);
let diff = to_position
- Point2 {
x: from.pos.x,
y: from.pos.y,
};
//let diff = diff - diff.normalize() * (target.size / 2.0) * 0.8;
// TODO: improve animation
// TODO: fade
// TODO: atmosphere burn
// TODO: land at random point
// TODO: don't jump camera
// TODO: time by distance
// TODO: keep momentum
let pos = Point2 {
x: from.pos.x,
y: from.pos.y,
} + (diff * (elapsed / total));
Point3 {
x: pos.x,
y: pos.y,
z: from.pos.z + ((1.0 - from.pos.z) * (elapsed / total)),
}
}),
_ => None,
}
}
} }
/// Represents all attributes of a single ship /// Represents all attributes of a single ship
@ -136,26 +226,47 @@ impl ShipData {
} }
} }
// TODO: position in data?
/// Land this ship on `target` /// Land this ship on `target`
pub fn land_on(&mut self, target: SystemObjectHandle) -> bool { pub fn land_on(
if self.state.is_flying() { &mut self,
self.state = ShipState::Landed { target }; target: SystemObjectHandle,
} else { from_position: Point2<f32>,
return false; from_angle: Rad<f32>,
} ) -> bool {
match self.state {
return true; ShipState::Flying => {
self.state = ShipState::Landing {
elapsed: 0.0,
total: 5.0,
target,
from_position,
from_angle,
};
return true;
}
_ => {
unreachable!("Called `land_on` on a ship that isn't flying!")
}
};
} }
/// Take off from `target` /// Take off from `target`
pub fn unland(&mut self) -> bool { pub fn unland(&mut self, to_position: Point2<f32>) {
if self.state.is_landed() { match self.state {
self.state = ShipState::Flying; ShipState::Landed { target } => {
} else { self.state = ShipState::UnLanding {
return false; to_position,
} to_angle: Rad(1.0),
from: target,
return true; total: 5.0,
elapsed: 0.0,
};
}
_ => {
unreachable!("Called `unland` on a ship that isn't landed!")
}
};
} }
/// Add an outfit to this ship /// Add an outfit to this ship
@ -194,9 +305,13 @@ 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, ct: &Content, mut d: f32) {
if self.state.is_collapsing() { match self.state {
return; ShipState::Flying => {}
_ => {
unreachable!("Cannot apply damage to a ship that is not flying!")
}
} }
if self.shields >= d { if self.shields >= d {
self.shields -= d self.shields -= d
} else { } else {
@ -218,10 +333,27 @@ impl ShipData {
/// Update this ship's state by `t` seconds /// Update this ship's state by `t` seconds
pub(crate) fn step(&mut self, t: f32) { pub(crate) fn step(&mut self, t: f32) {
match self.state { match self.state {
ShipState::Collapsing { ShipState::Landing {
ref mut elapsed, .. ref mut elapsed,
total,
target,
..
} => { } => {
*elapsed += t; *elapsed += t;
if *elapsed >= total {
self.state = ShipState::Landed { target };
}
}
ShipState::UnLanding {
ref mut elapsed,
total,
..
} => {
*elapsed += t;
if *elapsed >= total {
self.state = ShipState::Flying;
}
} }
ShipState::Landed { .. } => { ShipState::Landed { .. } => {
@ -260,6 +392,18 @@ impl ShipData {
} }
} }
} }
ShipState::Collapsing {
ref mut elapsed,
total,
} => {
*elapsed += t;
if *elapsed >= total {
self.state = ShipState::Dead
}
}
ShipState::Dead => {}
} }
} }
} }

View File

@ -3,6 +3,8 @@ use galactica_content::Relationship;
use rapier2d::{dynamics::RigidBodySet, geometry::ColliderHandle}; use rapier2d::{dynamics::RigidBodySet, geometry::ColliderHandle};
use std::collections::HashMap; use std::collections::HashMap;
use crate::data::ShipState;
use super::{ use super::{
super::{ super::{
objects::{PhysSimShip, ShipControls}, objects::{PhysSimShip, ShipControls},
@ -46,7 +48,10 @@ impl ShipControllerStruct for PointShipController {
.filter( .filter(
// Target only flying ships we're hostile towards // Target only flying ships we're hostile towards
|s| match my_faction.relationships.get(&s.data.get_faction()).unwrap() { |s| match my_faction.relationships.get(&s.data.get_faction()).unwrap() {
Relationship::Hostile => s.data.get_state().is_flying(), Relationship::Hostile => match s.data.get_state() {
ShipState::Flying => true,
_ => false,
},
_ => false, _ => false,
}, },
) )

View File

@ -99,7 +99,10 @@ impl PhysSimShip {
ShipState::Flying => { ShipState::Flying => {
return self.step_live(res, rigid_body, collider); return self.step_live(res, rigid_body, collider);
} }
ShipState::Landed { .. } => {} ShipState::UnLanding { .. }
| ShipState::Dead
| ShipState::Landing { .. }
| ShipState::Landed { .. } => {}
} }
} }

View File

@ -69,32 +69,47 @@ impl<'a> PhysSim {
fn land_ship(&mut self, collider: ColliderHandle, target: SystemObjectHandle) { fn land_ship(&mut self, collider: ColliderHandle, target: SystemObjectHandle) {
let ship = self.ships.get_mut(&collider).unwrap(); let ship = self.ships.get_mut(&collider).unwrap();
self.rigid_body_set
.get_mut(ship.rigid_body) let r = self.rigid_body_set.get(ship.rigid_body).unwrap();
.unwrap() ship.data.land_on(
.set_enabled(false); target,
util::rigidbody_position(r),
-util::rigidbody_rotation(r).angle(Vector2 { x: 0.0, y: 1.0 }),
);
let r = self.rigid_body_set.get_mut(ship.rigid_body).unwrap();
r.set_enabled(false);
r.set_angvel(0.0, false);
r.set_linvel(nalgebra::Vector2::new(0.0, 0.0), false);
self.collider_set self.collider_set
.get_mut(ship.collider) .get_mut(ship.collider)
.unwrap() .unwrap()
.set_enabled(false); .set_enabled(false);
ship.data.land_on(target);
} }
fn unland_ship(&mut self, ct: &Content, collider: ColliderHandle) { fn unland_ship(&mut self, ct: &Content, collider: ColliderHandle) {
let ship = self.ships.get_mut(&collider).unwrap(); let ship = self.ships.get_mut(&collider).unwrap();
let obj = ship.data.get_state().landed_on().unwrap(); let obj = ship.data.get_state().landed_on().unwrap();
let obj = ct.get_system_object(obj); let obj = ct.get_system_object(obj);
ship.data.unland();
self.rigid_body_set let target_pos = Point2 {
.get_mut(ship.rigid_body) x: obj.pos.x + 100.0,
.unwrap() y: obj.pos.y + 100.0,
.set_position(point![obj.pos.x, obj.pos.y].into(), true); };
self.rigid_body_set
.get_mut(ship.rigid_body) ship.data.unland(target_pos);
.unwrap()
.set_enabled(true); let r = self.rigid_body_set.get_mut(ship.rigid_body).unwrap();
r.set_enabled(true);
r.set_position(
nalgebra::Vector2::new(target_pos.x, target_pos.y).into(),
true,
);
r.set_rotation(nalgebra::Rotation2::new(1.0).into(), false);
self.collider_set self.collider_set
.get_mut(ship.collider) .get_mut(ship.collider)
.unwrap() .unwrap()
@ -136,10 +151,14 @@ impl<'a> PhysSim {
let f = res.ct.get_faction(projectile.faction); let f = res.ct.get_faction(projectile.faction);
let r = f.relationships.get(&ship.data.get_faction()).unwrap(); let r = f.relationships.get(&ship.data.get_faction()).unwrap();
let destory_projectile = match r { let destory_projectile = match r {
Relationship::Hostile => { Relationship::Hostile => match ship.data.get_state() {
ship.data.apply_damage(res.ct, projectile.content.damage); ShipState::Flying => {
true ship.data.apply_damage(res.ct, projectile.content.damage);
} true
}
ShipState::Collapsing { .. } => true,
_ => false,
},
_ => false, _ => false,
}; };
@ -291,15 +310,15 @@ impl PhysSim {
// Borrow immutably for now... // Borrow immutably for now...
// (required to compute controls) // (required to compute controls)
let ship = self.ships.get(&collider).unwrap(); let ship = self.ships.get(&collider).unwrap();
if ship.data.get_state().should_be_removed() {
to_remove.push(collider);
continue;
}
match ship.data.get_state() { match ship.data.get_state() {
ShipState::Landed { .. } => {} ShipState::Dead => {
to_remove.push(collider);
}
ShipState::Collapsing { .. } => { ShipState::UnLanding { .. }
| ShipState::Landing { .. }
| ShipState::Landed { .. }
| ShipState::Collapsing { .. } => {
let ship = self.ships.get_mut(&collider).unwrap(); let ship = self.ships.get_mut(&collider).unwrap();
ship.step( ship.step(
res, res,