Merge pull request #20 from kristoferssolo/fix/floor-transitions

This commit is contained in:
Kristofers Solo 2025-01-05 15:03:58 +02:00 committed by GitHub
commit e096216806
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 294 additions and 88 deletions

View File

@ -8,7 +8,7 @@ native-dev:
# Run native release
native-release:
cargo run --release --no-default-features
RUSTC_WRAPPER=sccache cargo run --release --no-default-features
# Run web dev
web-dev:
@ -16,8 +16,17 @@ web-dev:
# Run web release
web-release:
trunk serve --release --no-default-features
RUSTC_WRAPPER=sccache trunk serve --release --no-default-features
# Run tests
test:
RUSTC_WRAPPER=sccache RUST_BACKTRACE=full cargo nextest run --no-default-features --all-targets
# Run CI localy
ci:
#!/bin/bash
set -e
cargo fmt --all -- --check
cargo clippy --workspace --all-targets --all-features -- --deny warnings
cargo doc --workspace --all-features --document-private-items --no-deps
cargo test --workspace --no-default-features

View File

@ -1,4 +1,4 @@
pub const MOVEMENT_THRESHOLD: f32 = 0.01;
pub const WALL_OVERLAP_MODIFIER: f32 = 1.25;
pub const FLOOR_Y_OFFSET: u8 = 100;
pub const FLOOR_Y_OFFSET: u8 = 200;
pub const MOVEMENT_COOLDOWN: f32 = 1.0; // one second cooldown

View File

@ -6,10 +6,12 @@ pub struct Floor(pub u8);
#[derive(Debug, Reflect, Component)]
#[reflect(Component)]
#[require(Floor)]
pub struct CurrentFloor;
#[derive(Debug, Reflect, Component, Deref, DerefMut)]
#[reflect(Component)]
#[require(Floor)]
pub struct FloorYTarget(pub f32);
impl Default for Floor {

View File

@ -15,8 +15,9 @@ pub(super) fn plugin(app: &mut App) {
spawn_floor,
despawn_floor,
handle_floor_transition_events,
move_floors.after(handle_floor_transition_events),
move_floors,
)
.chain()
.run_if(resource_exists::<MazePluginLoaded>),
);
}

View File

