diff --git a/rusty-roguelike/Cargo.toml b/rusty-roguelike/Cargo.toml index ff5e8f9..4c459fe 100644 --- a/rusty-roguelike/Cargo.toml +++ b/rusty-roguelike/Cargo.toml @@ -7,3 +7,4 @@ edition = "2021" [dependencies] bracket-lib = "~0.8.1" +legion = "=0.3.1" diff --git a/rusty-roguelike/src/components.rs b/rusty-roguelike/src/components.rs new file mode 100644 index 0000000..ae87f8d --- /dev/null +++ b/rusty-roguelike/src/components.rs @@ -0,0 +1,10 @@ +pub use crate::prelude::*; + +#[derive(Clone, Debug, Copy, PartialEq)] +pub struct Render { + pub color: ColorPair, + pub glyph: FontCharType, +} + +#[derive(Clone, Debug, Copy, PartialEq)] +pub struct Player; diff --git a/rusty-roguelike/src/main.rs b/rusty-roguelike/src/main.rs index 887ccef..ffb5c4d 100644 --- a/rusty-roguelike/src/main.rs +++ b/rusty-roguelike/src/main.rs @@ -1,14 +1,22 @@ mod map; -mod player; mod map_builder; mod camera; +mod components; +mod spawner; +mod systems; mod prelude { pub use bracket_lib::prelude::*; + pub use legion::*; + pub use legion::world::SubWorld; + pub use legion::systems::CommandBuffer; + pub use crate::map::*; - pub use crate::player::*; pub use crate::map_builder::*; pub use crate::camera::*; + pub use crate::components::*; + pub use crate::spawner::*; + pub use crate::systems::*; pub const SCREEN_WIDTH: i32 = 80; pub const SCREEN_HEIGHT: i32 = 50; @@ -22,20 +30,27 @@ const FONT_PATH: &str = "dungeonfont.png"; use prelude::*; struct State { - map: Map, - player: Player, - camera: Camera, + ecs: World, + resources: Resources, + systems: Schedule, } impl State { fn new() -> Self { + let mut ecs = World::default(); + let mut resources = Resources::default(); let mut rng = RandomNumberGenerator::new(); - let mb = MapBuilder::new(&mut rng); + let map_builder = MapBuilder::new(&mut rng); + + resources.insert(map_builder.map); + resources.insert(Camera::new(map_builder.player_start)); + + spawn_player(&mut ecs, map_builder.player_start); return Self { - map: mb.map, - player: Player::new(mb.player_start), - camera: Camera::new(mb.player_start), + ecs, + resources, + systems: build_scheduler(), }; } } @@ -47,9 +62,9 @@ impl GameState for State { ctx.set_active_console(1); ctx.cls(); - self.player.update(ctx, &self.map, &mut self.camera); - self.map.render(ctx, &self.camera); - self.player.render(ctx, &self.camera); + self.resources.insert(ctx.key); + self.systems.execute(&mut self.ecs, &mut self.resources); + render_draw_buffer(ctx).expect("failed to draw buffer"); } } diff --git a/rusty-roguelike/src/map.rs b/rusty-roguelike/src/map.rs index 3efc7cc..342424c 100644 --- a/rusty-roguelike/src/map.rs +++ b/rusty-roguelike/src/map.rs @@ -38,37 +38,4 @@ impl Map { return None; } } - - pub fn render(&self, ctx: &mut BTerm, camera: &Camera) { - ctx.set_active_console(0); - - for y in camera.top_y..camera.bottom_y { - for x in camera.left_x..camera.right_x { - if self.in_bounds(Point::new(x, y)) { - let idx = map_idx(x, y); - - match self.tiles[idx] { - TileType::Wall => { - ctx.set( - x - camera.left_x, - y - camera.top_y, - WHITE, - BLACK, - to_cp437('#'), - ); - } - TileType::Floor => { - ctx.set( - x - camera.left_x, - y - camera.top_y, - WHITE, - BLACK, - to_cp437('.'), - ); - } - } - } - } - } - } } diff --git a/rusty-roguelike/src/player.rs b/rusty-roguelike/src/player.rs deleted file mode 100644 index a32eace..0000000 --- a/rusty-roguelike/src/player.rs +++ /dev/null @@ -1,45 +0,0 @@ -use crate::prelude::*; - -pub struct Player { - pub hp: i32, - pub position: Point, -} - -impl Player { - pub fn new(point: Point) -> Self { - return Self { - hp: 100, - position: point, - }; - } - - pub fn update(&mut self, ctx: &mut BTerm, map: &Map, camera: &mut Camera) { - if let Some(key) = ctx.key { - let delta = match key { - VirtualKeyCode::Up => Point::new(0, -1), - VirtualKeyCode::Right => Point::new(1, 0), - VirtualKeyCode::Down => Point::new(0, 1), - VirtualKeyCode::Left => Point::new(-1, 0), - _ => Point::zero(), - }; - - let new_pos = self.position + delta; - if map.can_enter_tile(new_pos) { - self.position = new_pos; - camera.on_player_move(new_pos); - } - } - } - - pub fn render(&self, ctx: &mut BTerm, camera: &Camera) { - ctx.set_active_console(1); - - return ctx.set( - self.position.x - camera.left_x, - self.position.y - camera.top_y, - WHITE, - BLACK, - to_cp437('@') - ); - } -} diff --git a/rusty-roguelike/src/spawner.rs b/rusty-roguelike/src/spawner.rs new file mode 100644 index 0000000..b2f4e6c --- /dev/null +++ b/rusty-roguelike/src/spawner.rs @@ -0,0 +1,12 @@ +pub use crate::prelude::*; + +pub fn spawn_player(ecs: &mut World, pos: Point) { + ecs.push(( + Player, + pos, + Render{ + color: ColorPair::new(WHITE, BLACK), + glyph: to_cp437('@'), + }, + )); +} diff --git a/rusty-roguelike/src/systems/entity_render.rs b/rusty-roguelike/src/systems/entity_render.rs new file mode 100644 index 0000000..5ce9d2d --- /dev/null +++ b/rusty-roguelike/src/systems/entity_render.rs @@ -0,0 +1,26 @@ +use crate::prelude::*; + +#[system] +#[read_component(Point)] +#[read_component(Render)] +pub fn entity_render( + ecs: &SubWorld, + #[resource] camera: &Camera, +) { + let mut draw_batch = DrawBatch::new(); + draw_batch.target(1); + + let offset = Point::new(camera.left_x, camera.top_y); + + <(&Point, &Render)>::query() + .iter(ecs) + .for_each(|(pos, render)| { + draw_batch.set( + *pos - offset, + render.color, + render.glyph, + ); + }); + + draw_batch.submit(5000).expect("failed to draw batch"); +} diff --git a/rusty-roguelike/src/systems/map_render.rs b/rusty-roguelike/src/systems/map_render.rs new file mode 100644 index 0000000..36cdc09 --- /dev/null +++ b/rusty-roguelike/src/systems/map_render.rs @@ -0,0 +1,28 @@ +use crate::prelude::*; + +#[system] +pub fn map_render( + #[resource] map: &Map, + #[resource] camera: &Camera, +) { + let mut draw_batch = DrawBatch::new(); + draw_batch.target(0); + + for y in camera.top_y ..= camera.bottom_y { + for x in camera.left_x .. camera.right_x { + let p = Point::new(x, y); + let offset = Point::new(camera.left_x, camera.top_y); + + if map.in_bounds(p) { + let idx = map_idx(x, y); + let glyph = match map.tiles[idx] { + TileType::Wall => to_cp437('#'), + TileType::Floor => to_cp437('.'), + }; + draw_batch.set(p - offset, ColorPair::new(WHITE, BLACK), glyph); + } + } + } + + draw_batch.submit(0).expect("failed to draw batch"); +} diff --git a/rusty-roguelike/src/systems/mod.rs b/rusty-roguelike/src/systems/mod.rs new file mode 100644 index 0000000..19a1f4f --- /dev/null +++ b/rusty-roguelike/src/systems/mod.rs @@ -0,0 +1,13 @@ +mod player_input; +mod map_render; +mod entity_render; + +pub use crate::prelude::*; + +pub fn build_scheduler() -> Schedule { + return Schedule::builder() + .add_system(player_input::player_input_system()) + .add_system(map_render::map_render_system()) + .add_system(entity_render::entity_render_system()) + .build(); +} diff --git a/rusty-roguelike/src/systems/player_input.rs b/rusty-roguelike/src/systems/player_input.rs new file mode 100644 index 0000000..4bf39d9 --- /dev/null +++ b/rusty-roguelike/src/systems/player_input.rs @@ -0,0 +1,32 @@ +use crate::prelude::*; + +#[system] +#[write_component(Point)] +#[read_component(Player)] +pub fn player_input( + ecs: &mut SubWorld, + #[resource] map: &Map, + #[resource] key: &Option, + #[resource] camera: &mut Camera, +) { + if let Some(key) = key { + let delta = match key { + VirtualKeyCode::Up => Point::new(0, -1), + VirtualKeyCode::Right => Point::new(1, 0), + VirtualKeyCode::Down => Point::new(0, 1), + VirtualKeyCode::Left => Point::new(-1, 0), + _ => Point::new(0, 0), + }; + + if delta.x != 0 || delta.y != 0 { + let mut players = <&mut Point>::query().filter(component::()); + players.iter_mut(ecs).for_each(|pos| { + let destination = *pos + delta; + if map.can_enter_tile(destination) { + *pos = destination; + camera.on_player_move(destination); + } + }); + } + } +}