mirror of
https://github.com/kristoferssolo/maze-ascension.git
synced 2025-10-21 19:20:34 +00:00
feat(hex): generate a hex grid
This commit is contained in:
parent
3f0ba6d0d8
commit
0a64641941
12
Cargo.lock
generated
12
Cargo.lock
generated
@ -2137,6 +2137,17 @@ version = "0.2.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
|
checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hexx"
|
||||||
|
version = "0.18.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8c40cfb11c06c0b7051c2c0df030c57c65921db962ee2b8e89de218bb749f173"
|
||||||
|
dependencies = [
|
||||||
|
"bevy_reflect",
|
||||||
|
"glam",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "image"
|
name = "image"
|
||||||
version = "0.25.2"
|
version = "0.25.2"
|
||||||
@ -3600,6 +3611,7 @@ name = "the-labyrinth-of-echoes"
|
|||||||
version = "0.0.3"
|
version = "0.0.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bevy",
|
"bevy",
|
||||||
|
"hexx",
|
||||||
"log",
|
"log",
|
||||||
"rand",
|
"rand",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
|||||||
@ -17,6 +17,7 @@ tracing = { version = "0.1", features = [
|
|||||||
"max_level_debug",
|
"max_level_debug",
|
||||||
"release_max_level_warn",
|
"release_max_level_warn",
|
||||||
] }
|
] }
|
||||||
|
hexx = { version = "0.18", features = ["bevy_reflect"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = [
|
default = [
|
||||||
@ -35,6 +36,7 @@ dev_native = [
|
|||||||
# Enable embedded asset hot reloading for native dev builds.
|
# Enable embedded asset hot reloading for native dev builds.
|
||||||
"bevy/embedded_watcher",
|
"bevy/embedded_watcher",
|
||||||
]
|
]
|
||||||
|
demo = []
|
||||||
|
|
||||||
|
|
||||||
# Idiomatic Bevy code often triggers these lints, and the CI workflow treats them as errors.
|
# Idiomatic Bevy code often triggers these lints, and the CI workflow treats them as errors.
|
||||||
|
|||||||
@ -1,10 +1,13 @@
|
|||||||
mod asset_tracking;
|
mod asset_tracking;
|
||||||
pub mod audio;
|
pub mod audio;
|
||||||
|
#[cfg(feature = "demo")]
|
||||||
mod demo;
|
mod demo;
|
||||||
#[cfg(feature = "dev")]
|
#[cfg(feature = "dev")]
|
||||||
mod dev_tools;
|
mod dev_tools;
|
||||||
mod screens;
|
mod screens;
|
||||||
mod theme;
|
mod theme;
|
||||||
|
#[cfg(not(feature = "demo"))]
|
||||||
|
mod tiles;
|
||||||
|
|
||||||
use bevy::{
|
use bevy::{
|
||||||
asset::AssetMetaCheck,
|
asset::AssetMetaCheck,
|
||||||
@ -57,7 +60,10 @@ impl Plugin for AppPlugin {
|
|||||||
// Add other plugins.
|
// Add other plugins.
|
||||||
app.add_plugins((
|
app.add_plugins((
|
||||||
asset_tracking::plugin,
|
asset_tracking::plugin,
|
||||||
|
#[cfg(feature = "demo")]
|
||||||
demo::plugin,
|
demo::plugin,
|
||||||
|
#[cfg(not(feature = "demo"))]
|
||||||
|
tiles::plugin,
|
||||||
screens::plugin,
|
screens::plugin,
|
||||||
theme::plugin,
|
theme::plugin,
|
||||||
));
|
));
|
||||||
|
|||||||
@ -2,10 +2,11 @@
|
|||||||
|
|
||||||
use bevy::{input::common_conditions::input_just_pressed, prelude::*};
|
use bevy::{input::common_conditions::input_just_pressed, prelude::*};
|
||||||
|
|
||||||
use crate::{
|
#[cfg(feature = "demo")]
|
||||||
asset_tracking::LoadResource, audio::Music, demo::level::spawn_level as spawn_level_command,
|
use crate::demo::level::spawn_level as spawn_level_command;
|
||||||
screens::Screen,
|
#[cfg(not(feature = "demo"))]
|
||||||
};
|
use crate::tiles::level::spawn_level as spawn_level_command;
|
||||||
|
use crate::{asset_tracking::LoadResource, audio::Music, screens::Screen};
|
||||||
|
|
||||||
pub(super) fn plugin(app: &mut App) {
|
pub(super) fn plugin(app: &mut App) {
|
||||||
app.add_systems(OnEnter(Screen::Gameplay), spawn_level);
|
app.add_systems(OnEnter(Screen::Gameplay), spawn_level);
|
||||||
|
|||||||
@ -4,7 +4,6 @@
|
|||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
demo::player::PlayerAssets,
|
|
||||||
screens::{credits::CreditsMusic, gameplay::GameplayMusic, Screen},
|
screens::{credits::CreditsMusic, gameplay::GameplayMusic, Screen},
|
||||||
theme::{interaction::InteractionAssets, prelude::*},
|
theme::{interaction::InteractionAssets, prelude::*},
|
||||||
};
|
};
|
||||||
@ -35,13 +34,9 @@ fn continue_to_title_screen(mut next_screen: ResMut<NextState<Screen>>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn all_assets_loaded(
|
fn all_assets_loaded(
|
||||||
player_assets: Option<Res<PlayerAssets>>,
|
|
||||||
interaction_assets: Option<Res<InteractionAssets>>,
|
interaction_assets: Option<Res<InteractionAssets>>,
|
||||||
credits_music: Option<Res<CreditsMusic>>,
|
credits_music: Option<Res<CreditsMusic>>,
|
||||||
gameplay_music: Option<Res<GameplayMusic>>,
|
gameplay_music: Option<Res<GameplayMusic>>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
player_assets.is_some()
|
interaction_assets.is_some() && credits_music.is_some() && gameplay_music.is_some()
|
||||||
&& interaction_assets.is_some()
|
|
||||||
&& credits_music.is_some()
|
|
||||||
&& gameplay_music.is_some()
|
|
||||||
}
|
}
|
||||||
|
|||||||
20
src/tiles/level.rs
Normal file
20
src/tiles/level.rs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
//! Spawn the main level.
|
||||||
|
|
||||||
|
use bevy::{ecs::world::Command, prelude::*};
|
||||||
|
|
||||||
|
use crate::tiles::player::SpawnPlayer;
|
||||||
|
|
||||||
|
use super::tile::{self, GridSettings, SpawnGrid};
|
||||||
|
|
||||||
|
pub(super) fn plugin(app: &mut App) {
|
||||||
|
app.insert_resource(GridSettings::default());
|
||||||
|
app.add_plugins(tile::plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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/tiles/mod.rs
Normal file
8
src/tiles/mod.rs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
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));
|
||||||
|
}
|
||||||
112
src/tiles/player.rs
Normal file
112
src/tiles/player.rs
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
183
src/tiles/tile.rs
Normal file
183
src/tiles/tile.rs
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
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