Commit ddc2c218 authored by Mathieu Nivoliez's avatar Mathieu Nivoliez

Add lecture "Field of view". Now the player will not see all the map but only...

Add lecture "Field of view". Now the player will not see all the map but only a portion close to him.
parent c4af6ec5
Pipeline #778 passed with stage
in 14 minutes and 46 seconds
......@@ -1040,7 +1040,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "rltk"
version = "0.5.17"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
......@@ -1090,7 +1090,7 @@ dependencies = [
name = "rusty_rogue"
version = "0.1.0"
dependencies = [
"rltk 0.5.17 (registry+https://github.com/rust-lang/crates.io-index)",
"rltk 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"specs 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)",
"specs-derive 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
......@@ -1711,7 +1711,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
"checksum regex 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b5508c1941e4e7cb19965abef075d35a9a8b5cdf0846f30b4050e9b55dc55e87"
"checksum regex-syntax 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "e734e891f5b408a29efbf8309e656876276f49ab6a6ac208600b4419bd893d90"
"checksum rltk 0.5.17 (registry+https://github.com/rust-lang/crates.io-index)" = "ab5d596e5b1d4da4928b86588b543109914642c63fe5bf2ae9758fd703c86860"
"checksum rltk 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ae4901dea894fea75a6193e0e5140a18ec68a904214ce89c25664917fe392da5"
"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
"checksum rusttype 0.7.9 (registry+https://github.com/rust-lang/crates.io-index)" = "310942406a39981bed7e12b09182a221a29e0990f3e7e0c971f131922ed135d5"
"checksum rusttype 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "14a911032fb5791ccbeec9f28fdcb9bf0983b81f227bafdfd227c658d0731c8a"
......
......@@ -7,6 +7,6 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
rltk = "0.5.17"
rltk = "0.6.0"
specs = "0.15.1"
specs-derive = "0.4.0"
......@@ -9,3 +9,4 @@ Little Rogue Like made in Rust using the [guide](http://bfnightly.bracketproduct
- [Entities and component](http://bfnightly.bracketproductions.com/rustbook/chapter_2.html)
- [Walking a Map](http://bfnightly.bracketproductions.com/rustbook/chapter_3.html)
- [A more interesting map](http://bfnightly.bracketproductions.com/rustbook/chapter_4.html)
- [Field of view](http://bfnightly.bracketproductions.com/rustbook/chapter_5.html)
......@@ -15,4 +15,11 @@ pub struct Renderable {
}
#[derive(Component, Debug)]
pub struct Player {}
\ No newline at end of file
pub struct Player {}
#[derive(Component)]
pub struct Viewshed {
pub visible_tiles: Vec<rltk::Point>,
pub range: i32,
pub dirty: bool
}
\ No newline at end of file
......@@ -14,6 +14,8 @@ mod player;
pub use player::*;
mod rect;
pub use rect::Rect;
mod visibility_system;
use visibility_system::VisibilitySystem;
// Main State
pub struct State {
......@@ -22,6 +24,8 @@ pub struct State {
impl State {
fn run_systems(&mut self) {
let mut vis = VisibilitySystem {};
vis.run_now(&self.ecs);
self.ecs.maintain();
}
}
......@@ -36,8 +40,7 @@ impl GameState for State {
let positions = self.ecs.read_storage::<Position>();
let renderables = self.ecs.read_storage::<Renderable>();
let map = self.ecs.fetch::<Vec<TileType>>();
map::draw_map(&map, ctx);
map::draw_map(&self.ecs, ctx);
for (pos, render) in (&positions, &renderables).join() {
ctx.set(pos.x, pos.y, render.fg, render.bg, render.glyph);
......@@ -57,22 +60,30 @@ fn main() {
gs.ecs.register::<Position>();
gs.ecs.register::<Renderable>();
gs.ecs.register::<Player>();
gs.ecs.register::<Viewshed>();
let (rooms, map) = new_map_rooms_and_corridors();
let map = Map::new_map_rooms_and_corridors();
let (player_x, player_y) = map.rooms[0].center();
gs.ecs.insert(map);
let (player_x, player_y) = rooms[0].center();
// Create an entity
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,
dirty: true
})
.build();
// Run the game
......
use super::Rect;
use rltk::{Console, RandomNumberGenerator, Rltk, RGB};
use super::{Player, Rect, Viewshed};
use rltk::{Algorithm2D, BaseMap, Console, Point, RandomNumberGenerator, Rltk, RGB};
use specs::prelude::*;
use std::cmp::{max, min};
#[derive(PartialEq, Copy, Clone)]
......@@ -8,138 +9,136 @@ pub enum TileType {
Floor,
}
pub fn xy_idx(x: i32, y: i32) -> usize {
(y as usize * 80) + x as usize
pub struct Map {
pub tiles: Vec<TileType>,
pub rooms: Vec<Rect>,
pub width: i32,
pub height: i32,
pub revealed_tiles: Vec<bool>,
pub visible_tiles: Vec<bool>,
}
/// Makes a map with solid boundaries and 400 randomly placed walls. No guarantees that it won't
/// look awful.
pub fn new_map_test() -> Vec<TileType> {
let mut map = vec![TileType::Floor; 80 * 50];
// Make the boundaries walls
for x in 0..80 {
map[xy_idx(x, 0)] = TileType::Wall;
map[xy_idx(x, 49)] = TileType::Wall;
}
for y in 0..50 {
map[xy_idx(0, y)] = TileType::Wall;
map[xy_idx(79, y)] = TileType::Wall;
impl Map {
pub fn xy_idx(&self, x: i32, y: i32) -> usize {
(y as usize * self.width as usize) + x as usize
}
// Now we'll randomly splat a bunch of walls. It won't be pretty, but it's a decent
// illustration
// First, obtain the thread-local RNG
let mut rng = rltk::RandomNumberGenerator::new();
for _i in 0..400 {
let x = rng.roll_dice(1, 79);
let y = rng.roll_dice(1, 49);
let idx = xy_idx(x, y);
if idx != xy_idx(40, 25) {
map[idx] = TileType::Wall;
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 {
let idx = self.xy_idx(x, y);
self.tiles[idx] = TileType::Floor;
}
}
}
map
}
pub fn new_map_rooms_and_corridors() -> (Vec<Rect>, Vec<TileType>) {
let mut map = vec![TileType::Wall; 80 * 50];
let mut rooms: Vec<Rect> = Vec::new();
const MAX_ROOMS: i32 = 30;
const MIN_SIZE: i32 = 6;
const MAX_SIZE: i32 = 10;
let mut rng = RandomNumberGenerator::new();
for _ in 0..MAX_ROOMS {
let w = rng.range(MIN_SIZE, MAX_SIZE);
let h = rng.range(MIN_SIZE, MAX_SIZE);
let x = rng.roll_dice(1, 80 - w - 1) - 1;
let y = rng.roll_dice(1, 50 - h - 1) - 1;
let new_room = Rect::new(x, y, w, h);
let mut ok = true;
for other_room in rooms.iter() {
if new_room.intersect(other_room) {
ok = false
fn apply_horizontal_tunnel(&mut self, x1: i32, x2: i32, y: i32) {
for x in min(x1, x2)..=max(x1, x2) {
let idx = self.xy_idx(x, y);
if idx > 0 && idx < 80 * 50 {
self.tiles[idx] = TileType::Floor;
}
}
if ok {
apply_room_to_map(&new_room, &mut map);
if !rooms.is_empty() {
let (new_x, new_y) = new_room.center();
let (prev_x, prev_y) = rooms[rooms.len() - 1].center();
if rng.range(0,2) == 1 {
apply_horizontal_tunnel(&mut map, prev_x, new_x, prev_y);
apply_vertical_tunnel(&mut map, prev_y, new_y, prev_x);
} else {
apply_vertical_tunnel(&mut map, prev_y, new_y, prev_x);
apply_horizontal_tunnel(&mut map, prev_x, new_x, prev_y);
}
}
fn apply_vertical_tunnel(&mut self, y1: i32, y2: i32, x: i32) {
for y in min(y1, y2)..=max(y1, y2) {
let idx = self.xy_idx(x, y);
if idx > 0 && idx < 80 * 50 {
self.tiles[idx] = TileType::Floor;
}
rooms.push(new_room);
}
}
(rooms, map)
}
fn apply_room_to_map(room: &Rect, map: &mut [TileType]) {
for y in room.y1..=room.y2 {
for x in room.x1..=room.x2 {
map[xy_idx(x, y)] = TileType::Floor;
pub fn new_map_rooms_and_corridors() -> Map {
let mut map = Map {
tiles: vec![TileType::Wall; 80 * 50],
rooms: Vec::new(),
width: 80,
height: 50,
revealed_tiles: vec![false; 80 * 50],
visible_tiles: vec![false; 80 * 50],
};
const MAX_ROOMS: i32 = 30;
const MIN_SIZE: i32 = 6;
const MAX_SIZE: i32 = 10;
let mut rng = RandomNumberGenerator::new();
for _ in 0..MAX_ROOMS {
let w = rng.range(MIN_SIZE, MAX_SIZE);
let h = rng.range(MIN_SIZE, MAX_SIZE);
let x = rng.roll_dice(1, map.width - w - 1) - 1;
let y = rng.roll_dice(1, map.height - h - 1) - 1;
let new_room = Rect::new(x, y, w, h);
let mut ok = true;
for other_room in map.rooms.iter() {
if new_room.intersect(other_room) {
ok = false
}
}
if ok {
map.apply_room_to_map(&new_room);
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();
if rng.range(0, 2) == 1 {
map.apply_horizontal_tunnel(prev_x, new_x, prev_y);
map.apply_vertical_tunnel(prev_y, new_y, new_x);
} else {
map.apply_vertical_tunnel(prev_y, new_y, prev_x);
map.apply_horizontal_tunnel(prev_x, new_x, new_y);
}
}
map.rooms.push(new_room);
}
}
map
}
}
fn apply_horizontal_tunnel(map: &mut [TileType], x1: i32, x2: i32, y: i32) {
for x in min(x1, x2)..=max(x1, x2) {
let idx = xy_idx(x, y);
if idx > 0 && idx < 80 * 50 {
map[idx as usize] = TileType::Floor;
}
impl Algorithm2D for Map {
fn dimensions(&self) -> Point {
Point::new(self.width, self.height)
}
}
fn apply_vertical_tunnel(map: &mut [TileType], y1: i32, y2: i32, x: i32) {
for y in min(y1, y2)..=max(y1, y2) {
let idx = xy_idx(x, y);
if idx > 0 && idx < 80 * 50 {
map[idx as usize] = TileType::Floor;
}
impl BaseMap for Map {
fn is_opaque(&self, idx: usize) -> bool {
self.tiles[idx] == TileType::Wall
}
}
pub fn draw_map(map: &[TileType], ctx: &mut Rltk) {
pub fn draw_map(ecs: &World, ctx: &mut Rltk) {
let map = ecs.fetch::<Map>();
let mut y = 0;
let mut x = 0;
for tile in map.iter() {
// Render a tile depending upon the tile TileType
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('.'),
);
for (idx, tile) in map.tiles.iter().enumerate() {
// Render a tile depending upon the tile type
if map.revealed_tiles[idx] {
let glyph;
let mut fg;
match tile {
TileType::Floor => {
glyph = rltk::to_cp437('.');
fg = RGB::from_f32(0.0, 0.5, 0.5);
}
TileType::Wall => {
glyph = rltk::to_cp437('#');
fg = RGB::from_f32(0.0, 1.0, 0.0);
}
}
TileType::Wall => {
ctx.set(
x,
y,
RGB::from_f32(0.0, 1.0, 0.0),
RGB::from_f32(0., 0., 0.),
rltk::to_cp437('#'),
);
if !map.visible_tiles[idx] {
fg = fg.to_greyscale()
}
ctx.set(x, y, fg, RGB::from_f32(0., 0., 0.), glyph);
}
// Move the coordinates
x += 1;
if x > 79 {
x = 0;
......
use super::{xy_idx, Player, Position, State, TileType};
use super::{Map, Player, Position, State, TileType, Viewshed};
use rltk::{Rltk, VirtualKeyCode};
use specs::prelude::*;
use std::cmp::{max, min};
......@@ -6,13 +6,16 @@ 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::<Position>();
let mut players = ecs.write_storage::<Player>();
let map = ecs.fetch::<Vec<TileType>>();
let mut viewsheds = ecs.write_storage::<Viewshed>();
let map = ecs.fetch::<Map>();
for (_player, pos) in (&mut players, &mut positions).join() {
let destination_idx = xy_idx(pos.x + delta_x, pos.y + delta_y);
if map[destination_idx] != TileType::Wall {
for (_player, pos, viewshed) in (&mut players, &mut positions, &mut viewsheds).join() {
let destination_idx = map.xy_idx(pos.x + delta_x, pos.y + delta_y);
if map.tiles[destination_idx] != TileType::Wall {
pos.x = min(79, max(0, pos.x + delta_x));
pos.y = min(49, max(0, pos.y + delta_y));
viewshed.dirty = true;
}
}
}
......
extern crate specs;
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() {
if viewshed.dirty {
viewshed.dirty = false;
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 - 1 && p.y > 0 && p.y < map.height - 1);
// If this is the player, reveal what they can see
let p: Option<&Player> = player.get(ent);
if let Some(_p) = p {
for t in map.visible_tiles.iter_mut() {
*t = false
}
for vis in viewshed.visible_tiles.iter() {
let idx = map.xy_idx(vis.x, vis.y);
map.revealed_tiles[idx] = true;
map.visible_tiles[idx] = true;
}
}
}
}
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment