diff --git a/src/grid/direction.rs b/src/grid/direction.rs new file mode 100644 index 0000000..c7438e4 --- /dev/null +++ b/src/grid/direction.rs @@ -0,0 +1,51 @@ +use bevy::prelude::*; +use hexx::EdgeDirection; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Reflect)] +pub enum HexDirection { + Top, + TopRight, + BottomRight, + Bottom, + BottomLeft, + TopLeft, +} + +impl HexDirection { + pub fn to_hexx_direction(self) -> EdgeDirection { + self.into() + } + + pub const ALL: [HexDirection; 6] = [ + Self::Top, + Self::TopRight, + Self::BottomRight, + Self::Bottom, + Self::BottomLeft, + Self::TopLeft, + ]; + + pub fn opposite(&self) -> Self { + match self { + Self::Top => Self::Bottom, + Self::TopRight => Self::BottomLeft, + Self::BottomRight => Self::TopLeft, + Self::Bottom => Self::Top, + Self::BottomLeft => Self::TopRight, + Self::TopLeft => Self::BottomRight, + } + } +} + +impl From for EdgeDirection { + fn from(value: HexDirection) -> Self { + match value { + HexDirection::Top => Self::FLAT_NORTH, + HexDirection::TopRight => Self::FLAT_NORTH_EAST, + HexDirection::BottomRight => Self::FLAT_SOUTH_EAST, + HexDirection::Bottom => Self::FLAT_SOUTH, + HexDirection::BottomLeft => Self::FLAT_SOUTH_WEST, + HexDirection::TopLeft => Self::FLAT_NORTH_WEST, + } + } +} diff --git a/src/grid/grid.rs b/src/grid/grid.rs new file mode 100644 index 0000000..1d3ea35 --- /dev/null +++ b/src/grid/grid.rs @@ -0,0 +1,94 @@ +use bevy::{ + ecs::{system::RunSystemOnce, world::Command}, + prelude::*, + render::{ + mesh::{Indices, PrimitiveTopology}, + render_asset::RenderAssetUsages, + }, + utils::hashbrown::HashMap, +}; +use hexx::{Hex, HexLayout, HexOrientation, InsetOptions, MeshInfo, PlaneMeshBuilder}; + +use super::direction::HexDirection; + +#[derive(Debug, Reflect, Component, Default)] +#[reflect(Component)] +pub struct Tile { + pub position: Hex, + pub walls: HashMap, +} + +const HEX_SIZE: Vec2 = Vec2::splat(13.0); + +#[derive(Debug)] +pub struct SpawnGrid; + +impl Command for SpawnGrid { + fn apply(self, world: &mut World) { + world.run_system_once(spawn_grid); + } +} + +pub fn spawn_grid( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, +) { + let layout = HexLayout { + hex_size: HEX_SIZE, + orientation: HexOrientation::Pointy, + ..default() + }; + let default_material = materials.add(Color::WHITE); + + let mesh = hexagonal_plane(&layout); + let mesh_handle = meshes.add(mesh); + + for hex in Hex::ZERO.range(15) { + let pos = layout.hex_to_world_pos(hex); + commands.spawn(( + Name::new("Tile"), + ColorMesh2dBundle { + mesh: mesh_handle.clone().into(), + transform: Transform::from_xyz(pos.x, pos.y, 0.), + material: default_material.clone(), + ..default() + }, + Tile::default(), + )); + } +} + +fn hexagonal_plane(hex_layout: &HexLayout) -> Mesh { + let mesh_info = PlaneMeshBuilder::new(hex_layout) + .facing(Vec3::Z) + .with_scale(Vec3::splat(0.98)) + .center_aligned() + .build(); + construct_mesh(mesh_info) +} + +fn border_plane(hex_layout: &HexLayout) -> Mesh { + let mesh_info = PlaneMeshBuilder::new(hex_layout) + .facing(Vec3::Z) + .with_inset_options(InsetOptions { + keep_inner_face: false, + scale: 0.2, + ..default() + }) + .center_aligned() + .build(); + + construct_mesh(mesh_info) +} + +fn construct_mesh(mesh_info: MeshInfo) -> Mesh { + Mesh::new( + PrimitiveTopology::TriangleList, + RenderAssetUsages::RENDER_WORLD, + ) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, mesh_info.vertices) + .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, mesh_info.normals) + .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, mesh_info.uvs) + .with_inserted_indices(Indices::U16(mesh_info.indices)) +} diff --git a/src/tiles/level.rs b/src/grid/level.rs similarity index 57% rename from src/tiles/level.rs rename to src/grid/level.rs index 2290946..b3ad1d2 100644 --- a/src/tiles/level.rs +++ b/src/grid/level.rs @@ -2,19 +2,13 @@ use bevy::{ecs::world::Command, prelude::*}; -use crate::tiles::player::SpawnPlayer; +use super::grid::SpawnGrid; -use super::tile::{self, GridSettings, SpawnGrid}; - -pub(super) fn plugin(app: &mut App) { - app.insert_resource(GridSettings::default()); - app.add_plugins(tile::plugin); -} +pub(super) fn plugin(_app: &mut App) {} /// A [`Command`] to spawn the level. /// Functions that accept only `&mut World` as their parameter implement [`Command`]. /// We use this style when a command requires no configuration. pub fn spawn_level(world: &mut World) { SpawnGrid.apply(world); - SpawnPlayer.apply(world); } diff --git a/src/grid/mod.rs b/src/grid/mod.rs new file mode 100644 index 0000000..de267df --- /dev/null +++ b/src/grid/mod.rs @@ -0,0 +1,8 @@ +use bevy::prelude::*; +pub mod direction; +pub mod grid; +pub mod level; + +pub(super) fn plugin(app: &mut App) { + app.add_plugins(level::plugin); +} diff --git a/src/lib.rs b/src/lib.rs index f673718..95b3b56 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,10 +4,10 @@ pub mod audio; mod demo; #[cfg(feature = "dev")] mod dev_tools; +#[cfg(not(feature = "demo"))] +mod grid; mod screens; mod theme; -#[cfg(not(feature = "demo"))] -mod tiles; use bevy::{ asset::AssetMetaCheck, @@ -63,7 +63,7 @@ impl Plugin for AppPlugin { #[cfg(feature = "demo")] demo::plugin, #[cfg(not(feature = "demo"))] - tiles::plugin, + grid::plugin, screens::plugin, theme::plugin, )); diff --git a/src/screens/gameplay.rs b/src/screens/gameplay.rs index da60175..b65ecc1 100644 --- a/src/screens/gameplay.rs +++ b/src/screens/gameplay.rs @@ -5,7 +5,7 @@ use bevy::{input::common_conditions::input_just_pressed, prelude::*}; #[cfg(feature = "demo")] use crate::demo::level::spawn_level as spawn_level_command; #[cfg(not(feature = "demo"))] -use crate::tiles::level::spawn_level as spawn_level_command; +use crate::grid::level::spawn_level as spawn_level_command; use crate::{asset_tracking::LoadResource, audio::Music, screens::Screen}; pub(super) fn plugin(app: &mut App) { diff --git a/src/tiles/mod.rs b/src/tiles/mod.rs deleted file mode 100644 index 841f267..0000000 --- a/src/tiles/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -use bevy::prelude::*; -pub mod level; -pub mod player; -pub mod tile; - -pub(super) fn plugin(app: &mut App) { - app.add_plugins((player::plugin, level::plugin)); -} diff --git a/src/tiles/player.rs b/src/tiles/player.rs deleted file mode 100644 index db13397..0000000 --- a/src/tiles/player.rs +++ /dev/null @@ -1,112 +0,0 @@ -use bevy::{ - color::palettes::css::BLUE, - ecs::{system::RunSystemOnce, world::Command}, - prelude::*, -}; -use hexx::{Hex, HexLayout, HexOrientation}; - -use crate::screens::Screen; - -use super::tile::{GridSettings, HexDirection, Tile}; - -pub(super) fn plugin(app: &mut App) { - app.register_type::(); - // app.add_systems(Update, move_player); -} - -#[derive(Debug)] -pub struct SpawnPlayer; - -impl Command for SpawnPlayer { - fn apply(self, world: &mut World) { - world.run_system_once(spawn_player); - } -} - -#[derive(Debug, Reflect, Component)] -#[reflect(Component)] -pub struct Player { - position: Hex, -} - -fn spawn_player(mut commands: Commands, grid_settings: Res) { - let starting_hex = Hex::ZERO; - let layout = HexLayout { - orientation: HexOrientation::Pointy, - origin: Vec2::ZERO, - hex_size: grid_settings.hex_size, - ..default() - }; - - let world_pos = layout.hex_to_world_pos(starting_hex); - - commands.spawn(( - Name::new("Player"), - SpriteBundle { - sprite: Sprite { - color: BLUE.into(), - custom_size: Some(grid_settings.hex_size * 0.8), - ..default() - }, - transform: Transform::from_translation(world_pos.extend(1.)), - ..default() - }, - Player { - position: starting_hex, - }, - StateScoped(Screen::Gameplay), - )); -} - -fn move_player( - input: Res>, - grid_settings: Res, - mut player_query: Query<&mut Player>, - tile_query: Query<&Tile>, -) { - let mut player = player_query.single_mut(); - - if let Some(direction) = get_move_direction(&input) { - let current_tile = tile_query - .iter() - .find(|tile| tile.position == player.position) - .unwrap(); - if current_tile.has_wall(&direction) { - return; - } - - let hexx_direction = direction.to_hexx_direction(); - player.position = player.position + hexx_direction; - - let layout = HexLayout { - orientation: HexOrientation::Pointy, - origin: Vec2::ZERO, - hex_size: grid_settings.hex_size, - ..default() - }; - - let world_pos = layout.hex_to_world_pos(player.position); - } -} - -fn get_move_direction(input: &Res>) -> Option { - if input.just_pressed(KeyCode::KeyW) { - return Some(HexDirection::Top); - } - if input.just_pressed(KeyCode::KeyE) { - return Some(HexDirection::TopRight); - } - if input.just_pressed(KeyCode::KeyD) { - return Some(HexDirection::BottomRight); - } - if input.just_pressed(KeyCode::KeyS) { - return Some(HexDirection::Bottom); - } - if input.just_pressed(KeyCode::KeyA) { - return Some(HexDirection::BottomLeft); - } - if input.just_pressed(KeyCode::KeyQ) { - return Some(HexDirection::TopLeft); - } - None -} diff --git a/src/tiles/tile.rs b/src/tiles/tile.rs deleted file mode 100644 index 98b067d..0000000 --- a/src/tiles/tile.rs +++ /dev/null @@ -1,183 +0,0 @@ -use bevy::{ - color::palettes::css::GRAY, - ecs::{system::RunSystemOnce, world::Command}, - prelude::*, - utils::hashbrown::{HashMap, HashSet}, -}; -use hexx::{EdgeDirection, Hex, HexLayout, HexOrientation}; -use rand::{seq::IteratorRandom, thread_rng}; - -pub(super) fn plugin(app: &mut App) { - app.add_systems(Startup, generate_maze); -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Reflect)] -pub enum HexDirection { - Top, - TopRight, - BottomRight, - Bottom, - BottomLeft, - TopLeft, -} - -#[derive(Debug)] -pub struct SpawnGrid; - -impl Command for SpawnGrid { - fn apply(self, world: &mut World) { - world.run_system_once(setup_hex_grid); - } -} - -impl HexDirection { - pub fn to_hexx_direction(self) -> EdgeDirection { - self.into() - } - - pub const ALL: [HexDirection; 6] = [ - Self::Top, - Self::TopRight, - Self::BottomRight, - Self::Bottom, - Self::BottomLeft, - Self::TopLeft, - ]; - - pub fn opposite(&self) -> Self { - match self { - Self::Top => Self::Bottom, - Self::TopRight => Self::BottomLeft, - Self::BottomRight => Self::TopLeft, - Self::Bottom => Self::Top, - Self::BottomLeft => Self::TopRight, - Self::TopLeft => Self::BottomRight, - } - } -} - -impl From for EdgeDirection { - fn from(value: HexDirection) -> Self { - match value { - HexDirection::Top => Self::FLAT_NORTH, - HexDirection::TopRight => Self::FLAT_NORTH_EAST, - HexDirection::BottomRight => Self::FLAT_SOUTH_EAST, - HexDirection::Bottom => Self::FLAT_SOUTH, - HexDirection::BottomLeft => Self::FLAT_SOUTH_WEST, - HexDirection::TopLeft => Self::FLAT_NORTH_WEST, - } - } -} - -#[derive(Debug, Reflect, Resource)] -#[reflect(Resource)] -pub struct GridSettings { - pub radius: u32, - pub hex_size: Vec2, -} - -#[derive(Debug, Clone, Reflect, Component)] -#[reflect(Component)] -pub struct Tile { - pub position: Hex, - pub walls: HashMap, -} - -impl Tile { - pub fn new(position: Hex) -> Self { - let mut walls = HashMap::new(); - for direction in HexDirection::ALL { - walls.insert(direction, true); - } - Self { position, walls } - } - - pub fn has_wall(&self, direction: &HexDirection) -> bool { - *self.walls.get(direction).unwrap_or(&false) - } - - pub fn remove_wall(&mut self, direction: HexDirection) { - self.walls.insert(direction, false); - } -} - -impl Default for GridSettings { - fn default() -> Self { - Self { - radius: 5, - hex_size: Vec2::splat(32.), - } - } -} - -pub fn setup_hex_grid(mut commands: Commands, grid_settings: Res) { - let GridSettings { radius, hex_size } = *grid_settings; - let layout = HexLayout { - orientation: HexOrientation::Pointy, - origin: Vec2::ZERO, - hex_size, - ..default() - }; - - let hexes = Hex::ZERO.range(radius); - - for hex in hexes { - let world_pos = layout.hex_to_world_pos(hex); - commands.spawn(( - SpriteBundle { - sprite: Sprite { - color: GRAY.into(), - custom_size: Some(hex_size), - ..default() - }, - transform: Transform::from_translation(world_pos.extend(0.)), - ..default() - }, - Tile::new(hex), - )); - } -} - -pub fn generate_maze(mut tile_query: Query<&mut Tile>, grid_settings: Res) { - let radius = grid_settings.radius; - let mut tiles = tile_query - .iter_mut() - .map(|tile| (tile.position, tile.clone())) - .collect::>(); - - let mut rng = thread_rng(); - let mut visited = HashSet::new(); - let mut stack = Vec::new(); - - let start_hex = Hex::ZERO; - visited.insert(start_hex); - stack.push(start_hex); - - while let Some(current_hex) = stack.pop() { - let mut unvisited_neighbors = Vec::new(); - for direction in HexDirection::ALL { - let neighbor_hex = current_hex + direction.to_hexx_direction(); - if neighbor_hex.distance_to(Hex::ZERO) > radius as i32 { - continue; - } - if !visited.contains(&neighbor_hex) { - unvisited_neighbors.push((neighbor_hex, direction)); - } - } - if !unvisited_neighbors.is_empty() { - stack.push(current_hex); - let &(neighbor_hex, direction) = unvisited_neighbors.iter().choose(&mut rng).unwrap(); - - if let Some(current_tile) = tiles.get_mut(¤t_hex) { - current_tile.remove_wall(direction); - } - - if let Some(neighbor_tile) = tiles.get_mut(&neighbor_hex) { - neighbor_tile.remove_wall(direction.opposite()); - } - - visited.insert(neighbor_hex); - stack.push(neighbor_hex); - } - } -}