From 3df0d8592b02f7698aa1b8e35cee68bc335a9d8f Mon Sep 17 00:00:00 2001 From: Timothy Warren Date: Mon, 25 Oct 2021 15:26:39 -0400 Subject: [PATCH] First good iteration of field of view --- src/components.rs | 8 +++- src/main.rs | 16 +++++++- src/map.rs | 83 ++++++++++++++++++++++++---------------- src/player.rs | 32 ++++++++-------- src/rect.rs | 8 ++-- src/visibility_system.rs | 36 +++++++++++++++++ 6 files changed, 128 insertions(+), 55 deletions(-) create mode 100644 src/visibility_system.rs diff --git a/src/components.rs b/src/components.rs index f5180a4..a1f72ec 100644 --- a/src/components.rs +++ b/src/components.rs @@ -1,6 +1,6 @@ +use rltk::RGB; use specs::prelude::*; use specs_derive::*; -use rltk::{RGB}; #[derive(Component)] pub struct Position { @@ -17,3 +17,9 @@ pub struct Renderable { #[derive(Component, Debug)] pub struct Player {} + +#[derive(Component)] +pub struct Viewshed { + pub visible_tiles: Vec, + pub range: i32, +} diff --git a/src/main.rs b/src/main.rs index bee6662..37ae6be 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,6 +8,9 @@ pub use map::*; mod player; use player::*; mod rect; +mod visibility_system; +use visibility_system::VisibilitySystem; + pub use rect::Rect; pub struct State { @@ -16,6 +19,9 @@ pub struct State { impl State { fn run_systems(&mut self) { + let mut vis = VisibilitySystem {}; + vis.run_now(&self.ecs); + self.ecs.maintain(); } } @@ -50,6 +56,7 @@ fn main() -> rltk::BError { gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); + gs.ecs.register::(); let map: Map = Map::new_map_rooms_and_corridors(); let (player_x, player_y) = map.rooms[0].center(); @@ -58,13 +65,20 @@ fn main() -> rltk::BError { gs.ecs .create_entity() - .with(Position { x: player_x, y: player_y }) + .with(Position { + x: player_x, + y: player_y, + }) .with(Renderable { glyph: rltk::to_cp437('@'), fg: RGB::named(rltk::YELLOW), bg: RGB::named(rltk::BLACK), }) .with(Player {}) + .with(Viewshed { + visible_tiles: Vec::new(), + range: 8, + }) .build(); rltk::main_loop(context, gs) diff --git a/src/map.rs b/src/map.rs index 86664d1..94ad4e7 100644 --- a/src/map.rs +++ b/src/map.rs @@ -1,7 +1,7 @@ -use rltk::{ RGB, Rltk, RandomNumberGenerator }; -use super::{Rect}; -use std::cmp::{min, max}; +use super::Rect; +use rltk::{Algorithm2D, BaseMap, Point, RandomNumberGenerator, Rltk, RGB}; use specs::prelude::*; +use std::cmp::{max, min}; #[derive(PartialEq, Copy, Clone)] pub enum TileType { @@ -14,6 +14,7 @@ pub struct Map { pub rooms: Vec, pub width: i32, pub height: i32, + pub revealed_tiles: Vec, } impl Map { @@ -22,8 +23,8 @@ impl Map { } fn apply_room_to_map(&mut self, room: &Rect) { - for y in room.y1+1 ..= room.y2 { - for x in room.x1+1 ..= room.x2 { + for y in room.y1 + 1..=room.y2 { + for x in room.x1 + 1..=room.x2 { let idx = self.xy_idx(x, y); self.tiles[idx] = TileType::Floor; } @@ -31,7 +32,7 @@ impl Map { } fn apply_horizontal_tunnel(&mut self, x1: i32, x2: i32, y: i32) { - for x in min(x1, x2) ..= max(x1, x2) { + for x in min(x1, x2)..=max(x1, x2) { let idx = self.xy_idx(x, y); if idx > 0 && idx < self.width as usize * self.height as usize { @@ -41,7 +42,7 @@ impl Map { } fn apply_vertical_tunnel(&mut self, y1: i32, y2: i32, x: i32) { - for y in min(y1, y2) ..= max(y1, y2) { + for y in min(y1, y2)..=max(y1, y2) { let idx = self.xy_idx(x, y); if idx > 0 && idx < self.width as usize * self.height as usize { @@ -57,12 +58,13 @@ impl Map { tiles: vec![TileType::Wall; 80 * 50], rooms: Vec::new(), width: 80, - height: 50 + height: 50, + revealed_tiles: vec![false; 80 * 50], }; - const MAX_ROOMS :i32 = 30; - const MIN_SIZE :i32 = 6; - const MAX_SIZE :i32 = 10; + const MAX_ROOMS: i32 = 30; + const MIN_SIZE: i32 = 6; + const MAX_SIZE: i32 = 10; let mut rng = RandomNumberGenerator::new(); @@ -84,9 +86,9 @@ impl Map { if ok { map.apply_room_to_map(&new_room); - if ! map.rooms.is_empty() { + if !map.rooms.is_empty() { let (new_x, new_y) = new_room.center(); - let (prev_x, prev_y) = map.rooms[map.rooms.len()-1].center(); + let (prev_x, prev_y) = map.rooms[map.rooms.len() - 1].center(); if rng.range(0, 2) == 1 { map.apply_horizontal_tunnel(prev_x, new_x, prev_y); @@ -105,31 +107,46 @@ impl Map { } } +impl Algorithm2D for Map { + fn dimensions(&self) -> Point { + Point::new(self.width, self.height) + } +} + +impl BaseMap for Map { + fn is_opaque(&self, idx: usize) -> bool { + self.tiles[idx as usize] == TileType::Wall + } +} + pub fn draw_map(ecs: &World, ctx: &mut Rltk) { let map = ecs.fetch::(); let mut y = 0; let mut x = 0; - for (_idx, tile) in map.tiles.iter().enumerate() { - match tile { - TileType::Floor => { - ctx.set( - x, - y, - RGB::from_f32(0.5, 0.5, 0.5), - RGB::from_f32(0., 0., 0.), - rltk::to_cp437('.'), - ); - } - TileType::Wall => { - ctx.set( - x, - y, - RGB::from_f32(0.0, 1.0, 0.0), - RGB::from_f32(0., 0., 0.), - rltk::to_cp437('#'), - ); + for (idx, tile) in map.tiles.iter().enumerate() { + // Render a tile depending on the tile type + if map.revealed_tiles[idx] { + match tile { + TileType::Floor => { + ctx.set( + x, + y, + RGB::from_f32(0.5, 0.5, 0.5), + RGB::from_f32(0., 0., 0.), + rltk::to_cp437('.'), + ); + } + TileType::Wall => { + ctx.set( + x, + y, + RGB::from_f32(0.0, 1.0, 0.0), + RGB::from_f32(0., 0., 0.), + rltk::to_cp437('#'), + ); + } } } @@ -140,4 +157,4 @@ pub fn draw_map(ecs: &World, ctx: &mut Rltk) { y += 1; } } -} \ No newline at end of file +} diff --git a/src/player.rs b/src/player.rs index dc5a4d9..0c80922 100644 --- a/src/player.rs +++ b/src/player.rs @@ -1,7 +1,7 @@ -use rltk::{VirtualKeyCode, Rltk}; +use super::{Map, Player, Position, State, TileType}; +use rltk::{Rltk, VirtualKeyCode}; use specs::prelude::*; -use super::{Position, Player, TileType, State, Map}; -use std::cmp::{min, max}; +use std::cmp::{max, min}; pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) { let mut positions = ecs.write_storage::(); @@ -22,23 +22,23 @@ pub fn player_input(gs: &mut State, ctx: &mut Rltk) { match ctx.key { None => {} // Nothing happened Some(key) => match key { - VirtualKeyCode::Left | - VirtualKeyCode::Numpad4 | - VirtualKeyCode::H => try_move_player(-1, 0, &mut gs.ecs), + VirtualKeyCode::Left | VirtualKeyCode::Numpad4 | VirtualKeyCode::H => { + try_move_player(-1, 0, &mut gs.ecs) + } - VirtualKeyCode::Right | - VirtualKeyCode::Numpad6 | - VirtualKeyCode::L=> try_move_player(1, 0, &mut gs.ecs), + VirtualKeyCode::Right | VirtualKeyCode::Numpad6 | VirtualKeyCode::L => { + try_move_player(1, 0, &mut gs.ecs) + } - VirtualKeyCode::Up | - VirtualKeyCode::Numpad8 | - VirtualKeyCode::K => try_move_player(0, -1, &mut gs.ecs), + VirtualKeyCode::Up | VirtualKeyCode::Numpad8 | VirtualKeyCode::K => { + try_move_player(0, -1, &mut gs.ecs) + } - VirtualKeyCode::Down | - VirtualKeyCode::Numpad2 | - VirtualKeyCode::J => try_move_player(0, 1, &mut gs.ecs), + VirtualKeyCode::Down | VirtualKeyCode::Numpad2 | VirtualKeyCode::J => { + try_move_player(0, 1, &mut gs.ecs) + } _ => {} }, } -} \ No newline at end of file +} diff --git a/src/rect.rs b/src/rect.rs index a989d5f..0b567ee 100644 --- a/src/rect.rs +++ b/src/rect.rs @@ -7,11 +7,11 @@ pub struct Rect { impl Rect { pub fn new(x: i32, y: i32, w: i32, h: i32) -> Rect { - Rect{ + Rect { x1: x, y1: y, - x2: x+w, - y2: y+h, + x2: x + w, + y2: y + h, } } @@ -23,4 +23,4 @@ impl Rect { pub fn center(&self) -> (i32, i32) { ((self.x1 + self.x2) / 2, (self.y1 + self.y2) / 2) } -} \ No newline at end of file +} diff --git a/src/visibility_system.rs b/src/visibility_system.rs new file mode 100644 index 0000000..eb10745 --- /dev/null +++ b/src/visibility_system.rs @@ -0,0 +1,36 @@ +use super::{Map, Player, Position, Viewshed}; +use rltk::{field_of_view, Point}; +use specs::prelude::*; + +pub struct VisibilitySystem {} + +impl<'a> System<'a> for VisibilitySystem { + type SystemData = ( + WriteExpect<'a, Map>, + Entities<'a>, + WriteStorage<'a, Viewshed>, + WriteStorage<'a, Position>, + ReadStorage<'a, Player>, + ); + + fn run(&mut self, data: Self::SystemData) { + let (mut map, entities, mut viewshed, pos, player) = data; + + for (ent, viewshed, pos) in (&entities, &mut viewshed, &pos).join() { + viewshed.visible_tiles.clear(); + viewshed.visible_tiles = field_of_view(Point::new(pos.x, pos.y), viewshed.range, &*map); + viewshed + .visible_tiles + .retain(|p| p.x >= 0 && p.x < map.width && p.y >= 0 && p.y < map.height); + + // if this is the player, reveal what they can see + let p: Option<&Player> = player.get(ent); + if let Some(p) = p { + for vis in viewshed.visible_tiles.iter() { + let idx = map.xy_idx(vis.x, vis.y); + map.revealed_tiles[idx] = true; + } + } + } + } +}