field of view

This commit is contained in:
Tyrel Souza 2021-08-31 00:40:48 -04:00
parent f5f73a52f2
commit 7ae2967522
11 changed files with 116 additions and 27 deletions

View File

@ -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,
}
}
}

View File

@ -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(),
} }
} }

View File

@ -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);

View File

@ -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),
)); ));
} }

View File

@ -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);

View File

@ -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
View 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;
});
}

View File

@ -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('#'));
}
}
} }
} }
} }

View File

@ -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())

View File

@ -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);

View File

@ -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