@ -12,10 +12,7 @@ use bevy::prelude::*;
pub fn move_floors(
mut commands: Commands,
mut maze_query: Query<
(Entity, &mut Transform, &FloorYTarget),
(With<HexMaze>, With<FloorYTarget>),
>,
mut maze_query: Query<(Entity, &mut Transform, &FloorYTarget), With<FloorYTarget>>,
player_query: Query<&MovementSpeed, With<Player>>,
time: Res<Time>,
) {
@ -48,7 +45,6 @@ pub fn handle_floor_transition_events(
}
for event in event_reader.read() {
dbg!(&event);
let Some((current_entity, current_floor)) = current_query.get_single().ok() else {
continue;
};
@ -68,7 +64,6 @@ pub fn handle_floor_transition_events(
for (entity, transforms, _, movement_state) in maze_query.iter_mut() {
let target_y = (FLOOR_Y_OFFSET as f32).mul_add(direction, transforms.translation.y);
dbg!(movement_state, target_y);
if movement_state.is_none() {
commands.entity(entity).insert(FloorYTarget(target_y));
}
@ -77,7 +72,6 @@ pub fn handle_floor_transition_events(
update_current_next_floor(&mut commands, current_entity, target_entity);
break;
}
event_reader.clear();
}
fn update_current_next_floor(commands: &mut Commands, current: Entity, target: Entity) {

View File

@ -4,17 +4,17 @@ use crate::{
events::TransitionFloor,
resources::HighestFloor,
},
maze::events::SpawnMaze,
maze::{components::MazeConfig, events::SpawnMaze},
};
use bevy::prelude::*;
pub(super) fn spawn_floor(
mut commands: Commands,
query: Query<&mut Floor, (With<CurrentFloor>, Without<FloorYTarget>)>,
query: Query<(&mut Floor, &MazeConfig), (With<CurrentFloor>, Without<FloorYTarget>)>,
mut event_reader: EventReader<TransitionFloor>,
mut highest_floor: ResMut<HighestFloor>,
) {
let Ok(current_floor) = query.get_single() else {
let Ok((current_floor, config)) = query.get_single() else {
return;
};
@ -24,14 +24,17 @@ pub(super) fn spawn_floor(
return;
}
let next_floor = event.next_floor_num(current_floor);
highest_floor.0 = highest_floor.0.max(next_floor);
let target_floor = event.next_floor_num(current_floor);
highest_floor.0 = highest_floor.0.max(target_floor);
info!("Creating level for floor {}", next_floor);
info!("Creating level for floor {}", target_floor);
commands.trigger(SpawnMaze {
floor: next_floor,
..default()
floor: target_floor,
config: MazeConfig {
start_pos: config.end_pos,
..default()
},
});
}
}

View File

@ -14,6 +14,7 @@ use bevy::{
audio::{AudioPlugin, Volume},
prelude::*,
};
use theme::{palette::rose_pine, prelude::ColorScheme};
pub struct AppPlugin;
@ -26,7 +27,7 @@ impl Plugin for AppPlugin {
);
// Spawn the main camera.
app.add_systems(Startup, spawn_camera);
app.add_systems(Startup, (spawn_camera, load_background));
// Add Bevy plugins.
app.add_plugins(
@ -100,3 +101,11 @@ fn spawn_camera(mut commands: Commands) {
IsDefaultUiCamera,
));
}
fn load_background(mut commands: Commands) {
#[cfg(feature = "dev")]
let colorcheme = rose_pine::RosePine::Base;
#[cfg(not(feature = "dev"))]
let colorcheme = rose_pine::RosePineDawn::Base;
commands.insert_resource(ClearColor(colorcheme.to_color()));
}

View File

@ -1,7 +1,7 @@
use super::resources::GlobalMazeConfig;
use crate::{
constants::WALL_OVERLAP_MODIFIER,
theme::{palette::rose_pine::RosePine, prelude::ColorScheme},
theme::{palette::rose_pine::RosePineDawn, prelude::ColorScheme},
};
use bevy::{prelude::*, utils::HashMap};
use std::f32::consts::FRAC_PI_2;
@ -15,7 +15,7 @@ pub struct MazeAssets {
pub wall_mesh: Handle<Mesh>,
pub hex_material: Handle<StandardMaterial>,
pub wall_material: Handle<StandardMaterial>,
pub custom_materials: HashMap<RosePine, Handle<StandardMaterial>>,
pub custom_materials: HashMap<RosePineDawn, Handle<StandardMaterial>>,
}
impl MazeAssets {
@ -24,7 +24,7 @@ impl MazeAssets {
materials: &mut ResMut<Assets<StandardMaterial>>,
global_config: &GlobalMazeConfig,
) -> Self {
let custom_materials = RosePine::iter()
let custom_materials = RosePineDawn::iter()
.map(|color| (color, materials.add(color.to_standart_material())))
.collect();
Self {

View File

@ -35,18 +35,17 @@ impl MazeConfig {
orientation: HexOrientation,
seed: Option<u64>,
global_conig: &GlobalMazeConfig,
start_pos: Option<Hex>,
) -> Self {
let seed = seed.unwrap_or_else(|| thread_rng().gen());
let mut rng = StdRng::seed_from_u64(seed);
// Generate start and end positions ensuring they're different
let mut start_pos;
let start_pos = start_pos.unwrap_or_else(|| generate_pos(radius, &mut rng));
// Generate end position ensuring start and end are different
let mut end_pos;
loop {
start_pos = generate_pos(radius, &mut rng);
end_pos = generate_pos(radius, &mut rng);
if start_pos != end_pos {
break;
}
@ -79,7 +78,13 @@ impl MazeConfig {
impl Default for MazeConfig {
fn default() -> Self {
Self::new(8, HexOrientation::Flat, None, &GlobalMazeConfig::default())
Self::new(
8,
HexOrientation::Flat,
None,
&GlobalMazeConfig::default(),
None,
)
}
}
@ -126,7 +131,7 @@ mod tests {
let seed = Some(12345);
let global_config = GlobalMazeConfig::default();
let config = MazeConfig::new(radius, orientation, seed, &global_config);
let config = MazeConfig::new(radius, orientation, seed, &global_config, None);
assert_eq!(config.radius, radius);
assert_eq!(config.seed, 12345);
@ -173,8 +178,13 @@ mod tests {
];
for seed in test_seeds {
let config =
MazeConfig::new(8, HexOrientation::Flat, seed, &GlobalMazeConfig::default());
let config = MazeConfig::new(
8,
HexOrientation::Flat,
seed,
&GlobalMazeConfig::default(),
None,
);
assert_eq!(config.radius, 8);
assert_eq!(config.layout.orientation, HexOrientation::Flat);
@ -226,12 +236,14 @@ mod tests {
HexOrientation::Flat,
Some(1),
&GlobalMazeConfig::default(),
None,
);
let config2 = MazeConfig::new(
8,
HexOrientation::Flat,
Some(2),
&GlobalMazeConfig::default(),
None,
);
assert_ne!(config1.start_pos, config2.start_pos);
@ -241,8 +253,20 @@ mod tests {
#[test]
fn same_seed_same_positions() {
let seed = Some(12345);
let config1 = MazeConfig::new(8, HexOrientation::Flat, seed, &GlobalMazeConfig::default());
let config2 = MazeConfig::new(8, HexOrientation::Flat, seed, &GlobalMazeConfig::default());
let config1 = MazeConfig::new(
8,
HexOrientation::Flat,
seed,
&GlobalMazeConfig::default(),
None,
);
let config2 = MazeConfig::new(
8,
HexOrientation::Flat,
seed,
&GlobalMazeConfig::default(),
None,
);
assert_eq!(config1.start_pos, config2.start_pos);
assert_eq!(config1.end_pos, config2.end_pos);
@ -255,6 +279,7 @@ mod tests {
HexOrientation::Pointy,
None,
&GlobalMazeConfig::default(),
None,
);
assert_eq!(config.layout.orientation, HexOrientation::Pointy);
}
@ -269,6 +294,7 @@ mod tests {
hex_size: 0.0,
..default()
},
None,
);
assert_eq!(config.layout.hex_size.x, 0.0);
assert_eq!(config.layout.hex_size.y, 0.0);

View File

@ -8,8 +8,9 @@ use crate::{
events::SpawnMaze,
resources::GlobalMazeConfig,
},
theme::palette::rose_pine::RosePine,
theme::palette::rose_pine::RosePineDawn,
};
use bevy::prelude::*;
use hexlab::prelude::{Tile as HexTile, *};
use hexx::HexOrientation;
@ -43,8 +44,6 @@ pub(super) fn spawn_maze(
_ => FLOOR_Y_OFFSET,
} as f32;
// (floor - 1) * FLOOR_Y_OFFSET
let entity = commands
.spawn((
Name::new(format!("Floor {}", floor)),
@ -100,12 +99,12 @@ pub(super) fn spawn_single_hex_tile(
let material = match tile.pos() {
pos if pos == maze_config.start_pos => assets
.custom_materials
.get(&RosePine::Pine)
.get(&RosePineDawn::Pine)
.cloned()
.unwrap_or_default(),
pos if pos == maze_config.end_pos => assets
.custom_materials
.get(&RosePine::Love)
.get(&RosePineDawn::Love)
.cloned()
.unwrap_or_default(),
_ => assets.hex_material.clone(),

View File

@ -1,6 +1,6 @@
use bevy::prelude::*;
use crate::theme::{palette::rose_pine::RosePine, prelude::ColorScheme};
use crate::theme::{palette::rose_pine::RosePineDawn, prelude::ColorScheme};
pub(super) fn generate_pill_mesh(radius: f32, half_length: f32) -> Mesh {
Mesh::from(Capsule3d {
@ -10,7 +10,7 @@ pub(super) fn generate_pill_mesh(radius: f32, half_length: f32) -> Mesh {
}
pub(super) fn blue_material() -> StandardMaterial {
let color = RosePine::Pine;
let color = RosePineDawn::Pine;
StandardMaterial {
base_color: color.to_color(),
emissive: color.to_linear_rgba() * 3.,

View File

@ -3,7 +3,7 @@ use hexx::Hex;
#[derive(Debug, Reflect, Component)]
#[reflect(Component)]
#[require(CurrentPosition, MovementSpeed, MovementTarget)]
#[require(CurrentPosition, MovementSpeed, MovementTarget, TranstitionState)]
pub struct Player;
#[derive(Debug, Reflect, Component, Deref, DerefMut, Default)]
@ -14,6 +14,13 @@ pub struct CurrentPosition(pub Hex);
#[reflect(Component)]
pub struct MovementSpeed(pub f32);
#[derive(Debug, Reflect, Component, Default)]
#[reflect(Component)]
pub struct TranstitionState {
pub just_transitioned: bool,
pub last_position: Hex,
}
impl Default for MovementSpeed {
fn default() -> Self {
Self(100.)

View File

@ -1,5 +1,5 @@
use crate::{
floor::components::CurrentFloor,
floor::components::{CurrentFloor, FloorYTarget},
maze::components::MazeConfig,
player::components::{CurrentPosition, MovementTarget, Player},
};
@ -10,12 +10,16 @@ use hexx::{EdgeDirection, HexOrientation};
pub(super) fn player_input(
input: Res<ButtonInput<KeyCode>>,
mut player_query: Query<(&mut MovementTarget, &CurrentPosition), With<Player>>,
maze_query: Query<(&Maze, &MazeConfig), With<CurrentFloor>>,
maze_query: Query<(&Maze, &MazeConfig, Has<FloorYTarget>), With<CurrentFloor>>,
) {
let Ok((maze, maze_config)) = maze_query.get_single() else {
let Ok((maze, maze_config, has_y_target)) = maze_query.get_single() else {
return;
};
if has_y_target {
return;
}
for (mut target_pos, current_pos) in player_query.iter_mut() {
if target_pos.is_some() {
continue;

View File

@ -6,11 +6,11 @@ use crate::{
events::TransitionFloor,
},
maze::components::MazeConfig,
player::components::{CurrentPosition, Player},
player::components::{CurrentPosition, Player, TranstitionState},
};
pub fn handle_floor_transition(
player_query: Query<&CurrentPosition, With<Player>>,
mut player_query: Query<(&CurrentPosition, &mut TranstitionState), With<Player>>,
maze_query: Query<(&MazeConfig, &Floor), With<CurrentFloor>>,
mut event_writer: EventWriter<TransitionFloor>,
) {
@ -19,17 +19,30 @@ pub fn handle_floor_transition(
return;
};
for current_hex in player_query.iter() {
for (current_hex, mut transition_state) in player_query.iter_mut() {
// Reset transition state if moved to a new position
if current_hex.0 != transition_state.last_position {
transition_state.just_transitioned = false;
}
transition_state.last_position = current_hex.0;
// Skip if transition just happened
if transition_state.just_transitioned {
continue;
}
// Check for ascending
if current_hex.0 == config.end_pos {
info!("Ascending");
event_writer.send(TransitionFloor::Ascend);
transition_state.just_transitioned = true;
}
// Check for descending
if current_hex.0 == config.start_pos && floor.0 != 1 {
info!("Descending");
event_writer.send(TransitionFloor::Descend);
transition_state.just_transitioned = true;
}
}
}

View File

@ -1,45 +1,184 @@
use super::rgb_u8;
use crate::theme::prelude::ColorScheme;
use crate::{
create_color_scheme,
theme::{colorscheme::ColorScheme, palette::rgb_u8},
};
use bevy::prelude::*;
use strum::EnumIter;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumIter)]
pub enum RosePine {
Base,
Surface,
Overlay,
Muted,
Subtle,
Text,
Love,
Gold,
Rose,
Pine,
Foam,
Iris,
HighlightLow,
HighlightMed,
HighlightHigh,
}
impl ColorScheme for RosePine {
fn to_color(&self) -> Color {
match self {
Self::Base => rgb_u8(25, 23, 36),
Self::Surface => rgb_u8(31, 29, 46),
Self::Overlay => rgb_u8(38, 35, 58),
Self::Muted => rgb_u8(110, 106, 134),
Self::Subtle => rgb_u8(144, 140, 170),
Self::Text => rgb_u8(224, 222, 244),
Self::Love => rgb_u8(235, 111, 146),
Self::Gold => rgb_u8(246, 193, 119),
Self::Rose => rgb_u8(235, 188, 186),
Self::Pine => rgb_u8(49, 116, 143),
Self::Foam => rgb_u8(156, 207, 216),
Self::Iris => rgb_u8(196, 167, 231),
Self::HighlightLow => rgb_u8(33, 32, 46),
Self::HighlightMed => rgb_u8(64, 61, 82),
Self::HighlightHigh => rgb_u8(82, 79, 103),
}
create_color_scheme!(
pub RosePine, {
Base: "#191724",
Surface: "#1f1d2e",
Overlay: "#26233a",
Muted: "#6e6a86",
Subtle: "#908caa",
Text: "#e0def4",
Love: "#eb6f92",
Gold: "#f6c177",
Rose: "#ebbcba",
Pine: "#31748f",
Foam: "#9ccfd8",
Iris: "#c4a7e7",
HighlightLow: "#21202e",
HighlightMed: "#403d52",
HighlightHigh: "#524f67"
}
);
create_color_scheme!(
pub RosePineMoon, {
Base: "#232136",
Surface: "#2a273f",
Overlay: "#393552",
Muted: "#6e6a86",
Subtle: "#908caa",
Text: "#e0def4",
Love: "#eb6f92",
Gold: "#f6c177",
Rose: "#ea9a97",
Pine: "#3e8fb0",
Foam: "#9ccfd8",
Iris: "#c4a7e7",
HighlightLow: "#2a283e",
HighlightMed: "#44415a",
HighlightHigh: "#56526e"
}
);
create_color_scheme!(
pub RosePineDawn, {
Base: "#faf4ed",
Surface: "#fffaf3",
Overlay: "#f2e9e1",
Muted: "#9893a5",
Subtle: "#797593",
Text: "#575279",
Love: "#b4637a",
Gold: "#ea9d34",
Rose: "#d7827e",
Pine: "#286983",
Foam: "#56949f",
Iris: "#907aa9",
HighlightLow: "#f4ede8",
HighlightMed: "#dfdad9",
HighlightHigh: "#cecacd"
}
);
#[macro_export]
macro_rules! create_color_scheme {
($(#[$meta:meta])* $vis:vis $name:ident, {
Base: $base:expr,
Surface: $surface:expr,
Overlay: $overlay:expr,
Muted: $muted:expr,
Subtle: $subtle:expr,
Text: $text:expr,
Love: $love:expr,
Gold: $gold:expr,
Rose: $rose:expr,
Pine: $pine:expr,
Foam: $foam:expr,
Iris: $iris:expr,
HighlightLow: $hl_low:expr,
HighlightMed: $hl_med:expr,
HighlightHigh: $hl_high:expr
}) => {
$(#[$meta])*
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumIter)]
$vis enum $name {
Base,
Surface,
Overlay,
Muted,
Subtle,
Text,
Love,
Gold,
Rose,
Pine,
Foam,
Iris,
HighlightLow,
HighlightMed,
HighlightHigh,
}
impl $name {
fn hex_to_rgb(hex: &str) -> (u8, u8, u8) {
let hex = hex.strip_prefix('#').unwrap_or(hex);
let r = u8::from_str_radix(&hex[0..2], 16).unwrap_or(0);
let g = u8::from_str_radix(&hex[2..4], 16).unwrap_or(0);
let b = u8::from_str_radix(&hex[4..6], 16).unwrap_or(0);
(r, g, b)
}
}
impl ColorScheme for $name {
fn to_color(&self) -> Color {
match self {
Self::Base => {
let (r, g, b) = Self::hex_to_rgb($base);
rgb_u8(r, g, b)
},
Self::Surface => {
let (r, g, b) = Self::hex_to_rgb($surface);
rgb_u8(r, g, b)
},
Self::Overlay => {
let (r, g, b) = Self::hex_to_rgb($overlay);
rgb_u8(r, g, b)
},
Self::Muted => {
let (r, g, b) = Self::hex_to_rgb($muted);
rgb_u8(r, g, b)
},
Self::Subtle => {
let (r, g, b) = Self::hex_to_rgb($subtle);
rgb_u8(r, g, b)
},
Self::Text => {
let (r, g, b) = Self::hex_to_rgb($text);
rgb_u8(r, g, b)
},
Self::Love => {
let (r, g, b) = Self::hex_to_rgb($love);
rgb_u8(r, g, b)
},
Self::Gold => {
let (r, g, b) = Self::hex_to_rgb($gold);
rgb_u8(r, g, b)
},
Self::Rose => {
let (r, g, b) = Self::hex_to_rgb($rose);
rgb_u8(r, g, b)
},
Self::Pine => {
let (r, g, b) = Self::hex_to_rgb($pine);
rgb_u8(r, g, b)
},
Self::Foam => {
let (r, g, b) = Self::hex_to_rgb($foam);
rgb_u8(r, g, b)
},
Self::Iris => {
let (r, g, b) = Self::hex_to_rgb($iris);
rgb_u8(r, g, b)
},
Self::HighlightLow => {
let (r, g, b) = Self::hex_to_rgb($hl_low);
rgb_u8(r, g, b)
},
Self::HighlightMed => {
let (r, g, b) = Self::hex_to_rgb($hl_med);
rgb_u8(r, g, b)
},
Self::HighlightHigh => {
let (r, g, b) = Self::hex_to_rgb($hl_high);
rgb_u8(r, g, b)
},
}
}
}
};
}