From f7586c81e213511bf5383e9fedbd8faea7f7c6fa Mon Sep 17 00:00:00 2001 From: "Timothy J. Warren" Date: Thu, 23 Jul 2020 18:12:52 -0400 Subject: [PATCH] Completed 2.4 Modules --- src/components.rs | 57 ++++++ src/constants.rs | 3 + src/entities.rs | 59 ++++++ src/main.rs | 317 ++------------------------------ src/map.rs | 40 ++++ src/resources.rs | 12 ++ src/systems/input_system.rs | 99 ++++++++++ src/systems/mod.rs | 5 + src/systems/rendering_system.rs | 45 +++++ 9 files changed, 333 insertions(+), 304 deletions(-) create mode 100644 src/components.rs create mode 100644 src/constants.rs create mode 100644 src/entities.rs create mode 100644 src/map.rs create mode 100644 src/resources.rs create mode 100644 src/systems/input_system.rs create mode 100644 src/systems/mod.rs create mode 100644 src/systems/rendering_system.rs diff --git a/src/components.rs b/src/components.rs new file mode 100644 index 0000000..38bb5f6 --- /dev/null +++ b/src/components.rs @@ -0,0 +1,57 @@ +use specs::{Component, NullStorage, VecStorage, World, WorldExt}; + +// Components +#[derive(Debug, Component, Clone, Copy)] +#[storage(VecStorage)] +pub struct Position { + pub x: u8, + pub y: u8, + pub z: u8, +} + +impl Position { + pub fn new(x: u8, y: u8) -> Self { + Position { x, y, z: 0 } + } +} + +#[derive(Component)] +#[storage(VecStorage)] +pub struct Renderable { + pub path: String, +} + +#[derive(Component)] +#[storage(VecStorage)] +pub struct Wall {} + +#[derive(Component)] +#[storage(VecStorage)] +pub struct Player {} + +#[derive(Component)] +#[storage(VecStorage)] +pub struct Box {} + +#[derive(Component)] +#[storage(VecStorage)] +pub struct BoxSpot {} + +#[derive(Component, Default)] +#[storage(NullStorage)] +pub struct Movable; + +#[derive(Component, Default)] +#[storage(NullStorage)] +pub struct Immovable; + +pub fn register_components(world: &mut World) { + world.register::(); + world.register::(); + world.register::(); + world.register::(); + world.register::(); + world.register::(); + world.register::(); + world.register::(); +} diff --git a/src/constants.rs b/src/constants.rs new file mode 100644 index 0000000..2aa0f5c --- /dev/null +++ b/src/constants.rs @@ -0,0 +1,3 @@ +pub const TILE_WIDTH: f32 = 32.0; +pub const MAP_WIDTH: u8 = 8; +pub const MAP_HEIGHT: u8 = 9; diff --git a/src/entities.rs b/src/entities.rs new file mode 100644 index 0000000..be09727 --- /dev/null +++ b/src/entities.rs @@ -0,0 +1,59 @@ +use crate::components::*; +use specs::{Builder, World, WorldExt}; + +pub fn create_wall(world: &mut World, position: Position) { + world + .create_entity() + .with(Position { z: 10, ..position }) + .with(Renderable { + path: "/images/wall.png".to_string(), + }) + .with(Wall {}) + .with(Immovable) + .build(); +} + +pub fn create_floor(world: &mut World, position: Position) { + world + .create_entity() + .with(Position { z: 5, ..position }) + .with(Renderable { + path: "/images/floor.png".to_string(), + }) + .build(); +} + +pub fn create_box(world: &mut World, position: Position) { + world + .create_entity() + .with(Position { z: 10, ..position }) + .with(Renderable { + path: "/images/box.png".to_string(), + }) + .with(Box {}) + .with(Movable) + .build(); +} + +pub fn create_box_spot(world: &mut World, position: Position) { + world + .create_entity() + .with(Position { z: 9, ..position }) + .with(Renderable { + path: "/images/box_spot.png".to_string(), + }) + .with(BoxSpot {}) + .build(); +} + +pub fn create_player(world: &mut World, position: Position) { + world + .create_entity() + .with(Position { z: 10, ..position }) + .with(Renderable { + path: "/images/player.png".to_string(), + }) + .with(Player {}) + .with(Movable) + .build(); +} diff --git a/src/main.rs b/src/main.rs index dd2ea19..1434a39 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,201 +1,21 @@ use ggez; -use ggez::event::{KeyCode, KeyMods}; -use ggez::graphics; -use ggez::graphics::DrawParam; -use ggez::graphics::Image; -use ggez::nalgebra as na; +use ggez::event::KeyCode; +use ggez::event::KeyMods; use ggez::{conf, event, Context, GameResult}; -use specs::world::Index; -use specs::Entities; -use specs::NullStorage; -use specs::WriteStorage; -use specs::{ - join::Join, Builder, Component, ReadStorage, RunNow, System, VecStorage, World, WorldExt, Write, -}; -use std::collections::HashMap; +use specs::{RunNow, World, WorldExt}; use std::path; -const TILE_WIDTH: f32 = 32.0; -const MAP_WIDTH: u8 = 8; -const MAP_HEIGHT: u8 = 9; +mod components; +mod constants; +mod entities; +mod map; +mod resources; +mod systems; -// Components -#[derive(Debug, Component, Clone, Copy)] -#[storage(VecStorage)] -pub struct Position { - x: u8, - y: u8, - z: u8, -} - -impl Position { - pub fn new(x: u8, y: u8) -> Self { - Position { x, y, z: 0 } - } -} - -#[derive(Component)] -#[storage(VecStorage)] -pub struct Renderable { - path: String, -} - -#[derive(Component)] -#[storage(VecStorage)] -pub struct Wall {} - -#[derive(Component)] -#[storage(VecStorage)] -pub struct Player {} - -#[derive(Component)] -#[storage(VecStorage)] -pub struct Box {} - -#[derive(Component)] -#[storage(VecStorage)] -pub struct BoxSpot {} - -#[derive(Component, Default)] -#[storage(NullStorage)] -pub struct Movable; - -#[derive(Component, Default)] -#[storage(NullStorage)] -pub struct Immovable; - -// Resources -#[derive(Default)] -pub struct InputQueue { - pub keys_pressed: Vec, -} - -// Systems -pub struct RenderingSystem<'a> { - context: &'a mut Context, -} - -impl<'a> System<'a> for RenderingSystem<'a> { - type SystemData = (ReadStorage<'a, Position>, ReadStorage<'a, Renderable>); - - fn run(&mut self, data: Self::SystemData) { - let (positions, renderables) = data; - - // Clear the screen/set the background - graphics::clear(self.context, graphics::Color::new(0.95, 0.95, 0.95, 1.0)); - - // Get all the renderables with their positions and sort by the position z - // This will allow us to have entities layered visually. - let mut rendering_data = (&positions, &renderables).join().collect::>(); - rendering_data.sort_by(|&a, &b| a.0.z.partial_cmp(&b.0.z).expect("expected comparison")); - - // Iterate through all paris of positions & renderables, load the image - // and draw it at the specified position. - for (position, renderable) in rendering_data.iter() { - // Load the image - let image = Image::new(self.context, renderable.path.clone()).expect("expected image"); - let x = position.x as f32 * TILE_WIDTH; - let y = position.y as f32 * TILE_WIDTH; - - // draw - let draw_params = DrawParam::new().dest(na::Point2::new(x, y)); - graphics::draw(self.context, &image, draw_params).expect("expected render"); - } - - // Finally, present the context, this will actually display everything - // on the screen. - graphics::present(self.context).expect("expected to present"); - } -} - -pub struct InputSystem {} - -impl<'a> System<'a> for InputSystem { - // Data - type SystemData = ( - Write<'a, InputQueue>, - Entities<'a>, - WriteStorage<'a, Position>, - ReadStorage<'a, Player>, - ReadStorage<'a, Movable>, - ReadStorage<'a, Immovable>, - ); - - fn run(&mut self, data: Self::SystemData) { - let (mut input_queue, entities, mut positions, players, movables, immovables) = data; - - let mut to_move = Vec::new(); - - for (position, _player) in (&positions, &players).join() { - // Get the first key pressed - if let Some(key) = input_queue.keys_pressed.pop() { - // get all the movables and immovables - let mov: HashMap<(u8, u8), Index> = (&entities, &movables, &positions) - .join() - .map(|t| ((t.2.x, t.2.y), t.0.id())) - .collect(); - let immov: HashMap<(u8, u8), Index> = (&entities, &immovables, &positions) - .join() - .map(|t| ((t.2.x, t.2.y), t.0.id())) - .collect(); - - // Now iterate through current position to the end of the map - // on the correct axis and check what needs to move. - let (start, end, is_x) = match key { - KeyCode::Up => (position.y, 0, false), - KeyCode::Down => (position.y, MAP_HEIGHT, false), - KeyCode::Left => (position.x, 0, true), - KeyCode::Right => (position.x, MAP_WIDTH, true), - _ => continue, - }; - - let range: Vec<_> = if start < end { - (start..=end).collect() - } else { - (end..=start).rev().collect() - }; - - for x_or_y in range { - let pos = if is_x { - (x_or_y, position.y) - } else { - (position.x, x_or_y) - }; - - // find a movable - // if it exists, we try to move it and continue - // if it doesn't exist, we continue and try to find an immovable instead - match mov.get(&pos) { - Some(id) => to_move.push((key, id.clone())), - None => { - // find an immovable - // if it exists, we need to stop and not move anything - // if it doesn't exist, we stop because we found a gap - match immov.get(&pos) { - Some(_) => to_move.clear(), - None => break, - } - } - } - } - } - } - - // Now actually move what needs to be moved - for (key, id) in to_move { - let position = positions.get_mut(entities.entity(id)); - if let Some(position) = position { - match key { - KeyCode::Up => position.y -= 1, - KeyCode::Down => position.y += 1, - KeyCode::Left => position.x -= 1, - KeyCode::Right => position.x += 1, - _ => (), - } - } - } - } -} +use crate::components::*; +use crate::map::*; +use crate::resources::*; +use crate::systems::*; // All the game state struct Game { @@ -230,122 +50,11 @@ impl event::EventHandler for Game { _keymod: KeyMods, _repeat: bool, ) { - println!("Key pressed: {:?}", keycode); - let mut input_queue = self.world.write_resource::(); input_queue.keys_pressed.push(keycode); } } -pub fn register_components(world: &mut World) { - world.register::(); - world.register::(); - world.register::(); - world.register::(); - world.register::(); - world.register::(); - world.register::(); - world.register::(); -} - -pub fn register_resources(world: &mut World) { - world.insert(InputQueue::default()) -} - -pub fn create_wall(world: &mut World, position: Position) { - world - .create_entity() - .with(Position { z: 10, ..position }) - .with(Renderable { - path: "/images/wall.png".to_string(), - }) - .with(Wall {}) - .with(Immovable) - .build(); -} - -pub fn create_floor(world: &mut World, position: Position) { - world - .create_entity() - .with(Position { z: 5, ..position }) - .with(Renderable { - path: "/images/floor.png".to_string(), - }) - .build(); -} - -pub fn create_box(world: &mut World, position: Position) { - world - .create_entity() - .with(Position { z: 10, ..position }) - .with(Renderable { - path: "/images/box.png".to_string(), - }) - .with(Box {}) - .with(Movable) - .build(); -} - -pub fn create_box_spot(world: &mut World, position: Position) { - world - .create_entity() - .with(Position { z: 9, ..position }) - .with(Renderable { - path: "/images/box_spot.png".to_string(), - }) - .with(BoxSpot {}) - .build(); -} - -pub fn create_player(world: &mut World, position: Position) { - world - .create_entity() - .with(Position { z: 10, ..position }) - .with(Renderable { - path: "/images/player.png".to_string(), - }) - .with(Player {}) - .with(Movable) - .build(); -} - -pub fn load_map(world: &mut World, map_string: String) { - // read all lines - let rows: Vec<&str> = map_string.trim().split('\n').map(|x| x.trim()).collect(); - - for (y, row) in rows.iter().enumerate() { - let columns: Vec<&str> = row.split(' ').collect(); - - for (x, column) in columns.iter().enumerate() { - // Create the position at which to create something on the map - let position = Position::new(x as u8, y as u8); - - // Figure out which object to create - match *column { - "." => create_floor(world, position), - "W" => { - create_floor(world, position); - create_wall(world, position); - } - "P" => { - create_floor(world, position); - create_player(world, position); - } - "B" => { - create_floor(world, position); - create_box(world, position); - } - "S" => { - create_floor(world, position); - create_box_spot(world, position); - } - "N" => (), - c => panic!("unrecognized map item {}", c), - } - } - } -} - pub fn initialize_level(world: &mut World) { const MAP: &str = " N N W W W W W W diff --git a/src/map.rs b/src/map.rs new file mode 100644 index 0000000..36c9c11 --- /dev/null +++ b/src/map.rs @@ -0,0 +1,40 @@ +use crate::components::Position; +use crate::entities::*; +use specs::World; + +pub fn load_map(world: &mut World, map_string: String) { + // read all lines + let rows: Vec<&str> = map_string.trim().split('\n').map(|x| x.trim()).collect(); + + for (y, row) in rows.iter().enumerate() { + let columns: Vec<&str> = row.split(' ').collect(); + + for (x, column) in columns.iter().enumerate() { + // Create the position at which to create something on the map + let position = Position::new(x as u8, y as u8); + + // Figure out which object to create + match *column { + "." => create_floor(world, position), + "W" => { + create_floor(world, position); + create_wall(world, position); + } + "P" => { + create_floor(world, position); + create_player(world, position); + } + "B" => { + create_floor(world, position); + create_box(world, position); + } + "S" => { + create_floor(world, position); + create_box_spot(world, position); + } + "N" => (), + c => panic!("unrecognized map item {}", c), + } + } + } +} diff --git a/src/resources.rs b/src/resources.rs new file mode 100644 index 0000000..10e8023 --- /dev/null +++ b/src/resources.rs @@ -0,0 +1,12 @@ +use ggez::event::KeyCode; +use specs::World; + +// Resources +#[derive(Default)] +pub struct InputQueue { + pub keys_pressed: Vec, +} + +pub fn register_resources(world: &mut World) { + world.insert(InputQueue::default()) +} diff --git a/src/systems/input_system.rs b/src/systems/input_system.rs new file mode 100644 index 0000000..e4f352a --- /dev/null +++ b/src/systems/input_system.rs @@ -0,0 +1,99 @@ +use ggez::event::KeyCode; +use specs::join::Join; +use specs::world::Index; +use specs::{Entities, ReadStorage, System, Write, WriteStorage}; + +use std::collections::HashMap; + +use crate::components::{Immovable, Movable, Player, Position}; +use crate::constants::{MAP_HEIGHT, MAP_WIDTH}; +use crate::resources::InputQueue; + +pub struct InputSystem {} + +impl<'a> System<'a> for InputSystem { + // Data + type SystemData = ( + Write<'a, InputQueue>, + Entities<'a>, + WriteStorage<'a, Position>, + ReadStorage<'a, Player>, + ReadStorage<'a, Movable>, + ReadStorage<'a, Immovable>, + ); + + fn run(&mut self, data: Self::SystemData) { + let (mut input_queue, entities, mut positions, players, movables, immovables) = data; + + let mut to_move = Vec::new(); + + for (position, _player) in (&positions, &players).join() { + // Get the first key pressed + if let Some(key) = input_queue.keys_pressed.pop() { + // get all the movables and immovables + let mov: HashMap<(u8, u8), Index> = (&entities, &movables, &positions) + .join() + .map(|t| ((t.2.x, t.2.y), t.0.id())) + .collect(); + let immov: HashMap<(u8, u8), Index> = (&entities, &immovables, &positions) + .join() + .map(|t| ((t.2.x, t.2.y), t.0.id())) + .collect(); + + // Now iterate through current position to the end of the map + // on the correct axis and check what needs to move. + let (start, end, is_x) = match key { + KeyCode::Up => (position.y, 0, false), + KeyCode::Down => (position.y, MAP_HEIGHT, false), + KeyCode::Left => (position.x, 0, true), + KeyCode::Right => (position.x, MAP_WIDTH, true), + _ => continue, + }; + + let range: Vec<_> = if start < end { + (start..=end).collect() + } else { + (end..=start).rev().collect() + }; + + for x_or_y in range { + let pos = if is_x { + (x_or_y, position.y) + } else { + (position.x, x_or_y) + }; + + // find a movable + // if it exists, we try to move it and continue + // if it doesn't exist, we continue and try to find an immovable instead + match mov.get(&pos) { + Some(id) => to_move.push((key, id.clone())), + None => { + // find an immovable + // if it exists, we need to stop and not move anything + // if it doesn't exist, we stop because we found a gap + match immov.get(&pos) { + Some(_) => to_move.clear(), + None => break, + } + } + } + } + } + } + + // Now actually move what needs to be moved + for (key, id) in to_move { + let position = positions.get_mut(entities.entity(id)); + if let Some(position) = position { + match key { + KeyCode::Up => position.y -= 1, + KeyCode::Down => position.y += 1, + KeyCode::Left => position.x -= 1, + KeyCode::Right => position.x += 1, + _ => (), + } + } + } + } +} diff --git a/src/systems/mod.rs b/src/systems/mod.rs new file mode 100644 index 0000000..e574ced --- /dev/null +++ b/src/systems/mod.rs @@ -0,0 +1,5 @@ +mod input_system; +mod rendering_system; + +pub use self::input_system::InputSystem; +pub use self::rendering_system::RenderingSystem; diff --git a/src/systems/rendering_system.rs b/src/systems/rendering_system.rs new file mode 100644 index 0000000..60e6236 --- /dev/null +++ b/src/systems/rendering_system.rs @@ -0,0 +1,45 @@ +use ggez::graphics::DrawParam; +use ggez::graphics::Image; +use ggez::nalgebra as na; +use ggez::{graphics, Context}; +use specs::{Join, ReadStorage, System}; + +use crate::components::{Position, Renderable}; +use crate::constants::TILE_WIDTH; + +pub struct RenderingSystem<'a> { + pub context: &'a mut Context, +} + +impl<'a> System<'a> for RenderingSystem<'a> { + type SystemData = (ReadStorage<'a, Position>, ReadStorage<'a, Renderable>); + + fn run(&mut self, data: Self::SystemData) { + let (positions, renderables) = data; + + // Clear the screen/set the background + graphics::clear(self.context, graphics::Color::new(0.95, 0.95, 0.95, 1.0)); + + // Get all the renderables with their positions and sort by the position z + // This will allow us to have entities layered visually. + let mut rendering_data = (&positions, &renderables).join().collect::>(); + rendering_data.sort_by(|&a, &b| a.0.z.partial_cmp(&b.0.z).expect("expected comparison")); + + // Iterate through all paris of positions & renderables, load the image + // and draw it at the specified position. + for (position, renderable) in rendering_data.iter() { + // Load the image + let image = Image::new(self.context, renderable.path.clone()).expect("expected image"); + let x = position.x as f32 * TILE_WIDTH; + let y = position.y as f32 * TILE_WIDTH; + + // draw + let draw_params = DrawParam::new().dest(na::Point2::new(x, y)); + graphics::draw(self.context, &image, draw_params).expect("expected render"); + } + + // Finally, present the context, this will actually display everything + // on the screen. + graphics::present(self.context).expect("expected to present"); + } +}