Added projectile parameters
parent
67f19e91b6
commit
e344c344ad
1
TODO.md
1
TODO.md
|
@ -1,5 +1,4 @@
|
|||
## Specific Jobs
|
||||
- Define projectile colliders & particles
|
||||
- Particle variation
|
||||
- Animated sprites
|
||||
- UI: health, shield, fuel, heat, energy bars
|
||||
|
|
|
@ -4,10 +4,10 @@ space.weapon = 10
|
|||
|
||||
# Average delay between shots
|
||||
rate = 0.2
|
||||
# Random rate variation (+- this in both directions)
|
||||
# Random rate variation (each cooldown is +- this)
|
||||
rate_rng = 0.1
|
||||
|
||||
|
||||
# TODO: apply force on fire
|
||||
projectile.sprite_texture = "projectile::blaster"
|
||||
# Height of projectile in game units
|
||||
projectile.size = 6
|
||||
|
@ -24,3 +24,18 @@ projectile.damage = 10.0
|
|||
# If this is 10, projectiles will form a cone of 10 degrees
|
||||
# ( 5 degrees on each side )
|
||||
projectile.angle_rng = 2
|
||||
# How much force this projectile will apply to an object it hits
|
||||
projectile.force = 0.0
|
||||
|
||||
projectile.collider.ball.radius = 2.0
|
||||
|
||||
projectile.impact.texture = "particle::blaster"
|
||||
projectile.impact.lifetime = "inherit"
|
||||
projectile.impact.inherit_velocity = "target"
|
||||
projectile.impact.size = 3.0
|
||||
|
||||
|
||||
projectile.expire.texture = "particle::blaster"
|
||||
projectile.expire.lifetime = "inherit"
|
||||
projectile.expire.inherit_velocity = "projectile"
|
||||
projectile.expire.size = 3.0
|
||||
|
|
|
@ -17,7 +17,7 @@ file = "planet/luna.png"
|
|||
file = "projectile/blaster.png"
|
||||
|
||||
[texture."ship::gypsum"]
|
||||
file = "ship/gypsum2.png"
|
||||
file = "ship/gypsum.png"
|
||||
|
||||
[texture."ui::radar"]
|
||||
file = "ui/radar.png"
|
||||
|
|
|
@ -19,8 +19,8 @@ use walkdir::WalkDir;
|
|||
|
||||
pub use handle::{FactionHandle, GunHandle, OutfitHandle, ShipHandle, SystemHandle, TextureHandle};
|
||||
pub use part::{
|
||||
EnginePoint, Faction, Gun, GunPoint, Outfit, OutfitSpace, Projectile, Relationship, RepeatMode,
|
||||
Ship, System, Texture,
|
||||
EnginePoint, Faction, Gun, GunPoint, ImpactInheritVelocity, Outfit, OutfitSpace, Projectile,
|
||||
ProjectileCollider, ProjectileParticle, Relationship, RepeatMode, Ship, System, Texture,
|
||||
};
|
||||
|
||||
mod syntax {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use anyhow::{bail, Context, Result};
|
||||
use cgmath::Deg;
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::{handle::TextureHandle, Content};
|
||||
|
||||
|
@ -32,7 +32,58 @@ pub(crate) mod syntax {
|
|||
pub lifetime_rng: f32,
|
||||
pub damage: f32,
|
||||
pub angle_rng: f32,
|
||||
pub impact: Option<ProjectileParticle>,
|
||||
pub expire: Option<ProjectileParticle>,
|
||||
pub collider: super::ProjectileCollider,
|
||||
pub force: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ProjectileParticle {
|
||||
pub texture: String,
|
||||
pub lifetime: ParticleLifetime,
|
||||
pub inherit_velocity: super::ImpactInheritVelocity,
|
||||
pub size: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum ParticleLifetime {
|
||||
Inherit(String),
|
||||
Seconds(f32),
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines a projectile's collider
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub enum ProjectileCollider {
|
||||
/// A ball collider
|
||||
#[serde(rename = "ball")]
|
||||
Ball(BallCollider),
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct BallCollider {
|
||||
pub radius: f32,
|
||||
}
|
||||
|
||||
/// How we should set an impact particle's velocity
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub enum ImpactInheritVelocity {
|
||||
/// Don't inherit any velocity.
|
||||
/// This impact particle will be still.
|
||||
#[serde(rename = "don't")]
|
||||
Dont,
|
||||
|
||||
/// Inherit target velocity.
|
||||
/// This impact particle will stick to the object it hits.
|
||||
#[serde(rename = "target")]
|
||||
Target,
|
||||
|
||||
/// Inherit projectile velocity.
|
||||
/// This impact particle will continue on its projectile's path.
|
||||
#[serde(rename = "projectile")]
|
||||
Projectile,
|
||||
}
|
||||
|
||||
/// Represents a gun outfit.
|
||||
|
@ -81,12 +132,74 @@ pub struct Projectile {
|
|||
/// The damage this projectile does
|
||||
pub damage: f32,
|
||||
|
||||
/// The force this projectile applies
|
||||
pub force: f32,
|
||||
|
||||
/// The angle variation of this projectile.
|
||||
/// Projectiles can be off center up to
|
||||
/// `spread / 2` degrees in both directions.
|
||||
///
|
||||
/// (Forming a "fire cone" of `spread` degrees)
|
||||
pub angle_rng: Deg<f32>,
|
||||
|
||||
/// The particle this projectile will spawn when it hits something
|
||||
pub impact_particle: Option<ProjectileParticle>,
|
||||
|
||||
/// The particle this projectile will spawn when it expires
|
||||
pub expire_particle: Option<ProjectileParticle>,
|
||||
|
||||
/// Collider parameters for this projectile
|
||||
pub collider: ProjectileCollider,
|
||||
}
|
||||
|
||||
/// The particle a projectile will spawn when it hits something
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ProjectileParticle {
|
||||
/// The texture to use for this particle.
|
||||
/// This is most likely animated.
|
||||
pub texture: TextureHandle,
|
||||
|
||||
/// How many seconds this particle should live
|
||||
pub lifetime: f32,
|
||||
|
||||
/// How we should set this particle's velocity
|
||||
pub inherit_velocity: ImpactInheritVelocity,
|
||||
|
||||
/// The height of this particle, in game units.
|
||||
pub size: f32,
|
||||
}
|
||||
|
||||
fn parse_projectile_particle(
|
||||
ct: &Content,
|
||||
p: Option<syntax::ProjectileParticle>,
|
||||
) -> Result<Option<ProjectileParticle>> {
|
||||
if let Some(impact) = p {
|
||||
let impact_texture = match ct.texture_index.get(&impact.texture) {
|
||||
None => bail!("impact texture `{}` doesn't exist", impact.texture),
|
||||
Some(t) => *t,
|
||||
};
|
||||
|
||||
let impact_lifetime = match impact.lifetime {
|
||||
syntax::ParticleLifetime::Seconds(s) => s,
|
||||
syntax::ParticleLifetime::Inherit(s) => {
|
||||
if s == "inherit" {
|
||||
let t = ct.get_texture(impact_texture);
|
||||
t.fps * t.frames.len() as f32
|
||||
} else {
|
||||
bail!("bad impact lifetime, must be float or \"inherit\"",)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Some(ProjectileParticle {
|
||||
texture: impact_texture,
|
||||
lifetime: impact_lifetime,
|
||||
inherit_velocity: impact.inherit_velocity,
|
||||
size: impact.size,
|
||||
}))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::Build for Gun {
|
||||
|
@ -94,22 +207,29 @@ impl crate::Build for Gun {
|
|||
|
||||
fn build(gun: Self::InputSyntax, ct: &mut Content) -> Result<()> {
|
||||
for (gun_name, gun) in gun {
|
||||
let th = match ct.texture_index.get(&gun.projectile.sprite_texture) {
|
||||
let projectile_texture = match ct.texture_index.get(&gun.projectile.sprite_texture) {
|
||||
None => bail!(
|
||||
"In gun `{}`: texture `{}` doesn't exist",
|
||||
"In gun `{}`: projectile texture `{}` doesn't exist",
|
||||
gun_name,
|
||||
gun.projectile.sprite_texture
|
||||
),
|
||||
Some(t) => *t,
|
||||
};
|
||||
|
||||
let impact_particle = parse_projectile_particle(ct, gun.projectile.impact)
|
||||
.with_context(|| format!("In gun `{}`", gun_name))?;
|
||||
|
||||
let expire_particle = parse_projectile_particle(ct, gun.projectile.expire)
|
||||
.with_context(|| format!("In gun `{}`", gun_name))?;
|
||||
|
||||
ct.guns.push(Self {
|
||||
name: gun_name,
|
||||
space: gun.space.into(),
|
||||
rate: gun.rate,
|
||||
rate_rng: gun.rate_rng,
|
||||
projectile: Projectile {
|
||||
sprite_texture: th,
|
||||
force: gun.projectile.force,
|
||||
sprite_texture: projectile_texture,
|
||||
size: gun.projectile.size,
|
||||
size_rng: gun.projectile.size_rng,
|
||||
speed: gun.projectile.speed,
|
||||
|
@ -118,6 +238,9 @@ impl crate::Build for Gun {
|
|||
lifetime_rng: gun.projectile.lifetime_rng,
|
||||
damage: gun.projectile.damage,
|
||||
angle_rng: Deg(gun.projectile.angle_rng),
|
||||
impact_particle,
|
||||
expire_particle,
|
||||
collider: gun.projectile.collider,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ pub mod system;
|
|||
pub mod texture;
|
||||
|
||||
pub use faction::{Faction, Relationship};
|
||||
pub use gun::{Gun, Projectile};
|
||||
pub use gun::{Gun, ImpactInheritVelocity, Projectile, ProjectileCollider, ProjectileParticle};
|
||||
pub use outfit::Outfit;
|
||||
pub use shared::OutfitSpace;
|
||||
pub use ship::{EnginePoint, GunPoint, Ship};
|
||||
|
|
|
@ -168,7 +168,7 @@ impl Game {
|
|||
.get_ship_mut(&self.player)
|
||||
.unwrap()
|
||||
.physics_handle;
|
||||
let r = self.world.get_rigid_body(r.0); // TODO: r.0 shouldn't be public
|
||||
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();
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use cgmath::{Deg, EuclideanSpace, InnerSpace, Matrix2, Point2, Rad, Vector2};
|
||||
use crossbeam::channel::Receiver;
|
||||
use nalgebra::vector;
|
||||
use nalgebra::{point, vector};
|
||||
use rand::Rng;
|
||||
use rapier2d::{
|
||||
dynamics::{RigidBody, RigidBodyBuilder, RigidBodyHandle},
|
||||
|
@ -27,19 +27,28 @@ pub struct World {
|
|||
|
||||
// Private methods
|
||||
impl<'a> World {
|
||||
fn remove_projectile(&mut self, c: ColliderHandle) {
|
||||
fn remove_projectile(
|
||||
&mut self,
|
||||
c: ColliderHandle,
|
||||
) -> Option<(RigidBody, ProjectileWorldObject)> {
|
||||
let p = match self.projectiles.remove(&c) {
|
||||
Some(p) => p,
|
||||
None => return,
|
||||
None => return None,
|
||||
};
|
||||
self.wrapper.rigid_body_set.remove(
|
||||
p.rigid_body,
|
||||
&mut self.wrapper.im,
|
||||
&mut self.wrapper.collider_set,
|
||||
&mut self.wrapper.ij,
|
||||
&mut self.wrapper.mj,
|
||||
true,
|
||||
);
|
||||
let r = self
|
||||
.wrapper
|
||||
.rigid_body_set
|
||||
.remove(
|
||||
p.rigid_body,
|
||||
&mut self.wrapper.im,
|
||||
&mut self.wrapper.collider_set,
|
||||
&mut self.wrapper.ij,
|
||||
&mut self.wrapper.mj,
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
return Some((r, p));
|
||||
}
|
||||
|
||||
fn remove_ship(&mut self, h: ShipPhysicsHandle) {
|
||||
|
@ -92,10 +101,12 @@ impl<'a> World {
|
|||
.linvel(vector![vel.x, vel.y])
|
||||
.build();
|
||||
|
||||
let collider = ColliderBuilder::ball(1.0)
|
||||
.sensor(true)
|
||||
.active_events(ActiveEvents::COLLISION_EVENTS)
|
||||
.build();
|
||||
let collider = match &projectile.content.collider {
|
||||
content::ProjectileCollider::Ball(b) => ColliderBuilder::ball(b.radius)
|
||||
.sensor(true)
|
||||
.active_events(ActiveEvents::COLLISION_EVENTS)
|
||||
.build(),
|
||||
};
|
||||
|
||||
let rigid_body = self.wrapper.rigid_body_set.insert(rigid_body);
|
||||
let collider = self.wrapper.collider_set.insert_with_parent(
|
||||
|
@ -114,6 +125,83 @@ impl<'a> World {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn collide_projectile_ship(
|
||||
&mut self,
|
||||
ct: &content::Content,
|
||||
particles: &mut Vec<ParticleBuilder>,
|
||||
projectile_h: ColliderHandle,
|
||||
ship_h: ColliderHandle,
|
||||
) {
|
||||
let projectile = self.projectiles.get(&projectile_h);
|
||||
let ship = self.ships.get_mut(&ship_h);
|
||||
if projectile.is_none() || ship.is_none() {
|
||||
return;
|
||||
}
|
||||
let projectile = projectile.unwrap();
|
||||
let ship = ship.unwrap();
|
||||
|
||||
let hit = ship
|
||||
.ship
|
||||
.handle_projectile_collision(ct, &projectile.projectile);
|
||||
let s = ship.physics_handle;
|
||||
if hit {
|
||||
let pr = self
|
||||
.wrapper
|
||||
.rigid_body_set
|
||||
.get(projectile.rigid_body)
|
||||
.unwrap();
|
||||
let v = util::rigidbody_velocity(pr).normalize() * projectile.projectile.content.force;
|
||||
let pos = util::rigidbody_position(pr);
|
||||
let _ = pr;
|
||||
|
||||
let r = self.wrapper.rigid_body_set.get_mut(s.0).unwrap();
|
||||
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
|
||||
let pr = self
|
||||
.wrapper
|
||||
.rigid_body_set
|
||||
.get(projectile.rigid_body)
|
||||
.unwrap();
|
||||
let pos = util::rigidbody_position(pr);
|
||||
let angle: Deg<f32> = util::rigidbody_rotation(pr)
|
||||
.angle(Vector2 { x: 1.0, y: 0.0 })
|
||||
.into();
|
||||
|
||||
match &projectile.projectile.content.impact_particle {
|
||||
None => {}
|
||||
Some(x) => {
|
||||
let velocity = match x.inherit_velocity {
|
||||
content::ImpactInheritVelocity::Dont => Vector2 { x: 0.0, y: 0.0 },
|
||||
content::ImpactInheritVelocity::Projectile => util::rigidbody_velocity(pr),
|
||||
content::ImpactInheritVelocity::Target => {
|
||||
// Match target ship velocity.
|
||||
// Particles will fly off if the ship is spinning fast, but I
|
||||
// haven't found a good way to fix that.
|
||||
let (_, sr) = self.get_ship_body(s).unwrap();
|
||||
let velocity =
|
||||
sr.velocity_at_point(&nalgebra::Point2::new(pos.x, pos.y));
|
||||
Vector2 {
|
||||
x: velocity.x,
|
||||
y: velocity.y,
|
||||
}
|
||||
}
|
||||
};
|
||||
particles.push(ParticleBuilder {
|
||||
texture: x.texture,
|
||||
pos: Point2 { x: pos.x, y: pos.y },
|
||||
velocity,
|
||||
angle: -angle,
|
||||
lifetime: x.lifetime,
|
||||
size: x.size,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
self.remove_projectile(projectile.collider);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Public methods
|
||||
|
@ -199,76 +287,71 @@ impl<'a> World {
|
|||
// Handle collision events
|
||||
while let Ok(event) = &self.collision_queue.try_recv() {
|
||||
if event.started() {
|
||||
let a = &event.collider1();
|
||||
let b = &event.collider2();
|
||||
let a = event.collider1();
|
||||
let b = event.collider2();
|
||||
|
||||
// If projectiles are part of this collision, make sure
|
||||
// `a` is one of them.
|
||||
let (a, b) = if self.projectiles.contains_key(b) {
|
||||
let (a, b) = if self.projectiles.contains_key(&b) {
|
||||
(b, a)
|
||||
} else {
|
||||
(a, b)
|
||||
};
|
||||
|
||||
if let Some(p) = self.projectiles.get(a) {
|
||||
let hit = if let Some(s) = self.ships.get_mut(b) {
|
||||
s.ship.handle_projectile_collision(ct, &p.projectile)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
// Stupid re-borrow trick.
|
||||
// We can't have s be mutable inside this block.
|
||||
if let Some(s) = self.ships.get(b) {
|
||||
if hit {
|
||||
let pr = self.get_rigid_body(p.rigid_body);
|
||||
let pos = util::rigidbody_position(pr);
|
||||
let angle: Deg<f32> = util::rigidbody_rotation(pr)
|
||||
.angle(Vector2 { x: 1.0, y: 0.0 })
|
||||
.into();
|
||||
|
||||
// Match target ship velocity, so impact particles are "sticky".
|
||||
// Particles will fly off if the ship is spinning fast, but I
|
||||
// haven't found a good way to fix that.
|
||||
let (_, sr) = self.get_ship_body(s.physics_handle).unwrap();
|
||||
let velocity =
|
||||
sr.velocity_at_point(&nalgebra::Point2::new(pos.x, pos.y));
|
||||
let velocity = Vector2 {
|
||||
x: velocity.x,
|
||||
y: velocity.y,
|
||||
};
|
||||
|
||||
particles.push(ParticleBuilder {
|
||||
texture: ct.get_texture_handle("particle::blaster"),
|
||||
pos: Point2 { x: pos.x, y: pos.y },
|
||||
velocity,
|
||||
angle: -angle,
|
||||
lifetime: 0.15,
|
||||
size: 5.0,
|
||||
});
|
||||
self.remove_projectile(*a);
|
||||
}
|
||||
}
|
||||
let p = self.projectiles.get(&a);
|
||||
let s = self.ships.get_mut(&b);
|
||||
if p.is_none() || s.is_none() {
|
||||
continue;
|
||||
}
|
||||
self.collide_projectile_ship(ct, particles, a, b);
|
||||
}
|
||||
}
|
||||
|
||||
// Delete projectiles
|
||||
let mut to_remove = Vec::new();
|
||||
for (_, p) in &mut self.projectiles {
|
||||
for (c, p) in &mut self.projectiles {
|
||||
p.projectile.tick(t);
|
||||
if p.projectile.is_expired() {
|
||||
to_remove.push(p.collider);
|
||||
to_remove.push(*c);
|
||||
}
|
||||
}
|
||||
for i in to_remove {
|
||||
self.remove_projectile(i);
|
||||
for c in to_remove {
|
||||
let (pr, p) = self.remove_projectile(c).unwrap();
|
||||
|
||||
match &p.projectile.content.expire_particle {
|
||||
None => {}
|
||||
Some(x) => {
|
||||
let pos = util::rigidbody_position(&pr);
|
||||
let angle: Deg<f32> = util::rigidbody_rotation(&pr)
|
||||
.angle(Vector2 { x: 1.0, y: 0.0 })
|
||||
.into();
|
||||
|
||||
let velocity = match x.inherit_velocity {
|
||||
content::ImpactInheritVelocity::Target
|
||||
| content::ImpactInheritVelocity::Dont => Vector2 { x: 0.0, y: 0.0 },
|
||||
content::ImpactInheritVelocity::Projectile => util::rigidbody_velocity(&pr),
|
||||
};
|
||||
particles.push(ParticleBuilder {
|
||||
texture: x.texture,
|
||||
pos: Point2 { x: pos.x, y: pos.y },
|
||||
velocity,
|
||||
angle: -angle,
|
||||
lifetime: x.lifetime,
|
||||
size: x.size,
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a rigid body from a handle
|
||||
pub fn get_rigid_body(&self, r: RigidBodyHandle) -> &RigidBody {
|
||||
&self.wrapper.rigid_body_set[r]
|
||||
pub fn get_rigid_body(&self, r: RigidBodyHandle) -> Option<&RigidBody> {
|
||||
self.wrapper.rigid_body_set.get(r)
|
||||
}
|
||||
|
||||
/// Get a rigid body from a handle
|
||||
pub fn get_rigid_body_mut(&mut self, r: RigidBodyHandle) -> Option<&mut RigidBody> {
|
||||
self.wrapper.rigid_body_set.get_mut(r)
|
||||
}
|
||||
|
||||
/// Get a ship from a handle
|
||||
|
|
Loading…
Reference in New Issue