mirror of
https://github.com/kristoferssolo/maze-ascension.git
synced 2026-03-22 00:26:29 +00:00
refactor(maze): add event based maze updates
This commit is contained in:
7
Cargo.lock
generated
7
Cargo.lock
generated
@@ -176,6 +176,12 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.94"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7"
|
||||
|
||||
[[package]]
|
||||
name = "approx"
|
||||
version = "0.5.1"
|
||||
@@ -3079,6 +3085,7 @@ dependencies = [
|
||||
name = "maze-ascension"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bevy",
|
||||
"bevy-inspector-egui",
|
||||
"bevy_egui",
|
||||
|
||||
@@ -22,6 +22,7 @@ hexlab = { version = "0.3", features = ["bevy"] }
|
||||
bevy-inspector-egui = { version = "0.28", optional = true }
|
||||
bevy_egui = { version = "0.31", optional = true }
|
||||
thiserror = "2.0"
|
||||
anyhow = "1"
|
||||
|
||||
|
||||
[features]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
floor::components::{CurrentFloor, Floor},
|
||||
maze::{components::MazeConfig, events::RecreateMazeEvent, GlobalMazeConfig, MazePluginLoaded},
|
||||
maze::{components::MazeConfig, events::MazeEvent, GlobalMazeConfig, MazePluginLoaded},
|
||||
player::events::RespawnPlayer,
|
||||
};
|
||||
use bevy::{prelude::*, window::PrimaryWindow};
|
||||
@@ -25,21 +25,23 @@ pub(crate) fn maze_controls_ui(world: &mut World) {
|
||||
};
|
||||
let mut egui_context = egui_context.clone();
|
||||
|
||||
let Ok((mut maze_config, floor)) = world
|
||||
.query_filtered::<(&mut MazeConfig, &Floor), With<CurrentFloor>>()
|
||||
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 mut maze_config = maze_config.clone();
|
||||
let floor_value = floor.0;
|
||||
|
||||
let global_config = world.get_resource::<GlobalMazeConfig>().cloned();
|
||||
let mut changed = false;
|
||||
|
||||
egui::Window::new("Maze Controls").show(egui_context.get_mut(), |ui| {
|
||||
if let Some(mut global_config) = world.get_resource_mut::<GlobalMazeConfig>() {
|
||||
let mut changed = false;
|
||||
if let Some(mut global_config) = global_config.clone() {
|
||||
ui.heading("Maze Configuration");
|
||||
|
||||
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, "Height:", &mut global_config.height, 0.5, 1.0..=50.0);
|
||||
@@ -50,27 +52,19 @@ pub(crate) fn maze_controls_ui(world: &mut World) {
|
||||
1.0,
|
||||
1.0..=100.0,
|
||||
);
|
||||
|
||||
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, "End Position:", &mut maze_config.end_pos);
|
||||
|
||||
// Trigger recreation if any value changed
|
||||
// Handle updates
|
||||
if changed {
|
||||
maze_config.update(&global_config);
|
||||
|
||||
if let Ok(mut actual_maze_config) = world
|
||||
.query_filtered::<&mut MazeConfig, With<CurrentFloor>>()
|
||||
.get_single_mut(world)
|
||||
{
|
||||
*actual_maze_config = maze_config;
|
||||
}
|
||||
|
||||
if let Some(mut event_writer) =
|
||||
world.get_resource_mut::<Events<RecreateMazeEvent>>()
|
||||
{
|
||||
event_writer.send(RecreateMazeEvent { floor: floor.0 });
|
||||
if let Some(mut event_writer) = world.get_resource_mut::<Events<MazeEvent>>() {
|
||||
event_writer.send(MazeEvent::Recreate {
|
||||
floor: floor_value,
|
||||
config: maze_config,
|
||||
});
|
||||
}
|
||||
if let Some(mut event_writer) = world.get_resource_mut::<Events<RespawnPlayer>>() {
|
||||
event_writer.send(RespawnPlayer);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
pub mod components;
|
||||
mod systems;
|
||||
|
||||
use bevy::prelude::*;
|
||||
|
||||
pub mod components;
|
||||
|
||||
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
@@ -8,3 +8,31 @@ pub enum MazeConfigError {
|
||||
#[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 super::components::MazeConfig;
|
||||
|
||||
#[derive(Debug, Event)]
|
||||
pub struct RecreateMazeEvent {
|
||||
pub floor: u8,
|
||||
pub enum MazeEvent {
|
||||
Create { floor: u8, config: MazeConfig },
|
||||
Recreate { floor: u8, config: MazeConfig },
|
||||
Remove { floor: u8 },
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
use bevy::{ecs::system::RunSystemOnce, prelude::*};
|
||||
use events::RecreateMazeEvent;
|
||||
mod assets;
|
||||
pub mod components;
|
||||
pub mod errors;
|
||||
@@ -7,11 +5,15 @@ pub mod events;
|
||||
pub mod resources;
|
||||
mod systems;
|
||||
|
||||
use bevy::{ecs::system::RunSystemOnce, prelude::*};
|
||||
use components::Maze;
|
||||
use events::MazeEvent;
|
||||
pub use resources::{GlobalMazeConfig, MazePluginLoaded};
|
||||
|
||||
pub(super) fn plugin(app: &mut App) {
|
||||
app.init_resource::<GlobalMazeConfig>()
|
||||
.add_event::<RecreateMazeEvent>()
|
||||
.add_event::<MazeEvent>()
|
||||
.register_type::<Maze>()
|
||||
.add_plugins(systems::plugin);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ use bevy::prelude::*;
|
||||
#[reflect(Resource)]
|
||||
pub struct MazePluginLoaded;
|
||||
|
||||
#[derive(Debug, Reflect, Resource)]
|
||||
#[derive(Debug, Reflect, Resource, Clone)]
|
||||
#[reflect(Resource)]
|
||||
pub struct GlobalMazeConfig {
|
||||
pub hex_size: f32,
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
use crate::floor::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, &Children, &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,11 @@
|
||||
pub mod despawn;
|
||||
pub mod recreation;
|
||||
pub mod event_handler;
|
||||
pub mod setup;
|
||||
pub mod spawn;
|
||||
pub mod update;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use recreation::recreate_maze;
|
||||
use event_handler::handle_maze_events;
|
||||
|
||||
pub(super) fn plugin(app: &mut App) {
|
||||
app.add_systems(Update, recreate_maze);
|
||||
app.add_systems(Update, handle_maze_events);
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
use crate::{
|
||||
floor::components::Floor,
|
||||
maze::{components::MazeConfig, events::RecreateMazeEvent, GlobalMazeConfig},
|
||||
};
|
||||
|
||||
use super::{despawn::despawn_floor, spawn::spawn_floor};
|
||||
|
||||
pub(crate) fn recreate_maze(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
query: Query<(Entity, &Floor)>,
|
||||
mut event_reader: EventReader<RecreateMazeEvent>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
global_config: Res<GlobalMazeConfig>,
|
||||
) {
|
||||
let maze_config = MazeConfig::default();
|
||||
for event in event_reader.read() {
|
||||
despawn_floor(&mut commands, &query, event.floor);
|
||||
spawn_floor(
|
||||
&mut commands,
|
||||
&mut meshes,
|
||||
&mut materials,
|
||||
&maze_config,
|
||||
&global_config,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,7 @@
|
||||
use super::spawn::spawn_floor;
|
||||
use crate::maze::{components::MazeConfig, resources::GlobalMazeConfig};
|
||||
use crate::maze::{components::MazeConfig, events::MazeEvent};
|
||||
use bevy::prelude::*;
|
||||
|
||||
pub(crate) fn setup(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
global_config: Res<GlobalMazeConfig>,
|
||||
) {
|
||||
let maze_config = MazeConfig::default();
|
||||
spawn_floor(
|
||||
&mut commands,
|
||||
&mut meshes,
|
||||
&mut materials,
|
||||
&maze_config,
|
||||
&global_config,
|
||||
);
|
||||
pub(crate) fn setup(mut event_writer: EventWriter<MazeEvent>) {
|
||||
let config = MazeConfig::default();
|
||||
event_writer.send(MazeEvent::Create { floor: 1, config });
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
floor::components::Floor,
|
||||
floor::components::{CurrentFloor, Floor},
|
||||
maze::{
|
||||
assets::MazeAssets,
|
||||
components::{Maze, MazeConfig, Tile, Wall},
|
||||
@@ -15,6 +15,7 @@ pub(super) fn spawn_floor(
|
||||
commands: &mut Commands,
|
||||
meshes: &mut ResMut<Assets<Mesh>>,
|
||||
materials: &mut ResMut<Assets<StandardMaterial>>,
|
||||
floor: u8,
|
||||
maze_config: &MazeConfig,
|
||||
global_config: &GlobalMazeConfig,
|
||||
) {
|
||||
@@ -25,18 +26,20 @@ pub(super) fn spawn_floor(
|
||||
.build()
|
||||
.expect("Something went wrong while creating maze");
|
||||
|
||||
let assets = MazeAssets::new(meshes, materials, global_config);
|
||||
let assets = MazeAssets::new(meshes, materials, &global_config);
|
||||
commands
|
||||
.spawn((
|
||||
Name::new("Floor"),
|
||||
Name::new(format!("Floor {}", floor)),
|
||||
Maze(maze.clone()),
|
||||
Floor(1),
|
||||
Floor(floor),
|
||||
CurrentFloor, // TODO: remove
|
||||
maze_config.clone(),
|
||||
Transform::from_translation(Vec3::ZERO),
|
||||
Visibility::Visible,
|
||||
))
|
||||
.with_children(|parent| {
|
||||
for tile in maze.values() {
|
||||
spawn_single_hex_tile(parent, &assets, tile, maze_config, global_config)
|
||||
spawn_single_hex_tile(parent, &assets, tile, &maze_config, &global_config)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
52
src/maze/systems/update.rs
Normal file
52
src/maze/systems/update.rs
Normal file
@@ -0,0 +1,52 @@
|
||||
use crate::{
|
||||
floor::components::Floor,
|
||||
maze::{
|
||||
assets::MazeAssets,
|
||||
components::{Maze, MazeConfig},
|
||||
errors::{MazeError, MazeResult},
|
||||
GlobalMazeConfig,
|
||||
},
|
||||
};
|
||||
use bevy::prelude::*;
|
||||
use hexlab::{GeneratorType, MazeBuilder};
|
||||
|
||||
use super::spawn::spawn_single_hex_tile;
|
||||
|
||||
pub(super) fn update_floor(
|
||||
commands: &mut Commands,
|
||||
meshes: &mut ResMut<Assets<Mesh>>,
|
||||
materials: &mut ResMut<Assets<StandardMaterial>>,
|
||||
maze_query: &mut Query<(Entity, &Floor, &Children, &mut Maze)>,
|
||||
floor: u8,
|
||||
maze_config: &MazeConfig,
|
||||
global_config: &GlobalMazeConfig,
|
||||
) -> MazeResult<()> {
|
||||
let (entity, _, children, mut maze) = maze_query
|
||||
.iter_mut()
|
||||
.find(|(_, f, _, _)| f.0 == floor)
|
||||
.ok_or(MazeError::FloorNotFound(floor))?;
|
||||
|
||||
let new_maze = MazeBuilder::new()
|
||||
.with_radius(maze_config.radius)
|
||||
.with_seed(maze_config.seed)
|
||||
.with_generator(GeneratorType::RecursiveBacktracking)
|
||||
.build()
|
||||
.map_err(|_| MazeError::generation_failed(maze_config.radius, maze_config.seed))?;
|
||||
|
||||
let new_maze = Maze(new_maze);
|
||||
|
||||
commands.entity(entity).clear_children();
|
||||
for &child in children.iter() {
|
||||
commands.entity(child).despawn_recursive();
|
||||
}
|
||||
let assets = MazeAssets::new(meshes, materials, global_config);
|
||||
commands.entity(entity).with_children(|parent| {
|
||||
for tile in new_maze.0.values() {
|
||||
spawn_single_hex_tile(parent, &assets, tile, maze_config, global_config);
|
||||
}
|
||||
});
|
||||
|
||||
*maze = new_maze;
|
||||
commands.entity(entity).insert(maze_config.clone());
|
||||
Ok(())
|
||||
}
|
||||
@@ -6,6 +6,37 @@ use crate::{
|
||||
use bevy::prelude::*;
|
||||
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(¤t_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(
|
||||
input: &ButtonInput<KeyCode>,
|
||||
orientation: &HexOrientation,
|
||||
@@ -41,34 +72,3 @@ fn create_direction(
|
||||
}?;
|
||||
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, &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(¤t_pos) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if tile.walls().contains(direction) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let next_hex = current_pos.0.neighbor(direction);
|
||||
target_pos.0 = Some(next_hex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ use input::player_input;
|
||||
use movement::player_movement;
|
||||
use respawn::respawn_player;
|
||||
|
||||
use crate::maze::MazePluginLoaded;
|
||||
|
||||
pub(super) fn plugin(app: &mut App) {
|
||||
app.add_systems(
|
||||
Update,
|
||||
@@ -17,6 +19,7 @@ pub(super) fn plugin(app: &mut App) {
|
||||
player_input,
|
||||
player_movement.after(player_input),
|
||||
respawn_player,
|
||||
),
|
||||
)
|
||||
.run_if(resource_exists::<MazePluginLoaded>),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,11 @@ pub(super) fn player_movement(
|
||||
>,
|
||||
maze_config_query: Query<&MazeConfig, With<CurrentFloor>>,
|
||||
) {
|
||||
let maze_config = maze_config_query.single();
|
||||
let Ok(maze_config) = maze_config_query.get_single() else {
|
||||
error!("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() {
|
||||
if let Some(target_hex) = target.0 {
|
||||
let current_pos = transform.translation;
|
||||
|
||||
@@ -16,7 +16,10 @@ pub(crate) fn respawn_player(
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
global_config: Res<GlobalMazeConfig>,
|
||||
) {
|
||||
let maze_config = maze_config_query.single();
|
||||
let Ok(maze_config) = maze_config_query.get_single() else {
|
||||
error!("Failed to get maze configuration for current floor - cannot respawn player");
|
||||
return;
|
||||
};
|
||||
for _ in event_reader.read() {
|
||||
despawn_players(&mut commands, &query);
|
||||
spawn_player(
|
||||
|
||||
@@ -14,7 +14,11 @@ pub(crate) fn setup(
|
||||
maze_config_query: Query<&MazeConfig, With<CurrentFloor>>,
|
||||
global_config: Res<GlobalMazeConfig>,
|
||||
) {
|
||||
let maze_config = maze_config_query.single();
|
||||
let Ok(maze_config) = maze_config_query.get_single() else {
|
||||
error!("Failed to get maze configuration for current floor - cannot spawn player");
|
||||
return;
|
||||
};
|
||||
|
||||
spawn_player(
|
||||
&mut commands,
|
||||
&mut meshes,
|
||||
|
||||
Reference in New Issue
Block a user