mirror of
https://github.com/kristoferssolo/maze-ascension.git
synced 2025-10-21 19:20:34 +00:00
Merge pull request #7 from kristoferssolo/feature/player
Feature/player
This commit is contained in:
commit
72b16dc8bb
796
Cargo.lock
generated
796
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "maze-ascension"
|
name = "maze-ascension"
|
||||||
authors = ["Kristofers Solo <dev@kristofers.xyz>"]
|
authors = ["Kristofers Solo <dev@kristofers.xyz>"]
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
@ -18,7 +18,7 @@ tracing = { version = "0.1", features = [
|
|||||||
"release_max_level_warn",
|
"release_max_level_warn",
|
||||||
] }
|
] }
|
||||||
hexx = { version = "0.19", features = ["bevy_reflect", "grid"] }
|
hexx = { version = "0.19", features = ["bevy_reflect", "grid"] }
|
||||||
hexlab = { version = "0.2", features = ["bevy"] }
|
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"
|
||||||
|
|||||||
@ -1,4 +1,32 @@
|
|||||||
mod plugin;
|
|
||||||
mod ui;
|
mod ui;
|
||||||
|
|
||||||
pub use plugin::DevToolsPlugin;
|
use crate::screens::Screen;
|
||||||
|
use bevy::{
|
||||||
|
dev_tools::{
|
||||||
|
states::log_transitions,
|
||||||
|
ui_debug_overlay::{DebugUiPlugin, UiDebugOptions},
|
||||||
|
},
|
||||||
|
input::common_conditions::input_just_pressed,
|
||||||
|
prelude::*,
|
||||||
|
};
|
||||||
|
use bevy_egui::EguiPlugin;
|
||||||
|
use bevy_inspector_egui::quick::WorldInspectorPlugin;
|
||||||
|
use ui::maze_controls_ui;
|
||||||
|
|
||||||
|
pub(super) fn plugin(app: &mut App) {
|
||||||
|
app.add_systems(Update, log_transitions::<Screen>)
|
||||||
|
.add_plugins(EguiPlugin)
|
||||||
|
.add_plugins(WorldInspectorPlugin::new())
|
||||||
|
.add_plugins(DebugUiPlugin)
|
||||||
|
.add_systems(Update, maze_controls_ui)
|
||||||
|
.add_systems(
|
||||||
|
Update,
|
||||||
|
toggle_debug_ui.run_if(input_just_pressed(TOGGLE_KEY)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const TOGGLE_KEY: KeyCode = KeyCode::Backquote;
|
||||||
|
|
||||||
|
fn toggle_debug_ui(mut options: ResMut<UiDebugOptions>) {
|
||||||
|
options.toggle();
|
||||||
|
}
|
||||||
|
|||||||
@ -1,36 +0,0 @@
|
|||||||
use crate::screens::Screen;
|
|
||||||
use bevy::{
|
|
||||||
dev_tools::{
|
|
||||||
states::log_transitions,
|
|
||||||
ui_debug_overlay::{DebugUiPlugin, UiDebugOptions},
|
|
||||||
},
|
|
||||||
input::common_conditions::input_just_pressed,
|
|
||||||
prelude::*,
|
|
||||||
};
|
|
||||||
use bevy_egui::EguiPlugin;
|
|
||||||
use bevy_inspector_egui::quick::WorldInspectorPlugin;
|
|
||||||
|
|
||||||
use super::ui::maze_controls_ui;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct DevToolsPlugin;
|
|
||||||
|
|
||||||
impl Plugin for DevToolsPlugin {
|
|
||||||
fn build(&self, app: &mut App) {
|
|
||||||
app.add_systems(Update, log_transitions::<Screen>)
|
|
||||||
.add_plugins(EguiPlugin)
|
|
||||||
.add_plugins(WorldInspectorPlugin::new())
|
|
||||||
.add_plugins(DebugUiPlugin)
|
|
||||||
.add_systems(Update, maze_controls_ui)
|
|
||||||
.add_systems(
|
|
||||||
Update,
|
|
||||||
toggle_debug_ui.run_if(input_just_pressed(TOGGLE_KEY)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const TOGGLE_KEY: KeyCode = KeyCode::Backquote;
|
|
||||||
|
|
||||||
fn toggle_debug_ui(mut options: ResMut<UiDebugOptions>) {
|
|
||||||
options.toggle();
|
|
||||||
}
|
|
||||||
@ -1,14 +1,15 @@
|
|||||||
use std::ops::RangeInclusive;
|
use crate::{
|
||||||
|
maze::{events::RecreateMazeEvent, MazeConfig, MazePluginLoaded},
|
||||||
|
player::events::RespawnPlayer,
|
||||||
|
};
|
||||||
use bevy::{prelude::*, window::PrimaryWindow};
|
use bevy::{prelude::*, window::PrimaryWindow};
|
||||||
use hexx::{Hex, HexOrientation};
|
|
||||||
use rand::{thread_rng, Rng};
|
|
||||||
|
|
||||||
use crate::maze::{events::RecreateMazeEvent, MazeConfig, MazePluginLoaded};
|
|
||||||
use bevy_egui::{
|
use bevy_egui::{
|
||||||
egui::{self, emath::Numeric, DragValue, TextEdit, Ui},
|
egui::{self, emath::Numeric, DragValue, TextEdit, Ui},
|
||||||
EguiContext,
|
EguiContext,
|
||||||
};
|
};
|
||||||
|
use hexx::{Hex, HexOrientation};
|
||||||
|
use rand::{thread_rng, Rng};
|
||||||
|
use std::ops::RangeInclusive;
|
||||||
|
|
||||||
pub(crate) fn maze_controls_ui(world: &mut World) {
|
pub(crate) fn maze_controls_ui(world: &mut World) {
|
||||||
if world.get_resource::<MazePluginLoaded>().is_none() {
|
if world.get_resource::<MazePluginLoaded>().is_none() {
|
||||||
@ -55,6 +56,9 @@ pub(crate) fn maze_controls_ui(world: &mut World) {
|
|||||||
{
|
{
|
||||||
event_writer.send(RecreateMazeEvent { floor: 1 });
|
event_writer.send(RecreateMazeEvent { floor: 1 });
|
||||||
}
|
}
|
||||||
|
if let Some(mut event_writer) = world.get_resource_mut::<Events<RespawnPlayer>>() {
|
||||||
|
event_writer.send(RespawnPlayer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -82,9 +86,9 @@ fn add_position_control(ui: &mut Ui, label: &str, pos: &mut Hex) -> bool {
|
|||||||
|
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label(label);
|
ui.label(label);
|
||||||
let response_x = ui.add(DragValue::new(&mut pos.x).speed(1).prefix("x: "));
|
let response_q = ui.add(DragValue::new(&mut pos.x).speed(1).prefix("q: "));
|
||||||
let response_y = ui.add(DragValue::new(&mut pos.y).speed(1).prefix("y: "));
|
let response_r = ui.add(DragValue::new(&mut pos.y).speed(1).prefix("r: "));
|
||||||
changed = response_x.changed() || response_y.changed();
|
changed = response_r.changed() || response_q.changed();
|
||||||
});
|
});
|
||||||
changed
|
changed
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,8 +3,9 @@ pub mod audio;
|
|||||||
#[cfg(feature = "dev")]
|
#[cfg(feature = "dev")]
|
||||||
mod dev_tools;
|
mod dev_tools;
|
||||||
mod maze;
|
mod maze;
|
||||||
|
mod player;
|
||||||
mod screens;
|
mod screens;
|
||||||
mod theme;
|
pub mod theme;
|
||||||
|
|
||||||
use bevy::{
|
use bevy::{
|
||||||
asset::AssetMetaCheck,
|
asset::AssetMetaCheck,
|
||||||
@ -57,14 +58,15 @@ impl Plugin for AppPlugin {
|
|||||||
// Add other plugins.
|
// Add other plugins.
|
||||||
app.add_plugins((
|
app.add_plugins((
|
||||||
asset_tracking::plugin,
|
asset_tracking::plugin,
|
||||||
maze::plugin::MazePlugin,
|
|
||||||
screens::plugin,
|
screens::plugin,
|
||||||
theme::plugin,
|
theme::plugin,
|
||||||
|
maze::plugin,
|
||||||
|
player::plugin,
|
||||||
));
|
));
|
||||||
|
|
||||||
// Enable dev tools for dev builds.
|
// Enable dev tools for dev builds.
|
||||||
#[cfg(feature = "dev")]
|
#[cfg(feature = "dev")]
|
||||||
app.add_plugins(dev_tools::DevToolsPlugin);
|
app.add_plugins(dev_tools::plugin);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,13 +1,18 @@
|
|||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
use hexlab::HexMaze;
|
||||||
|
|
||||||
#[derive(Debug, Reflect, Component)]
|
#[derive(Debug, Reflect, Component)]
|
||||||
#[reflect(Component)]
|
#[reflect(Component)]
|
||||||
pub(crate) struct MazeFloor(pub(crate) u8);
|
pub(crate) struct Maze(pub(crate) HexMaze);
|
||||||
|
|
||||||
#[derive(Debug, Reflect, Component)]
|
#[derive(Debug, Reflect, Component)]
|
||||||
#[reflect(Component)]
|
#[reflect(Component)]
|
||||||
pub(crate) struct MazeTile;
|
pub(crate) struct Floor(pub(crate) u8);
|
||||||
|
|
||||||
#[derive(Debug, Reflect, Component)]
|
#[derive(Debug, Reflect, Component)]
|
||||||
#[reflect(Component)]
|
#[reflect(Component)]
|
||||||
pub(crate) struct MazeWall;
|
pub(crate) struct Tile;
|
||||||
|
|
||||||
|
#[derive(Debug, Reflect, Component)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
pub(crate) struct Wall;
|
||||||
|
|||||||
@ -1,14 +1,20 @@
|
|||||||
use bevy::{ecs::world::Command, prelude::*};
|
use bevy::{ecs::system::RunSystemOnce, prelude::*};
|
||||||
use plugin::MazePlugin;
|
use events::RecreateMazeEvent;
|
||||||
mod assets;
|
mod assets;
|
||||||
mod components;
|
pub mod components;
|
||||||
pub mod events;
|
pub mod events;
|
||||||
pub mod plugin;
|
|
||||||
mod resources;
|
mod resources;
|
||||||
mod systems;
|
mod systems;
|
||||||
|
|
||||||
pub use resources::{MazeConfig, MazePluginLoaded};
|
pub use resources::{MazeConfig, MazePluginLoaded};
|
||||||
|
|
||||||
pub fn spawn_maze(world: &mut World) {
|
pub(super) fn plugin(app: &mut App) {
|
||||||
MazePlugin.apply(world);
|
app.init_resource::<MazeConfig>()
|
||||||
|
.add_event::<RecreateMazeEvent>()
|
||||||
|
.add_plugins(systems::plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn spawn_level_command(world: &mut World) {
|
||||||
|
world.insert_resource(MazePluginLoaded);
|
||||||
|
let _ = world.run_system_once(systems::setup::setup);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,28 +0,0 @@
|
|||||||
use bevy::{
|
|
||||||
ecs::{system::RunSystemOnce, world::Command},
|
|
||||||
prelude::*,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{
|
|
||||||
events::RecreateMazeEvent,
|
|
||||||
systems::{self, recreation::handle_maze_recreation_event},
|
|
||||||
MazeConfig, MazePluginLoaded,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub(crate) struct MazePlugin;
|
|
||||||
|
|
||||||
impl Plugin for MazePlugin {
|
|
||||||
fn build(&self, app: &mut App) {
|
|
||||||
app.init_resource::<MazeConfig>()
|
|
||||||
.add_event::<RecreateMazeEvent>()
|
|
||||||
.add_systems(Update, handle_maze_recreation_event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Command for MazePlugin {
|
|
||||||
fn apply(self, world: &mut World) {
|
|
||||||
world.insert_resource(MazePluginLoaded);
|
|
||||||
let _ = world.run_system_once(systems::setup::setup);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,8 +1,7 @@
|
|||||||
use std::num::TryFromIntError;
|
|
||||||
|
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use hexx::{Hex, HexLayout, HexOrientation};
|
use hexx::{Hex, HexLayout, HexOrientation};
|
||||||
use rand::{rngs::StdRng, thread_rng, Rng, SeedableRng};
|
use rand::{rngs::StdRng, thread_rng, Rng, SeedableRng};
|
||||||
|
use std::num::TryFromIntError;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Debug, Default, Reflect, Resource)]
|
#[derive(Debug, Default, Reflect, Resource)]
|
||||||
@ -41,8 +40,8 @@ impl MazeConfig {
|
|||||||
let start_pos = generate_pos(radius, &mut rng)?;
|
let start_pos = generate_pos(radius, &mut rng)?;
|
||||||
let end_pos = generate_pos(radius, &mut rng)?;
|
let end_pos = generate_pos(radius, &mut rng)?;
|
||||||
|
|
||||||
debug!("Start pos: ({},{})", start_pos.x, start_pos.y);
|
info!("Start pos: (q={}, r={})", start_pos.x, start_pos.y);
|
||||||
debug!("End pos: ({},{})", end_pos.x, end_pos.y);
|
info!("End pos: (q={}, r={})", end_pos.x, end_pos.y);
|
||||||
|
|
||||||
let layout = HexLayout {
|
let layout = HexLayout {
|
||||||
orientation,
|
orientation,
|
||||||
|
|||||||
@ -1,3 +1,14 @@
|
|||||||
|
use crate::maze::components::Floor;
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
|
||||||
use crate::maze::components::MazeFloor;
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,3 +1,11 @@
|
|||||||
|
pub mod despawn;
|
||||||
pub mod recreation;
|
pub mod recreation;
|
||||||
pub mod setup;
|
pub mod setup;
|
||||||
mod spawn;
|
pub mod spawn;
|
||||||
|
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use recreation::recreate_maze;
|
||||||
|
|
||||||
|
pub(super) fn plugin(app: &mut App) {
|
||||||
|
app.add_systems(Update, recreate_maze);
|
||||||
|
}
|
||||||
|
|||||||
@ -1,27 +1,19 @@
|
|||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
|
||||||
use crate::maze::{components::MazeFloor, events::RecreateMazeEvent, MazeConfig};
|
use crate::maze::{components::Floor, events::RecreateMazeEvent, MazeConfig};
|
||||||
|
|
||||||
use super::setup::setup_maze;
|
use super::{despawn::despawn_floor, spawn::spawn_floor};
|
||||||
|
|
||||||
pub(crate) fn handle_maze_recreation_event(
|
pub(crate) fn recreate_maze(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut meshes: ResMut<Assets<Mesh>>,
|
mut meshes: ResMut<Assets<Mesh>>,
|
||||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||||
config: Res<MazeConfig>,
|
config: Res<MazeConfig>,
|
||||||
query: Query<(Entity, &MazeFloor)>,
|
query: Query<(Entity, &Floor)>,
|
||||||
mut event_reader: EventReader<RecreateMazeEvent>,
|
mut event_reader: EventReader<RecreateMazeEvent>,
|
||||||
) {
|
) {
|
||||||
for event in event_reader.read() {
|
for event in event_reader.read() {
|
||||||
despawn_floor(&mut commands, &query, event.floor);
|
despawn_floor(&mut commands, &query, event.floor);
|
||||||
setup_maze(&mut commands, &mut meshes, &mut materials, &config);
|
spawn_floor(&mut commands, &mut meshes, &mut materials, &config);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn despawn_floor(commands: &mut Commands, query: &Query<(Entity, &MazeFloor)>, floor_num: u8) {
|
|
||||||
for (entity, maze_floor) in query.iter() {
|
|
||||||
if maze_floor.0 == floor_num {
|
|
||||||
commands.entity(entity).despawn_recursive();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,6 @@
|
|||||||
|
use super::spawn::spawn_floor;
|
||||||
|
use crate::maze::MazeConfig;
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use hexlab::{GeneratorType, MazeBuilder};
|
|
||||||
|
|
||||||
use crate::maze::{assets::MazeAssets, components::MazeFloor, MazeConfig};
|
|
||||||
|
|
||||||
use super::spawn::spawn_single_hex_tile;
|
|
||||||
|
|
||||||
pub(crate) fn setup(
|
pub(crate) fn setup(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
@ -11,33 +8,5 @@ pub(crate) fn setup(
|
|||||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||||
config: Res<MazeConfig>,
|
config: Res<MazeConfig>,
|
||||||
) {
|
) {
|
||||||
setup_maze(&mut commands, &mut meshes, &mut materials, &config);
|
spawn_floor(&mut commands, &mut meshes, &mut materials, &config);
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn setup_maze(
|
|
||||||
commands: &mut Commands,
|
|
||||||
meshes: &mut ResMut<Assets<Mesh>>,
|
|
||||||
materials: &mut ResMut<Assets<StandardMaterial>>,
|
|
||||||
config: &MazeConfig,
|
|
||||||
) {
|
|
||||||
let maze = MazeBuilder::new()
|
|
||||||
.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);
|
|
||||||
commands
|
|
||||||
.spawn((
|
|
||||||
Name::new("Floor"),
|
|
||||||
MazeFloor(1),
|
|
||||||
Transform::from_translation(Vec3::ZERO),
|
|
||||||
Visibility::Visible,
|
|
||||||
))
|
|
||||||
.with_children(|parent| {
|
|
||||||
for tile in maze.values() {
|
|
||||||
spawn_single_hex_tile(parent, &assets, tile, config)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +1,41 @@
|
|||||||
use std::f32::consts::{FRAC_PI_2, FRAC_PI_3, FRAC_PI_6};
|
use crate::maze::{
|
||||||
|
assets::MazeAssets,
|
||||||
|
components::{Floor, Maze, Tile, Wall},
|
||||||
|
MazeConfig,
|
||||||
|
};
|
||||||
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 crate::maze::{
|
pub(super) fn spawn_floor(
|
||||||
assets::MazeAssets,
|
commands: &mut Commands,
|
||||||
components::{MazeTile, MazeWall},
|
meshes: &mut ResMut<Assets<Mesh>>,
|
||||||
MazeConfig,
|
materials: &mut ResMut<Assets<StandardMaterial>>,
|
||||||
};
|
config: &MazeConfig,
|
||||||
|
) {
|
||||||
|
let maze = MazeBuilder::new()
|
||||||
|
.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);
|
||||||
|
commands
|
||||||
|
.spawn((
|
||||||
|
Name::new("Floor"),
|
||||||
|
Maze(maze.clone()),
|
||||||
|
Floor(1),
|
||||||
|
Transform::from_translation(Vec3::ZERO),
|
||||||
|
Visibility::Visible,
|
||||||
|
))
|
||||||
|
.with_children(|parent| {
|
||||||
|
for tile in maze.values() {
|
||||||
|
spawn_single_hex_tile(parent, &assets, tile, config)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn spawn_single_hex_tile(
|
pub(super) fn spawn_single_hex_tile(
|
||||||
parent: &mut ChildBuilder,
|
parent: &mut ChildBuilder,
|
||||||
@ -25,7 +52,7 @@ pub(super) fn spawn_single_hex_tile(
|
|||||||
parent
|
parent
|
||||||
.spawn((
|
.spawn((
|
||||||
Name::new(format!("Hex {}", tile)),
|
Name::new(format!("Hex {}", tile)),
|
||||||
MazeTile,
|
Tile,
|
||||||
Mesh3d(assets.hex_mesh.clone()),
|
Mesh3d(assets.hex_mesh.clone()),
|
||||||
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),
|
||||||
@ -58,7 +85,7 @@ fn spawn_walls(parent: &mut ChildBuilder, assets: &MazeAssets, config: &MazeConf
|
|||||||
fn spawn_single_wall(parent: &mut ChildBuilder, assets: &MazeAssets, rotation: Quat, offset: Vec3) {
|
fn spawn_single_wall(parent: &mut ChildBuilder, assets: &MazeAssets, rotation: Quat, offset: Vec3) {
|
||||||
parent.spawn((
|
parent.spawn((
|
||||||
Name::new("Wall"),
|
Name::new("Wall"),
|
||||||
MazeWall,
|
Wall,
|
||||||
Mesh3d(assets.wall_mesh.clone()),
|
Mesh3d(assets.wall_mesh.clone()),
|
||||||
MeshMaterial3d(assets.wall_material.clone()),
|
MeshMaterial3d(assets.wall_material.clone()),
|
||||||
Transform::from_translation(offset).with_rotation(rotation),
|
Transform::from_translation(offset).with_rotation(rotation),
|
||||||
|
|||||||
17
src/player/assets.rs
Normal file
17
src/player/assets.rs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
use crate::theme::palette::rose_pine::PINE;
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
pub(super) fn generate_pill_mesh(radius: f32, half_length: f32) -> Mesh {
|
||||||
|
Mesh::from(Capsule3d {
|
||||||
|
radius,
|
||||||
|
half_length,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn blue_material() -> StandardMaterial {
|
||||||
|
StandardMaterial {
|
||||||
|
base_color: PINE,
|
||||||
|
emissive: PINE.to_linear() * 3.,
|
||||||
|
..default()
|
||||||
|
}
|
||||||
|
}
|
||||||
25
src/player/components.rs
Normal file
25
src/player/components.rs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
use bevy::prelude::*;
|
||||||
|
use hexx::Hex;
|
||||||
|
|
||||||
|
#[derive(Debug, Reflect, Component)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
#[require(CurrentPosition, MovementSpeed, MovementTarget)]
|
||||||
|
pub struct Player;
|
||||||
|
|
||||||
|
#[derive(Debug, Reflect, Component, Deref, DerefMut, Default)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
pub struct CurrentPosition(pub Hex);
|
||||||
|
|
||||||
|
#[derive(Debug, Reflect, Component, Deref, DerefMut)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
pub struct MovementSpeed(pub f32);
|
||||||
|
|
||||||
|
impl Default for MovementSpeed {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(100.)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Reflect, Component, Deref, DerefMut, Default)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
pub struct MovementTarget(pub Option<Hex>);
|
||||||
4
src/player/events.rs
Normal file
4
src/player/events.rs
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Event)]
|
||||||
|
pub(crate) struct RespawnPlayer;
|
||||||
18
src/player/mod.rs
Normal file
18
src/player/mod.rs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
mod assets;
|
||||||
|
pub mod components;
|
||||||
|
pub mod events;
|
||||||
|
mod systems;
|
||||||
|
|
||||||
|
use bevy::{ecs::system::RunSystemOnce, prelude::*};
|
||||||
|
use components::Player;
|
||||||
|
use events::RespawnPlayer;
|
||||||
|
|
||||||
|
pub(super) fn plugin(app: &mut App) {
|
||||||
|
app.register_type::<Player>()
|
||||||
|
.add_event::<RespawnPlayer>()
|
||||||
|
.add_plugins(systems::plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn spawn_player_command(world: &mut World) {
|
||||||
|
let _ = world.run_system_once(systems::setup::setup);
|
||||||
|
}
|
||||||
9
src/player/systems/despawn.rs
Normal file
9
src/player/systems/despawn.rs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
use crate::player::components::Player;
|
||||||
|
|
||||||
|
pub(crate) fn despawn_players(commands: &mut Commands, query: &Query<Entity, With<Player>>) {
|
||||||
|
for entity in query.iter() {
|
||||||
|
commands.entity(entity).despawn_recursive();
|
||||||
|
}
|
||||||
|
}
|
||||||
77
src/player/systems/input.rs
Normal file
77
src/player/systems/input.rs
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
use crate::{
|
||||||
|
maze::{
|
||||||
|
components::{Floor, Maze},
|
||||||
|
MazeConfig,
|
||||||
|
},
|
||||||
|
player::components::{CurrentPosition, MovementTarget, Player},
|
||||||
|
};
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use hexx::{EdgeDirection, HexOrientation};
|
||||||
|
|
||||||
|
fn create_direction(
|
||||||
|
input: &ButtonInput<KeyCode>,
|
||||||
|
orientation: &HexOrientation,
|
||||||
|
) -> Option<EdgeDirection> {
|
||||||
|
let w = input.pressed(KeyCode::KeyW);
|
||||||
|
let a = input.pressed(KeyCode::KeyA);
|
||||||
|
let s = input.pressed(KeyCode::KeyS);
|
||||||
|
let d = input.pressed(KeyCode::KeyD);
|
||||||
|
|
||||||
|
let direction = match orientation {
|
||||||
|
HexOrientation::Pointy => {
|
||||||
|
match (w, a, s, d) {
|
||||||
|
(true, false, false, false) => Some(EdgeDirection::POINTY_WEST), // W
|
||||||
|
(false, false, true, false) => Some(EdgeDirection::POINTY_EAST), // S
|
||||||
|
(false, true, true, false) => Some(EdgeDirection::POINTY_NORTH_EAST), // A+S
|
||||||
|
(false, false, true, true) => Some(EdgeDirection::POINTY_SOUTH_EAST), // S+D
|
||||||
|
(true, true, false, false) => Some(EdgeDirection::POINTY_NORTH_WEST), // W+A
|
||||||
|
(true, false, false, true) => Some(EdgeDirection::POINTY_SOUTH_WEST), // W+D
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HexOrientation::Flat => {
|
||||||
|
match (w, a, s, d) {
|
||||||
|
(false, true, false, false) => Some(EdgeDirection::FLAT_NORTH), // A
|
||||||
|
(false, false, false, true) => Some(EdgeDirection::FLAT_SOUTH), // D
|
||||||
|
(false, true, true, false) => Some(EdgeDirection::FLAT_NORTH_EAST), // A+S
|
||||||
|
(false, false, true, true) => Some(EdgeDirection::FLAT_SOUTH_EAST), // S+D
|
||||||
|
(true, true, false, false) => Some(EdgeDirection::FLAT_NORTH_WEST), // W+A
|
||||||
|
(true, false, false, true) => Some(EdgeDirection::FLAT_SOUTH_WEST), // W+D
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}?;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
22
src/player/systems/mod.rs
Normal file
22
src/player/systems/mod.rs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
pub mod despawn;
|
||||||
|
mod input;
|
||||||
|
mod movement;
|
||||||
|
pub mod respawn;
|
||||||
|
pub mod setup;
|
||||||
|
pub mod spawn;
|
||||||
|
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use input::player_input;
|
||||||
|
use movement::player_movement;
|
||||||
|
use respawn::respawn_player;
|
||||||
|
|
||||||
|
pub(super) fn plugin(app: &mut App) {
|
||||||
|
app.add_systems(
|
||||||
|
Update,
|
||||||
|
(
|
||||||
|
player_input,
|
||||||
|
player_movement.after(player_input),
|
||||||
|
respawn_player,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
68
src/player/systems/movement.rs
Normal file
68
src/player/systems/movement.rs
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
use crate::{
|
||||||
|
maze::MazeConfig,
|
||||||
|
player::components::{CurrentPosition, MovementSpeed, MovementTarget, Player},
|
||||||
|
};
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use hexx::Hex;
|
||||||
|
|
||||||
|
pub(super) fn player_movement(
|
||||||
|
time: Res<Time>,
|
||||||
|
mut query: Query<
|
||||||
|
(
|
||||||
|
&mut MovementTarget,
|
||||||
|
&MovementSpeed,
|
||||||
|
&mut CurrentPosition,
|
||||||
|
&mut Transform,
|
||||||
|
),
|
||||||
|
With<Player>,
|
||||||
|
>,
|
||||||
|
maze_config: Res<MazeConfig>,
|
||||||
|
) {
|
||||||
|
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;
|
||||||
|
let target_pos = calculate_target_position(&maze_config, target_hex, current_pos.y);
|
||||||
|
|
||||||
|
if should_complete_movement(current_pos, target_pos) {
|
||||||
|
transform.translation = target_pos;
|
||||||
|
current_hex.0 = target_hex;
|
||||||
|
target.0 = None;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
update_position(
|
||||||
|
&mut transform,
|
||||||
|
current_pos,
|
||||||
|
target_pos,
|
||||||
|
speed.0,
|
||||||
|
time.delta_secs(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn should_complete_movement(current_pos: Vec3, target_pos: Vec3) -> bool {
|
||||||
|
(target_pos - current_pos).length() < 0.1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_position(
|
||||||
|
transform: &mut Transform,
|
||||||
|
current_pos: Vec3,
|
||||||
|
target_pos: Vec3,
|
||||||
|
speed: f32,
|
||||||
|
delta_time: f32,
|
||||||
|
) {
|
||||||
|
let direction = target_pos - current_pos;
|
||||||
|
let movement = direction.normalize() * speed * delta_time;
|
||||||
|
|
||||||
|
if movement.length() > direction.length() {
|
||||||
|
transform.translation = target_pos;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
transform.translation += movement;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calculate_target_position(maze_config: &MazeConfig, target_hex: Hex, y: f32) -> Vec3 {
|
||||||
|
let world_pos = maze_config.layout.hex_to_world_pos(target_hex);
|
||||||
|
Vec3::new(world_pos.x, y, world_pos.y)
|
||||||
|
}
|
||||||
21
src/player/systems/respawn.rs
Normal file
21
src/player/systems/respawn.rs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
use crate::{
|
||||||
|
maze::MazeConfig,
|
||||||
|
player::{components::Player, events::RespawnPlayer},
|
||||||
|
};
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
use super::{despawn::despawn_players, spawn::spawn_player};
|
||||||
|
|
||||||
|
pub(crate) fn respawn_player(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut meshes: ResMut<Assets<Mesh>>,
|
||||||
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||||
|
config: Res<MazeConfig>,
|
||||||
|
query: Query<Entity, With<Player>>,
|
||||||
|
mut event_reader: EventReader<RespawnPlayer>,
|
||||||
|
) {
|
||||||
|
for _ in event_reader.read() {
|
||||||
|
despawn_players(&mut commands, &query);
|
||||||
|
spawn_player(&mut commands, &mut meshes, &mut materials, &config);
|
||||||
|
}
|
||||||
|
}
|
||||||
14
src/player/systems/setup.rs
Normal file
14
src/player/systems/setup.rs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
use crate::maze::MazeConfig;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
31
src/player/systems/spawn.rs
Normal file
31
src/player/systems/spawn.rs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
use crate::{
|
||||||
|
maze::MazeConfig,
|
||||||
|
player::{
|
||||||
|
assets::{blue_material, generate_pill_mesh},
|
||||||
|
components::{CurrentPosition, Player},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
pub(super) fn spawn_player(
|
||||||
|
commands: &mut Commands,
|
||||||
|
meshes: &mut ResMut<Assets<Mesh>>,
|
||||||
|
materials: &mut ResMut<Assets<StandardMaterial>>,
|
||||||
|
config: &MazeConfig,
|
||||||
|
) {
|
||||||
|
let player_radius = config.hex_size * 0.5;
|
||||||
|
let player_height = player_radius * 3.5;
|
||||||
|
|
||||||
|
let y_offset = config.height / 2. + player_height / 1.3;
|
||||||
|
|
||||||
|
let start_pos = config.layout.hex_to_world_pos(config.start_pos);
|
||||||
|
|
||||||
|
commands.spawn((
|
||||||
|
Name::new("Player"),
|
||||||
|
Player,
|
||||||
|
CurrentPosition(config.start_pos),
|
||||||
|
Mesh3d(meshes.add(generate_pill_mesh(player_radius, player_height / 2.))),
|
||||||
|
MeshMaterial3d(materials.add(blue_material())),
|
||||||
|
Transform::from_xyz(start_pos.x, y_offset, start_pos.y),
|
||||||
|
));
|
||||||
|
}
|
||||||
@ -2,11 +2,18 @@
|
|||||||
|
|
||||||
use bevy::{input::common_conditions::input_just_pressed, prelude::*};
|
use bevy::{input::common_conditions::input_just_pressed, prelude::*};
|
||||||
|
|
||||||
use crate::maze::spawn_maze as spawn_level_command;
|
use crate::maze::spawn_level_command;
|
||||||
|
use crate::player::spawn_player_command;
|
||||||
use crate::{asset_tracking::LoadResource, audio::Music, screens::Screen};
|
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_command,
|
||||||
|
spawn_player_command.after(spawn_level_command),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
app.load_resource::<GameplayMusic>();
|
app.load_resource::<GameplayMusic>();
|
||||||
app.add_systems(OnEnter(Screen::Gameplay), play_gameplay_music);
|
app.add_systems(OnEnter(Screen::Gameplay), play_gameplay_music);
|
||||||
@ -19,10 +26,6 @@ pub(super) fn plugin(app: &mut App) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn spawn_level(mut commands: Commands) {
|
|
||||||
commands.queue(spawn_level_command);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Resource, Asset, Reflect, Clone)]
|
#[derive(Resource, Asset, Reflect, Clone)]
|
||||||
pub struct GameplayMusic {
|
pub struct GameplayMusic {
|
||||||
#[dependency]
|
#[dependency]
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
//! Reusable UI widgets & theming.
|
//! Reusable UI widgets & theming.
|
||||||
|
|
||||||
// Unused utilities may trigger this lints undesirably.
|
// Unused utilities may trigger this lints undesirably.
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
pub mod interaction;
|
pub mod interaction;
|
||||||
pub mod palette;
|
pub mod palette;
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
pub mod rose_pine;
|
||||||
|
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
|
||||||
pub const BUTTON_HOVERED_BACKGROUND: Color = Color::srgb(0.186, 0.328, 0.573);
|
pub const BUTTON_HOVERED_BACKGROUND: Color = Color::srgb(0.186, 0.328, 0.573);
|
||||||
@ -8,3 +10,11 @@ pub const LABEL_TEXT: Color = Color::srgb(0.867, 0.827, 0.412);
|
|||||||
pub const HEADER_TEXT: Color = Color::srgb(0.867, 0.827, 0.412);
|
pub const HEADER_TEXT: Color = Color::srgb(0.867, 0.827, 0.412);
|
||||||
|
|
||||||
pub const NODE_BACKGROUND: Color = Color::srgb(0.286, 0.478, 0.773);
|
pub const NODE_BACKGROUND: Color = Color::srgb(0.286, 0.478, 0.773);
|
||||||
|
|
||||||
|
pub(super) const fn rgb_u8(red: u8, green: u8, blue: u8) -> Color {
|
||||||
|
Color::srgb(scale(red), scale(green), scale(blue))
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn scale(value: u8) -> f32 {
|
||||||
|
value as f32 / 255.
|
||||||
|
}
|
||||||
18
src/theme/palette/rose_pine.rs
Normal file
18
src/theme/palette/rose_pine.rs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
use super::rgb_u8;
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
pub const BASE: Color = rgb_u8(25, 23, 36);
|
||||||
|
pub const SURFACE: Color = rgb_u8(31, 29, 46);
|
||||||
|
pub const OVERLAY: Color = rgb_u8(38, 35, 58);
|
||||||
|
pub const MUTED: Color = rgb_u8(110, 106, 134);
|
||||||
|
pub const SUBTLE: Color = rgb_u8(144, 140, 170);
|
||||||
|
pub const TEXT: Color = rgb_u8(224, 222, 244);
|
||||||
|
pub const LOVE: Color = rgb_u8(235, 111, 146);
|
||||||
|
pub const GOLD: Color = rgb_u8(246, 193, 119);
|
||||||
|
pub const ROSE: Color = rgb_u8(235, 188, 186);
|
||||||
|
pub const PINE: Color = rgb_u8(49, 116, 143);
|
||||||
|
pub const FOAM: Color = rgb_u8(156, 207, 216);
|
||||||
|
pub const IRIS: Color = rgb_u8(196, 167, 231);
|
||||||
|
pub const HIGHLIGHT_LOW: Color = rgb_u8(33, 32, 46);
|
||||||
|
pub const HIGHLIGHT_MED: Color = rgb_u8(64, 61, 82);
|
||||||
|
pub const HIGHLIGHT_HIGH: Color = rgb_u8(82, 79, 103);
|
||||||
Loading…
Reference in New Issue
Block a user