feat(player): add collision check

This commit is contained in:
Kristofers Solo 2024-12-12 17:24:14 +02:00
parent 6bdfc2f672
commit 30d6cf5fba
13 changed files with 150 additions and 86 deletions

2
Cargo.lock generated
View File

@ -2590,8 +2590,6 @@ checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
[[package]] [[package]]
name = "hexlab" name = "hexlab"
version = "0.2.1" version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9414db96618050e085e36c92ed9e87f0140d3a611914764ee73c7c0e4ebaa2de"
dependencies = [ dependencies = [
"bevy", "bevy",
"hexx", "hexx",

View File

@ -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 = { path = "../hexlab", 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"

View File

@ -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;

View File

@ -1,7 +1,7 @@
use bevy::{ecs::system::RunSystemOnce, prelude::*}; use bevy::{ecs::system::RunSystemOnce, prelude::*};
use events::RecreateMazeEvent; use events::RecreateMazeEvent;
mod assets; mod assets;
mod components; pub mod components;
pub mod events; pub mod events;
mod resources; mod resources;
mod systems; mod systems;
@ -17,5 +17,5 @@ pub(super) fn plugin(app: &mut App) {
pub fn spawn_level_command(world: &mut World) { pub fn spawn_level_command(world: &mut World) {
world.insert_resource(MazePluginLoaded); world.insert_resource(MazePluginLoaded);
world.run_system_once(systems::setup::setup); let _ = world.run_system_once(systems::setup::setup);
} }

View File

@ -1,6 +1,6 @@
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::setup::setup_maze;
@ -9,7 +9,7 @@ pub(crate) fn handle_maze_recreation_event(
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() {
@ -18,9 +18,9 @@ pub(crate) fn handle_maze_recreation_event(
} }
} }
fn despawn_floor(commands: &mut Commands, query: &Query<(Entity, &MazeFloor)>, floor_num: u8) { fn despawn_floor(commands: &mut Commands, query: &Query<(Entity, &Floor)>, floor_num: u8) {
for (entity, maze_floor) in query.iter() { for (entity, floor) in query.iter() {
if maze_floor.0 == floor_num { if floor.0 == floor_num {
commands.entity(entity).despawn_recursive(); commands.entity(entity).despawn_recursive();
} }
} }

View File

@ -1,7 +1,11 @@
use bevy::prelude::*; use bevy::prelude::*;
use hexlab::{GeneratorType, MazeBuilder}; use hexlab::{GeneratorType, MazeBuilder};
use crate::maze::{assets::MazeAssets, components::MazeFloor, MazeConfig}; use crate::maze::{
assets::MazeAssets,
components::{Floor, Maze},
MazeConfig,
};
use super::spawn::spawn_single_hex_tile; use super::spawn::spawn_single_hex_tile;
@ -31,7 +35,8 @@ pub(super) fn setup_maze(
commands commands
.spawn(( .spawn((
Name::new("Floor"), Name::new("Floor"),
MazeFloor(1), Maze(maze.clone()),
Floor(1),
Transform::from_translation(Vec3::ZERO), Transform::from_translation(Vec3::ZERO),
Visibility::Visible, Visibility::Visible,
)) ))

View File

@ -6,7 +6,7 @@ use hexx::HexOrientation;
use crate::maze::{ use crate::maze::{
assets::MazeAssets, assets::MazeAssets,
components::{MazeTile, MazeWall}, components::{Tile, Wall},
MazeConfig, MazeConfig,
}; };
@ -25,7 +25,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 +58,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),

View File

@ -1,20 +1,25 @@
use bevy::prelude::*; use bevy::prelude::*;
use hexx::Hex; use hexx::Hex;
#[derive(Component, Debug, Clone, Copy, PartialEq, Reflect)] #[derive(Debug, Reflect, Component)]
#[reflect(Component)] #[reflect(Component)]
pub struct Player { #[require(CurrentPosition, MovementSpeed, MovementTarget)]
pub speed: f32, pub struct Player;
pub current_hex: Hex,
pub target_hex: Option<Hex>,
}
impl Default for 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 { fn default() -> Self {
Self { Self(50.)
speed: 50.,
current_hex: Hex::ZERO,
target_hex: None,
}
} }
} }
#[derive(Debug, Reflect, Component, Deref, DerefMut, Default)]
#[reflect(Component)]
pub struct MovementTarget(pub Option<Hex>);

View File

@ -10,5 +10,5 @@ pub(super) fn plugin(app: &mut App) {
} }
pub fn spawn_player_command(world: &mut World) { pub fn spawn_player_command(world: &mut World) {
world.run_system_once(systems::spawn::spawn_player); let _ = world.run_system_once(systems::spawn::spawn_player);
} }

View File

@ -1,28 +1,77 @@
use crate::player::components::Player; use crate::{
maze::{
components::{Floor, Maze},
MazeConfig,
},
player::components::{CurrentPosition, MovementTarget, Player},
};
use bevy::prelude::*; use bevy::prelude::*;
use hexx::EdgeDirection; use hexx::{EdgeDirection, HexOrientation};
const fn create_direction(key: &KeyCode) -> Option<EdgeDirection> { fn create_direction(
match key { input: &ButtonInput<KeyCode>,
KeyCode::KeyD => Some(EdgeDirection::FLAT_SOUTH), orientation: &HexOrientation,
KeyCode::KeyS => Some(EdgeDirection::FLAT_NORTH_EAST), ) -> Option<EdgeDirection> {
KeyCode::KeyA => Some(EdgeDirection::FLAT_NORTH), let w = input.pressed(KeyCode::KeyW);
KeyCode::KeyQ => Some(EdgeDirection::FLAT_NORTH_WEST), let a = input.pressed(KeyCode::KeyA);
KeyCode::KeyW => Some(EdgeDirection::FLAT_SOUTH_WEST), let s = input.pressed(KeyCode::KeyS);
KeyCode::KeyE => Some(EdgeDirection::FLAT_SOUTH_EAST), let d = input.pressed(KeyCode::KeyD);
_ => None,
} 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 Player>) { pub(super) fn player_input(
for mut player in player_query.iter_mut() { input: Res<ButtonInput<KeyCode>>,
if player.target_hex.is_some() { 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; continue;
} }
if let Some(direction) = input.get_pressed().find_map(|key| create_direction(key)) { let Some(direction) = create_direction(&input, &maze_config.layout.orientation) else {
let next_hex = player.current_hex + direction.into_hex(); continue;
player.target_hex = Some(next_hex); };
let Some(tile) = maze.0.get_tile(&current_pos) else {
continue;
};
if tile.walls().contains(direction) {
continue;
} }
let next_hex = current_pos.0.neighbor(direction);
target_pos.0 = Some(next_hex);
} }
} }

View File

@ -1,19 +1,32 @@
use crate::{maze::MazeConfig, player::components::Player}; use crate::{
maze::MazeConfig,
player::components::{CurrentPosition, MovementSpeed, MovementTarget, Player},
};
use bevy::prelude::*; use bevy::prelude::*;
use hexx::Hex; use hexx::Hex;
pub(super) fn player_movement( pub(super) fn player_movement(
time: Res<Time>, time: Res<Time>,
mut player_query: Query<(&mut Player, &mut Transform)>, mut query: Query<
(
&mut MovementTarget,
&MovementSpeed,
&mut CurrentPosition,
&mut Transform,
),
With<Player>,
>,
maze_config: Res<MazeConfig>, maze_config: Res<MazeConfig>,
) { ) {
for (mut player, mut transform) in player_query.iter_mut() { for (mut target, speed, mut current_hex, mut transform) in query.iter_mut() {
if let Some(target_hex) = player.target_hex { if let Some(target_hex) = target.0 {
let current_pos = transform.translation; let current_pos = transform.translation;
let target_pos = calculate_target_position(&maze_config, target_hex, current_pos.y); let target_pos = calculate_target_position(&maze_config, target_hex, current_pos.y);
if should_complete_movement(current_pos, target_pos) { if should_complete_movement(current_pos, target_pos) {
complete_movement(&mut player, &mut transform, target_pos, target_hex); transform.translation = target_pos;
current_hex.0 = target_hex;
target.0 = None;
continue; continue;
} }
@ -21,33 +34,17 @@ pub(super) fn player_movement(
&mut transform, &mut transform,
current_pos, current_pos,
target_pos, target_pos,
player.speed, speed.0,
time.delta_seconds(), time.delta_secs(),
); );
} }
} }
} }
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)
}
fn should_complete_movement(current_pos: Vec3, target_pos: Vec3) -> bool { fn should_complete_movement(current_pos: Vec3, target_pos: Vec3) -> bool {
(target_pos - current_pos).length() < 0.1 (target_pos - current_pos).length() < 0.1
} }
fn complete_movement(
player: &mut Player,
transform: &mut Transform,
target_pos: Vec3,
target_hex: Hex,
) {
transform.translation = target_pos;
player.current_hex = target_hex;
player.target_hex = None;
}
fn update_position( fn update_position(
transform: &mut Transform, transform: &mut Transform,
current_pos: Vec3, current_pos: Vec3,
@ -64,3 +61,8 @@ fn update_position(
} }
transform.translation += movement; 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)
}

