diff --git a/game_core/src/assets/startup.rs b/game_core/src/assets/startup.rs index 50b235cbebbe25b17a141fd8c5075dbffb027e64..aaf32237458044eaf7b2ae279504aa7f49e2ec15 100644 --- a/game_core/src/assets/startup.rs +++ b/game_core/src/assets/startup.rs @@ -20,6 +20,7 @@ pub fn start_load_resources(mut loader: AssetTypeLoader) { loader.load_images(&[("splash.png", "splash")]); loader.load_audio(&[("splash_sting.mp3", "splash_sting")]); + loader.load_font(&[("fonts/Kaph.ttf", "main")]); let sheet_config = SpriteSheetConfig::squares(16, SHEET_WIDTH, SHEET_HEIGHT); loader.load_spritesheet( &sheet_config, diff --git a/game_core/src/control/actions.rs b/game_core/src/control/actions.rs new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/game_core/src/control/mod.rs b/game_core/src/control/mod.rs index f28d7c205ea9a9b4c0a1c6bf1404f8a0f7421669..632784b683760f1c0d88f21d0a25a4b2dd03ccfc 100644 --- a/game_core/src/control/mod.rs +++ b/game_core/src/control/mod.rs @@ -1 +1,25 @@ +pub mod actions; pub mod player; + +mod __plugin { + use bevy::app::{App, CoreStage}; + use bevy::prelude::Plugin; + use iyes_loopless::prelude::ConditionSet; + + use crate::system::flow::AppState; + + pub struct ControlPlugin; + impl Plugin for ControlPlugin { + fn build(&self, app: &mut App) { + app.add_system_set_to_stage( + CoreStage::PreUpdate, + ConditionSet::new() + .run_in_state(AppState::InGame) + .with_system(super::player::handle_player_input) + .into(), + ); + } + } +} + +pub use __plugin::ControlPlugin; diff --git a/game_core/src/control/player.rs b/game_core/src/control/player.rs new file mode 100644 index 0000000000000000000000000000000000000000..44f52965a1ae898105d399cd39a807cde946fc09 --- /dev/null +++ b/game_core/src/control/player.rs @@ -0,0 +1,31 @@ +use bevy::math::ivec2; +use bevy::prelude::*; + +use crate::entities::lifecycle::Player; +use crate::world::level_map::GridPosition; + +pub fn handle_player_input( + input: Res<Input<KeyCode>>, + mut query: Query<&mut GridPosition, With<Player>>, +) { + let mut dx = 0; + let mut dy = 0; + + if input.just_released(KeyCode::D) || input.just_released(KeyCode::Right) { + dx += 1; + } + if input.just_released(KeyCode::W) || input.just_released(KeyCode::Up) { + dy += 1; + } + if input.just_released(KeyCode::A) || input.just_released(KeyCode::Left) { + dx -= 1; + } + if input.just_released(KeyCode::S) || input.just_released(KeyCode::Down) { + dy -= 1; + } + + for mut position in &mut query { + let next_position = (position.0.as_ivec2()) + ivec2(dx, dy); + **position = next_position.as_uvec2(); + } +} diff --git a/game_core/src/debug.rs b/game_core/src/debug.rs index 928add3d7a5eacb15566f4c082ac034ae662e8cb..2722f4b0a7d2800f350e7f094d7cfab2f86dd38a 100644 --- a/game_core/src/debug.rs +++ b/game_core/src/debug.rs @@ -3,17 +3,64 @@ use bevy::prelude::*; use iyes_loopless::prelude::AppLooplessStateExt; use iyes_loopless::state::NextState; -use crate::entities::spawner::EntitySpawner; +use crate::assets::AssetHandles; use crate::system::flow::AppState; -use crate::world::generation::generate_map; +use crate::world::generators::drunkard_corridor::DrunkardGenerator; use crate::world::level_map::LevelMapBundle; -pub fn spawn_player(mut spawner: EntitySpawner) { - log::info!("Spawning player"); - spawner.spawn_player(uvec2(25, 25)); - spawner - .commands - .spawn_bundle(LevelMapBundle::generate(50, 50)); +pub fn spawn_player(mut commands: Commands) { + commands.spawn_bundle(LevelMapBundle::generate::<DrunkardGenerator>(150, 150)); +} + +#[derive(Component)] +pub struct FpsText; + +pub fn spawn_fps_overlay(mut commands: Commands, assets: Res<AssetHandles>) { + commands + .spawn_bundle(NodeBundle { + style: Style { + position_type: PositionType::Absolute, + position: UiRect::new(Val::Px(20.0), Val::Auto, Val::Px(20.0), Val::Auto), + flex_direction: FlexDirection::Row, + ..Default::default() + }, + color: Color::rgba(0.0, 0.0, 0.0, 0.0).into(), + ..Default::default() + }) + .with_children(|child_builder| { + child_builder.spawn_bundle(TextBundle { + text: Text::from_section( + "FPS: ", + TextStyle { + font_size: 20.0, + font: assets.font("main"), + color: Color::WHITE, + }, + ), + ..Default::default() + }); + + child_builder + .spawn_bundle(TextBundle { + text: Text::from_section( + "000", + TextStyle { + font_size: 20.0, + font: assets.font("main"), + color: Color::YELLOW, + }, + ), + ..Default::default() + }) + .insert(FpsText); + }); +} + +pub fn update_fps_text(time: Res<Time>, mut text_query: Query<&mut Text, With<FpsText>>) { + let fps = format!("{:03.0}", 1.0 / time.delta_seconds()); + for mut text in &mut text_query { + text.sections[0].value = fps.clone(); + } } pub fn skip_menu(mut commands: Commands) { @@ -24,6 +71,8 @@ pub struct DebugPlugin; impl Plugin for DebugPlugin { fn build(&self, app: &mut App) { app.add_enter_system(AppState::Menu, skip_menu) - .add_enter_system(AppState::InGame, spawn_player); + .add_enter_system(AppState::Menu, spawn_fps_overlay) + .add_enter_system(AppState::InGame, spawn_player) + .add_system(update_fps_text); } } diff --git a/game_core/src/entities/lifecycle.rs b/game_core/src/entities/lifecycle.rs index 8007a1dbfe41240fc35766f76eacb0646b92bf90..d4dcc2aee4fe5e4a6eef6aa01fe23db6ffb48a2c 100644 --- a/game_core/src/entities/lifecycle.rs +++ b/game_core/src/entities/lifecycle.rs @@ -5,6 +5,8 @@ use crate::system::flow::AppState; #[derive(Debug, Clone, Copy, Component)] pub struct GameEntity; +#[derive(Debug, Clone, Copy, Component)] +pub struct Player; pub fn remove_game_entities(mut commands: Commands, query: Query<Entity, With<GameEntity>>) { for entity in &query { diff --git a/game_core/src/entities/spawner.rs b/game_core/src/entities/spawner.rs index 7d78cbd43f5cae102ad802866f953aa051128e1d..32bacc64c631798b6704b5a57a52a3f45b4bf935 100644 --- a/game_core/src/entities/spawner.rs +++ b/game_core/src/entities/spawner.rs @@ -2,7 +2,7 @@ use bevy::ecs::system::SystemParam; use bevy::prelude::*; use crate::assets::AssetHandles; -use crate::entities::lifecycle::GameEntity; +use crate::entities::lifecycle::{GameEntity, Player}; use crate::system::camera::ChaseCam; use crate::system::graphics::LAYER_CREATURE; use crate::world::level_map::GridPosition; @@ -22,6 +22,7 @@ impl<'w, 's> EntitySpawner<'w, 's> { entity .insert(ChaseCam) .insert(GameEntity) + .insert(Player) .insert(GridPosition(grid_position)); entity.insert_bundle(SpriteSheetBundle { diff --git a/game_core/src/main.rs b/game_core/src/main.rs index bbe51c5948b1f5cb9158439cb9fecbce79d7c115..9e165a9060d5f313726a791cae039629f1bfe09d 100644 --- a/game_core/src/main.rs +++ b/game_core/src/main.rs @@ -21,5 +21,6 @@ fn main() { >::new()) .add_plugin(game_core::debug::DebugPlugin) .add_plugin(game_core::world::WorldPlugin) + .add_plugin(game_core::control::ControlPlugin) .run(); } diff --git a/game_core/src/world/generation.rs b/game_core/src/world/generation.rs deleted file mode 100644 index 4f39d6b74238c9d647ebc1c9554132504f24445b..0000000000000000000000000000000000000000 --- a/game_core/src/world/generation.rs +++ /dev/null @@ -1,262 +0,0 @@ -use fastrand::Rng; - -use crate::world::level_map::{Indexer, LevelMap, MapLayer, MapTile}; - -const TMP_FLOOR_GROUP: usize = 6 * 64; -const TMP_WALL_GROUP: usize = 3 * 64 + 28; - -#[derive(Copy, Clone, Debug, Default)] -enum GenerationDirection { - Top, - Bottom, - Left, - Right, - #[default] - None, -} - -pub fn generate_map(width: usize, height: usize) -> LevelMap { - generate_map_with_seed(fastrand::u64(0..u64::MAX), width, height) -} - -pub fn generate_map_with_seed(seed: u64, width: usize, height: usize) -> LevelMap { - let rng = fastrand::Rng::with_seed(seed); - generate_map_with_rng(&rng, width, height) -} - -pub fn generate_map_with_rng(rng: &Rng, width: usize, height: usize) -> LevelMap { - let indexer = Indexer::new(width, height); - - let mut floor_layer = vec![0; width * height]; - let mut wall_layer = vec![0; width * height]; - let mut decoration_layer = vec![0; width * height]; - - let initial_x = width / 2; - let initial_y = width / 2; - let initial_width = rng.usize(3..=5); - let initial_height = rng.usize(3..=5); - - draw_box( - initial_x - initial_width / 2, - initial_y - initial_height / 2, - initial_width, - initial_height, - TMP_FLOOR_GROUP, - &mut floor_layer, - &indexer, - ); - - for _ in 0..1000 { - let initial = find_next_door(1000, &floor_layer, &rng, &indexer); - if let Some((next_x, next_y, direction)) = initial { - let size_selection = rng.usize(1..=10); - let (room_width, room_height) = if size_selection < 8 { - (rng.usize(3..=6), rng.usize(3..=6)) - } else { - (rng.usize(7..=12), rng.usize(7..=12)) - }; - - // Align room drawing to top left corner of new box - match direction { - GenerationDirection::Top => { - let start_x = next_x - (room_width / 4); - let start_y = next_y + room_height; - draw_box( - start_x, - start_y, - room_width, - room_height, - TMP_FLOOR_GROUP, - &mut floor_layer, - &indexer, - ); - } - GenerationDirection::Bottom => { - let start_x = next_x - (room_width / 4); - let start_y = next_y; - draw_box( - start_x, - start_y, - room_width, - room_height, - TMP_FLOOR_GROUP, - &mut floor_layer, - &indexer, - ); - } - GenerationDirection::Left => { - let start_x = next_x - room_width; - let start_y = next_y; - draw_box( - start_x, - start_y, - room_width, - room_height, - TMP_FLOOR_GROUP, - &mut floor_layer, - &indexer, - ); - } - GenerationDirection::Right => { - let start_x = next_x; - let start_y = next_y; - draw_box( - start_x, - start_y, - room_width, - room_height, - TMP_FLOOR_GROUP, - &mut floor_layer, - &indexer, - ); - } - GenerationDirection::None => {} - } - } - } - - LevelMap { - width, - height, - layers: vec![ - MapLayer::from_sized_list( - width, - height, - floor_layer - .into_iter() - .map(|tile| match tile { - 0 => None, - rest => Some(MapTile::new_floor(rest)), - }) - .collect(), - ), - MapLayer::from_sized_list( - width, - height, - wall_layer - .into_iter() - .map(|tile| match tile { - 0 => None, - rest => Some(MapTile::new_wall(rest)), - }) - .collect(), - ), - MapLayer::from_sized_list( - width, - height, - decoration_layer - .into_iter() - .map(|tile| match tile { - 0 => None, - rest => Some(MapTile::new_obstacle(rest)), - }) - .collect(), - ), - ], - } -} - -fn find_next_door( - max_attempts: usize, - floor: &Vec<usize>, - rng: &Rng, - idx: &Indexer, -) -> Option<(usize, usize, GenerationDirection)> { - let mut remaining = max_attempts; - let mut found = None; - let mut last_direction = GenerationDirection::None; - - while remaining > 0 && found.is_none() { - remaining = remaining.saturating_sub(1); - - let target_index = rng.usize(0..floor.len()); - let (x, y) = idx.reverse(target_index); - - // Edges of the map are unsuitable for new rooms - if x == 0 || x == idx.width() - 1 || y == 0 || y == idx.height() - 1 { - continue; - } - - let mut adjacents = 0; - - // Check Left - if floor[idx.index(x - 1, y)] != 0 { - adjacents += 1; - last_direction = GenerationDirection::Right; - } - // Check Right - if floor[idx.index(x + 1, y)] != 0 { - adjacents += 1; - last_direction = GenerationDirection::Left; - } - // Check Top - if floor[idx.index(x, y + 1)] != 0 { - adjacents += 1; - last_direction = GenerationDirection::Bottom; - } - // Check Bottom - if floor[idx.index(x, y - 1)] != 0 { - adjacents += 1; - last_direction = GenerationDirection::Top; - } - - if adjacents == 1 { - found = Some((x, y, last_direction)); - break; - } - } - - found -} - -fn draw_box( - start_x: usize, - start_y: usize, - width: usize, - height: usize, - value: usize, - layer: &mut Vec<usize>, - indexer: &Indexer, -) { - if !validate_box(start_x, start_y, width, height, layer, indexer) { - return; - } - - for x in start_x..(start_x + width) { - for y in start_y..(start_y + height) { - let idx = indexer.index(x, y); - layer[idx] = value; - } - } -} - -fn validate_box( - start_x: usize, - start_y: usize, - width: usize, - height: usize, - layer: &Vec<usize>, - indexer: &Indexer, -) -> bool { - let map_width = indexer.width(); - let map_height = indexer.height(); - - if start_y + height >= map_height { - return false; - } - - if start_x + width >= map_width { - return false; - } - - for x in start_x..(start_x + width) { - for y in start_y..(start_y + height) { - let idx = indexer.index(x, y); - if layer[idx] != 0 { - return false; - } - } - } - - true -} diff --git a/game_core/src/world/generators/blobular.rs b/game_core/src/world/generators/blobular.rs new file mode 100644 index 0000000000000000000000000000000000000000..b552923c0a729bc5006585ba1cb74cc5c0ad6628 --- /dev/null +++ b/game_core/src/world/generators/blobular.rs @@ -0,0 +1,208 @@ +use bevy::math::uvec2; +use fastrand::Rng; + +use crate::world::generators::utils::{draw_box, GenerationDirection}; +use crate::world::generators::{MapGenerator, TMP_FLOOR_GROUP}; +use crate::world::level_map::{Indexer, LevelMap, MapLayer, MapTile}; + +pub struct Blobular; +impl MapGenerator for Blobular { + fn generate(indexer: &Indexer, rng: &Rng) -> LevelMap { + let width = indexer.width(); + let height = indexer.height(); + + let mut floor_layer = vec![0; width * height]; + let mut wall_layer = vec![0; width * height]; + let mut decoration_layer = vec![0; width * height]; + + let initial_x = width / 2; + let initial_y = width / 2; + let initial_width = rng.usize(3..=5); + let initial_height = rng.usize(3..=5); + + draw_box( + initial_x - (6 / 2), + initial_y - (4 / 2), + 6, + 4, + TMP_FLOOR_GROUP, + &mut floor_layer, + &indexer, + ); + + draw_box(0, 0, 6, 10, TMP_FLOOR_GROUP, &mut floor_layer, &indexer); + + // draw_box( + // initial_x - initial_width / 2, + // initial_y - initial_height / 2, + // initial_width, + // initial_height, + // TMP_FLOOR_GROUP, + // &mut floor_layer, + // &indexer, + // ); + // + for _ in 0..1000 { + let initial = find_next_door(1000, &floor_layer, &rng, &indexer); + if let Some((next_x, next_y, direction)) = initial { + let size_selection = rng.usize(1..=10); + let (room_width, room_height) = if size_selection < 8 { + (rng.usize(3..=6), rng.usize(3..=6)) + } else { + (rng.usize(7..=12), rng.usize(7..=12)) + }; + + // Align room drawing to top left corner of new box + match direction { + GenerationDirection::Top => { + let start_x = next_x - (room_width / 4); + let start_y = next_y + room_height - 1; + draw_box( + start_x, + start_y, + room_width, + room_height, + TMP_FLOOR_GROUP, + &mut floor_layer, + &indexer, + ); + } + GenerationDirection::Bottom => { + let start_x = next_x - (room_width / 4); + let start_y = next_y; + draw_box( + start_x, + start_y, + room_width, + room_height, + TMP_FLOOR_GROUP, + &mut floor_layer, + &indexer, + ); + } + GenerationDirection::Left => { + let start_x = next_x - (room_width - 1); + let start_y = next_y; + draw_box( + start_x, + start_y, + room_width, + room_height, + TMP_FLOOR_GROUP, + &mut floor_layer, + &indexer, + ); + } + GenerationDirection::Right => { + let start_x = next_x; + let start_y = next_y; + draw_box( + start_x, + start_y, + room_width, + room_height, + TMP_FLOOR_GROUP, + &mut floor_layer, + &indexer, + ); + } + GenerationDirection::None => {} + } + } + } + + LevelMap { + spawn: uvec2(initial_x as u32, initial_y as u32), + width, + height, + layers: vec![ + MapLayer::from_sized_list( + width, + height, + floor_layer + .into_iter() + .map(|tile| match tile { + 0 => None, + rest => Some(MapTile::new_floor(rest)), + }) + .collect(), + ), + MapLayer::from_sized_list( + width, + height, + wall_layer + .into_iter() + .map(|tile| match tile { + 0 => None, + rest => Some(MapTile::new_wall(rest)), + }) + .collect(), + ), + MapLayer::from_sized_list( + width, + height, + decoration_layer + .into_iter() + .map(|tile| match tile { + 0 => None, + rest => Some(MapTile::new_obstacle(rest)), + }) + .collect(), + ), + ], + } + } +} + +fn find_next_door( + max_attempts: usize, + floor: &Vec<usize>, + rng: &Rng, + idx: &Indexer, +) -> Option<(usize, usize, GenerationDirection)> { + let mut remaining = max_attempts; + let mut found = None; + let mut last_direction = GenerationDirection::None; + + while remaining > 0 && found.is_none() { + remaining = remaining.saturating_sub(1); + + let target_index = rng.usize(0..floor.len()); + let (x, y) = idx.reverse(target_index); + + // Edges of the map are unsuitable for new rooms + if x == 0 || x == idx.width() - 1 || y == 0 || y == idx.height() - 1 { + continue; + } + + let mut adjacents = 0; + + // Check Left + if floor[idx.index(x - 1, y)] != 0 { + adjacents += 1; + last_direction = GenerationDirection::Right; + } + // Check Right + if floor[idx.index(x + 1, y)] != 0 { + adjacents += 1; + last_direction = GenerationDirection::Left; + } + // Check Top + if floor[idx.index(x, y + 1)] != 0 { + adjacents += 1; + last_direction = GenerationDirection::Bottom; + } + // Check Bottom + if floor[idx.index(x, y - 1)] != 0 { + adjacents += 1; + last_direction = GenerationDirection::Top; + } + + if adjacents == 1 { + found = Some((x, y, last_direction)); + break; + } + } + + found +} diff --git a/game_core/src/world/generators/drunkard_corridor.rs b/game_core/src/world/generators/drunkard_corridor.rs new file mode 100644 index 0000000000000000000000000000000000000000..2161d994d8d69ebd5d9ff6d51a3e82bf75cfd3c7 --- /dev/null +++ b/game_core/src/world/generators/drunkard_corridor.rs @@ -0,0 +1,70 @@ +use bevy::math::uvec2; +use fastrand::Rng; + +use crate::world::generators::{MapGenerator, TMP_FLOOR_GROUP}; +use crate::world::level_map::{Indexer, LevelMap, MapLayer, MapTile}; + +pub struct DrunkardGenerator; +impl MapGenerator for DrunkardGenerator { + fn generate(indexer: &Indexer, rng: &Rng) -> LevelMap { + let map_width = indexer.width(); + let map_height = indexer.height(); + + let mut iterations = rng.usize(700..1500); + let mut floor = vec![0; map_width * map_height]; + + let start = uvec2(map_width as u32 / 2, map_height as u32 / 2); + let mut previous = start.clone(); + floor[indexer.index(start.x as usize, start.y as usize)] = TMP_FLOOR_GROUP; + + while iterations > 0 { + iterations -= 1; + let mut dx = 0; + let mut dy = 0; + + match rng.u8(0..4) { + 0 => { + dx += 1; + } + 1 => { + dx -= 1; + } + 2 => { + dy += 1; + } + 3 => { + dy -= 1; + } + _ => unreachable!(), + } + + let target_x = (previous.x as i32 + dx).max(0) as usize; + let target_y = (previous.y as i32 + dy).max(0) as usize; + let target_idx = indexer.index(target_x, target_y); + if indexer.index_within(target_idx) { + floor[target_idx] = TMP_FLOOR_GROUP; + previous = uvec2(target_x as u32, target_y as u32); + } + } + + LevelMap::new( + map_width, + map_height, + start, + vec![MapLayer::from_sized_list( + map_width, + map_height, + floor + .into_iter() + .map(|tile| { + if tile == 0 { + None + } else { + Some(MapTile::new_floor(tile)) + } + }) + .collect(), + )], + ) + } +} diff --git a/game_core/src/world/generators/mod.rs b/game_core/src/world/generators/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..c8dc5112ebbcbd04b5e784d63abb4dc0af4bed2e --- /dev/null +++ b/game_core/src/world/generators/mod.rs @@ -0,0 +1,14 @@ +use fastrand::Rng; + +use crate::world::level_map::{Indexer, LevelMap}; + +pub mod blobular; +pub mod drunkard_corridor; +pub(crate) mod utils; + +pub(crate) const TMP_FLOOR_GROUP: usize = 6 * 64; +pub(crate) const TMP_WALL_GROUP: usize = 3 * 64 + 28; + +pub trait MapGenerator { + fn generate(indexer: &Indexer, rng: &Rng) -> LevelMap; +} diff --git a/game_core/src/world/generators/utils.rs b/game_core/src/world/generators/utils.rs new file mode 100644 index 0000000000000000000000000000000000000000..51b926c6f9c7f74adc73c793ad57b15b1071248e --- /dev/null +++ b/game_core/src/world/generators/utils.rs @@ -0,0 +1,63 @@ +use crate::world::level_map::Indexer; + +#[derive(Copy, Clone, Debug, Default)] +pub enum GenerationDirection { + Top, + Bottom, + Left, + Right, + #[default] + None, +} + +pub fn draw_box( + start_x: usize, + start_y: usize, + width: usize, + height: usize, + value: usize, + layer: &mut Vec<usize>, + indexer: &Indexer, +) { + if !validate_box(start_x, start_y, width, height, layer, indexer) { + return; + } + + for y in start_y..(start_y + height) { + for x in start_x..(start_x + width) { + let idx = indexer.index(x, y); + layer[idx] = value; + } + } +} + +pub fn validate_box( + start_x: usize, + start_y: usize, + width: usize, + height: usize, + layer: &Vec<usize>, + indexer: &Indexer, +) -> bool { + let map_width = indexer.width(); + let map_height = indexer.height(); + + if start_y + height >= map_height { + return false; + } + + if start_x + width >= map_width { + return false; + } + + for x in start_x..(start_x + width) { + for y in start_y..(start_y + height) { + let idx = indexer.index(x, y); + if layer[idx] != 0 { + return false; + } + } + } + + true +} diff --git a/game_core/src/world/handlers.rs b/game_core/src/world/handlers.rs index 14d0d582c86cd923e3f635a2b3b49c6685623149..b528375ada7ab566c45d1c758a08dbb60b8e94ef 100644 --- a/game_core/src/world/handlers.rs +++ b/game_core/src/world/handlers.rs @@ -3,6 +3,7 @@ use bevy::prelude::*; use crate::assets::AssetHandles; use crate::entities::lifecycle::GameEntity; +use crate::entities::spawner::EntitySpawner; use crate::system::graphics::LAYER_TILE; use crate::world::adjacency::get_floor_sprite_offset; use crate::world::level_map::{GridPosition, Indexer, LevelMap, Tile, WORLD_TILE_SIZE}; @@ -10,6 +11,7 @@ use crate::world::level_map::{GridPosition, Indexer, LevelMap, Tile, WORLD_TILE_ pub fn spawn_new_world( mut commands: Commands, assets: Res<AssetHandles>, + mut spawner: EntitySpawner, query: Query<(Entity, &LevelMap), Added<LevelMap>>, ) { for (entity, map) in &query { @@ -41,5 +43,7 @@ pub fn spawn_new_world( } }); } + + spawner.spawn_player(map.spawn); } } diff --git a/game_core/src/world/level_map.rs b/game_core/src/world/level_map.rs index b674c1dd3496a4f722f7622256dda706a9259f8d..57be529f07c5d962c65d203e4e76ff5901a37563 100644 --- a/game_core/src/world/level_map.rs +++ b/game_core/src/world/level_map.rs @@ -1,12 +1,12 @@ use std::fmt::{Debug, Formatter}; -use std::ops::Deref; +use std::ops::{Deref, DerefMut}; use bevy::math::UVec2; use bevy::prelude::*; use fastrand::Rng; use crate::world::adjacency::{BOTTOM, LEFT, NONE, RIGHT, TOP}; -use crate::world::generation::{generate_map, generate_map_with_rng, generate_map_with_seed}; +use crate::world::generators::MapGenerator; pub const WORLD_TILE_SIZE: f32 = 16.0; @@ -24,6 +24,11 @@ impl Deref for GridPosition { &self.0 } } +impl DerefMut for GridPosition { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} /// Take an entity's position on a grid, and sync it up to the transform used to render /// the sprite @@ -147,6 +152,18 @@ pub struct LevelMap { pub width: usize, pub height: usize, pub layers: Vec<MapLayer>, + pub spawn: UVec2, +} + +impl LevelMap { + pub fn new(width: usize, height: usize, spawn: UVec2, layers: Vec<MapLayer>) -> Self { + LevelMap { + width, + height, + layers, + spawn, + } + } } #[derive(Bundle)] @@ -167,14 +184,15 @@ impl LevelMapBundle { } } - pub fn generate(width: usize, height: usize) -> Self { - Self::new(generate_map(width, height)) + pub fn generate<Gen: MapGenerator>(width: usize, height: usize) -> Self { + Self::generate_with_rng::<Gen>(&Rng::new(), width, height) } - pub fn generate_with_seed(seed: u64, width: usize, height: usize) -> Self { - Self::new(generate_map_with_seed(seed, width, height)) + pub fn generate_with_seed<Gen: MapGenerator>(seed: u64, width: usize, height: usize) -> Self { + Self::generate_with_rng::<Gen>(&Rng::with_seed(seed), width, height) } - pub fn generate_with_rng(rng: &Rng, width: usize, height: usize) -> Self { - Self::new(generate_map_with_rng(rng, width, height)) + pub fn generate_with_rng<Gen: MapGenerator>(rng: &Rng, width: usize, height: usize) -> Self { + let indexer = Indexer::new(width, height); + LevelMapBundle::new(Gen::generate(&indexer, rng)) } } @@ -192,7 +210,11 @@ impl Indexer { (y * self.width) + x } pub fn reverse(&self, index: usize) -> (usize, usize) { - (index / self.width, index % self.width) + (index % self.width, index / self.width) + } + pub fn index_within(&self, idx: usize) -> bool { + let (x, y) = self.reverse(idx); + x >= 0 && x < self.width && y >= 0 && y < self.height } pub fn width(&self) -> usize { self.width diff --git a/game_core/src/world/mod.rs b/game_core/src/world/mod.rs index 0997770ae74867bef99c1e8ee6cb3457d44ea0a5..0e3838efdb8c565847bac2a8e99342f67657c605 100644 --- a/game_core/src/world/mod.rs +++ b/game_core/src/world/mod.rs @@ -1,9 +1,9 @@ pub mod adjacency; -pub mod generation; +pub mod generators; pub mod handlers; pub mod level_map; -mod __internal { +mod __plugin { use bevy::app::{App, CoreStage, Plugin}; use iyes_loopless::condition::ConditionSet; @@ -24,4 +24,4 @@ mod __internal { } } -pub use __internal::WorldPlugin; +pub use __plugin::WorldPlugin;