field of view
This commit is contained in:
parent
f5f73a52f2
commit
7ae2967522
@ -1,4 +1,5 @@
|
|||||||
pub use crate::prelude::*;
|
pub use crate::prelude::*;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
pub struct Render {
|
pub struct Render {
|
||||||
@ -44,3 +45,27 @@ pub struct Item;
|
|||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
pub struct AmuletOfYala;
|
pub struct AmuletOfYala;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct FieldOfView {
|
||||||
|
pub visible_tiles: HashSet<Point>,
|
||||||
|
pub radius: i32,
|
||||||
|
pub is_dirty: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FieldOfView {
|
||||||
|
pub fn new(radius: i32) -> Self {
|
||||||
|
Self {
|
||||||
|
visible_tiles: HashSet::new(),
|
||||||
|
radius,
|
||||||
|
is_dirty: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn clone_dirty(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
visible_tiles: HashSet::new(),
|
||||||
|
radius: self.radius,
|
||||||
|
is_dirty: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -54,9 +54,9 @@ impl State {
|
|||||||
State {
|
State {
|
||||||
ecs,
|
ecs,
|
||||||
resources,
|
resources,
|
||||||
input_systems: input_build_scheduler(),
|
input_systems: build_input_scheduler(),
|
||||||
player_systems: player_build_scheduler(),
|
player_systems: build_player_scheduler(),
|
||||||
monster_systems: monster_build_scheduler(),
|
monster_systems: build_monster_scheduler(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ pub enum TileType {
|
|||||||
|
|
||||||
pub struct Map {
|
pub struct Map {
|
||||||
pub tiles: Vec<TileType>,
|
pub tiles: Vec<TileType>,
|
||||||
|
pub revealed_tiles: Vec<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn map_idx(x: i32, y: i32) -> usize {
|
pub fn map_idx(x: i32, y: i32) -> usize {
|
||||||
@ -19,6 +20,7 @@ impl Map {
|
|||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
tiles: vec![TileType::Floor; NUM_TILES],
|
tiles: vec![TileType::Floor; NUM_TILES],
|
||||||
|
revealed_tiles: vec![false; NUM_TILES],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,6 +64,9 @@ impl Algorithm2D for Map {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl BaseMap for Map {
|
impl BaseMap for Map {
|
||||||
|
fn is_opaque(&self, idx: usize) -> bool {
|
||||||
|
self.tiles[idx as usize] != TileType::Floor
|
||||||
|
}
|
||||||
fn get_available_exits(&self, idx: usize) -> SmallVec<[(usize, f32); 10]> {
|
fn get_available_exits(&self, idx: usize) -> SmallVec<[(usize, f32); 10]> {
|
||||||
let mut exits = SmallVec::new();
|
let mut exits = SmallVec::new();
|
||||||
let location = self.index_to_point2d(idx);
|
let location = self.index_to_point2d(idx);
|
||||||
|
@ -9,9 +9,10 @@ pub fn spawn_player(ecs: &mut World, pos: Point) {
|
|||||||
glyph: to_cp437('@'),
|
glyph: to_cp437('@'),
|
||||||
},
|
},
|
||||||
Health {
|
Health {
|
||||||
current: 20,
|
current: 10,
|
||||||
max: 20,
|
max: 10,
|
||||||
},
|
},
|
||||||
|
FieldOfView::new(8),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,6 +35,7 @@ pub fn spawn_monster(ecs: &mut World, rng: &mut RandomNumberGenerator, pos: Poin
|
|||||||
max: hp,
|
max: hp,
|
||||||
},
|
},
|
||||||
Name(name),
|
Name(name),
|
||||||
|
FieldOfView::new(6),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,10 +3,11 @@ use crate::prelude::*;
|
|||||||
#[system]
|
#[system]
|
||||||
#[read_component(Point)]
|
#[read_component(Point)]
|
||||||
#[read_component(ChasingPlayer)]
|
#[read_component(ChasingPlayer)]
|
||||||
|
#[read_component(FieldOfView)]
|
||||||
#[read_component(Health)]
|
#[read_component(Health)]
|
||||||
#[read_component(Player)]
|
#[read_component(Player)]
|
||||||
pub fn chasing(#[resource] map: &Map, ecs: &SubWorld, commands: &mut CommandBuffer) {
|
pub fn chasing(#[resource] map: &Map, ecs: &SubWorld, commands: &mut CommandBuffer) {
|
||||||
let mut movers = <(Entity, &Point, &ChasingPlayer)>::query();
|
let mut movers = <(Entity, &Point, &ChasingPlayer, &FieldOfView)>::query();
|
||||||
let mut positions = <(Entity, &Point, &Health)>::query();
|
let mut positions = <(Entity, &Point, &Health)>::query();
|
||||||
let mut player = <(&Point, &Player)>::query();
|
let mut player = <(&Point, &Player)>::query();
|
||||||
|
|
||||||
@ -16,7 +17,10 @@ pub fn chasing(#[resource] map: &Map, ecs: &SubWorld, commands: &mut CommandBuff
|
|||||||
let search_targets = vec![player_idx];
|
let search_targets = vec![player_idx];
|
||||||
let dijkstra_map = DijkstraMap::new(SCREEN_WIDTH, SCREEN_HEIGHT, &search_targets, map, 1024.0);
|
let dijkstra_map = DijkstraMap::new(SCREEN_WIDTH, SCREEN_HEIGHT, &search_targets, map, 1024.0);
|
||||||
|
|
||||||
movers.iter(ecs).for_each(|(entity, pos, _)| {
|
movers.iter(ecs).for_each(|(entity, pos, _, fov)| {
|
||||||
|
if !fov.visible_tiles.contains(&player_pos) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let idx = map_idx(pos.x, pos.y);
|
let idx = map_idx(pos.x, pos.y);
|
||||||
if let Some(destination) = DijkstraMap::find_lowest_exit(&dijkstra_map, idx, map) {
|
if let Some(destination) = DijkstraMap::find_lowest_exit(&dijkstra_map, idx, map) {
|
||||||
let distance = DistanceAlg::Pythagoras.distance2d(*pos, *player_pos);
|
let distance = DistanceAlg::Pythagoras.distance2d(*pos, *player_pos);
|
||||||
|
@ -3,13 +3,20 @@ use crate::prelude::*;
|
|||||||
#[system]
|
#[system]
|
||||||
#[read_component(Point)]
|
#[read_component(Point)]
|
||||||
#[read_component(Render)]
|
#[read_component(Render)]
|
||||||
|
#[read_component(Player)]
|
||||||
|
#[read_component(FieldOfView)]
|
||||||
pub fn entity_render(ecs: &mut SubWorld, #[resource] camera: &mut Camera) {
|
pub fn entity_render(ecs: &mut SubWorld, #[resource] camera: &mut Camera) {
|
||||||
|
let mut renderables = <(&Point, &Render)>::query();
|
||||||
|
let mut fov = <&FieldOfView>::query().filter(component::<Player>());
|
||||||
|
let player_fov = fov.iter(ecs).nth(0).unwrap();
|
||||||
|
|
||||||
let mut draw_batch = DrawBatch::new();
|
let mut draw_batch = DrawBatch::new();
|
||||||
draw_batch.target(1);
|
draw_batch.target(1);
|
||||||
let offset = Point::new(camera.left_x, camera.top_y);
|
let offset = Point::new(camera.left_x, camera.top_y);
|
||||||
|
|
||||||
<(&Point, &Render)>::query()
|
renderables
|
||||||
.iter(ecs)
|
.iter(ecs)
|
||||||
|
.filter(|(pos, _)| player_fov.visible_tiles.contains(&pos))
|
||||||
.for_each(|(pos, render)| {
|
.for_each(|(pos, render)| {
|
||||||
draw_batch.set(*pos - offset, render.color, render.glyph);
|
draw_batch.set(*pos - offset, render.color, render.glyph);
|
||||||
});
|
});
|
||||||
|
15
src/systems/fov.rs
Normal file
15
src/systems/fov.rs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
#[system]
|
||||||
|
#[read_component(Point)]
|
||||||
|
#[write_component(FieldOfView)]
|
||||||
|
pub fn fov(ecs: &mut SubWorld, #[resource] map: &Map) {
|
||||||
|
let mut views = <(&Point, &mut FieldOfView)>::query();
|
||||||
|
views
|
||||||
|
.iter_mut(ecs)
|
||||||
|
.filter(|(_, fov)| fov.is_dirty)
|
||||||
|
.for_each(|(pos, mut fov)| {
|
||||||
|
fov.visible_tiles = field_of_view_set(*pos, fov.radius, map);
|
||||||
|
fov.is_dirty = true;
|
||||||
|
});
|
||||||
|
}
|
@ -1,20 +1,34 @@
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
#[system]
|
#[system]
|
||||||
pub fn map_render(#[resource] map: &Map, #[resource] camera: &Camera) {
|
#[read_component(Player)]
|
||||||
|
#[read_component(FieldOfView)]
|
||||||
|
pub fn map_render(ecs: &SubWorld, #[resource] map: &Map, #[resource] camera: &Camera) {
|
||||||
|
let mut fov = <&FieldOfView>::query().filter(component::<Player>());
|
||||||
|
let player_fov = fov.iter(ecs).nth(0).unwrap();
|
||||||
let mut draw_batch = DrawBatch::new();
|
let mut draw_batch = DrawBatch::new();
|
||||||
draw_batch.target(0);
|
draw_batch.target(0);
|
||||||
for y in camera.top_y..=camera.bottom_y {
|
for y in camera.top_y..=camera.bottom_y {
|
||||||
for x in camera.left_x..=camera.right_x {
|
for x in camera.left_x..=camera.right_x {
|
||||||
let pt = Point::new(x, y);
|
let pt = Point::new(x, y);
|
||||||
let offset = Point::new(camera.left_x, camera.top_y);
|
let offset = Point::new(camera.left_x, camera.top_y);
|
||||||
if map.in_bounds(pt) {
|
let idx = map_idx(x, y);
|
||||||
let idx = map_idx(x, y);
|
if map.in_bounds(pt)
|
||||||
let glyph = match map.tiles[idx] {
|
&& (player_fov.visible_tiles.contains(&pt) | map.revealed_tiles[idx])
|
||||||
TileType::Floor => to_cp437('.'),
|
{
|
||||||
TileType::Wall => to_cp437('#'),
|
let tint = if player_fov.visible_tiles.contains(&pt) {
|
||||||
|
WHITE
|
||||||
|
} else {
|
||||||
|
DARK_GRAY
|
||||||
};
|
};
|
||||||
draw_batch.set(pt - offset, ColorPair::new(WHITE, BLACK), glyph);
|
match map.tiles[idx] {
|
||||||
|
TileType::Floor => {
|
||||||
|
draw_batch.set(pt - offset, ColorPair::new(tint, BLACK), to_cp437('.'));
|
||||||
|
}
|
||||||
|
TileType::Wall => {
|
||||||
|
draw_batch.set(pt - offset, ColorPair::new(tint, BLACK), to_cp437('#'));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ mod chasing;
|
|||||||
mod combat;
|
mod combat;
|
||||||
mod end_turn;
|
mod end_turn;
|
||||||
mod entity_render;
|
mod entity_render;
|
||||||
|
mod fov;
|
||||||
mod hud;
|
mod hud;
|
||||||
mod map_render;
|
mod map_render;
|
||||||
mod movement;
|
mod movement;
|
||||||
@ -10,9 +11,10 @@ mod player_input;
|
|||||||
mod random_move;
|
mod random_move;
|
||||||
mod tooltips;
|
mod tooltips;
|
||||||
|
|
||||||
pub fn input_build_scheduler() -> Schedule {
|
pub fn build_input_scheduler() -> Schedule {
|
||||||
Schedule::builder()
|
Schedule::builder()
|
||||||
.add_system(player_input::player_input_system())
|
.add_system(player_input::player_input_system())
|
||||||
|
.add_system(fov::fov_system())
|
||||||
.flush()
|
.flush()
|
||||||
.add_system(map_render::map_render_system())
|
.add_system(map_render::map_render_system())
|
||||||
.add_system(entity_render::entity_render_system())
|
.add_system(entity_render::entity_render_system())
|
||||||
@ -20,23 +22,27 @@ pub fn input_build_scheduler() -> Schedule {
|
|||||||
.add_system(tooltips::tooltips_system())
|
.add_system(tooltips::tooltips_system())
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
pub fn player_build_scheduler() -> Schedule {
|
pub fn build_player_scheduler() -> Schedule {
|
||||||
Schedule::builder()
|
Schedule::builder()
|
||||||
.add_system(combat::combat_system())
|
.add_system(combat::combat_system())
|
||||||
.flush()
|
.flush()
|
||||||
.add_system(movement::movement_system())
|
.add_system(movement::movement_system())
|
||||||
.flush()
|
.flush()
|
||||||
|
.add_system(fov::fov_system())
|
||||||
|
.flush()
|
||||||
.add_system(map_render::map_render_system())
|
.add_system(map_render::map_render_system())
|
||||||
.add_system(entity_render::entity_render_system())
|
.add_system(entity_render::entity_render_system())
|
||||||
.add_system(hud::hud_system())
|
.add_system(hud::hud_system())
|
||||||
.add_system(end_turn::end_turn_system())
|
.add_system(end_turn::end_turn_system())
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
pub fn monster_build_scheduler() -> Schedule {
|
pub fn build_monster_scheduler() -> Schedule {
|
||||||
Schedule::builder()
|
Schedule::builder()
|
||||||
.add_system(random_move::random_move_system())
|
.add_system(random_move::random_move_system())
|
||||||
.add_system(chasing::chasing_system())
|
.add_system(chasing::chasing_system())
|
||||||
.flush()
|
.flush()
|
||||||
|
.add_system(fov::fov_system())
|
||||||
|
.flush()
|
||||||
.add_system(combat::combat_system())
|
.add_system(combat::combat_system())
|
||||||
.flush()
|
.flush()
|
||||||
.add_system(movement::movement_system())
|
.add_system(movement::movement_system())
|
||||||
|
@ -2,23 +2,29 @@ pub use crate::prelude::*;
|
|||||||
|
|
||||||
#[system(for_each)]
|
#[system(for_each)]
|
||||||
#[read_component(Player)]
|
#[read_component(Player)]
|
||||||
|
#[read_component(FieldOfView)]
|
||||||
pub fn movement(
|
pub fn movement(
|
||||||
entity: &Entity,
|
entity: &Entity,
|
||||||
want_move: &WantsToMove,
|
want_move: &WantsToMove,
|
||||||
#[resource] map: &Map,
|
#[resource] map: &mut Map,
|
||||||
#[resource] camera: &mut Camera,
|
#[resource] camera: &mut Camera,
|
||||||
ecs: &mut SubWorld,
|
ecs: &mut SubWorld,
|
||||||
commands: &mut CommandBuffer,
|
commands: &mut CommandBuffer,
|
||||||
) {
|
) {
|
||||||
if map.can_enter_tile(want_move.destination) {
|
if map.can_enter_tile(want_move.destination) {
|
||||||
commands.add_component(want_move.entity, want_move.destination);
|
commands.add_component(want_move.entity, want_move.destination);
|
||||||
if ecs
|
|
||||||
.entry_ref(want_move.entity)
|
if let Ok(entry) = ecs.entry_ref(want_move.entity) {
|
||||||
.unwrap()
|
if let Ok(fov) = entry.get_component::<FieldOfView>() {
|
||||||
.get_component::<Player>()
|
commands.add_component(want_move.entity, fov.clone_dirty());
|
||||||
.is_ok()
|
|
||||||
{
|
if entry.get_component::<Player>().is_ok() {
|
||||||
camera.on_player_move(want_move.destination);
|
camera.on_player_move(want_move.destination);
|
||||||
|
fov.visible_tiles.iter().for_each(|pos| {
|
||||||
|
map.revealed_tiles[map_idx(pos.x, pos.y)] = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
commands.remove(*entity);
|
commands.remove(*entity);
|
||||||
|
@ -4,11 +4,16 @@ use crate::prelude::*;
|
|||||||
#[read_component(Point)]
|
#[read_component(Point)]
|
||||||
#[read_component(Name)]
|
#[read_component(Name)]
|
||||||
#[read_component(Health)]
|
#[read_component(Health)]
|
||||||
|
#[read_component(Player)]
|
||||||
|
#[read_component(FieldOfView)]
|
||||||
pub fn tooltips(
|
pub fn tooltips(
|
||||||
ecs: &SubWorld,
|
ecs: &SubWorld,
|
||||||
#[resource] mouse_pos: &Point, // (1)
|
#[resource] mouse_pos: &Point, // (1)
|
||||||
#[resource] camera: &Camera, // (2)
|
#[resource] camera: &Camera, // (2)
|
||||||
) {
|
) {
|
||||||
|
let mut fov = <&FieldOfView>::query().filter(component::<Player>());
|
||||||
|
let player_fov = fov.iter(ecs).nth(0).unwrap();
|
||||||
|
|
||||||
let mut positions = <(Entity, &Point, &Name)>::query(); // (3)
|
let mut positions = <(Entity, &Point, &Name)>::query(); // (3)
|
||||||
let offset = Point::new(camera.left_x, camera.top_y);
|
let offset = Point::new(camera.left_x, camera.top_y);
|
||||||
let map_pos = *mouse_pos + offset; // (4)
|
let map_pos = *mouse_pos + offset; // (4)
|
||||||
@ -16,7 +21,7 @@ pub fn tooltips(
|
|||||||
draw_batch.target(2);
|
draw_batch.target(2);
|
||||||
positions
|
positions
|
||||||
.iter(ecs)
|
.iter(ecs)
|
||||||
.filter(|(_, pos, _)| **pos == map_pos) // (5)
|
.filter(|(_, pos, _)| **pos == map_pos && player_fov.visible_tiles.contains(&pos)) // (5)
|
||||||
.for_each(|(entity, _, name)| {
|
.for_each(|(entity, _, name)| {
|
||||||
let screen_pos = *mouse_pos * 4; // (6)
|
let screen_pos = *mouse_pos * 4; // (6)
|
||||||
let display = if let Ok(health) = ecs
|
let display = if let Ok(health) = ecs
|
||||||
|
Loading…
Reference in New Issue
Block a user