mirror of
https://github.com/kristoferssolo/maze-ascension.git
synced 2025-10-21 19:20:34 +00:00
feat(grid): generate a hex grid
This commit is contained in:
parent
7db07f7e4d
commit
590c293dc2
51
src/grid/direction.rs
Normal file
51
src/grid/direction.rs
Normal file
@ -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<HexDirection> 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
94
src/grid/grid.rs
Normal file
94
src/grid/grid.rs
Normal file
@ -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<HexDirection, bool>,
|
||||
}
|
||||
|
||||
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<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<ColorMaterial>>,
|
||||
) {
|
||||
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))
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
8
src/grid/mod.rs
Normal file
8
src/grid/mod.rs
Normal file
@ -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);
|
||||
}
|
||||
@ -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,
|
||||
));
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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));
|
||||
}
|
||||
@ -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::<Player>();
|
||||
// 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<GridSettings>) {
|
||||
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<ButtonInput<KeyCode>>,
|
||||
grid_settings: Res<GridSettings>,
|
||||
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<ButtonInput<KeyCode>>) -> Option<HexDirection> {
|
||||
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
|
||||
}
|
||||
@ -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<HexDirection> 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<HexDirection, bool>,
|
||||
}
|
||||
|
||||
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<GridSettings>) {
|
||||
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<GridSettings>) {
|
||||
let radius = grid_settings.radius;
|
||||
let mut tiles = tile_query
|
||||
.iter_mut()
|
||||
.map(|tile| (tile.position, tile.clone()))
|
||||
.collect::<HashMap<Hex, Tile>>();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user