mirror of
https://github.com/kristoferssolo/maze-ascension.git
synced 2025-10-21 19:20:34 +00:00
Merge pull request #9 from kristoferssolo/feature/event-based-spawns
Feature/event based spawns
This commit is contained in:
commit
e65790c743
7
Cargo.lock
generated
7
Cargo.lock
generated
@ -176,6 +176,12 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyhow"
|
||||||
|
version = "1.0.94"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "approx"
|
name = "approx"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
@ -3079,6 +3085,7 @@ dependencies = [
|
|||||||
name = "maze-ascension"
|
name = "maze-ascension"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
"bevy",
|
"bevy",
|
||||||
"bevy-inspector-egui",
|
"bevy-inspector-egui",
|
||||||
"bevy_egui",
|
"bevy_egui",
|
||||||
|
|||||||
@ -22,6 +22,7 @@ hexlab = { version = "0.3", features = ["bevy"] }
|
|||||||
bevy-inspector-egui = { version = "0.28", optional = true }
|
bevy-inspector-egui = { version = "0.28", optional = true }
|
||||||
bevy_egui = { version = "0.31", optional = true }
|
bevy_egui = { version = "0.31", optional = true }
|
||||||
thiserror = "2.0"
|
thiserror = "2.0"
|
||||||
|
anyhow = "1"
|
||||||
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
maze::{events::RecreateMazeEvent, MazeConfig, MazePluginLoaded},
|
floor::components::{CurrentFloor, Floor},
|
||||||
player::events::RespawnPlayer,
|
maze::{components::MazeConfig, events::MazeEvent, GlobalMazeConfig, MazePluginLoaded},
|
||||||
|
player::events::PlayerEvent,
|
||||||
};
|
};
|
||||||
use bevy::{prelude::*, window::PrimaryWindow};
|
use bevy::{prelude::*, window::PrimaryWindow};
|
||||||
use bevy_egui::{
|
use bevy_egui::{
|
||||||
@ -22,42 +23,50 @@ pub(crate) fn maze_controls_ui(world: &mut World) {
|
|||||||
else {
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut egui_context = egui_context.clone();
|
let mut egui_context = egui_context.clone();
|
||||||
|
|
||||||
|
let Ok((maze_config, floor)) = world
|
||||||
|
.query_filtered::<(&MazeConfig, &Floor), With<CurrentFloor>>()
|
||||||
|
.get_single(world)
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let mut maze_config = maze_config.clone();
|
||||||
|
let floor_value = floor.0;
|
||||||
|
|
||||||
|
let mut changed = false;
|
||||||
|
|
||||||
egui::Window::new("Maze Controls").show(egui_context.get_mut(), |ui| {
|
egui::Window::new("Maze Controls").show(egui_context.get_mut(), |ui| {
|
||||||
if let Some(mut maze_config) = world.get_resource_mut::<MazeConfig>() {
|
if let Some(mut global_config) = world.get_resource_mut::<GlobalMazeConfig>() {
|
||||||
let mut changed = false;
|
|
||||||
ui.heading("Maze Configuration");
|
ui.heading("Maze Configuration");
|
||||||
|
|
||||||
changed |= add_seed_control(ui, &mut maze_config.seed);
|
changed |= add_seed_control(ui, &mut maze_config.seed);
|
||||||
|
|
||||||
changed |= add_drag_value_control(ui, "Radius:", &mut maze_config.radius, 1.0, 1..=100);
|
changed |= add_drag_value_control(ui, "Radius:", &mut maze_config.radius, 1.0, 1..=100);
|
||||||
changed |=
|
changed |=
|
||||||
add_drag_value_control(ui, "Height:", &mut maze_config.height, 0.5, 1.0..=50.0);
|
add_drag_value_control(ui, "Height:", &mut global_config.height, 0.5, 1.0..=50.0);
|
||||||
changed |= add_drag_value_control(
|
changed |= add_drag_value_control(
|
||||||
ui,
|
ui,
|
||||||
"Hex Size:",
|
"Hex Size:",
|
||||||
&mut maze_config.hex_size,
|
&mut global_config.hex_size,
|
||||||
1.0,
|
1.0,
|
||||||
1.0..=100.0,
|
1.0..=100.0,
|
||||||
);
|
);
|
||||||
|
|
||||||
changed |= add_orientation_control(ui, &mut maze_config.layout.orientation);
|
changed |= add_orientation_control(ui, &mut maze_config.layout.orientation);
|
||||||
|
|
||||||
changed |= add_position_control(ui, "Start Position:", &mut maze_config.start_pos);
|
changed |= add_position_control(ui, "Start Position:", &mut maze_config.start_pos);
|
||||||
changed |= add_position_control(ui, "End Position:", &mut maze_config.end_pos);
|
changed |= add_position_control(ui, "End Position:", &mut maze_config.end_pos);
|
||||||
|
|
||||||
// Trigger recreation if any value changed
|
// Handle updates
|
||||||
if changed {
|
if changed {
|
||||||
maze_config.update();
|
maze_config.update(&global_config);
|
||||||
if let Some(mut event_writer) =
|
|
||||||
world.get_resource_mut::<Events<RecreateMazeEvent>>()
|
if let Some(mut event_writer) = world.get_resource_mut::<Events<MazeEvent>>() {
|
||||||
{
|
event_writer.send(MazeEvent::Recreate {
|
||||||
event_writer.send(RecreateMazeEvent { floor: 1 });
|
floor: floor_value,
|
||||||
|
config: maze_config,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if let Some(mut event_writer) = world.get_resource_mut::<Events<RespawnPlayer>>() {
|
if let Some(mut event_writer) = world.get_resource_mut::<Events<PlayerEvent>>() {
|
||||||
event_writer.send(RespawnPlayer);
|
event_writer.send(PlayerEvent::Respawn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
19
src/floor/components.rs
Normal file
19
src/floor/components.rs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Reflect, Component)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
pub struct Floor(pub u8);
|
||||||
|
|
||||||
|
#[derive(Debug, Reflect, Component)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
pub struct TargetFloor(pub u8);
|
||||||
|
|
||||||
|
#[derive(Debug, Reflect, Component)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
pub struct CurrentFloor;
|
||||||
|
|
||||||
|
impl Default for Floor {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
6
src/floor/mod.rs
Normal file
6
src/floor/mod.rs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
pub mod components;
|
||||||
|
mod systems;
|
||||||
|
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
pub(super) fn plugin(app: &mut App) {}
|
||||||
1
src/floor/systems/mod.rs
Normal file
1
src/floor/systems/mod.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
mod spawn;
|
||||||
0
src/floor/systems/spawn.rs
Normal file
0
src/floor/systems/spawn.rs
Normal file
@ -2,8 +2,9 @@ mod asset_tracking;
|
|||||||
pub mod audio;
|
pub mod audio;
|
||||||
#[cfg(feature = "dev")]
|
#[cfg(feature = "dev")]
|
||||||
mod dev_tools;
|
mod dev_tools;
|
||||||
mod maze;
|
pub mod floor;
|
||||||
mod player;
|
pub mod maze;
|
||||||
|
pub mod player;
|
||||||
mod screens;
|
mod screens;
|
||||||
pub mod theme;
|
pub mod theme;
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use super::MazeConfig;
|
use super::resources::GlobalMazeConfig;
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use std::f32::consts::FRAC_PI_2;
|
use std::f32::consts::FRAC_PI_2;
|
||||||
|
|
||||||
@ -17,13 +17,16 @@ impl MazeAssets {
|
|||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
meshes: &mut ResMut<Assets<Mesh>>,
|
meshes: &mut ResMut<Assets<Mesh>>,
|
||||||
materials: &mut ResMut<Assets<StandardMaterial>>,
|
materials: &mut ResMut<Assets<StandardMaterial>>,
|
||||||
config: &MazeConfig,
|
global_config: &GlobalMazeConfig,
|
||||||
) -> MazeAssets {
|
) -> MazeAssets {
|
||||||
MazeAssets {
|
MazeAssets {
|
||||||
hex_mesh: meshes.add(generate_hex_mesh(config.hex_size, config.height)),
|
hex_mesh: meshes.add(generate_hex_mesh(
|
||||||
|
global_config.hex_size,
|
||||||
|
global_config.height,
|
||||||
|
)),
|
||||||
wall_mesh: meshes.add(generate_square_mesh(
|
wall_mesh: meshes.add(generate_square_mesh(
|
||||||
config.hex_size + config.wall_size() / WALL_OVERLAP_MODIFIER,
|
global_config.hex_size + global_config.wall_size() / WALL_OVERLAP_MODIFIER,
|
||||||
config.wall_size(),
|
global_config.wall_size(),
|
||||||
)),
|
)),
|
||||||
hex_material: materials.add(white_material()),
|
hex_material: materials.add(white_material()),
|
||||||
wall_material: materials.add(Color::BLACK),
|
wall_material: materials.add(Color::BLACK),
|
||||||
|
|||||||
@ -1,18 +1,90 @@
|
|||||||
|
use crate::floor::components::Floor;
|
||||||
|
|
||||||
|
use super::{errors::MazeConfigError, GlobalMazeConfig};
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use hexlab::HexMaze;
|
use hexlab::HexMaze;
|
||||||
|
use hexx::{Hex, HexLayout, HexOrientation};
|
||||||
|
use rand::{rngs::StdRng, thread_rng, Rng, SeedableRng};
|
||||||
|
|
||||||
#[derive(Debug, Reflect, Component)]
|
#[derive(Debug, Reflect, Component)]
|
||||||
#[reflect(Component)]
|
#[reflect(Component)]
|
||||||
pub(crate) struct Maze(pub(crate) HexMaze);
|
#[require(MazeConfig, Floor)]
|
||||||
|
pub struct Maze(pub HexMaze);
|
||||||
|
|
||||||
#[derive(Debug, Reflect, Component)]
|
#[derive(Debug, Reflect, Component)]
|
||||||
#[reflect(Component)]
|
#[reflect(Component)]
|
||||||
pub(crate) struct Floor(pub(crate) u8);
|
pub struct Tile;
|
||||||
|
|
||||||
#[derive(Debug, Reflect, Component)]
|
#[derive(Debug, Reflect, Component)]
|
||||||
#[reflect(Component)]
|
#[reflect(Component)]
|
||||||
pub(crate) struct Tile;
|
pub struct Wall;
|
||||||
|
|
||||||
#[derive(Debug, Reflect, Component)]
|
#[derive(Debug, Reflect, Component, Clone)]
|
||||||
#[reflect(Component)]
|
#[reflect(Component)]
|
||||||
pub(crate) struct Wall;
|
pub struct MazeConfig {
|
||||||
|
pub radius: u32,
|
||||||
|
pub start_pos: Hex,
|
||||||
|
pub end_pos: Hex,
|
||||||
|
pub seed: u64,
|
||||||
|
pub layout: HexLayout,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MazeConfig {
|
||||||
|
fn new(
|
||||||
|
radius: u32,
|
||||||
|
orientation: HexOrientation,
|
||||||
|
seed: Option<u64>,
|
||||||
|
global_conig: &GlobalMazeConfig,
|
||||||
|
) -> Result<Self, MazeConfigError> {
|
||||||
|
let seed = seed.unwrap_or_else(|| thread_rng().gen());
|
||||||
|
let mut rng = StdRng::seed_from_u64(seed);
|
||||||
|
|
||||||
|
let start_pos = generate_pos(radius, &mut rng)?;
|
||||||
|
let end_pos = generate_pos(radius, &mut rng)?;
|
||||||
|
|
||||||
|
info!("Start pos: (q={}, r={})", start_pos.x, start_pos.y);
|
||||||
|
info!("End pos: (q={}, r={})", end_pos.x, end_pos.y);
|
||||||
|
|
||||||
|
let layout = HexLayout {
|
||||||
|
orientation,
|
||||||
|
hex_size: Vec2::splat(global_conig.hex_size),
|
||||||
|
..default()
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
radius,
|
||||||
|
start_pos,
|
||||||
|
end_pos,
|
||||||
|
seed,
|
||||||
|
layout,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_unchecked(
|
||||||
|
radius: u32,
|
||||||
|
orientation: HexOrientation,
|
||||||
|
seed: Option<u64>,
|
||||||
|
global_conig: &GlobalMazeConfig,
|
||||||
|
) -> Self {
|
||||||
|
Self::new(radius, orientation, seed, global_conig)
|
||||||
|
.expect("Failed to create MazeConfig with supposedly safe values")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self, global_conig: &GlobalMazeConfig) {
|
||||||
|
self.layout.hex_size = Vec2::splat(global_conig.hex_size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for MazeConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new_unchecked(7, HexOrientation::Flat, None, &GlobalMazeConfig::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_pos<R: Rng>(radius: u32, rng: &mut R) -> Result<Hex, MazeConfigError> {
|
||||||
|
let radius = i32::try_from(radius)?;
|
||||||
|
Ok(Hex::new(
|
||||||
|
rng.gen_range(-radius..radius),
|
||||||
|
rng.gen_range(-radius..radius),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|||||||
38
src/maze/errors.rs
Normal file
38
src/maze/errors.rs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
use std::num::TryFromIntError;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum MazeConfigError {
|
||||||
|
#[error("Failed to convert radius from u32 to i32: {0}")]
|
||||||
|
RadiusConverions(#[from] TryFromIntError),
|
||||||
|
#[error("Invalid maze configuration: {0}")]
|
||||||
|
InvalidConfig(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum MazeError {
|
||||||
|
#[error("Floor {0} not found")]
|
||||||
|
FloorNotFound(u8),
|
||||||
|
#[error("Failed to generate maze with config: {radius}, seed: {seed}")]
|
||||||
|
GenerationFailed { radius: u32, seed: u64 },
|
||||||
|
#[error("Invalid tile entity: {0:?}")]
|
||||||
|
TileNotFound(bevy::prelude::Entity),
|
||||||
|
#[error("Failed to create maze assets")]
|
||||||
|
AssetCreationFailed,
|
||||||
|
#[error("Invalid maze configuration: {0}")]
|
||||||
|
ConfigurationError(String),
|
||||||
|
#[error(transparent)]
|
||||||
|
Other(#[from] anyhow::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type MazeResult<T> = Result<T, MazeError>;
|
||||||
|
|
||||||
|
impl MazeError {
|
||||||
|
pub fn config_error(msg: impl Into<String>) -> Self {
|
||||||
|
Self::ConfigurationError(msg.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generation_failed(radius: u32, seed: u64) -> Self {
|
||||||
|
Self::GenerationFailed { radius, seed }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,10 @@
|
|||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
use super::components::MazeConfig;
|
||||||
|
|
||||||
#[derive(Debug, Event)]
|
#[derive(Debug, Event)]
|
||||||
pub(crate) struct RecreateMazeEvent {
|
pub enum MazeEvent {
|
||||||
pub(crate) floor: u8,
|
Create { floor: u8, config: MazeConfig },
|
||||||
|
Recreate { floor: u8, config: MazeConfig },
|
||||||
|
Remove { floor: u8 },
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,20 +1,23 @@
|
|||||||
use bevy::{ecs::system::RunSystemOnce, prelude::*};
|
|
||||||
use events::RecreateMazeEvent;
|
|
||||||
mod assets;
|
mod assets;
|
||||||
pub mod components;
|
pub mod components;
|
||||||
|
pub mod errors;
|
||||||
pub mod events;
|
pub mod events;
|
||||||
mod resources;
|
pub mod resources;
|
||||||
mod systems;
|
mod systems;
|
||||||
|
|
||||||
pub use resources::{MazeConfig, MazePluginLoaded};
|
use bevy::{ecs::system::RunSystemOnce, prelude::*};
|
||||||
|
use components::Maze;
|
||||||
|
use events::MazeEvent;
|
||||||
|
pub use resources::{GlobalMazeConfig, MazePluginLoaded};
|
||||||
|
|
||||||
pub(super) fn plugin(app: &mut App) {
|
pub(super) fn plugin(app: &mut App) {
|
||||||
app.init_resource::<MazeConfig>()
|
app.init_resource::<GlobalMazeConfig>()
|
||||||
.add_event::<RecreateMazeEvent>()
|
.add_event::<MazeEvent>()
|
||||||
|
.register_type::<Maze>()
|
||||||
.add_plugins(systems::plugin);
|
.add_plugins(systems::plugin);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn spawn_level_command(world: &mut World) {
|
pub fn spawn_level_command(world: &mut World) {
|
||||||
world.insert_resource(MazePluginLoaded);
|
|
||||||
let _ = world.run_system_once(systems::setup::setup);
|
let _ = world.run_system_once(systems::setup::setup);
|
||||||
|
world.insert_resource(MazePluginLoaded);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,76 +1,18 @@
|
|||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use hexx::{Hex, HexLayout, HexOrientation};
|
|
||||||
use rand::{rngs::StdRng, thread_rng, Rng, SeedableRng};
|
|
||||||
use std::num::TryFromIntError;
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Reflect, Resource)]
|
#[derive(Debug, Default, Reflect, Resource)]
|
||||||
#[reflect(Resource)]
|
#[reflect(Resource)]
|
||||||
pub struct MazePluginLoaded;
|
pub struct MazePluginLoaded;
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Reflect, Resource, Clone)]
|
||||||
pub enum MazeConfigError {
|
|
||||||
#[error("Failed to convert radius from u32 to i32: {0}")]
|
|
||||||
RadiusConverions(#[from] TryFromIntError),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Reflect, Resource)]
|
|
||||||
#[reflect(Resource)]
|
#[reflect(Resource)]
|
||||||
pub struct MazeConfig {
|
pub struct GlobalMazeConfig {
|
||||||
pub radius: u32,
|
|
||||||
pub height: f32,
|
|
||||||
pub hex_size: f32,
|
pub hex_size: f32,
|
||||||
pub start_pos: Hex,
|
pub wall_thickness: f32,
|
||||||
pub end_pos: Hex,
|
pub height: f32,
|
||||||
pub seed: u64,
|
|
||||||
pub layout: HexLayout,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MazeConfig {
|
impl GlobalMazeConfig {
|
||||||
fn new(
|
|
||||||
radius: u32,
|
|
||||||
height: f32,
|
|
||||||
hex_size: f32,
|
|
||||||
orientation: HexOrientation,
|
|
||||||
seed: Option<u64>,
|
|
||||||
) -> Result<Self, MazeConfigError> {
|
|
||||||
let seed = seed.unwrap_or_else(|| thread_rng().gen());
|
|
||||||
let mut rng = StdRng::seed_from_u64(seed);
|
|
||||||
|
|
||||||
let start_pos = generate_pos(radius, &mut rng)?;
|
|
||||||
let end_pos = generate_pos(radius, &mut rng)?;
|
|
||||||
|
|
||||||
info!("Start pos: (q={}, r={})", start_pos.x, start_pos.y);
|
|
||||||
info!("End pos: (q={}, r={})", end_pos.x, end_pos.y);
|
|
||||||
|
|
||||||
let layout = HexLayout {
|
|
||||||
orientation,
|
|
||||||
hex_size: Vec2::splat(hex_size),
|
|
||||||
..default()
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
radius,
|
|
||||||
height,
|
|
||||||
hex_size,
|
|
||||||
start_pos,
|
|
||||||
end_pos,
|
|
||||||
seed,
|
|
||||||
layout,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_unchecked(
|
|
||||||
radius: u32,
|
|
||||||
height: f32,
|
|
||||||
hex_size: f32,
|
|
||||||
orientation: HexOrientation,
|
|
||||||
seed: Option<u64>,
|
|
||||||
) -> Self {
|
|
||||||
Self::new(radius, height, hex_size, orientation, seed)
|
|
||||||
.expect("Failed to create MazeConfig with supposedly safe values")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn wall_size(&self) -> f32 {
|
pub fn wall_size(&self) -> f32 {
|
||||||
self.hex_size / 6.
|
self.hex_size / 6.
|
||||||
}
|
}
|
||||||
@ -78,22 +20,14 @@ impl MazeConfig {
|
|||||||
pub fn wall_offset(&self) -> f32 {
|
pub fn wall_offset(&self) -> f32 {
|
||||||
self.hex_size - self.wall_size()
|
self.hex_size - self.wall_size()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&mut self) {
|
|
||||||
self.layout.hex_size = Vec2::splat(self.hex_size);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for MazeConfig {
|
impl Default for GlobalMazeConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new_unchecked(7, 20., 6., HexOrientation::Flat, None)
|
Self {
|
||||||
|
hex_size: 6.,
|
||||||
|
wall_thickness: 1.,
|
||||||
|
height: 20.,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_pos<R: Rng>(radius: u32, rng: &mut R) -> Result<Hex, MazeConfigError> {
|
|
||||||
let radius = i32::try_from(radius)?;
|
|
||||||
Ok(Hex::new(
|
|
||||||
rng.gen_range(-radius..radius),
|
|
||||||
rng.gen_range(-radius..radius),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|||||||
14
src/maze/systems/common.rs
Normal file
14
src/maze/systems/common.rs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
use crate::maze::{
|
||||||
|
components::MazeConfig,
|
||||||
|
errors::{MazeError, MazeResult},
|
||||||
|
};
|
||||||
|
use hexlab::{GeneratorType, HexMaze, MazeBuilder};
|
||||||
|
|
||||||
|
pub(super) fn generate_maze(config: &MazeConfig) -> MazeResult<HexMaze> {
|
||||||
|
MazeBuilder::new()
|
||||||
|
.with_radius(config.radius)
|
||||||
|
.with_seed(config.seed)
|
||||||
|
.with_generator(GeneratorType::RecursiveBacktracking)
|
||||||
|
.build()
|
||||||
|
.map_err(|_| MazeError::generation_failed(config.radius, config.seed))
|
||||||
|
}
|
||||||
@ -1,14 +0,0 @@
|
|||||||
use crate::maze::components::Floor;
|
|
||||||
use bevy::prelude::*;
|
|
||||||
|
|
||||||
pub(crate) fn despawn_floor(
|
|
||||||
commands: &mut Commands,
|
|
||||||
query: &Query<(Entity, &Floor)>,
|
|
||||||
floor_num: u8,
|
|
||||||
) {
|
|
||||||
for (entity, floor) in query.iter() {
|
|
||||||
if floor.0 == floor_num {
|
|
||||||
commands.entity(entity).despawn_recursive();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
54
src/maze/systems/event_handler.rs
Normal file
54
src/maze/systems/event_handler.rs
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
use super::{spawn::spawn_floor, update::update_floor};
|
||||||
|
use crate::{
|
||||||
|
floor::components::Floor,
|
||||||
|
maze::{components::Maze, events::MazeEvent, GlobalMazeConfig},
|
||||||
|
};
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
pub(crate) fn handle_maze_events(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut meshes: ResMut<Assets<Mesh>>,
|
||||||
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||||
|
mut event_reader: EventReader<MazeEvent>,
|
||||||
|
mut maze_query: Query<(Entity, &Floor, &mut Maze)>,
|
||||||
|
global_config: Res<GlobalMazeConfig>,
|
||||||
|
) {
|
||||||
|
for event in event_reader.read() {
|
||||||
|
match event {
|
||||||
|
MazeEvent::Create { floor, config } => {
|
||||||
|
if maze_query.iter().any(|(_, f, _)| f.0 == *floor) {
|
||||||
|
warn!("Floor {} already exists, skipping creation", floor);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
spawn_floor(
|
||||||
|
&mut commands,
|
||||||
|
&mut meshes,
|
||||||
|
&mut materials,
|
||||||
|
*floor,
|
||||||
|
config,
|
||||||
|
&global_config,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
MazeEvent::Recreate { floor, config } => {
|
||||||
|
let result = update_floor(
|
||||||
|
&mut commands,
|
||||||
|
&mut meshes,
|
||||||
|
&mut materials,
|
||||||
|
&mut maze_query,
|
||||||
|
*floor,
|
||||||
|
config,
|
||||||
|
&global_config,
|
||||||
|
);
|
||||||
|
if let Err(e) = result {
|
||||||
|
warn!("Failed to update floor {}: {}", floor, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MazeEvent::Remove { floor } => {
|
||||||
|
match maze_query.iter().find(|(_, f, _)| f.0 == *floor) {
|
||||||
|
Some((entity, _, _)) => commands.entity(entity).despawn_recursive(),
|
||||||
|
_ => warn!("Floor {} not found for removal", floor),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,11 +1,12 @@
|
|||||||
pub mod despawn;
|
pub mod common;
|
||||||
pub mod recreation;
|
pub mod event_handler;
|
||||||
pub mod setup;
|
pub mod setup;
|
||||||
pub mod spawn;
|
pub mod spawn;
|
||||||
|
pub mod update;
|
||||||
|
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use recreation::recreate_maze;
|
use event_handler::handle_maze_events;
|
||||||
|
|
||||||
pub(super) fn plugin(app: &mut App) {
|
pub(super) fn plugin(app: &mut App) {
|
||||||
app.add_systems(Update, recreate_maze);
|
app.add_systems(Update, handle_maze_events);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,19 +0,0 @@
|
|||||||
use bevy::prelude::*;
|
|
||||||
|
|
||||||
use crate::maze::{components::Floor, events::RecreateMazeEvent, MazeConfig};
|
|
||||||
|
|
||||||
use super::{despawn::despawn_floor, spawn::spawn_floor};
|
|
||||||
|
|
||||||
pub(crate) fn recreate_maze(
|
|
||||||
mut commands: Commands,
|
|
||||||
mut meshes: ResMut<Assets<Mesh>>,
|
|
||||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
|
||||||
config: Res<MazeConfig>,
|
|
||||||
query: Query<(Entity, &Floor)>,
|
|
||||||
mut event_reader: EventReader<RecreateMazeEvent>,
|
|
||||||
) {
|
|
||||||
for event in event_reader.read() {
|
|
||||||
despawn_floor(&mut commands, &query, event.floor);
|
|
||||||
spawn_floor(&mut commands, &mut meshes, &mut materials, &config);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,12 +1,7 @@
|
|||||||
use super::spawn::spawn_floor;
|
use crate::maze::{components::MazeConfig, events::MazeEvent};
|
||||||
use crate::maze::MazeConfig;
|
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
|
||||||
pub(crate) fn setup(
|
pub(crate) fn setup(mut event_writer: EventWriter<MazeEvent>) {
|
||||||
mut commands: Commands,
|
let config = MazeConfig::default();
|
||||||
mut meshes: ResMut<Assets<Mesh>>,
|
event_writer.send(MazeEvent::Create { floor: 1, config });
|
||||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
|
||||||
config: Res<MazeConfig>,
|
|
||||||
) {
|
|
||||||
spawn_floor(&mut commands, &mut meshes, &mut materials, &config);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,50 +1,68 @@
|
|||||||
use crate::maze::{
|
use crate::{
|
||||||
assets::MazeAssets,
|
floor::components::{CurrentFloor, Floor},
|
||||||
components::{Floor, Maze, Tile, Wall},
|
maze::{
|
||||||
MazeConfig,
|
assets::MazeAssets,
|
||||||
|
components::{Maze, MazeConfig, Tile, Wall},
|
||||||
|
resources::GlobalMazeConfig,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use hexlab::prelude::*;
|
use hexlab::prelude::*;
|
||||||
use hexx::HexOrientation;
|
use hexx::HexOrientation;
|
||||||
use std::f32::consts::{FRAC_PI_2, FRAC_PI_3, FRAC_PI_6};
|
use std::f32::consts::{FRAC_PI_2, FRAC_PI_3, FRAC_PI_6};
|
||||||
|
|
||||||
|
use super::common::generate_maze;
|
||||||
|
|
||||||
pub(super) fn spawn_floor(
|
pub(super) fn spawn_floor(
|
||||||
commands: &mut Commands,
|
commands: &mut Commands,
|
||||||
meshes: &mut ResMut<Assets<Mesh>>,
|
meshes: &mut ResMut<Assets<Mesh>>,
|
||||||
materials: &mut ResMut<Assets<StandardMaterial>>,
|
materials: &mut ResMut<Assets<StandardMaterial>>,
|
||||||
config: &MazeConfig,
|
floor: u8,
|
||||||
|
maze_config: &MazeConfig,
|
||||||
|
global_config: &GlobalMazeConfig,
|
||||||
) {
|
) {
|
||||||
let maze = MazeBuilder::new()
|
let maze = generate_maze(maze_config).expect("Failed to generate maze during spawn");
|
||||||
.with_radius(config.radius)
|
|
||||||
.with_seed(config.seed)
|
|
||||||
.with_generator(GeneratorType::RecursiveBacktracking)
|
|
||||||
.build()
|
|
||||||
.expect("Something went wrong while creating maze");
|
|
||||||
|
|
||||||
let assets = MazeAssets::new(meshes, materials, config);
|
let entity = commands
|
||||||
commands
|
|
||||||
.spawn((
|
.spawn((
|
||||||
Name::new("Floor"),
|
Name::new(format!("Floor {}", floor)),
|
||||||
Maze(maze.clone()),
|
Maze(maze.clone()),
|
||||||
Floor(1),
|
Floor(floor),
|
||||||
|
CurrentFloor, // TODO: remove
|
||||||
|
maze_config.clone(),
|
||||||
Transform::from_translation(Vec3::ZERO),
|
Transform::from_translation(Vec3::ZERO),
|
||||||
Visibility::Visible,
|
Visibility::Visible,
|
||||||
))
|
))
|
||||||
.with_children(|parent| {
|
.id();
|
||||||
for tile in maze.values() {
|
|
||||||
spawn_single_hex_tile(parent, &assets, tile, config)
|
let assets = MazeAssets::new(meshes, materials, global_config);
|
||||||
}
|
spawn_maze_tiles(commands, entity, &maze, &assets, maze_config, global_config);
|
||||||
});
|
}
|
||||||
|
|
||||||
|
pub(super) fn spawn_maze_tiles(
|
||||||
|
commands: &mut Commands,
|
||||||
|
parent_entity: Entity,
|
||||||
|
maze: &HexMaze,
|
||||||
|
assets: &MazeAssets,
|
||||||
|
maze_config: &MazeConfig,
|
||||||
|
global_config: &GlobalMazeConfig,
|
||||||
|
) {
|
||||||
|
commands.entity(parent_entity).with_children(|parent| {
|
||||||
|
for tile in maze.values() {
|
||||||
|
spawn_single_hex_tile(parent, assets, tile, maze_config, global_config);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn spawn_single_hex_tile(
|
pub(super) fn spawn_single_hex_tile(
|
||||||
parent: &mut ChildBuilder,
|
parent: &mut ChildBuilder,
|
||||||
assets: &MazeAssets,
|
assets: &MazeAssets,
|
||||||
tile: &HexTile,
|
tile: &HexTile,
|
||||||
config: &MazeConfig,
|
maze_config: &MazeConfig,
|
||||||
|
global_config: &GlobalMazeConfig,
|
||||||
) {
|
) {
|
||||||
let world_pos = tile.to_vec3(&config.layout);
|
let world_pos = tile.to_vec3(&maze_config.layout);
|
||||||
let rotation = match config.layout.orientation {
|
let rotation = match maze_config.layout.orientation {
|
||||||
HexOrientation::Pointy => Quat::from_rotation_y(0.0),
|
HexOrientation::Pointy => Quat::from_rotation_y(0.0),
|
||||||
HexOrientation::Flat => Quat::from_rotation_y(FRAC_PI_6), // 30 degrees rotation
|
HexOrientation::Flat => Quat::from_rotation_y(FRAC_PI_6), // 30 degrees rotation
|
||||||
};
|
};
|
||||||
@ -57,12 +75,17 @@ pub(super) fn spawn_single_hex_tile(
|
|||||||
MeshMaterial3d(assets.hex_material.clone()),
|
MeshMaterial3d(assets.hex_material.clone()),
|
||||||
Transform::from_translation(world_pos).with_rotation(rotation),
|
Transform::from_translation(world_pos).with_rotation(rotation),
|
||||||
))
|
))
|
||||||
.with_children(|parent| spawn_walls(parent, assets, config, tile.walls()));
|
.with_children(|parent| spawn_walls(parent, assets, tile.walls(), global_config));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn spawn_walls(parent: &mut ChildBuilder, assets: &MazeAssets, config: &MazeConfig, walls: &Walls) {
|
fn spawn_walls(
|
||||||
|
parent: &mut ChildBuilder,
|
||||||
|
assets: &MazeAssets,
|
||||||
|
walls: &Walls,
|
||||||
|
global_config: &GlobalMazeConfig,
|
||||||
|
) {
|
||||||
let z_rotation = Quat::from_rotation_z(-FRAC_PI_2);
|
let z_rotation = Quat::from_rotation_z(-FRAC_PI_2);
|
||||||
let y_offset = config.height / 2.;
|
let y_offset = global_config.height / 2.;
|
||||||
|
|
||||||
for i in 0..6 {
|
for i in 0..6 {
|
||||||
if !walls.contains(i) {
|
if !walls.contains(i) {
|
||||||
@ -71,8 +94,8 @@ fn spawn_walls(parent: &mut ChildBuilder, assets: &MazeAssets, config: &MazeConf
|
|||||||
|
|
||||||
let wall_angle = -FRAC_PI_3 * i as f32;
|
let wall_angle = -FRAC_PI_3 * i as f32;
|
||||||
|
|
||||||
let x_offset = config.wall_offset() * f32::cos(wall_angle);
|
let x_offset = global_config.wall_offset() * f32::cos(wall_angle);
|
||||||
let z_offset = config.wall_offset() * f32::sin(wall_angle);
|
let z_offset = global_config.wall_offset() * f32::sin(wall_angle);
|
||||||
let pos = Vec3::new(x_offset, y_offset, z_offset);
|
let pos = Vec3::new(x_offset, y_offset, z_offset);
|
||||||
|
|
||||||
let x_rotation = Quat::from_rotation_x(wall_angle + FRAC_PI_2);
|
let x_rotation = Quat::from_rotation_x(wall_angle + FRAC_PI_2);
|
||||||
|
|||||||
43
src/maze/systems/update.rs
Normal file
43
src/maze/systems/update.rs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
use super::{common::generate_maze, spawn::spawn_maze_tiles};
|
||||||
|
use crate::{
|
||||||
|
floor::components::Floor,
|
||||||
|
maze::{
|
||||||
|
assets::MazeAssets,
|
||||||
|
components::{Maze, MazeConfig},
|
||||||
|
errors::{MazeError, MazeResult},
|
||||||
|
GlobalMazeConfig,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
pub(super) fn update_floor(
|
||||||
|
commands: &mut Commands,
|
||||||
|
meshes: &mut ResMut<Assets<Mesh>>,
|
||||||
|
materials: &mut ResMut<Assets<StandardMaterial>>,
|
||||||
|
maze_query: &mut Query<(Entity, &Floor, &mut Maze)>,
|
||||||
|
floor: u8,
|
||||||
|
maze_config: &MazeConfig,
|
||||||
|
global_config: &GlobalMazeConfig,
|
||||||
|
) -> MazeResult<()> {
|
||||||
|
let (entity, _, mut maze) = maze_query
|
||||||
|
.iter_mut()
|
||||||
|
.find(|(_, f, _)| f.0 == floor)
|
||||||
|
.ok_or(MazeError::FloorNotFound(floor))?;
|
||||||
|
|
||||||
|
maze.0 = generate_maze(maze_config)?;
|
||||||
|
|
||||||
|
commands.entity(entity).despawn_descendants();
|
||||||
|
let assets = MazeAssets::new(meshes, materials, global_config);
|
||||||
|
spawn_maze_tiles(
|
||||||
|
commands,
|
||||||
|
entity,
|
||||||
|
&maze.0,
|
||||||
|
&assets,
|
||||||
|
maze_config,
|
||||||
|
global_config,
|
||||||
|
);
|
||||||
|
|
||||||
|
commands.entity(entity).insert(maze_config.clone());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@ -1,4 +1,8 @@
|
|||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
|
||||||
#[derive(Debug, Event)]
|
#[derive(Debug, Event)]
|
||||||
pub(crate) struct RespawnPlayer;
|
pub enum PlayerEvent {
|
||||||
|
Spawn,
|
||||||
|
Respawn,
|
||||||
|
Despawn,
|
||||||
|
}
|
||||||
|
|||||||
@ -5,11 +5,11 @@ mod systems;
|
|||||||
|
|
||||||
use bevy::{ecs::system::RunSystemOnce, prelude::*};
|
use bevy::{ecs::system::RunSystemOnce, prelude::*};
|
||||||
use components::Player;
|
use components::Player;
|
||||||
use events::RespawnPlayer;
|
use events::PlayerEvent;
|
||||||
|
|
||||||
pub(super) fn plugin(app: &mut App) {
|
pub(super) fn plugin(app: &mut App) {
|
||||||
app.register_type::<Player>()
|
app.register_type::<Player>()
|
||||||
.add_event::<RespawnPlayer>()
|
.add_event::<PlayerEvent>()
|
||||||
.add_plugins(systems::plugin);
|
.add_plugins(systems::plugin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@ use bevy::prelude::*;
|
|||||||
|
|
||||||
use crate::player::components::Player;
|
use crate::player::components::Player;
|
||||||
|
|
||||||
pub(crate) fn despawn_players(commands: &mut Commands, query: &Query<Entity, With<Player>>) {
|
pub(super) fn despawn_players(commands: &mut Commands, query: &Query<Entity, With<Player>>) {
|
||||||
for entity in query.iter() {
|
for entity in query.iter() {
|
||||||
commands.entity(entity).despawn_recursive();
|
commands.entity(entity).despawn_recursive();
|
||||||
}
|
}
|
||||||
|
|||||||
53
src/player/systems/event_handler.rs
Normal file
53
src/player/systems/event_handler.rs
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
use crate::{
|
||||||
|
floor::components::CurrentFloor,
|
||||||
|
maze::{components::MazeConfig, GlobalMazeConfig},
|
||||||
|
player::{components::Player, events::PlayerEvent},
|
||||||
|
};
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
use super::{despawn::despawn_players, respawn::respawn_player, spawn::spawn_player};
|
||||||
|
|
||||||
|
pub(crate) fn handle_player_events(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut meshes: ResMut<Assets<Mesh>>,
|
||||||
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||||
|
mut event_reader: EventReader<PlayerEvent>,
|
||||||
|
maze_config_query: Query<&MazeConfig, With<CurrentFloor>>,
|
||||||
|
player_query: Query<Entity, With<Player>>,
|
||||||
|
global_config: Res<GlobalMazeConfig>,
|
||||||
|
) {
|
||||||
|
for event in event_reader.read() {
|
||||||
|
match event {
|
||||||
|
PlayerEvent::Spawn => {
|
||||||
|
let Ok(maze_config) = maze_config_query.get_single() else {
|
||||||
|
warn!(
|
||||||
|
"Failed to get maze configuration for current floor - cannot spawn player"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
spawn_player(
|
||||||
|
&mut commands,
|
||||||
|
&mut meshes,
|
||||||
|
&mut materials,
|
||||||
|
maze_config,
|
||||||
|
&global_config,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
PlayerEvent::Respawn => {
|
||||||
|
let Ok(maze_config) = maze_config_query.get_single() else {
|
||||||
|
warn!("Failed to get maze configuration for current floor - cannot respawn player");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
respawn_player(
|
||||||
|
&mut commands,
|
||||||
|
&mut meshes,
|
||||||
|
&mut materials,
|
||||||
|
&player_query,
|
||||||
|
maze_config,
|
||||||
|
&global_config,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
PlayerEvent::Despawn => despawn_players(&mut commands, &player_query),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,13 +1,42 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
maze::{
|
floor::components::CurrentFloor,
|
||||||
components::{Floor, Maze},
|
maze::components::{Maze, MazeConfig},
|
||||||
MazeConfig,
|
|
||||||
},
|
|
||||||
player::components::{CurrentPosition, MovementTarget, Player},
|
player::components::{CurrentPosition, MovementTarget, Player},
|
||||||
};
|
};
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use hexx::{EdgeDirection, HexOrientation};
|
use hexx::{EdgeDirection, HexOrientation};
|
||||||
|
|
||||||
|
pub(super) fn player_input(
|
||||||
|
input: Res<ButtonInput<KeyCode>>,
|
||||||
|
mut player_query: Query<(&mut MovementTarget, &CurrentPosition), With<Player>>,
|
||||||
|
maze_query: Query<(&Maze, &MazeConfig), With<CurrentFloor>>,
|
||||||
|
) {
|
||||||
|
let Ok((maze, maze_config)) = maze_query.get_single() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
for (mut target_pos, current_pos) in player_query.iter_mut() {
|
||||||
|
if target_pos.is_some() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(direction) = create_direction(&input, &maze_config.layout.orientation) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(tile) = maze.0.get_tile(current_pos) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
if tile.walls().contains(direction) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let next_hex = current_pos.0.neighbor(direction);
|
||||||
|
target_pos.0 = Some(next_hex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn create_direction(
|
fn create_direction(
|
||||||
input: &ButtonInput<KeyCode>,
|
input: &ButtonInput<KeyCode>,
|
||||||
orientation: &HexOrientation,
|
orientation: &HexOrientation,
|
||||||
@ -43,35 +72,3 @@ fn create_direction(
|
|||||||
}?;
|
}?;
|
||||||
Some(direction.rotate_cw(0))
|
Some(direction.rotate_cw(0))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn player_input(
|
|
||||||
input: Res<ButtonInput<KeyCode>>,
|
|
||||||
mut player_query: Query<(&mut MovementTarget, &CurrentPosition), With<Player>>,
|
|
||||||
maze_query: Query<(&Maze, &Floor)>,
|
|
||||||
maze_config: Res<MazeConfig>,
|
|
||||||
) {
|
|
||||||
let Ok((maze, _floor)) = maze_query.get_single() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
for (mut target_pos, current_pos) in player_query.iter_mut() {
|
|
||||||
if target_pos.is_some() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let Some(direction) = create_direction(&input, &maze_config.layout.orientation) else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
let Some(tile) = maze.0.get_tile(¤t_pos) else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
if tile.walls().contains(direction) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let next_hex = current_pos.0.neighbor(direction);
|
|
||||||
target_pos.0 = Some(next_hex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,14 +1,15 @@
|
|||||||
pub mod despawn;
|
mod despawn;
|
||||||
|
mod event_handler;
|
||||||
mod input;
|
mod input;
|
||||||
mod movement;
|
mod movement;
|
||||||
pub mod respawn;
|
mod respawn;
|
||||||
pub mod setup;
|
pub mod setup;
|
||||||
pub mod spawn;
|
mod spawn;
|
||||||
|
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
use event_handler::handle_player_events;
|
||||||
use input::player_input;
|
use input::player_input;
|
||||||
use movement::player_movement;
|
use movement::player_movement;
|
||||||
use respawn::respawn_player;
|
|
||||||
|
|
||||||
pub(super) fn plugin(app: &mut App) {
|
pub(super) fn plugin(app: &mut App) {
|
||||||
app.add_systems(
|
app.add_systems(
|
||||||
@ -16,7 +17,7 @@ pub(super) fn plugin(app: &mut App) {
|
|||||||
(
|
(
|
||||||
player_input,
|
player_input,
|
||||||
player_movement.after(player_input),
|
player_movement.after(player_input),
|
||||||
respawn_player,
|
handle_player_events,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
maze::MazeConfig,
|
floor::components::CurrentFloor,
|
||||||
|
maze::components::MazeConfig,
|
||||||
player::components::{CurrentPosition, MovementSpeed, MovementTarget, Player},
|
player::components::{CurrentPosition, MovementSpeed, MovementTarget, Player},
|
||||||
};
|
};
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
@ -16,12 +17,17 @@ pub(super) fn player_movement(
|
|||||||
),
|
),
|
||||||
With<Player>,
|
With<Player>,
|
||||||
>,
|
>,
|
||||||
maze_config: Res<MazeConfig>,
|
maze_config_query: Query<&MazeConfig, With<CurrentFloor>>,
|
||||||
) {
|
) {
|
||||||
|
let Ok(maze_config) = maze_config_query.get_single() else {
|
||||||
|
warn!("Failed to get maze configuration for current floor - cannot move player");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
for (mut target, speed, mut current_hex, mut transform) in query.iter_mut() {
|
for (mut target, speed, mut current_hex, mut transform) in query.iter_mut() {
|
||||||
if let Some(target_hex) = target.0 {
|
if let Some(target_hex) = target.0 {
|
||||||
let current_pos = transform.translation;
|
let current_pos = transform.translation;
|
||||||
let target_pos = calculate_target_position(&maze_config, target_hex, current_pos.y);
|
let target_pos = calculate_target_position(maze_config, target_hex, current_pos.y);
|
||||||
|
|
||||||
if should_complete_movement(current_pos, target_pos) {
|
if should_complete_movement(current_pos, target_pos) {
|
||||||
transform.translation = target_pos;
|
transform.translation = target_pos;
|
||||||
|
|||||||
@ -1,21 +1,18 @@
|
|||||||
|
use super::{despawn::despawn_players, spawn::spawn_player};
|
||||||
use crate::{
|
use crate::{
|
||||||
maze::MazeConfig,
|
maze::{components::MazeConfig, GlobalMazeConfig},
|
||||||
player::{components::Player, events::RespawnPlayer},
|
player::components::Player,
|
||||||
};
|
};
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
|
||||||
use super::{despawn::despawn_players, spawn::spawn_player};
|
pub(super) fn respawn_player(
|
||||||
|
commands: &mut Commands,
|
||||||
pub(crate) fn respawn_player(
|
meshes: &mut ResMut<Assets<Mesh>>,
|
||||||
mut commands: Commands,
|
materials: &mut ResMut<Assets<StandardMaterial>>,
|
||||||
mut meshes: ResMut<Assets<Mesh>>,
|
query: &Query<Entity, With<Player>>,
|
||||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
maze_config: &MazeConfig,
|
||||||
config: Res<MazeConfig>,
|
global_config: &GlobalMazeConfig,
|
||||||
query: Query<Entity, With<Player>>,
|
|
||||||
mut event_reader: EventReader<RespawnPlayer>,
|
|
||||||
) {
|
) {
|
||||||
for _ in event_reader.read() {
|
despawn_players(commands, query);
|
||||||
despawn_players(&mut commands, &query);
|
spawn_player(commands, meshes, materials, maze_config, global_config);
|
||||||
spawn_player(&mut commands, &mut meshes, &mut materials, &config);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +1,6 @@
|
|||||||
|
use crate::player::events::PlayerEvent;
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
|
||||||
use crate::maze::MazeConfig;
|
pub(crate) fn setup(mut event_writer: EventWriter<PlayerEvent>) {
|
||||||
|
event_writer.send(PlayerEvent::Spawn);
|
||||||
use super::spawn::spawn_player;
|
|
||||||
|
|
||||||
pub(crate) fn setup(
|
|
||||||
mut commands: Commands,
|
|
||||||
mut meshes: ResMut<Assets<Mesh>>,
|
|
||||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
|
||||||
config: Res<MazeConfig>,
|
|
||||||
) {
|
|
||||||
spawn_player(&mut commands, &mut meshes, &mut materials, &config);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
maze::MazeConfig,
|
maze::{components::MazeConfig, GlobalMazeConfig},
|
||||||
player::{
|
player::{
|
||||||
assets::{blue_material, generate_pill_mesh},
|
assets::{blue_material, generate_pill_mesh},
|
||||||
components::{CurrentPosition, Player},
|
components::{CurrentPosition, Player},
|
||||||
@ -11,19 +11,20 @@ pub(super) fn spawn_player(
|
|||||||
commands: &mut Commands,
|
commands: &mut Commands,
|
||||||
meshes: &mut ResMut<Assets<Mesh>>,
|
meshes: &mut ResMut<Assets<Mesh>>,
|
||||||
materials: &mut ResMut<Assets<StandardMaterial>>,
|
materials: &mut ResMut<Assets<StandardMaterial>>,
|
||||||
config: &MazeConfig,
|
maze_config: &MazeConfig,
|
||||||
|
global_config: &GlobalMazeConfig,
|
||||||
) {
|
) {
|
||||||
let player_radius = config.hex_size * 0.5;
|
let player_radius = global_config.hex_size * 0.5;
|
||||||
let player_height = player_radius * 3.5;
|
let player_height = player_radius * 3.5;
|
||||||
|
|
||||||
let y_offset = config.height / 2. + player_height / 1.3;
|
let y_offset = global_config.height / 2. + player_height / 1.3;
|
||||||
|
|
||||||
let start_pos = config.layout.hex_to_world_pos(config.start_pos);
|
let start_pos = maze_config.layout.hex_to_world_pos(maze_config.start_pos);
|
||||||
|
|
||||||
commands.spawn((
|
commands.spawn((
|
||||||
Name::new("Player"),
|
Name::new("Player"),
|
||||||
Player,
|
Player,
|
||||||
CurrentPosition(config.start_pos),
|
CurrentPosition(maze_config.start_pos),
|
||||||
Mesh3d(meshes.add(generate_pill_mesh(player_radius, player_height / 2.))),
|
Mesh3d(meshes.add(generate_pill_mesh(player_radius, player_height / 2.))),
|
||||||
MeshMaterial3d(materials.add(blue_material())),
|
MeshMaterial3d(materials.add(blue_material())),
|
||||||
Transform::from_xyz(start_pos.x, y_offset, start_pos.y),
|
Transform::from_xyz(start_pos.x, y_offset, start_pos.y),
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user