View File

@ -2,10 +2,11 @@ use crate::{
maze::MazeConfig, maze::MazeConfig,
player::{ player::{
assets::{blue_material, generate_pill_mesh}, assets::{blue_material, generate_pill_mesh},
components::Player, components::{CurrentPosition, Player},
}, },
}; };
use bevy::prelude::*; use bevy::prelude::*;
use hexx::Hex;
pub fn spawn_player( pub fn spawn_player(
mut commands: Commands, mut commands: Commands,
@ -18,12 +19,10 @@ pub fn spawn_player(
commands.spawn(( commands.spawn((
Name::new("Player"), Name::new("Player"),
Player::default(), Player,
PbrBundle { CurrentPosition(Hex::new(1, 1)),
mesh: meshes.add(generate_pill_mesh(player_radius, player_height / 2.)), Mesh3d(meshes.add(generate_pill_mesh(player_radius, player_height / 2.))),
material: materials.add(blue_material()), MeshMaterial3d(materials.add(blue_material())),
transform: Transform::from_xyz(0., player_height * 2., 0.), Transform::from_xyz(0., player_height * 2., 0.),
..default()
},
)); ));
} }

View File

@ -7,7 +7,13 @@ 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);
@ -20,11 +26,6 @@ pub(super) fn plugin(app: &mut App) {
); );
} }
fn spawn_level(mut commands: Commands) {
commands.queue(spawn_level_command);
commands.queue(spawn_player_command);
}
#[derive(Resource, Asset, Reflect, Clone)] #[derive(Resource, Asset, Reflect, Clone)]
pub struct GameplayMusic { pub struct GameplayMusic {
#[dependency] #[dependency]