basic particles
This commit is contained in:
parent
a79669d55c
commit
f76b705fe6
6 changed files with 146 additions and 5 deletions
|
|
@ -97,3 +97,8 @@ pub struct WantsToUseItem {
|
||||||
|
|
||||||
#[derive(Component, Debug)]
|
#[derive(Component, Debug)]
|
||||||
pub struct Consumable {}
|
pub struct Consumable {}
|
||||||
|
|
||||||
|
#[derive(Component, Clone)]
|
||||||
|
pub struct ParticleLifetime {
|
||||||
|
pub lifetime_ms: f32,
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use super::{
|
use super::{
|
||||||
gamelog::GameLog, CombatStats, Consumable, InBackpack, Name, Position, ProvidesHealing, WantsToDropItem,
|
gamelog::GameLog, CombatStats, Consumable, InBackpack, Name, ParticleBuilder, Position, ProvidesHealing,
|
||||||
WantsToPickupItem, WantsToUseItem,
|
WantsToDropItem, WantsToPickupItem, WantsToUseItem, DEFAULT_PARTICLE_LIFETIME,
|
||||||
};
|
};
|
||||||
use specs::prelude::*;
|
use specs::prelude::*;
|
||||||
|
|
||||||
|
|
@ -45,10 +45,23 @@ impl<'a> System<'a> for ItemUseSystem {
|
||||||
ReadStorage<'a, Consumable>,
|
ReadStorage<'a, Consumable>,
|
||||||
ReadStorage<'a, ProvidesHealing>,
|
ReadStorage<'a, ProvidesHealing>,
|
||||||
WriteStorage<'a, CombatStats>,
|
WriteStorage<'a, CombatStats>,
|
||||||
|
WriteExpect<'a, ParticleBuilder>,
|
||||||
|
ReadStorage<'a, Position>,
|
||||||
);
|
);
|
||||||
|
|
||||||
fn run(&mut self, data: Self::SystemData) {
|
fn run(&mut self, data: Self::SystemData) {
|
||||||
let (player_entity, mut gamelog, entities, mut wants_use, names, consumables, healing, mut combat_stats) = data;
|
let (
|
||||||
|
player_entity,
|
||||||
|
mut gamelog,
|
||||||
|
entities,
|
||||||
|
mut wants_use,
|
||||||
|
names,
|
||||||
|
consumables,
|
||||||
|
healing,
|
||||||
|
mut combat_stats,
|
||||||
|
mut particle_builder,
|
||||||
|
positions,
|
||||||
|
) = data;
|
||||||
|
|
||||||
for (entity, use_item, stats) in (&entities, &wants_use, &mut combat_stats).join() {
|
for (entity, use_item, stats) in (&entities, &wants_use, &mut combat_stats).join() {
|
||||||
let item_heals = healing.get(use_item.item);
|
let item_heals = healing.get(use_item.item);
|
||||||
|
|
@ -56,6 +69,17 @@ impl<'a> System<'a> for ItemUseSystem {
|
||||||
None => {}
|
None => {}
|
||||||
Some(healer) => {
|
Some(healer) => {
|
||||||
stats.hp = i32::min(stats.max_hp, stats.hp + healer.heal_amount);
|
stats.hp = i32::min(stats.max_hp, stats.hp + healer.heal_amount);
|
||||||
|
let pos = positions.get(entity);
|
||||||
|
if let Some(pos) = pos {
|
||||||
|
particle_builder.request(
|
||||||
|
pos.x,
|
||||||
|
pos.y,
|
||||||
|
rltk::RGB::named(rltk::GREEN),
|
||||||
|
rltk::RGB::named(rltk::BLACK),
|
||||||
|
rltk::to_cp437('♥'),
|
||||||
|
DEFAULT_PARTICLE_LIFETIME,
|
||||||
|
);
|
||||||
|
}
|
||||||
if entity == *player_entity {
|
if entity == *player_entity {
|
||||||
gamelog.entries.push(format!(
|
gamelog.entries.push(format!(
|
||||||
"You quaff the {}, and heal {} hp.",
|
"You quaff the {}, and heal {} hp.",
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,8 @@ mod melee_combat_system;
|
||||||
use melee_combat_system::MeleeCombatSystem;
|
use melee_combat_system::MeleeCombatSystem;
|
||||||
mod inventory_system;
|
mod inventory_system;
|
||||||
use inventory_system::*;
|
use inventory_system::*;
|
||||||
|
mod particle_system;
|
||||||
|
use particle_system::{ParticleBuilder, DEFAULT_PARTICLE_LIFETIME};
|
||||||
|
|
||||||
// Embedded resources for use in wasm build
|
// Embedded resources for use in wasm build
|
||||||
rltk::embedded_resource!(TERMINAL8X8, "../resources/terminal8x8.jpg");
|
rltk::embedded_resource!(TERMINAL8X8, "../resources/terminal8x8.jpg");
|
||||||
|
|
@ -62,6 +64,8 @@ impl State {
|
||||||
item_use_system.run_now(&self.ecs);
|
item_use_system.run_now(&self.ecs);
|
||||||
let mut drop_system = ItemDropSystem {};
|
let mut drop_system = ItemDropSystem {};
|
||||||
drop_system.run_now(&self.ecs);
|
drop_system.run_now(&self.ecs);
|
||||||
|
let mut particle_system = particle_system::ParticleSpawnSystem {};
|
||||||
|
particle_system.run_now(&self.ecs);
|
||||||
self.ecs.maintain();
|
self.ecs.maintain();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -70,6 +74,7 @@ impl GameState for State {
|
||||||
fn tick(&mut self, ctx: &mut Rltk) {
|
fn tick(&mut self, ctx: &mut Rltk) {
|
||||||
// Clear screen
|
// Clear screen
|
||||||
ctx.cls();
|
ctx.cls();
|
||||||
|
particle_system::cull_dead_particles(&mut self.ecs, ctx);
|
||||||
|
|
||||||
// Draw map and ui
|
// Draw map and ui
|
||||||
draw_map(&self.ecs, ctx);
|
draw_map(&self.ecs, ctx);
|
||||||
|
|
@ -186,6 +191,7 @@ fn main() -> rltk::BError {
|
||||||
gs.ecs.register::<WantsToDropItem>();
|
gs.ecs.register::<WantsToDropItem>();
|
||||||
gs.ecs.register::<WantsToUseItem>();
|
gs.ecs.register::<WantsToUseItem>();
|
||||||
gs.ecs.register::<Consumable>();
|
gs.ecs.register::<Consumable>();
|
||||||
|
gs.ecs.register::<ParticleLifetime>();
|
||||||
|
|
||||||
let map = Map::new_map_rooms_and_corridors();
|
let map = Map::new_map_rooms_and_corridors();
|
||||||
let (player_x, player_y) = map.rooms[0].centre();
|
let (player_x, player_y) = map.rooms[0].centre();
|
||||||
|
|
@ -202,6 +208,7 @@ fn main() -> rltk::BError {
|
||||||
gs.ecs.insert(player_entity);
|
gs.ecs.insert(player_entity);
|
||||||
gs.ecs.insert(gamelog::GameLog { entries: vec!["Here's your welcome message.".to_string()] });
|
gs.ecs.insert(gamelog::GameLog { entries: vec!["Here's your welcome message.".to_string()] });
|
||||||
gs.ecs.insert(RunState::PreRun);
|
gs.ecs.insert(RunState::PreRun);
|
||||||
|
gs.ecs.insert(particle_system::ParticleBuilder::new());
|
||||||
|
|
||||||
rltk::main_loop(context, gs)
|
rltk::main_loop(context, gs)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use super::{gamelog::GameLog, CombatStats, Name, SufferDamage, WantsToMelee};
|
use super::{gamelog::GameLog, CombatStats, Name, ParticleBuilder, Position, SufferDamage, WantsToMelee};
|
||||||
use specs::prelude::*;
|
use specs::prelude::*;
|
||||||
|
|
||||||
pub struct MeleeCombatSystem {}
|
pub struct MeleeCombatSystem {}
|
||||||
|
|
@ -11,10 +11,21 @@ impl<'a> System<'a> for MeleeCombatSystem {
|
||||||
ReadStorage<'a, Name>,
|
ReadStorage<'a, Name>,
|
||||||
ReadStorage<'a, CombatStats>,
|
ReadStorage<'a, CombatStats>,
|
||||||
WriteStorage<'a, SufferDamage>,
|
WriteStorage<'a, SufferDamage>,
|
||||||
|
WriteExpect<'a, ParticleBuilder>,
|
||||||
|
ReadStorage<'a, Position>,
|
||||||
);
|
);
|
||||||
|
|
||||||
fn run(&mut self, data: Self::SystemData) {
|
fn run(&mut self, data: Self::SystemData) {
|
||||||
let (entities, mut log, mut wants_melee, names, combat_stats, mut inflict_damage) = data;
|
let (
|
||||||
|
entities,
|
||||||
|
mut log,
|
||||||
|
mut wants_melee,
|
||||||
|
names,
|
||||||
|
combat_stats,
|
||||||
|
mut inflict_damage,
|
||||||
|
mut particle_builder,
|
||||||
|
positions,
|
||||||
|
) = data;
|
||||||
|
|
||||||
for (_entity, wants_melee, name, stats) in (&entities, &wants_melee, &names, &combat_stats).join() {
|
for (_entity, wants_melee, name, stats) in (&entities, &wants_melee, &names, &combat_stats).join() {
|
||||||
if stats.hp <= 0 {
|
if stats.hp <= 0 {
|
||||||
|
|
@ -26,6 +37,17 @@ impl<'a> System<'a> for MeleeCombatSystem {
|
||||||
}
|
}
|
||||||
|
|
||||||
let target_name = names.get(wants_melee.target).unwrap();
|
let target_name = names.get(wants_melee.target).unwrap();
|
||||||
|
let pos = positions.get(wants_melee.target);
|
||||||
|
if let Some(pos) = pos {
|
||||||
|
particle_builder.request(
|
||||||
|
pos.x,
|
||||||
|
pos.y,
|
||||||
|
rltk::RGB::named(rltk::ORANGE),
|
||||||
|
rltk::RGB::named(rltk::BLACK),
|
||||||
|
rltk::to_cp437('‼'),
|
||||||
|
150.0,
|
||||||
|
);
|
||||||
|
}
|
||||||
let damage = i32::max(0, stats.power - target_stats.defence);
|
let damage = i32::max(0, stats.power - target_stats.defence);
|
||||||
|
|
||||||
if damage == 0 {
|
if damage == 0 {
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,10 @@ impl<'a> System<'a> for MonsterAI {
|
||||||
.insert(entity, WantsToMelee { target: *player_entity })
|
.insert(entity, WantsToMelee { target: *player_entity })
|
||||||
.expect("Unable to insert attack.");
|
.expect("Unable to insert attack.");
|
||||||
} else if viewshed.visible_tiles.contains(&*player_pos) {
|
} else if viewshed.visible_tiles.contains(&*player_pos) {
|
||||||
|
// If the player is visible, but the path is obstructed, this will currently search
|
||||||
|
// the entire map (i.e. Will do a huge ASTAR to find an alternate route), and the
|
||||||
|
// mob will follow that path until it leaves vision, then lose sight of the player
|
||||||
|
// and stop.
|
||||||
let path = rltk::a_star_search(map.xy_idx(pos.x, pos.y), map.xy_idx(player_pos.x, player_pos.y), &*map);
|
let path = rltk::a_star_search(map.xy_idx(pos.x, pos.y), map.xy_idx(player_pos.x, player_pos.y), &*map);
|
||||||
if path.success && path.steps.len() > 1 {
|
if path.success && path.steps.len() > 1 {
|
||||||
let mut idx = map.xy_idx(pos.x, pos.y);
|
let mut idx = map.xy_idx(pos.x, pos.y);
|
||||||
|
|
|
||||||
79
src/particle_system.rs
Normal file
79
src/particle_system.rs
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
use super::{ParticleLifetime, Position, Renderable, Rltk};
|
||||||
|
use rltk::RGB;
|
||||||
|
use specs::prelude::*;
|
||||||
|
|
||||||
|
pub const DEFAULT_PARTICLE_LIFETIME: f32 = 150.0;
|
||||||
|
|
||||||
|
pub fn cull_dead_particles(ecs: &mut World, ctx: &Rltk) {
|
||||||
|
let mut dead_particles: Vec<Entity> = Vec::new();
|
||||||
|
{
|
||||||
|
// Age out particles
|
||||||
|
let mut particles = ecs.write_storage::<ParticleLifetime>();
|
||||||
|
let entities = ecs.entities();
|
||||||
|
for (entity, mut particle) in (&entities, &mut particles).join() {
|
||||||
|
particle.lifetime_ms -= ctx.frame_time_ms;
|
||||||
|
if particle.lifetime_ms < 0.0 {
|
||||||
|
dead_particles.push(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for dead in dead_particles.iter() {
|
||||||
|
ecs.delete_entity(*dead).expect("Particle will not die");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ParticleRequest {
|
||||||
|
x: i32,
|
||||||
|
y: i32,
|
||||||
|
fg: RGB,
|
||||||
|
bg: RGB,
|
||||||
|
glyph: rltk::FontCharType,
|
||||||
|
lifetime: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ParticleBuilder {
|
||||||
|
requests: Vec<ParticleRequest>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ParticleBuilder {
|
||||||
|
#[allow(clippy::new_without_default)]
|
||||||
|
pub fn new() -> ParticleBuilder {
|
||||||
|
ParticleBuilder { requests: Vec::new() }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn request(&mut self, x: i32, y: i32, fg: RGB, bg: RGB, glyph: rltk::FontCharType, lifetime: f32) {
|
||||||
|
self.requests.push(ParticleRequest { x, y, fg, bg, glyph, lifetime });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ParticleSpawnSystem {}
|
||||||
|
|
||||||
|
impl<'a> System<'a> for ParticleSpawnSystem {
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
type SystemData = (
|
||||||
|
Entities<'a>,
|
||||||
|
WriteStorage<'a, Position>,
|
||||||
|
WriteStorage<'a, Renderable>,
|
||||||
|
WriteStorage<'a, ParticleLifetime>,
|
||||||
|
WriteExpect<'a, ParticleBuilder>,
|
||||||
|
);
|
||||||
|
|
||||||
|
fn run(&mut self, data: Self::SystemData) {
|
||||||
|
let (entities, mut positions, mut renderables, mut particles, mut particle_builder) = data;
|
||||||
|
|
||||||
|
for new_particle in particle_builder.requests.iter() {
|
||||||
|
let p = entities.create();
|
||||||
|
positions.insert(p, Position { x: new_particle.x, y: new_particle.y }).expect("Could not insert position");
|
||||||
|
renderables
|
||||||
|
.insert(
|
||||||
|
p,
|
||||||
|
Renderable { fg: new_particle.fg, bg: new_particle.bg, glyph: new_particle.glyph, render_order: 0 },
|
||||||
|
)
|
||||||
|
.expect("Could not insert renderables");
|
||||||
|
particles
|
||||||
|
.insert(p, ParticleLifetime { lifetime_ms: new_particle.lifetime })
|
||||||
|
.expect("Could not insert lifetime");
|
||||||
|
}
|
||||||
|
particle_builder.requests.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue