mirror of
https://github.com/kristoferssolo/maze-ascension.git
synced 2025-10-21 19:20:34 +00:00
Merge pull request #5 from kristoferssolo/fix/maze-generation
Fix/maze generation
This commit is contained in:
commit
3a7f8b6401
447
Cargo.lock
generated
447
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
10
Cargo.toml
10
Cargo.toml
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "maze-ascension"
|
||||
authors = ["Kristofers Solo <dev@kristofers.xyz>"]
|
||||
version = "0.0.5"
|
||||
version = "0.0.6"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
@ -18,8 +18,10 @@ tracing = { version = "0.1", features = [
|
||||
"release_max_level_warn",
|
||||
] }
|
||||
hexx = { version = "0.18", features = ["bevy_reflect", "grid"] }
|
||||
bevy_prototype_lyon = "0.12"
|
||||
hexlab = { version = "0.1", features = ["bevy"] }
|
||||
bevy-inspector-egui = { version = "0.27", optional = true }
|
||||
bevy_egui = { version = "0.30", optional = true }
|
||||
thiserror = "2.0"
|
||||
|
||||
|
||||
[features]
|
||||
@ -31,7 +33,8 @@ dev = [
|
||||
# Improve compile times for dev builds by linking Bevy as a dynamic library.
|
||||
"bevy/dynamic_linking",
|
||||
"bevy/bevy_dev_tools",
|
||||
"bevy-inspector-egui",
|
||||
"dep:bevy-inspector-egui",
|
||||
"dep:bevy_egui",
|
||||
]
|
||||
dev_native = [
|
||||
"dev",
|
||||
@ -40,7 +43,6 @@ dev_native = [
|
||||
# Enable embedded asset hot reloading for native dev builds.
|
||||
"bevy/embedded_watcher",
|
||||
]
|
||||
demo = []
|
||||
|
||||
|
||||
# Idiomatic Bevy code often triggers these lints, and the CI workflow treats them as errors.
|
||||
|
||||
131
QUICKSTART.md
131
QUICKSTART.md
@ -1,131 +0,0 @@
|
||||
_Brought to you by the Bevy Jam working group._
|
||||
|
||||
# Bevy Quickstart
|
||||
|
||||
This template is a great way to get started on a new [Bevy](https://bevyengine.org/) game—especially for a game jam!
|
||||
Start with a [basic project structure](#write-your-game) and [CI / CD](#release-your-game) that can deploy to [itch.io](https://itch.io).
|
||||
You can [try this template in your web browser!](https://the-bevy-flock.itch.io/bevy-quickstart)
|
||||
|
||||
[@ChristopherBiscardi](https://github.com/ChristopherBiscardi) made a video on how to use this template from start to finish:
|
||||
|
||||
[<img src="./docs/img/thumbnail.png" width=40% height=40% alt="A video tutorial for bevy_quickstart"/>](https://www.youtube.com/watch?v=ESBRyXClaYc)
|
||||
|
||||
## Prerequisites
|
||||
|
||||
We assume that you know how to use Bevy already and have seen the [official Quick Start Guide](https://bevyengine.org/learn/quick-start/introduction/).
|
||||
|
||||
If you're new to Bevy, the patterns used in this template may look a bit weird at first glance.
|
||||
See our [Design Document](./docs/design.md) for more information on how we structured the code and why.
|
||||
|
||||
## Create a new game
|
||||
|
||||
Install [`cargo-generate`](https://github.com/cargo-generate/cargo-generate) and run the following command:
|
||||
|
||||
```sh
|
||||
cargo generate TheBevyFlock/bevy_quickstart --branch cargo-generate
|
||||
```
|
||||
|
||||
Then navigate to the newly generated directory and run the following commands:
|
||||
|
||||
```sh
|
||||
git branch --move main
|
||||
cargo update
|
||||
git commit -am 'Initial commit'
|
||||
```
|
||||
|
||||
Then [create a GitHub repository](https://github.com/new) and push your local repository to it.
|
||||
|
||||
<details>
|
||||
<summary>This template can also be set up manually.</summary>
|
||||
|
||||
Navigate to the top of [this GitHub repository](https://github.com/TheBevyFlock/bevy_quickstart/) and select `Use this template > Create a new repository`:
|
||||
|
||||

|
||||
|
||||
Clone your new Github repository to a local repository and push a commit with the following changes:
|
||||
|
||||
- Delete `LICENSE`, `README`, and `docs/` files.
|
||||
- Search for and replace instances of `bevy_quickstart` with the name of your project.
|
||||
- Adjust the `env` variables in [`.github/workflows/release.yaml`](./.github/workflows/release.yaml).
|
||||
|
||||
</details>
|
||||
|
||||
## Write your game
|
||||
|
||||
The best way to get started is to play around with what you find in [`src/demo/`](./src/demo).
|
||||
|
||||
This template comes with a basic project structure that you may find useful:
|
||||
|
||||
| Path | Description |
|
||||
| -------------------------------------------------- | ------------------------------------------------------------------ |
|
||||
| [`src/lib.rs`](./src/lib.rs) | App setup |
|
||||
| [`src/asset_tracking.rs`](./src/asset_tracking.rs) | A high-level way to load collections of asset handles as resources |
|
||||
| [`src/audio/`](./src/audio) | Marker components for sound effects and music |
|
||||
| [`src/demo/`](./src/demo) | Example game mechanics & content (replace with your own code) |
|
||||
| [`src/dev_tools.rs`](./src/dev_tools.rs) | Dev tools for dev builds (press \` aka backtick to toggle) |
|
||||
| [`src/screens/`](./src/screens) | Splash screen, title screen, gameplay screen, etc. |
|
||||
| [`src/theme/`](./src/theme) | Reusable UI widgets & theming |
|
||||
|
||||
Feel free to move things around however you want, though.
|
||||
|
||||
> [!Tip]
|
||||
> Be sure to check out the [3rd-party tools](./docs/tooling.md) we recommend!
|
||||
|
||||
## Run your game
|
||||
|
||||
Running your game locally is very simple:
|
||||
|
||||
- Use `cargo run` to run a native dev build.
|
||||
- Use [`trunk serve`](https://trunkrs.dev/) to run a web dev build.
|
||||
|
||||
If you're using [VS Code](https://code.visualstudio.com/), this template comes with a [`.vscode/tasks.json`](./.vscode/tasks.json) file.
|
||||
|
||||
<details>
|
||||
<summary>Run release builds</summary>
|
||||
|
||||
- Use `cargo run --profile release-native --no-default-features` to run a native release build.
|
||||
- Use `trunk serve --release --no-default-features` to run a web release build.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Linux dependencies</summary>
|
||||
|
||||
If you are using Linux, make sure you take a look at Bevy's [Linux dependencies](https://github.com/bevyengine/bevy/blob/main/docs/linux_dependencies.md).
|
||||
Note that this template enables Wayland support, which requires additional dependencies as detailed in the link above.
|
||||
Wayland is activated by using the `bevy/wayland` feature in the [`Cargo.toml`](./Cargo.toml).
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>(Optional) Improve your compile times</summary>
|
||||
|
||||
[`.cargo/config_fast_builds.toml`](./.cargo/config_fast_builds.toml) contains documentation on how to set up your environment to improve compile times.
|
||||
After you've fiddled with it, rename it to `.cargo/config.toml` to enable it.
|
||||
|
||||
</details>
|
||||
|
||||
## Release your game
|
||||
|
||||
This template uses [GitHub workflows](https://docs.github.com/en/actions/using-workflows) to run tests and build releases.
|
||||
See [Workflows](./docs/workflows.md) for more information.
|
||||
|
||||
## Known Issues
|
||||
|
||||
There are some known issues in Bevy that require some arcane workarounds.
|
||||
To keep this template simple, we have opted not to include those workarounds.
|
||||
You can read about them in the [Known Issues](./docs/known-issues.md) document.
|
||||
|
||||
## License
|
||||
|
||||
The source code in this repository is licensed under any of the following at your option:
|
||||
|
||||
- [CC0-1.0 License](./LICENSE-CC0-1.0.txt)
|
||||
- [MIT License](./LICENSE-MIT.txt)
|
||||
- [Apache License, Version 2.0](./LICENSE-Apache-2.0.txt)
|
||||
|
||||
The CC0 license explicitly does not waive patent rights, but we confirm that we hold no patent rights to anything presented in this repository.
|
||||
|
||||
## Credits
|
||||
|
||||
The [assets](./assets) in this repository are all 3rd-party. See the [credits screen](./src/screens/credits.rs) for more information.
|
||||
@ -1,177 +0,0 @@
|
||||
//! Player sprite animation.
|
||||
//! This is based on multiple examples and may be very different for your game.
|
||||
//! - [Sprite flipping](https://github.com/bevyengine/bevy/blob/latest/examples/2d/sprite_flipping.rs)
|
||||
//! - [Sprite animation](https://github.com/bevyengine/bevy/blob/latest/examples/2d/sprite_animation.rs)
|
||||
//! - [Timers](https://github.com/bevyengine/bevy/blob/latest/examples/time/timers.rs)
|
||||
|
||||
use bevy::prelude::*;
|
||||
use rand::prelude::*;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::{
|
||||
audio::SoundEffect,
|
||||
demo::{movement::MovementController, player::PlayerAssets},
|
||||
AppSet,
|
||||
};
|
||||
|
||||
pub(super) fn plugin(app: &mut App) {
|
||||
// Animate and play sound effects based on controls.
|
||||
app.register_type::<PlayerAnimation>();
|
||||
app.add_systems(
|
||||
Update,
|
||||
(
|
||||
update_animation_timer.in_set(AppSet::TickTimers),
|
||||
(
|
||||
update_animation_movement,
|
||||
update_animation_atlas,
|
||||
trigger_step_sound_effect,
|
||||
)
|
||||
.chain()
|
||||
.run_if(resource_exists::<PlayerAssets>)
|
||||
.in_set(AppSet::Update),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Update the sprite direction and animation state (idling/walking).
|
||||
fn update_animation_movement(
|
||||
mut player_query: Query<(&MovementController, &mut Sprite, &mut PlayerAnimation)>,
|
||||
) {
|
||||
for (controller, mut sprite, mut animation) in &mut player_query {
|
||||
let dx = controller.intent.x;
|
||||
if dx != 0.0 {
|
||||
sprite.flip_x = dx < 0.0;
|
||||
}
|
||||
|
||||
let animation_state = if controller.intent == Vec2::ZERO {
|
||||
PlayerAnimationState::Idling
|
||||
} else {
|
||||
PlayerAnimationState::Walking
|
||||
};
|
||||
animation.update_state(animation_state);
|
||||
}
|
||||
}
|
||||
|
||||
/// Update the animation timer.
|
||||
fn update_animation_timer(time: Res<Time>, mut query: Query<&mut PlayerAnimation>) {
|
||||
for mut animation in &mut query {
|
||||
animation.update_timer(time.delta());
|
||||
}
|
||||
}
|
||||
|
||||
/// Update the texture atlas to reflect changes in the animation.
|
||||
fn update_animation_atlas(mut query: Query<(&PlayerAnimation, &mut TextureAtlas)>) {
|
||||
for (animation, mut atlas) in &mut query {
|
||||
if animation.changed() {
|
||||
atlas.index = animation.get_atlas_index();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// If the player is moving, play a step sound effect synchronized with the
|
||||
/// animation.
|
||||
fn trigger_step_sound_effect(
|
||||
mut commands: Commands,
|
||||
player_assets: Res<PlayerAssets>,
|
||||
mut step_query: Query<&PlayerAnimation>,
|
||||
) {
|
||||
for animation in &mut step_query {
|
||||
if animation.state == PlayerAnimationState::Walking
|
||||
&& animation.changed()
|
||||
&& (animation.frame == 2 || animation.frame == 5)
|
||||
{
|
||||
let rng = &mut rand::thread_rng();
|
||||
let random_step = player_assets.steps.choose(rng).unwrap();
|
||||
commands.spawn((
|
||||
AudioBundle {
|
||||
source: random_step.clone(),
|
||||
settings: PlaybackSettings::DESPAWN,
|
||||
},
|
||||
SoundEffect,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Component that tracks player's animation state.
|
||||
/// It is tightly bound to the texture atlas we use.
|
||||
#[derive(Component, Reflect)]
|
||||
#[reflect(Component)]
|
||||
pub struct PlayerAnimation {
|
||||
timer: Timer,
|
||||
frame: usize,
|
||||
state: PlayerAnimationState,
|
||||
}
|
||||
|
||||
#[derive(Reflect, PartialEq)]
|
||||
pub enum PlayerAnimationState {
|
||||
Idling,
|
||||
Walking,
|
||||
}
|
||||
|
||||
impl PlayerAnimation {
|
||||
/// The number of idle frames.
|
||||
const IDLE_FRAMES: usize = 2;
|
||||
/// The duration of each idle frame.
|
||||
const IDLE_INTERVAL: Duration = Duration::from_millis(500);
|
||||
/// The number of walking frames.
|
||||
const WALKING_FRAMES: usize = 6;
|
||||
/// The duration of each walking frame.
|
||||
const WALKING_INTERVAL: Duration = Duration::from_millis(50);
|
||||
|
||||
fn idling() -> Self {
|
||||
Self {
|
||||
timer: Timer::new(Self::IDLE_INTERVAL, TimerMode::Repeating),
|
||||
frame: 0,
|
||||
state: PlayerAnimationState::Idling,
|
||||
}
|
||||
}
|
||||
|
||||
fn walking() -> Self {
|
||||
Self {
|
||||
timer: Timer::new(Self::WALKING_INTERVAL, TimerMode::Repeating),
|
||||
frame: 0,
|
||||
state: PlayerAnimationState::Walking,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new() -> Self {
|
||||
Self::idling()
|
||||
}
|
||||
|
||||
/// Update animation timers.
|
||||
pub fn update_timer(&mut self, delta: Duration) {
|
||||
self.timer.tick(delta);
|
||||
if !self.timer.finished() {
|
||||
return;
|
||||
}
|
||||
self.frame = (self.frame + 1)
|
||||
% match self.state {
|
||||
PlayerAnimationState::Idling => Self::IDLE_FRAMES,
|
||||
PlayerAnimationState::Walking => Self::WALKING_FRAMES,
|
||||
};
|
||||
}
|
||||
|
||||
/// Update animation state if it changes.
|
||||
pub fn update_state(&mut self, state: PlayerAnimationState) {
|
||||
if self.state != state {
|
||||
match state {
|
||||
PlayerAnimationState::Idling => *self = Self::idling(),
|
||||
PlayerAnimationState::Walking => *self = Self::walking(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether animation changed this tick.
|
||||
pub fn changed(&self) -> bool {
|
||||
self.timer.finished()
|
||||
}
|
||||
|
||||
/// Return sprite index in the atlas.
|
||||
pub fn get_atlas_index(&self) -> usize {
|
||||
match self.state {
|
||||
PlayerAnimationState::Idling => self.frame,
|
||||
PlayerAnimationState::Walking => 6 + self.frame,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
//! Spawn the main level.
|
||||
|
||||
use bevy::{ecs::world::Command, prelude::*};
|
||||
|
||||
use crate::demo::player::SpawnPlayer;
|
||||
|
||||
pub(super) fn plugin(_app: &mut App) {
|
||||
// No setup required for this plugin.
|
||||
// It's still good to have a function here so that we can add some setup
|
||||
// later if needed.
|
||||
}
|
||||
|
||||
/// A [`Command`] to spawn the level.
|
||||
/// Functions that accept only `&mut World` as their parameter implement [`Command`].
|
||||
/// We use this style when a command requires no configuration.
|
||||
pub fn spawn_level(world: &mut World) {
|
||||
// The only thing we have in our level is a player,
|
||||
// but add things like walls etc. here.
|
||||
SpawnPlayer { max_speed: 400.0 }.apply(world);
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
//! Demo gameplay. All of these modules are only intended for demonstration
|
||||
//! purposes and should be replaced with your own game logic.
|
||||
//! Feel free to change the logic found here if you feel like tinkering around
|
||||
//! to get a feeling for the template.
|
||||
|
||||
use bevy::prelude::*;
|
||||
|
||||
mod animation;
|
||||
pub mod level;
|
||||
mod movement;
|
||||
pub mod player;
|
||||
|
||||
pub(super) fn plugin(app: &mut App) {
|
||||
app.add_plugins((
|
||||
animation::plugin,
|
||||
movement::plugin,
|
||||
player::plugin,
|
||||
level::plugin,
|
||||
));
|
||||
}
|
||||
@ -1,84 +0,0 @@
|
||||
//! Handle player input and translate it into movement through a character
|
||||
//! controller. A character controller is the collection of systems that govern
|
||||
//! the movement of characters.
|
||||
//!
|
||||
//! In our case, the character controller has the following logic:
|
||||
//! - Set [`MovementController`] intent based on directional keyboard input.
|
||||
//! This is done in the `player` module, as it is specific to the player
|
||||
//! character.
|
||||
//! - Apply movement based on [`MovementController`] intent and maximum speed.
|
||||
//! - Wrap the character within the window.
|
||||
//!
|
||||
//! Note that the implementation used here is limited for demonstration
|
||||
//! purposes. If you want to move the player in a smoother way,
|
||||
//! consider using a [fixed timestep](https://github.com/bevyengine/bevy/blob/main/examples/movement/physics_in_fixed_timestep.rs).
|
||||
|
||||
use bevy::{prelude::*, window::PrimaryWindow};
|
||||
|
||||
use crate::AppSet;
|
||||
|
||||
pub(super) fn plugin(app: &mut App) {
|
||||
app.register_type::<(MovementController, ScreenWrap)>();
|
||||
|
||||
app.add_systems(
|
||||
Update,
|
||||
(apply_movement, apply_screen_wrap)
|
||||
.chain()
|
||||
.in_set(AppSet::Update),
|
||||
);
|
||||
}
|
||||
|
||||
/// These are the movement parameters for our character controller.
|
||||
/// For now, this is only used for a single player, but it could power NPCs or
|
||||
/// other players as well.
|
||||
#[derive(Component, Reflect)]
|
||||
#[reflect(Component)]
|
||||
pub struct MovementController {
|
||||
/// The direction the character wants to move in.
|
||||
pub intent: Vec2,
|
||||
|
||||
/// Maximum speed in world units per second.
|
||||
/// 1 world unit = 1 pixel when using the default 2D camera and no physics
|
||||
/// engine.
|
||||
pub max_speed: f32,
|
||||
}
|
||||
|
||||
impl Default for MovementController {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
intent: Vec2::ZERO,
|
||||
// 400 pixels per second is a nice default, but we can still vary this per character.
|
||||
max_speed: 400.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_movement(
|
||||
time: Res<Time>,
|
||||
mut movement_query: Query<(&MovementController, &mut Transform)>,
|
||||
) {
|
||||
for (controller, mut transform) in &mut movement_query {
|
||||
let velocity = controller.max_speed * controller.intent;
|
||||
transform.translation += velocity.extend(0.0) * time.delta_seconds();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component, Reflect)]
|
||||
#[reflect(Component)]
|
||||
pub struct ScreenWrap;
|
||||
|
||||
fn apply_screen_wrap(
|
||||
window_query: Query<&Window, With<PrimaryWindow>>,
|
||||
mut wrap_query: Query<&mut Transform, With<ScreenWrap>>,
|
||||
) {
|
||||
let Ok(window) = window_query.get_single() else {
|
||||
return;
|
||||
};
|
||||
let size = window.size() + 256.0;
|
||||
let half_size = size / 2.0;
|
||||
for mut transform in &mut wrap_query {
|
||||
let position = transform.translation.xy();
|
||||
let wrapped = (position + half_size).rem_euclid(size) - half_size;
|
||||
transform.translation = wrapped.extend(transform.translation.z);
|
||||
}
|
||||
}
|
||||
@ -1,153 +0,0 @@
|
||||
//! Plugin handling the player character in particular.
|
||||
//! Note that this is separate from the `movement` module as that could be used
|
||||
//! for other characters as well.
|
||||
|
||||
use bevy::{
|
||||
ecs::{system::RunSystemOnce as _, world::Command},
|
||||
prelude::*,
|
||||
render::texture::{ImageLoaderSettings, ImageSampler},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
asset_tracking::LoadResource,
|
||||
demo::{
|
||||
animation::PlayerAnimation,
|
||||
movement::{MovementController, ScreenWrap},
|
||||
},
|
||||
screens::Screen,
|
||||
AppSet,
|
||||
};
|
||||
|
||||
pub(super) fn plugin(app: &mut App) {
|
||||
app.register_type::<Player>();
|
||||
app.load_resource::<PlayerAssets>();
|
||||
|
||||
// Record directional input as movement controls.
|
||||
app.add_systems(
|
||||
Update,
|
||||
record_player_directional_input.in_set(AppSet::RecordInput),
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Component, Debug, Clone, Copy, PartialEq, Eq, Default, Reflect)]
|
||||
#[reflect(Component)]
|
||||
pub struct Player;
|
||||
|
||||
/// A command to spawn the player character.
|
||||
#[derive(Debug)]
|
||||
pub struct SpawnPlayer {
|
||||
/// See [`MovementController::max_speed`].
|
||||
pub max_speed: f32,
|
||||
}
|
||||
|
||||
impl Command for SpawnPlayer {
|
||||
fn apply(self, world: &mut World) {
|
||||
world.run_system_once_with(self, spawn_player);
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_player(
|
||||
In(config): In<SpawnPlayer>,
|
||||
mut commands: Commands,
|
||||
player_assets: Res<PlayerAssets>,
|
||||
mut texture_atlas_layouts: ResMut<Assets<TextureAtlasLayout>>,
|
||||
) {
|
||||
// A texture atlas is a way to split one image with a grid into multiple
|
||||
// sprites. By attaching it to a [`SpriteBundle`] and providing an index, we
|
||||
// can specify which section of the image we want to see. We will use this
|
||||
// to animate our player character. You can learn more about texture atlases in
|
||||
// this example: https://github.com/bevyengine/bevy/blob/latest/examples/2d/texture_atlas.rs
|
||||
let layout = TextureAtlasLayout::from_grid(UVec2::splat(32), 6, 2, Some(UVec2::splat(1)), None);
|
||||
let texture_atlas_layout = texture_atlas_layouts.add(layout);
|
||||
let player_animation = PlayerAnimation::new();
|
||||
|
||||
commands.spawn((
|
||||
Name::new("Player"),
|
||||
Player,
|
||||
SpriteBundle {
|
||||
texture: player_assets.ducky.clone(),
|
||||
transform: Transform::from_scale(Vec2::splat(8.0).extend(1.0)),
|
||||
..Default::default()
|
||||
},
|
||||
TextureAtlas {
|
||||
layout: texture_atlas_layout.clone(),
|
||||
index: player_animation.get_atlas_index(),
|
||||
},
|
||||
MovementController {
|
||||
max_speed: config.max_speed,
|
||||
..default()
|
||||
},
|
||||
ScreenWrap,
|
||||
player_animation,
|
||||
StateScoped(Screen::Gameplay),
|
||||
));
|
||||
}
|
||||
|
||||
fn record_player_directional_input(
|
||||
input: Res<ButtonInput<KeyCode>>,
|
||||
mut controller_query: Query<&mut MovementController, With<Player>>,
|
||||
) {
|
||||
// Collect directional input.
|
||||
let mut intent = Vec2::ZERO;
|
||||
if input.pressed(KeyCode::KeyW) || input.pressed(KeyCode::ArrowUp) {
|
||||
intent.y += 1.0;
|
||||
}
|
||||
if input.pressed(KeyCode::KeyS) || input.pressed(KeyCode::ArrowDown) {
|
||||
intent.y -= 1.0;
|
||||
}
|
||||
if input.pressed(KeyCode::KeyA) || input.pressed(KeyCode::ArrowLeft) {
|
||||
intent.x -= 1.0;
|
||||
}
|
||||
if input.pressed(KeyCode::KeyD) || input.pressed(KeyCode::ArrowRight) {
|
||||
intent.x += 1.0;
|
||||
}
|
||||
|
||||
// Normalize so that diagonal movement has the same speed as
|
||||
// horizontal and vertical movement.
|
||||
// This should be omitted if the input comes from an analog stick instead.
|
||||
let intent = intent.normalize_or_zero();
|
||||
|
||||
// Apply movement intent to controllers.
|
||||
for mut controller in &mut controller_query {
|
||||
controller.intent = intent;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Resource, Asset, Reflect, Clone)]
|
||||
pub struct PlayerAssets {
|
||||
// This #[dependency] attribute marks the field as a dependency of the Asset.
|
||||
// This means that it will not finish loading until the labeled asset is also loaded.
|
||||
#[dependency]
|
||||
pub ducky: Handle<Image>,
|
||||
#[dependency]
|
||||
pub steps: Vec<Handle<AudioSource>>,
|
||||
}
|
||||
|
||||
impl PlayerAssets {
|
||||
pub const PATH_DUCKY: &'static str = "images/ducky.png";
|
||||
pub const PATH_STEP_1: &'static str = "audio/sound_effects/step1.ogg";
|
||||
pub const PATH_STEP_2: &'static str = "audio/sound_effects/step2.ogg";
|
||||
pub const PATH_STEP_3: &'static str = "audio/sound_effects/step3.ogg";
|
||||
pub const PATH_STEP_4: &'static str = "audio/sound_effects/step4.ogg";
|
||||
}
|
||||
|
||||
impl FromWorld for PlayerAssets {
|
||||
fn from_world(world: &mut World) -> Self {
|
||||
let assets = world.resource::<AssetServer>();
|
||||
Self {
|
||||
ducky: assets.load_with_settings(
|
||||
PlayerAssets::PATH_DUCKY,
|
||||
|settings: &mut ImageLoaderSettings| {
|
||||
// Use `nearest` image sampling to preserve the pixel art style.
|
||||
settings.sampler = ImageSampler::nearest();
|
||||
},
|
||||
),
|
||||
steps: vec![
|
||||
assets.load(PlayerAssets::PATH_STEP_1),
|
||||
assets.load(PlayerAssets::PATH_STEP_2),
|
||||
assets.load(PlayerAssets::PATH_STEP_3),
|
||||
assets.load(PlayerAssets::PATH_STEP_4),
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,33 +0,0 @@
|
||||
//! Development tools for the game. This plugin is only enabled in dev builds.
|
||||
|
||||
use bevy::{
|
||||
dev_tools::{
|
||||
states::log_transitions,
|
||||
ui_debug_overlay::{DebugUiPlugin, UiDebugOptions},
|
||||
},
|
||||
input::common_conditions::input_just_pressed,
|
||||
prelude::*,
|
||||
};
|
||||
|
||||
use bevy_inspector_egui::quick::WorldInspectorPlugin;
|
||||
|
||||
use crate::screens::Screen;
|
||||
|
||||
pub(super) fn plugin(app: &mut App) {
|
||||
// Log `Screen` state transitions.
|
||||
app.add_systems(Update, log_transitions::<Screen>);
|
||||
|
||||
// Toggle the debug overlay for UI.
|
||||
app.add_plugins(DebugUiPlugin);
|
||||
app.add_plugins(WorldInspectorPlugin::default());
|
||||
app.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();
|
||||
}
|
||||
4
src/dev_tools/mod.rs
Normal file
4
src/dev_tools/mod.rs
Normal file
@ -0,0 +1,4 @@
|
||||
mod plugin;
|
||||
mod ui;
|
||||
|
||||
pub use plugin::DevToolsPlugin;
|
||||
36
src/dev_tools/plugin.rs
Normal file
36
src/dev_tools/plugin.rs
Normal file
@ -0,0 +1,36 @@
|
||||
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();
|
||||
}
|
||||
143
src/dev_tools/ui/maze_controls.rs
Normal file
143
src/dev_tools/ui/maze_controls.rs
Normal file
@ -0,0 +1,143 @@
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
use bevy::{prelude::*, window::PrimaryWindow};
|
||||
use hexx::{Hex, HexOrientation};
|
||||
use rand::{thread_rng, Rng};
|
||||
|
||||
use crate::maze::{events::RecreateMazeEvent, MazeConfig, MazePluginLoaded};
|
||||
use bevy_egui::{
|
||||
egui::{self, emath::Numeric, DragValue, TextEdit, Ui},
|
||||
EguiContext,
|
||||
};
|
||||
|
||||
pub(crate) fn maze_controls_ui(world: &mut World) {
|
||||
if world.get_resource::<MazePluginLoaded>().is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
let Ok(egui_context) = world
|
||||
.query_filtered::<&mut EguiContext, With<PrimaryWindow>>()
|
||||
.get_single(world)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut egui_context = egui_context.clone();
|
||||
|
||||
egui::Window::new("Maze Controls").show(egui_context.get_mut(), |ui| {
|
||||
if let Some(mut maze_config) = world.get_resource_mut::<MazeConfig>() {
|
||||
let mut changed = false;
|
||||
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 maze_config.height, 0.5, 1.0..=50.0);
|
||||
changed |= add_drag_value_control(
|
||||
ui,
|
||||
"Hex Size:",
|
||||
&mut maze_config.hex_size,
|
||||
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
|
||||
if changed {
|
||||
maze_config.update();
|
||||
if let Some(mut event_writer) =
|
||||
world.get_resource_mut::<Events<RecreateMazeEvent>>()
|
||||
{
|
||||
event_writer.send(RecreateMazeEvent { floor: 1 });
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn add_drag_value_control<T: Numeric>(
|
||||
ui: &mut egui::Ui,
|
||||
label: &str,
|
||||
value: &mut T,
|
||||
speed: f64,
|
||||
range: RangeInclusive<T>,
|
||||
) -> bool {
|
||||
let mut changed = false;
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(label);
|
||||
let response = ui.add(DragValue::new(value).speed(speed).range(range));
|
||||
changed = response.changed();
|
||||
});
|
||||
changed
|
||||
}
|
||||
|
||||
fn add_position_control(ui: &mut Ui, label: &str, pos: &mut Hex) -> bool {
|
||||
let mut changed = false;
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(label);
|
||||
let response_x = ui.add(DragValue::new(&mut pos.x).speed(1).prefix("x: "));
|
||||
let response_y = ui.add(DragValue::new(&mut pos.y).speed(1).prefix("y: "));
|
||||
changed = response_x.changed() || response_y.changed();
|
||||
});
|
||||
changed
|
||||
}
|
||||
|
||||
fn add_seed_control(ui: &mut Ui, seed: &mut u64) -> bool {
|
||||
let mut changed = false;
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Seed:");
|
||||
|
||||
let mut seed_text = seed.to_string();
|
||||
|
||||
let response = ui.add(
|
||||
TextEdit::singleline(&mut seed_text)
|
||||
.desired_width(150.0)
|
||||
.hint_text("Enter seed"),
|
||||
);
|
||||
|
||||
// Parse text input when changed
|
||||
if response.changed() {
|
||||
if let Ok(new_seed) = seed_text.parse::<u64>() {
|
||||
*seed = new_seed;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// New random seed button
|
||||
if ui.button("🎲").clicked() {
|
||||
*seed = thread_rng().gen();
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// Copy button
|
||||
if ui.button("📋").clicked() {
|
||||
ui.output_mut(|o| o.copied_text = seed.to_string());
|
||||
}
|
||||
});
|
||||
|
||||
changed
|
||||
}
|
||||
|
||||
fn add_orientation_control(ui: &mut Ui, orientation: &mut HexOrientation) -> bool {
|
||||
let mut changed = false;
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Orientation:");
|
||||
|
||||
let response = ui.radio_value(orientation, HexOrientation::Flat, "Flat");
|
||||
changed |= response.changed();
|
||||
|
||||
let response = ui.radio_value(orientation, HexOrientation::Pointy, "Pointy");
|
||||
changed |= response.changed();
|
||||
});
|
||||
|
||||
changed
|
||||
}
|
||||
3
src/dev_tools/ui/mod.rs
Normal file
3
src/dev_tools/ui/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
mod maze_controls;
|
||||
|
||||
pub(crate) use maze_controls::maze_controls_ui;
|
||||
10
src/lib.rs
10
src/lib.rs
@ -1,10 +1,7 @@
|
||||
mod asset_tracking;
|
||||
pub mod audio;
|
||||
#[cfg(feature = "demo")]
|
||||
mod demo;
|
||||
#[cfg(feature = "dev")]
|
||||
mod dev_tools;
|
||||
#[cfg(not(feature = "demo"))]
|
||||
mod maze;
|
||||
mod screens;
|
||||
mod theme;
|
||||
@ -60,9 +57,6 @@ impl Plugin for AppPlugin {
|
||||
// Add other plugins.
|
||||
app.add_plugins((
|
||||
asset_tracking::plugin,
|
||||
#[cfg(feature = "demo")]
|
||||
demo::plugin,
|
||||
#[cfg(not(feature = "demo"))]
|
||||
maze::plugin::MazePlugin,
|
||||
screens::plugin,
|
||||
theme::plugin,
|
||||
@ -70,7 +64,7 @@ impl Plugin for AppPlugin {
|
||||
|
||||
// Enable dev tools for dev builds.
|
||||
#[cfg(feature = "dev")]
|
||||
app.add_plugins(dev_tools::plugin);
|
||||
app.add_plugins(dev_tools::DevToolsPlugin);
|
||||
}
|
||||
}
|
||||
|
||||
@ -91,7 +85,7 @@ fn spawn_camera(mut commands: Commands) {
|
||||
commands.spawn((
|
||||
Name::new("Camera"),
|
||||
Camera3dBundle {
|
||||
transform: Transform::from_xyz(0., 300., 300.).looking_at(Vec3::ZERO, Vec3::Y),
|
||||
transform: Transform::from_xyz(200., 200., 0.).looking_at(Vec3::ZERO, Vec3::Y),
|
||||
..default()
|
||||
},
|
||||
// Render all UI to this camera.
|
||||
|
||||
64
src/maze/assets.rs
Normal file
64
src/maze/assets.rs
Normal file
@ -0,0 +1,64 @@
|
||||
use super::MazeConfig;
|
||||
use bevy::prelude::*;
|
||||
use std::f32::consts::FRAC_PI_2;
|
||||
|
||||
const WALL_OVERLAP_MODIFIER: f32 = 1.25;
|
||||
const HEX_SIDES: usize = 6;
|
||||
const WHITE_EMISSION_INTENSITY: f32 = 10.;
|
||||
|
||||
pub(crate) struct MazeAssets {
|
||||
pub(crate) hex_mesh: Handle<Mesh>,
|
||||
pub(crate) wall_mesh: Handle<Mesh>,
|
||||
pub(crate) hex_material: Handle<StandardMaterial>,
|
||||
pub(crate) wall_material: Handle<StandardMaterial>,
|
||||
}
|
||||
|
||||
impl MazeAssets {
|
||||
pub(crate) fn new(
|
||||
meshes: &mut ResMut<Assets<Mesh>>,
|
||||
materials: &mut ResMut<Assets<StandardMaterial>>,
|
||||
config: &MazeConfig,
|
||||
) -> MazeAssets {
|
||||
MazeAssets {
|
||||
hex_mesh: meshes.add(generate_hex_mesh(config.hex_size, config.height)),
|
||||
wall_mesh: meshes.add(generate_square_mesh(
|
||||
config.hex_size + config.wall_size() / WALL_OVERLAP_MODIFIER,
|
||||
config.wall_size(),
|
||||
)),
|
||||
hex_material: materials.add(white_material()),
|
||||
wall_material: materials.add(Color::BLACK),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_hex_mesh(radius: f32, depth: f32) -> Mesh {
|
||||
let hexagon = RegularPolygon {
|
||||
sides: HEX_SIDES,
|
||||
circumcircle: Circle::new(radius),
|
||||
};
|
||||
let prism_shape = Extrusion::new(hexagon, depth);
|
||||
let rotation = Quat::from_rotation_x(FRAC_PI_2);
|
||||
|
||||
Mesh::from(prism_shape).rotated_by(rotation)
|
||||
}
|
||||
|
||||
fn generate_square_mesh(depth: f32, wall_size: f32) -> Mesh {
|
||||
let square = Rectangle::new(wall_size, wall_size);
|
||||
let rectangular_prism = Extrusion::new(square, depth);
|
||||
let rotation = Quat::from_rotation_x(FRAC_PI_2);
|
||||
|
||||
Mesh::from(rectangular_prism).rotated_by(rotation)
|
||||
}
|
||||
|
||||
fn white_material() -> StandardMaterial {
|
||||
StandardMaterial {
|
||||
base_color: Color::WHITE,
|
||||
emissive: LinearRgba::new(
|
||||
WHITE_EMISSION_INTENSITY,
|
||||
WHITE_EMISSION_INTENSITY,
|
||||
WHITE_EMISSION_INTENSITY,
|
||||
WHITE_EMISSION_INTENSITY,
|
||||
),
|
||||
..default()
|
||||
}
|
||||
}
|
||||
13
src/maze/components.rs
Normal file
13
src/maze/components.rs
Normal file
@ -0,0 +1,13 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
#[derive(Debug, Reflect, Component)]
|
||||
#[reflect(Component)]
|
||||
pub(crate) struct MazeFloor(pub(crate) u8);
|
||||
|
||||
#[derive(Debug, Reflect, Component)]
|
||||
#[reflect(Component)]
|
||||
pub(crate) struct MazeTile;
|
||||
|
||||
#[derive(Debug, Reflect, Component)]
|
||||
#[reflect(Component)]
|
||||
pub(crate) struct MazeWall;
|
||||
6
src/maze/events.rs
Normal file
6
src/maze/events.rs
Normal file
@ -0,0 +1,6 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
#[derive(Debug, Event)]
|
||||
pub(crate) struct RecreateMazeEvent {
|
||||
pub(crate) floor: u8,
|
||||
}
|
||||
220
src/maze/grid.rs
220
src/maze/grid.rs
@ -1,220 +0,0 @@
|
||||
use bevy::{
|
||||
color::palettes::css::{BLACK, GREEN, RED},
|
||||
pbr::wireframe::{WireframeConfig, WireframePlugin},
|
||||
prelude::*,
|
||||
utils::hashbrown::HashMap,
|
||||
};
|
||||
use bevy_prototype_lyon::{
|
||||
draw::{Fill, Stroke},
|
||||
entity::ShapeBundle,
|
||||
path::PathBuilder,
|
||||
plugin::ShapePlugin,
|
||||
};
|
||||
use hexx::{EdgeDirection, Hex};
|
||||
use rand::{prelude::SliceRandom, rngs::ThreadRng, thread_rng};
|
||||
|
||||
use super::{
|
||||
resource::{Layout, MazeConfig, HEX_SIZE},
|
||||
tile::{Tile, TileBundle, Walls},
|
||||
};
|
||||
|
||||
pub(super) fn plugin(app: &mut App) {
|
||||
app.add_plugins((ShapePlugin, WireframePlugin));
|
||||
app.init_resource::<MazeConfig>();
|
||||
app.init_resource::<Layout>();
|
||||
app.insert_resource(WireframeConfig {
|
||||
global: false,
|
||||
..default()
|
||||
});
|
||||
}
|
||||
|
||||
pub(super) fn _spawn_hex_grid(mut commands: Commands, config: Res<MazeConfig>) {
|
||||
let radius = config.radius as i32;
|
||||
|
||||
for q in -radius..=radius {
|
||||
let r1 = (-radius).max(-q - radius);
|
||||
let r2 = radius.min(-q + radius);
|
||||
for r in r1..=r2 {
|
||||
let tile = Tile::new(q, r);
|
||||
commands.spawn((
|
||||
Name::new(format!("Tile {}", &tile.to_string())),
|
||||
TileBundle {
|
||||
hex: tile,
|
||||
..default()
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn _generate_maze(
|
||||
mut commands: Commands,
|
||||
query: Query<(Entity, &Tile, &Walls)>,
|
||||
config: Res<MazeConfig>,
|
||||
) {
|
||||
let mut tiles = query
|
||||
.into_iter()
|
||||
.map(|(entity, tile, walls)| (tile.hex, (entity, tile.clone(), walls.clone())))
|
||||
.collect();
|
||||
|
||||
let mut rng = thread_rng();
|
||||
_recursive_maze(&mut tiles, config.start_pos, &mut rng);
|
||||
|
||||
for (entity, tile, walls) in tiles.values() {
|
||||
commands
|
||||
.entity(*entity)
|
||||
.insert(tile.clone())
|
||||
.insert(walls.clone());
|
||||
}
|
||||
}
|
||||
|
||||
fn _recursive_maze(
|
||||
tiles: &mut HashMap<Hex, (Entity, Tile, Walls)>,
|
||||
current_hex: Hex,
|
||||
rng: &mut ThreadRng,
|
||||
) {
|
||||
{
|
||||
let (_, tile, _) = tiles.get_mut(¤t_hex).unwrap();
|
||||
tile._visit();
|
||||
}
|
||||
|
||||
let mut directions = EdgeDirection::ALL_DIRECTIONS;
|
||||
directions.shuffle(rng);
|
||||
|
||||
for direction in directions.into_iter() {
|
||||
let neighbor_hex = current_hex + direction;
|
||||
if let Some((_, neighbor_tile, _)) = tiles.get(&neighbor_hex) {
|
||||
if !neighbor_tile.visited {
|
||||
_remove_wall_between(tiles, current_hex, neighbor_hex, direction);
|
||||
_recursive_maze(tiles, neighbor_hex, rng);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn _remove_wall_between(
|
||||
tiles: &mut HashMap<Hex, (Entity, Tile, Walls)>,
|
||||
current_hex: Hex,
|
||||
neighbor_hex: Hex,
|
||||
direction: EdgeDirection,
|
||||
) {
|
||||
{
|
||||
let (_, _, walls) = tiles.get_mut(¤t_hex).unwrap();
|
||||
walls.0[direction.index() as usize] = false;
|
||||
}
|
||||
{
|
||||
let (_, _, walls) = tiles.get_mut(&neighbor_hex).unwrap();
|
||||
walls.0[direction.const_neg().index() as usize] = false;
|
||||
}
|
||||
}
|
||||
|
||||
fn _add_hex_tile(
|
||||
commands: &mut Commands,
|
||||
position: Vec3,
|
||||
size: f32,
|
||||
tile: &Tile,
|
||||
walls: &Walls,
|
||||
fill_color: Color,
|
||||
layout: &Layout,
|
||||
) {
|
||||
let hex_points = tile
|
||||
.hex
|
||||
.all_vertices()
|
||||
.into_iter()
|
||||
.map(|v| {
|
||||
let mut layout = layout.clone();
|
||||
layout.origin = position.xy();
|
||||
layout.hex_size = Vec2::splat(size);
|
||||
layout.hex_to_world_pos(v.origin + v.direction)
|
||||
})
|
||||
.collect::<Vec<Vec2>>();
|
||||
|
||||
let mut path_builder = PathBuilder::new();
|
||||
path_builder.move_to(hex_points[0]);
|
||||
for point in &hex_points[1..] {
|
||||
path_builder.line_to(*point);
|
||||
}
|
||||
path_builder.close();
|
||||
let hexagon = path_builder.build();
|
||||
|
||||
// Create the hexagon fill
|
||||
commands
|
||||
.spawn((
|
||||
ShapeBundle {
|
||||
path: hexagon,
|
||||
spatial: SpatialBundle {
|
||||
transform: Transform::from_xyz(position.x, position.y, 0.),
|
||||
..default()
|
||||
},
|
||||
..default()
|
||||
},
|
||||
Fill::color(fill_color),
|
||||
))
|
||||
.with_children(|p| {
|
||||
p.spawn(Text2dBundle {
|
||||
text: Text {
|
||||
sections: vec![TextSection {
|
||||
value: tile.to_string(),
|
||||
style: TextStyle {
|
||||
font_size: 16.,
|
||||
color: Color::BLACK,
|
||||
..default()
|
||||
},
|
||||
}],
|
||||
..default()
|
||||
},
|
||||
transform: Transform::from_xyz(position.x * 2., position.y * 2., 1.),
|
||||
..default()
|
||||
});
|
||||
});
|
||||
|
||||
// Draw walls
|
||||
for direction in EdgeDirection::iter() {
|
||||
let idx = direction.index() as usize;
|
||||
if walls[idx] {
|
||||
let start = hex_points[idx];
|
||||
let end = hex_points[(idx + 1) % 6];
|
||||
let mut line_builder = PathBuilder::new();
|
||||
line_builder.move_to(start);
|
||||
line_builder.line_to(end);
|
||||
let line = line_builder.build();
|
||||
|
||||
commands.spawn((
|
||||
ShapeBundle {
|
||||
path: line,
|
||||
spatial: SpatialBundle {
|
||||
transform: Transform::from_xyz(position.x, position.y, 1.),
|
||||
..default()
|
||||
},
|
||||
..default()
|
||||
},
|
||||
Stroke::new(BLACK, 2.),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn _render_maze(
|
||||
mut commands: Commands,
|
||||
query: Query<(&Tile, &mut Walls)>,
|
||||
layout: Res<Layout>,
|
||||
config: Res<MazeConfig>,
|
||||
) {
|
||||
for (tile, walls) in query.iter() {
|
||||
let world_pos = layout.hex_to_world_pos(tile.hex).extend(0.);
|
||||
let fill_color = match tile.hex {
|
||||
pos if pos == config.start_pos => GREEN.into(),
|
||||
pos if pos == config.end_pos => RED.into(),
|
||||
_ => Color::srgb(0.8, 0.8, 0.8),
|
||||
};
|
||||
_add_hex_tile(
|
||||
&mut commands,
|
||||
world_pos,
|
||||
HEX_SIZE,
|
||||
tile,
|
||||
walls,
|
||||
fill_color,
|
||||
&layout,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,11 +1,14 @@
|
||||
use bevy::{ecs::world::Command, prelude::*};
|
||||
use plugin::MazePlugin;
|
||||
pub mod grid;
|
||||
mod assets;
|
||||
mod components;
|
||||
pub mod events;
|
||||
pub mod plugin;
|
||||
pub mod prism;
|
||||
pub mod resource;
|
||||
pub mod tile;
|
||||
mod resources;
|
||||
mod systems;
|
||||
|
||||
pub fn spawn_grid(world: &mut World) {
|
||||
pub use resources::{MazeConfig, MazePluginLoaded};
|
||||
|
||||
pub fn spawn_maze(world: &mut World) {
|
||||
MazePlugin.apply(world);
|
||||
}
|
||||
|
||||
@ -3,27 +3,26 @@ use bevy::{
|
||||
prelude::*,
|
||||
};
|
||||
|
||||
use super::{grid, prism};
|
||||
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.add_plugins(prism::plugin);
|
||||
app.add_plugins(grid::plugin);
|
||||
// app.insert_resource(AmbientLight {
|
||||
// brightness: f32::MAX,
|
||||
// color: Color::WHITE,
|
||||
// });
|
||||
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.run_system_once(spawn_hex_grid);
|
||||
// world.run_system_once(generate_maze);
|
||||
// world.run_system_once(render_maze);
|
||||
world.run_system_once(prism::setup);
|
||||
world.insert_resource(MazePluginLoaded);
|
||||
world.run_system_once(systems::setup::setup);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,146 +0,0 @@
|
||||
use bevy::prelude::*;
|
||||
use core::f32;
|
||||
use std::f32::consts::{FRAC_PI_2, FRAC_PI_3};
|
||||
|
||||
use super::{
|
||||
resource::{Layout, MazeConfig, HEX_SIZE},
|
||||
tile::Tile,
|
||||
};
|
||||
|
||||
pub(super) fn plugin(_app: &mut App) {}
|
||||
const WALL_SIZE: f32 = 1.0;
|
||||
|
||||
pub(super) fn setup(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
config: Res<MazeConfig>,
|
||||
layout: Res<Layout>,
|
||||
) {
|
||||
let radius = config.radius as i32;
|
||||
|
||||
let assets = create_base_assets(&mut meshes, &mut materials, &config);
|
||||
// spawn_single_hex_tile(&mut commands, &assets, &config);
|
||||
commands
|
||||
.spawn((
|
||||
Name::new("Floor"),
|
||||
SpatialBundle {
|
||||
transform: Transform::from_translation(Vec3::ZERO),
|
||||
..default()
|
||||
},
|
||||
))
|
||||
.with_children(|parent| {
|
||||
for q in -radius..=radius {
|
||||
let r1 = (-radius).max(-q - radius);
|
||||
let r2 = radius.min(-q + radius);
|
||||
for r in r1..=r2 {
|
||||
let tile = Tile::new(q, r);
|
||||
spawn_single_hex_tile(parent, &tile, &layout, &assets, &config);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn spawn_single_hex_tile(
|
||||
parent: &mut ChildBuilder,
|
||||
tile: &Tile,
|
||||
layout: &Res<Layout>,
|
||||
assets: &MazeAssets,
|
||||
config: &Res<MazeConfig>,
|
||||
) {
|
||||
let pos = tile.to_vec3(layout);
|
||||
parent
|
||||
.spawn((
|
||||
Name::new(format!("Hex {}", &tile.to_string())),
|
||||
PbrBundle {
|
||||
mesh: assets.hex_mesh.clone(),
|
||||
material: assets.hex_material.clone(),
|
||||
transform: Transform::from_translation(pos),
|
||||
..default()
|
||||
},
|
||||
))
|
||||
.with_children(|parent| spawn_walls(parent, assets, config));
|
||||
}
|
||||
|
||||
fn spawn_walls(parent: &mut ChildBuilder, asstets: &MazeAssets, config: &Res<MazeConfig>) {
|
||||
let y_offset = config.height / 2.;
|
||||
let z_rotation = Quat::from_rotation_z(-FRAC_PI_2);
|
||||
|
||||
for i in 0..6 {
|
||||
let wall_angle = FRAC_PI_3 * i as f32;
|
||||
|
||||
let x_offset = (HEX_SIZE - WALL_SIZE) * f32::cos(wall_angle);
|
||||
let z_offset = (HEX_SIZE - WALL_SIZE) * f32::sin(wall_angle);
|
||||
let pos = Vec3::new(x_offset, y_offset, z_offset);
|
||||
|
||||
let x_rotation = Quat::from_rotation_x(wall_angle + FRAC_PI_2);
|
||||
let final_rotation = z_rotation * x_rotation;
|
||||
|
||||
spawn_single_wall(parent, asstets, final_rotation, pos);
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_single_wall(
|
||||
parent: &mut ChildBuilder,
|
||||
asstets: &MazeAssets,
|
||||
rotation: Quat,
|
||||
offset: Vec3,
|
||||
) {
|
||||
parent.spawn((
|
||||
Name::new("Wall"),
|
||||
PbrBundle {
|
||||
mesh: asstets.wall_mesh.clone(),
|
||||
material: asstets.wall_material.clone(),
|
||||
transform: Transform::from_translation(offset).with_rotation(rotation),
|
||||
..default()
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
fn create_base_assets(
|
||||
meshes: &mut ResMut<Assets<Mesh>>,
|
||||
materials: &mut ResMut<Assets<StandardMaterial>>,
|
||||
config: &Res<MazeConfig>,
|
||||
) -> MazeAssets {
|
||||
MazeAssets {
|
||||
hex_mesh: meshes.add(generate_hex_mesh(HEX_SIZE, config.height)),
|
||||
wall_mesh: meshes.add(generate_square_mesh(HEX_SIZE)),
|
||||
hex_material: materials.add(white_material()),
|
||||
wall_material: materials.add(Color::BLACK),
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_hex_mesh(radius: f32, depth: f32) -> Mesh {
|
||||
let hexagon = RegularPolygon {
|
||||
sides: 6,
|
||||
circumcircle: Circle::new(radius),
|
||||
};
|
||||
let prism_shape = Extrusion::new(hexagon, depth);
|
||||
let rotation = Quat::from_rotation_x(FRAC_PI_2);
|
||||
|
||||
Mesh::from(prism_shape).rotated_by(rotation)
|
||||
}
|
||||
|
||||
fn generate_square_mesh(depth: f32) -> Mesh {
|
||||
let square = Rectangle::new(WALL_SIZE, WALL_SIZE);
|
||||
let rectangular_prism = Extrusion::new(square, depth);
|
||||
let rotation = Quat::from_rotation_x(FRAC_PI_2);
|
||||
|
||||
Mesh::from(rectangular_prism).rotated_by(rotation)
|
||||
}
|
||||
|
||||
fn white_material() -> StandardMaterial {
|
||||
let val = 10.;
|
||||
StandardMaterial {
|
||||
base_color: Color::WHITE,
|
||||
emissive: LinearRgba::new(val, val, val, val),
|
||||
..default()
|
||||
}
|
||||
}
|
||||
|
||||
struct MazeAssets {
|
||||
hex_mesh: Handle<Mesh>,
|
||||
wall_mesh: Handle<Mesh>,
|
||||
hex_material: Handle<StandardMaterial>,
|
||||
wall_material: Handle<StandardMaterial>,
|
||||
}
|
||||
@ -1,51 +0,0 @@
|
||||
use bevy::prelude::*;
|
||||
use hexx::{Hex, HexLayout, HexOrientation};
|
||||
use rand::{thread_rng, Rng};
|
||||
|
||||
pub(crate) const HEX_SIZE: f32 = 6.;
|
||||
|
||||
#[derive(Debug, Reflect, Resource)]
|
||||
#[reflect(Resource)]
|
||||
pub struct MazeConfig {
|
||||
pub radius: u32,
|
||||
pub height: f32,
|
||||
pub start_pos: Hex,
|
||||
pub end_pos: Hex,
|
||||
}
|
||||
|
||||
impl Default for MazeConfig {
|
||||
fn default() -> Self {
|
||||
let mut rng = thread_rng();
|
||||
let radius = 11;
|
||||
let start_pos = Hex::new(
|
||||
rng.gen_range(-radius..radius),
|
||||
rng.gen_range(-radius..radius),
|
||||
);
|
||||
let end_pos = Hex::new(
|
||||
rng.gen_range(-radius..radius),
|
||||
rng.gen_range(-radius..radius),
|
||||
);
|
||||
debug!("Start pos: ({},{})", start_pos.x, start_pos.y);
|
||||
debug!("End pos: ({},{})", end_pos.x, end_pos.y);
|
||||
Self {
|
||||
radius: radius as u32,
|
||||
height: 20.,
|
||||
start_pos,
|
||||
end_pos,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Reflect, Resource, Deref, DerefMut, Clone)]
|
||||
#[reflect(Resource)]
|
||||
pub struct Layout(pub HexLayout);
|
||||
|
||||
impl FromWorld for Layout {
|
||||
fn from_world(_world: &mut World) -> Self {
|
||||
Self(HexLayout {
|
||||
orientation: HexOrientation::Pointy,
|
||||
hex_size: Vec2::splat(HEX_SIZE),
|
||||
..default()
|
||||
})
|
||||
}
|
||||
}
|
||||
100
src/maze/resources.rs
Normal file
100
src/maze/resources.rs
Normal file
@ -0,0 +1,100 @@
|
||||
use std::num::TryFromIntError;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use hexx::{Hex, HexLayout, HexOrientation};
|
||||
use rand::{rngs::StdRng, thread_rng, Rng, SeedableRng};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Default, Reflect, Resource)]
|
||||
#[reflect(Resource)]
|
||||
pub struct MazePluginLoaded;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum MazeConfigError {
|
||||
#[error("Failed to convert radius from u32 to i32: {0}")]
|
||||
RadiusConverions(#[from] TryFromIntError),
|
||||
}
|
||||
|
||||
#[derive(Debug, Reflect, Resource)]
|
||||
#[reflect(Resource)]
|
||||
pub struct MazeConfig {
|
||||
pub radius: u32,
|
||||
pub height: f32,
|
||||
pub hex_size: f32,
|
||||
pub start_pos: Hex,
|
||||
pub end_pos: Hex,
|
||||
pub seed: u64,
|
||||
pub layout: HexLayout,
|
||||
}
|
||||
|
||||
impl MazeConfig {
|
||||
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)?;
|
||||
|
||||
debug!("Start pos: ({},{})", start_pos.x, start_pos.y);
|
||||
debug!("End pos: ({},{})", 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 {
|
||||
self.hex_size / 6.
|
||||
}
|
||||
|
||||
pub fn wall_offset(&self) -> f32 {
|
||||
self.hex_size - self.wall_size()
|
||||
}
|
||||
|
||||
pub fn update(&mut self) {
|
||||
self.layout.hex_size = Vec2::splat(self.hex_size);
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MazeConfig {
|
||||
fn default() -> Self {
|
||||
Self::new_unchecked(7, 20., 6., HexOrientation::Flat, None)
|
||||
}
|
||||
}
|
||||
|
||||
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),
|
||||
))
|
||||
}
|
||||
3
src/maze/systems/despawn.rs
Normal file
3
src/maze/systems/despawn.rs
Normal file
@ -0,0 +1,3 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
use crate::maze::components::MazeFloor;
|
||||
3
src/maze/systems/mod.rs
Normal file
3
src/maze/systems/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
pub mod recreation;
|
||||
pub mod setup;
|
||||
mod spawn;
|
||||
27
src/maze/systems/recreation.rs
Normal file
27
src/maze/systems/recreation.rs
Normal file
@ -0,0 +1,27 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
use crate::maze::{components::MazeFloor, events::RecreateMazeEvent, MazeConfig};
|
||||
|
||||
use super::setup::setup_maze;
|
||||
|
||||
pub(crate) fn handle_maze_recreation_event(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
config: Res<MazeConfig>,
|
||||
query: Query<(Entity, &MazeFloor)>,
|
||||
mut event_reader: EventReader<RecreateMazeEvent>,
|
||||
) {
|
||||
for event in event_reader.read() {
|
||||
despawn_floor(&mut commands, &query, event.floor);
|
||||
setup_maze(&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();
|
||||
}
|
||||
}
|
||||
}
|
||||
45
src/maze/systems/setup.rs
Normal file
45
src/maze/systems/setup.rs
Normal file
@ -0,0 +1,45 @@
|
||||
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(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
config: Res<MazeConfig>,
|
||||
) {
|
||||
setup_maze(&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),
|
||||
SpatialBundle {
|
||||
transform: Transform::from_translation(Vec3::ZERO),
|
||||
..default()
|
||||
},
|
||||
))
|
||||
.with_children(|parent| {
|
||||
for tile in maze.values() {
|
||||
spawn_single_hex_tile(parent, &assets, tile, config)
|
||||
}
|
||||
});
|
||||
}
|
||||
77
src/maze/systems/spawn.rs
Normal file
77
src/maze/systems/spawn.rs
Normal file
@ -0,0 +1,77 @@
|
||||
use std::f32::consts::{FRAC_PI_2, FRAC_PI_3, FRAC_PI_6};
|
||||
|
||||
use bevy::prelude::*;
|
||||
use hexlab::prelude::*;
|
||||
use hexx::HexOrientation;
|
||||
|
||||
use crate::maze::{
|
||||
assets::MazeAssets,
|
||||
components::{MazeTile, MazeWall},
|
||||
MazeConfig,
|
||||
};
|
||||
|
||||
pub(super) fn spawn_single_hex_tile(
|
||||
parent: &mut ChildBuilder,
|
||||
assets: &MazeAssets,
|
||||
tile: &HexTile,
|
||||
config: &MazeConfig,
|
||||
) {
|
||||
let world_pos = tile.to_vec3(&config.layout);
|
||||
let rotation = match config.layout.orientation {
|
||||
HexOrientation::Pointy => Quat::from_rotation_y(0.0),
|
||||
HexOrientation::Flat => Quat::from_rotation_y(FRAC_PI_6), // 30 degrees rotation
|
||||
};
|
||||
|
||||
parent
|
||||
.spawn((
|
||||
Name::new(format!("Hex {}", tile)),
|
||||
MazeTile,
|
||||
PbrBundle {
|
||||
mesh: assets.hex_mesh.clone(),
|
||||
material: assets.hex_material.clone(),
|
||||
transform: Transform::from_translation(world_pos).with_rotation(rotation),
|
||||
..default()
|
||||
},
|
||||
))
|
||||
.with_children(|parent| spawn_walls(parent, assets, config, tile.walls()));
|
||||
}
|
||||
|
||||
fn spawn_walls(parent: &mut ChildBuilder, assets: &MazeAssets, config: &MazeConfig, walls: &Walls) {
|
||||
let z_rotation = Quat::from_rotation_z(-FRAC_PI_2);
|
||||
let y_offset = config.height / 2.;
|
||||
|
||||
for i in 0..6 {
|
||||
if !walls.contains(i) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let wall_angle = -FRAC_PI_3 * i as f32;
|
||||
|
||||
let x_offset = config.wall_offset() * f32::cos(wall_angle);
|
||||
let z_offset = config.wall_offset() * f32::sin(wall_angle);
|
||||
let pos = Vec3::new(x_offset, y_offset, z_offset);
|
||||
|
||||
let x_rotation = Quat::from_rotation_x(wall_angle + FRAC_PI_2);
|
||||
let final_rotation = z_rotation * x_rotation;
|
||||
|
||||
spawn_single_wall(parent, assets, final_rotation, pos);
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_single_wall(
|
||||
parent: &mut ChildBuilder,
|
||||
asstets: &MazeAssets,
|
||||
rotation: Quat,
|
||||
offset: Vec3,
|
||||
) {
|
||||
parent.spawn((
|
||||
Name::new("Wall"),
|
||||
MazeWall,
|
||||
PbrBundle {
|
||||
mesh: asstets.wall_mesh.clone(),
|
||||
material: asstets.wall_material.clone(),
|
||||
transform: Transform::from_translation(offset).with_rotation(rotation),
|
||||
..default()
|
||||
},
|
||||
));
|
||||
}
|
||||
@ -1,55 +0,0 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use hexx::{Hex, HexLayout};
|
||||
|
||||
#[derive(Debug, Reflect, Component, Default, PartialEq, Eq, Hash, Clone)]
|
||||
#[reflect(Component)]
|
||||
pub struct Tile {
|
||||
pub hex: Hex,
|
||||
pub visited: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Reflect, Component, Deref, DerefMut, Clone)]
|
||||
#[reflect(Component)]
|
||||
pub struct Walls(pub [bool; 6]);
|
||||
|
||||
#[derive(Debug, Reflect, Bundle, Default)]
|
||||
pub struct TileBundle {
|
||||
pub hex: Tile,
|
||||
pub walls: Walls,
|
||||
}
|
||||
|
||||
impl Tile {
|
||||
pub fn new(q: i32, r: i32) -> Self {
|
||||
Self {
|
||||
hex: Hex::new(q, r),
|
||||
visited: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn _visit(&mut self) {
|
||||
self.visited = true;
|
||||
}
|
||||
|
||||
pub fn to_vec2(&self, layout: &HexLayout) -> Vec2 {
|
||||
layout.hex_to_world_pos(self.hex)
|
||||
}
|
||||
|
||||
pub fn to_vec3(&self, layout: &HexLayout) -> Vec3 {
|
||||
let pos = self.to_vec2(layout);
|
||||
Vec3::new(pos.x, 0., pos.y)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Tile {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "({},{})", self.hex.x, self.hex.y)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Walls {
|
||||
fn default() -> Self {
|
||||
Self([true; 6])
|
||||
}
|
||||
}
|
||||
@ -2,10 +2,7 @@
|
||||
|
||||
use bevy::{input::common_conditions::input_just_pressed, prelude::*};
|
||||
|
||||
#[cfg(feature = "demo")]
|
||||
use crate::demo::level::spawn_level as spawn_level_command;
|
||||
#[cfg(not(feature = "demo"))]
|
||||
use crate::maze::spawn_grid as spawn_level_command;
|
||||
use crate::maze::spawn_maze as spawn_level_command;
|
||||
use crate::{asset_tracking::LoadResource, audio::Music, screens::Screen};
|
||||
|
||||
pub(super) fn plugin(app: &mut App) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user