diff --git a/assets/start.json b/assets/start.json new file mode 100644 index 0000000..a86e037 --- /dev/null +++ b/assets/start.json @@ -0,0 +1,3 @@ +{ + "color": "#0E9F6C" +} diff --git a/src/components.rs b/src/components.rs index 318b640..aeddfa1 100644 --- a/src/components.rs +++ b/src/components.rs @@ -1,8 +1,8 @@ pub mod clickable; -pub mod obstacle; pub mod player_camera; pub mod tile; pub mod tile_builder; pub mod tile_collider; pub mod tile_grid; +pub mod tile_type; pub mod use_asset; diff --git a/src/components/clickable.rs b/src/components/clickable.rs index 3f5ee0d..deda96a 100644 --- a/src/components/clickable.rs +++ b/src/components/clickable.rs @@ -2,71 +2,155 @@ use crate::{ resources::mouse_world_position::MouseWorldPosition, traits::{ asset_handle::AssetHandle, + get_key::GetKey, is_point_hit::{IsPointHit, Relative}, }, }; use bevy::prelude::*; +use std::{hash::Hash, marker::PhantomData}; #[derive(Component, Debug, PartialEq, Default)] -pub struct Clickable { +pub struct Clickable +where + TKeyDefinition: GetKey, +{ clicked: bool, + _p: PhantomData, } -impl Clickable { - pub fn update_using( - mut entities: Query<(&mut Self, &TCollider, &Transform)>, - colliders: Res>, - mouse_world_position: Res, - mouse_input: Res>, +impl Clickable +where + TKeyDefinition: GetKey + Sync + Send + 'static, + TKeyDefinition::TKey: Copy + Eq + Hash + Send + Sync + 'static, +{ + fn set_clicked( + mut clickable: Mut>, + collider: &TCollider, + transform: &Transform, + colliders: &Res>, + mouse_position: Vec2, ) where TCollider: Component + AssetHandle, TCollider::TAsset: IsPointHit, { - if !mouse_input.pressed(MouseButton::Right) { + let Some(collider) = colliders.get(collider.get_handle()) else { return; - } + }; + + let relative_mouse_position = Relative::position(mouse_position).to(transform); - let MouseWorldPosition(Some(mouse_position)) = *mouse_world_position else { + if clickable.clicked == collider.is_point_hit(relative_mouse_position) { return; }; - for (mut clickable, collider, transform) in &mut entities { - let Some(collider) = colliders.get(collider.get_handle()) else { - continue; - }; - let relative_mouse_position = Relative::position(mouse_position).to(transform); + clickable.clicked = !clickable.clicked; + } + + fn set_not_clicked(mut clickable: Mut>) { + if !clickable.clicked { + return; + } - if clickable.clicked == collider.is_point_hit(relative_mouse_position) { - continue; - }; + clickable.clicked = false; + } - clickable.clicked = !clickable.clicked; + pub fn detect_click_on( + mut entities: Query<(&mut Self, &TCollider, &Transform)>, + colliders: Res>, + mouse_world_position: Res, + input: Res>, + ) where + TCollider: Component + AssetHandle, + TCollider::TAsset: IsPointHit, + { + let pressed = input.pressed(TKeyDefinition::get_key()); + + let MouseWorldPosition(Some(position)) = *mouse_world_position else { + return; + }; + + for (clickable, collider, transform) in &mut entities { + match pressed { + false => Self::set_not_clicked(clickable), + true => Self::set_clicked(clickable, collider, transform, &colliders, position), + } } } pub fn toggle( - mut commands: Commands, - entities: Query<(Entity, &Clickable, Option<&TComponent>), Changed>, - ) where - TComponent: Component + Default, + toggle_on: TComponent, + ) -> impl Fn(Query<(&Self, &mut TComponent), Changed>) + where + TComponent: Component + Default + PartialEq + Copy, { - for (entity, Clickable { clicked }, component) in &entities { - if !clicked { - continue; + move |mut toggles| { + for (Self { clicked, .. }, mut toggle) in &mut toggles { + if !clicked { + continue; + } + + *toggle = match *toggle == toggle_on { + true => TComponent::default(), + false => toggle_on, + } } + } + } + + fn just_clicked(clickable: &Ref) -> bool { + clickable.is_changed() && clickable.clicked + } + + fn only_others_clicked(clickable: &Ref, any_clicked: bool) -> bool { + any_clicked && !clickable.clicked + } - let Some(mut entity) = commands.get_entity(entity) else { - continue; - }; + pub fn switch_on_single( + switch_on_state: TComponent, + ) -> impl Fn(Query<(Ref, &mut TComponent)>) + where + TComponent: Component + Default + PartialEq + Copy, + { + let switched_on = move |switch: &TComponent| switch == &switch_on_state; - match component { - Some(_) => entity.remove::(), - None => entity.try_insert(TComponent::default()), - }; + move |mut switches| { + let any_clicked = switches.iter().any(|(clickable, _)| clickable.clicked); + + for (clickable, mut switch) in &mut switches { + if Self::just_clicked(&clickable) { + *switch = switch_on_state + } + + if Self::only_others_clicked(&clickable, any_clicked) && switched_on(&switch) { + *switch = TComponent::default() + } + } } } } +#[derive(Debug, PartialEq, Default)] +pub struct MouseLeft; + +impl GetKey for MouseLeft { + type TKey = MouseButton; + + fn get_key() -> Self::TKey { + const { MouseButton::Left } + } +} + +#[derive(Debug, PartialEq, Default)] +pub struct MouseRight; + +impl GetKey for MouseRight { + type TKey = MouseButton; + + fn get_key() -> Self::TKey { + const { MouseButton::Right } + } +} + #[cfg(test)] mod test_update { use super::*; @@ -74,6 +158,20 @@ mod test_update { use bevy::ecs::system::{RunSystemError, RunSystemOnce}; use mockall::{automock, predicate::eq}; + #[derive(Debug, PartialEq, Eq, Hash, Default, Clone, Copy)] + struct _DeviceKey; + + #[derive(Debug, PartialEq, Eq, Default)] + struct _Button; + + impl GetKey for _Button { + type TKey = _DeviceKey; + + fn get_key() -> Self::TKey { + _DeviceKey + } + } + #[derive(Asset, TypePath)] struct _ColliderAsset { mock: Mock_ColliderAsset, @@ -108,31 +206,31 @@ mod test_update { } } - enum _MouseClick { - RightJustNot(Option), - RightHold(Option), - Nothing, + enum _Device { + Pressed(Option), + Held(Option), + Released, } fn setup( handle: &Handle<_ColliderAsset>, collider_asset: _ColliderAsset, - mouse_click: _MouseClick, + mouse_click: _Device, ) -> App { let mut app = App::new().single_threaded(Update); let mut assets = Assets::<_ColliderAsset>::default(); - let mut mouse_input = ButtonInput::::default(); + let mut mouse_input = ButtonInput::<_DeviceKey>::default(); let mouse_position = MouseWorldPosition(match mouse_click { - _MouseClick::RightJustNot(mouse_position) => { - mouse_input.press(MouseButton::Right); + _Device::Pressed(mouse_position) => { + mouse_input.press(_DeviceKey); mouse_position } - _MouseClick::RightHold(mouse_position) => { - mouse_input.press(MouseButton::Right); - mouse_input.clear_just_pressed(MouseButton::Right); + _Device::Held(mouse_position) => { + mouse_input.press(_DeviceKey); + mouse_input.clear_just_pressed(_DeviceKey); mouse_position } - _MouseClick::Nothing => Some(Vec2::default()), + _Device::Released => Some(Vec2::default()), }); assets.insert(handle, collider_asset); @@ -147,18 +245,27 @@ mod test_update { fn set_to_not_clicked_when_not_hit() -> Result<(), RunSystemError> { let asset = _ColliderAsset::default(); let handle = new_handle!(_ColliderAsset); - let mut app = setup(&handle, asset, _MouseClick::RightJustNot(Some(Vec2::ZERO))); + let mut app = setup(&handle, asset, _Device::Pressed(Some(Vec2::ZERO))); let entity = app .world_mut() - .spawn((Clickable { clicked: true }, _Collider(handle))) + .spawn(( + Clickable::<_Button> { + clicked: true, + ..default() + }, + _Collider(handle), + )) .id(); app.world_mut() - .run_system_once(Clickable::update_using::<_Collider>)?; + .run_system_once(Clickable::<_Button>::detect_click_on::<_Collider>)?; assert_eq!( - Some(&Clickable { clicked: false }), - app.world().entity(entity).get::(), + Some(&Clickable::<_Button> { + clicked: false, + ..default() + }), + app.world().entity(entity).get::>(), ); Ok(()) } @@ -167,13 +274,13 @@ mod test_update { fn do_not_insert_clicked_when_clicked_not_already_present() -> Result<(), RunSystemError> { let asset = _ColliderAsset::default(); let handle = new_handle!(_ColliderAsset); - let mut app = setup(&handle, asset, _MouseClick::RightJustNot(Some(Vec2::ZERO))); + let mut app = setup(&handle, asset, _Device::Pressed(Some(Vec2::ZERO))); let entity = app.world_mut().spawn(_Collider(handle)).id(); app.world_mut() - .run_system_once(Clickable::update_using::<_Collider>)?; + .run_system_once(Clickable::<_Button>::detect_click_on::<_Collider>)?; - assert_eq!(None, app.world().entity(entity).get::()); + assert_eq!(None, app.world().entity(entity).get::>()); Ok(()) } @@ -185,18 +292,27 @@ mod test_update { }), }; let handle = new_handle!(_ColliderAsset); - let mut app = setup(&handle, asset, _MouseClick::RightJustNot(Some(Vec2::ZERO))); + let mut app = setup(&handle, asset, _Device::Pressed(Some(Vec2::ZERO))); let entity = app .world_mut() - .spawn((Clickable { clicked: false }, _Collider(handle))) + .spawn(( + Clickable::<_Button> { + clicked: false, + ..default() + }, + _Collider(handle), + )) .id(); app.world_mut() - .run_system_once(Clickable::update_using::<_Collider>)?; + .run_system_once(Clickable::<_Button>::detect_click_on::<_Collider>)?; assert_eq!( - Some(&Clickable { clicked: true }), - app.world().entity(entity).get::(), + Some(&Clickable::<_Button> { + clicked: true, + ..default() + }), + app.world().entity(entity).get::>(), ); Ok(()) } @@ -213,16 +329,17 @@ mod test_update { }; let handle = new_handle!(_ColliderAsset); - let mut app = setup( - &handle, - asset, - _MouseClick::RightJustNot(Some(Vec2::new(1., 2.))), - ); - app.world_mut() - .spawn((Clickable { clicked: false }, _Collider(handle))); + let mut app = setup(&handle, asset, _Device::Pressed(Some(Vec2::new(1., 2.)))); + app.world_mut().spawn(( + Clickable::<_Button> { + clicked: false, + ..default() + }, + _Collider(handle), + )); app.world_mut() - .run_system_once(Clickable::update_using::<_Collider>) + .run_system_once(Clickable::<_Button>::detect_click_on::<_Collider>) } #[test] @@ -237,41 +354,49 @@ mod test_update { }; let handle = new_handle!(_ColliderAsset); - let mut app = setup( - &handle, - asset, - _MouseClick::RightJustNot(Some(Vec2::new(1., 2.))), - ); + let mut app = setup(&handle, asset, _Device::Pressed(Some(Vec2::new(1., 2.)))); app.world_mut().spawn(( - Clickable { clicked: false }, + Clickable::<_Button> { + clicked: false, + ..default() + }, _Collider(handle), Transform::from_xyz(3., 3., 0.), )); app.world_mut() - .run_system_once(Clickable::update_using::<_Collider>) + .run_system_once(Clickable::<_Button>::detect_click_on::<_Collider>) } #[test] - fn do_nothing_if_not_mouse_right_clicked() -> Result<(), RunSystemError> { + fn set_not_clicked_when_released() -> Result<(), RunSystemError> { let asset = _ColliderAsset { mock: new_mock!(Mock_ColliderAsset, |mock| { mock.expect_is_point_hit().never().return_const(true); }), }; let handle = new_handle!(_ColliderAsset); - let mut app = setup(&handle, asset, _MouseClick::Nothing); + let mut app = setup(&handle, asset, _Device::Released); let entity = app .world_mut() - .spawn((Clickable { clicked: false }, _Collider(handle))) + .spawn(( + Clickable::<_Button> { + clicked: true, + ..default() + }, + _Collider(handle), + )) .id(); app.world_mut() - .run_system_once(Clickable::update_using::<_Collider>)?; + .run_system_once(Clickable::<_Button>::detect_click_on::<_Collider>)?; assert_eq!( - Some(&Clickable { clicked: false }), - app.world().entity(entity).get::(), + Some(&Clickable::<_Button> { + clicked: false, + ..default() + }), + app.world().entity(entity).get::>(), ); Ok(()) } @@ -284,18 +409,27 @@ mod test_update { }), }; let handle = new_handle!(_ColliderAsset); - let mut app = setup(&handle, asset, _MouseClick::RightHold(Some(Vec2::ZERO))); + let mut app = setup(&handle, asset, _Device::Held(Some(Vec2::ZERO))); let entity = app .world_mut() - .spawn((Clickable { clicked: false }, _Collider(handle))) + .spawn(( + Clickable::<_Button> { + clicked: false, + ..default() + }, + _Collider(handle), + )) .id(); app.world_mut() - .run_system_once(Clickable::update_using::<_Collider>)?; + .run_system_once(Clickable::<_Button>::detect_click_on::<_Collider>)?; assert_eq!( - Some(&Clickable { clicked: true }), - app.world().entity(entity).get::(), + Some(&Clickable::<_Button> { + clicked: true, + ..default() + }), + app.world().entity(entity).get::>(), ); Ok(()) } @@ -304,7 +438,7 @@ mod test_update { struct _Changed(bool); impl _Changed { - fn detect(mut commands: Commands, entities: Query<(Entity, Ref)>) { + fn detect(mut commands: Commands, entities: Query<(Entity, Ref>)>) { for (entity, clickable) in &entities { let mut entity = commands.entity(entity); entity.insert(_Changed(clickable.is_changed())); @@ -313,22 +447,69 @@ mod test_update { } #[test] - fn do_not_mut_deref_clickable_when_nothing_changed() { + fn do_not_mut_deref_clickable_when_nothing_changed_on_hold() { let asset = _ColliderAsset { mock: new_mock!(Mock_ColliderAsset, |mock| { mock.expect_is_point_hit().return_const(true); }), }; let handle = new_handle!(_ColliderAsset); - let mut app = setup(&handle, asset, _MouseClick::RightHold(Some(Vec2::ZERO))); + let mut app = setup(&handle, asset, _Device::Held(Some(Vec2::ZERO))); let entity = app .world_mut() - .spawn((Clickable { clicked: true }, _Collider(handle))) + .spawn(( + Clickable::<_Button> { + clicked: true, + ..default() + }, + _Collider(handle), + )) .id(); app.add_systems( Update, - (Clickable::update_using::<_Collider>, _Changed::detect).chain(), + ( + Clickable::<_Button>::detect_click_on::<_Collider>, + _Changed::detect, + ) + .chain(), + ); + app.update(); + app.update(); + + assert_eq!( + Some(&_Changed(false)), + app.world().entity(entity).get::<_Changed>(), + ); + } + + #[test] + fn do_not_mut_deref_clickable_when_nothing_changed_on_released() { + let asset = _ColliderAsset { + mock: new_mock!(Mock_ColliderAsset, |mock| { + mock.expect_is_point_hit().return_const(true); + }), + }; + let handle = new_handle!(_ColliderAsset); + let mut app = setup(&handle, asset, _Device::Released); + let entity = app + .world_mut() + .spawn(( + Clickable::<_Button> { + clicked: false, + ..default() + }, + _Collider(handle), + )) + .id(); + + app.add_systems( + Update, + ( + Clickable::<_Button>::detect_click_on::<_Collider>, + _Changed::detect, + ) + .chain(), ); app.update(); app.update(); @@ -346,55 +527,109 @@ mod test_toggle { use crate::test_tools::SingleThreaded; use std::ops::DerefMut; - #[derive(Component, Debug, PartialEq, Default)] - struct _Component; + #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] + struct _DeviceKey; + + #[derive(Debug, PartialEq, Default)] + struct _Button; + + impl GetKey for _Button { + type TKey = _DeviceKey; + + fn get_key() -> Self::TKey { + _DeviceKey + } + } + + #[derive(Component, Debug, PartialEq, Default, Clone, Copy)] + enum _Component { + #[default] + ToggleOff, + ToggleOn, + } fn setup() -> App { let mut app = App::new().single_threaded(Update); - app.add_systems(Update, Clickable::toggle::<_Component>); + app.add_systems(Update, Clickable::<_Button>::toggle(_Component::ToggleOn)); app } #[test] - fn insert_component_when_clicked() { + fn toggle_on_when_clicked() { let mut app = setup(); - let entity = app.world_mut().spawn(Clickable { clicked: true }).id(); + let entity = app + .world_mut() + .spawn(( + Clickable::<_Button> { + clicked: true, + ..default() + }, + _Component::ToggleOff, + )) + .id(); app.update(); assert_eq!( - Some(&_Component), + Some(&_Component::ToggleOn), app.world().entity(entity).get::<_Component>(), ); } #[test] - fn insert_component_when_clicked_only_once() { + fn toggle_on_when_clicked_only_once() { let mut app = setup(); - let entity = app.world_mut().spawn(Clickable { clicked: true }).id(); + let entity = app + .world_mut() + .spawn(( + Clickable::<_Button> { + clicked: true, + ..default() + }, + _Component::ToggleOff, + )) + .id(); app.update(); - app.world_mut().entity_mut(entity).remove::<_Component>(); + app.world_mut() + .entity_mut(entity) + .insert(_Component::ToggleOff); app.update(); - assert_eq!(None, app.world().entity(entity).get::<_Component>()); + assert_eq!( + Some(&_Component::ToggleOff), + app.world().entity(entity).get::<_Component>() + ); } #[test] - fn insert_component_when_clicked_again_after_mut_deref() { + fn toggle_on_when_clicked_again_after_mut_deref() { let mut app = setup(); - let entity = app.world_mut().spawn(Clickable { clicked: true }).id(); + let entity = app + .world_mut() + .spawn(( + Clickable::<_Button> { + clicked: true, + ..default() + }, + _Component::ToggleOff, + )) + .id(); app.update(); - app.world_mut().entity_mut(entity).remove::<_Component>(); - let mut clickable = app.world_mut().entity_mut(entity); - let mut clickable = clickable.get_mut::().unwrap(); - clickable.deref_mut(); + app.world_mut() + .entity_mut(entity) + .insert(_Component::ToggleOff); + app.world_mut() + .entity_mut(entity) + .get_mut::>() + .unwrap() + .deref_mut(); app.update(); assert_eq!( - Some(&_Component), + Some(&_Component::ToggleOn), app.world().entity(entity).get::<_Component>() ); } @@ -404,60 +639,272 @@ mod test_toggle { let mut app = setup(); let entity = app .world_mut() - .spawn((Clickable { clicked: false }, _Component)) + .spawn(( + Clickable::<_Button> { + clicked: false, + ..default() + }, + _Component::ToggleOff, + )) + .id(); + + app.update(); + + assert_eq!( + Some(&_Component::ToggleOff), + app.world().entity(entity).get::<_Component>() + ); + } + + #[test] + fn toggle_off_when_clicked() { + let mut app = setup(); + let entity = app + .world_mut() + .spawn(( + Clickable::<_Button> { + clicked: true, + ..default() + }, + _Component::ToggleOn, + )) .id(); app.update(); assert_eq!( - Some(&_Component), + Some(&_Component::ToggleOff), app.world().entity(entity).get::<_Component>() ); } +} + +#[cfg(test)] +mod test_switch_on_single { + use super::*; + use crate::test_tools::SingleThreaded; + use std::ops::DerefMut; + + #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] + struct _DeviceKey; + + #[derive(Debug, PartialEq, Default)] + struct _Button; + + impl GetKey for _Button { + type TKey = _DeviceKey; + + fn get_key() -> Self::TKey { + _DeviceKey + } + } + + #[derive(Component, Debug, PartialEq, Default, Clone, Copy)] + enum _Component { + #[default] + SwitchedOff, + SwitchedOn, + OtherState, + } + + fn setup() -> App { + let mut app = App::new().single_threaded(Update); + app.add_systems( + Update, + Clickable::<_Button>::switch_on_single(_Component::SwitchedOn), + ); + + app + } #[test] - fn remove_component_when_clicked() { + fn switch_component_on() { let mut app = setup(); let entity = app .world_mut() - .spawn((Clickable { clicked: true }, _Component)) + .spawn(( + _Component::SwitchedOff, + Clickable::<_Button> { + clicked: true, + ..default() + }, + )) .id(); app.update(); - assert_eq!(None, app.world().entity(entity).get::<_Component>()); + assert_eq!( + Some(&_Component::SwitchedOn), + app.world().entity(entity).get::<_Component>(), + ); } #[test] - fn remove_component_when_clicked_only_once() { + fn switch_component_off_if_new_switched_on() { let mut app = setup(); let entity = app .world_mut() - .spawn((Clickable { clicked: true }, _Component)) + .spawn(( + _Component::SwitchedOn, + Clickable::<_Button> { + clicked: false, + ..default() + }, + )) .id(); + app.world_mut().spawn(( + _Component::SwitchedOff, + Clickable::<_Button> { + clicked: true, + ..default() + }, + )); app.update(); - app.world_mut().entity_mut(entity).insert(_Component); + + assert_eq!( + Some(&_Component::SwitchedOff), + app.world().entity(entity).get::<_Component>(), + ); + } + + #[test] + fn do_not_switch_component_off_if_not_on_and_new_switched_on() { + let mut app = setup(); + let entity = app + .world_mut() + .spawn(( + _Component::OtherState, + Clickable::<_Button> { + clicked: false, + ..default() + }, + )) + .id(); + app.world_mut().spawn(( + _Component::SwitchedOff, + Clickable::<_Button> { + clicked: true, + ..default() + }, + )); + app.update(); assert_eq!( - Some(&_Component), + Some(&_Component::OtherState), app.world().entity(entity).get::<_Component>(), ); } #[test] - fn remove_component_when_clicked_again_after_mut_deref() { + fn do_not_switch_component_off_if_no_other_switched_on() { let mut app = setup(); - let entity = app.world_mut().spawn(Clickable { clicked: true }).id(); + let entity = app + .world_mut() + .spawn(( + _Component::SwitchedOn, + Clickable::<_Button> { + clicked: false, + ..default() + }, + )) + .id(); app.update(); - app.world_mut().entity_mut(entity).insert(_Component); - let mut clickable = app.world_mut().entity_mut(entity); - let mut clickable = clickable.get_mut::().unwrap(); - clickable.deref_mut(); + + assert_eq!( + Some(&_Component::SwitchedOn), + app.world().entity(entity).get::<_Component>(), + ); + } + + #[test] + fn switch_component_on_only_once() { + let mut app = setup(); + let entity = app + .world_mut() + .spawn(( + _Component::SwitchedOff, + Clickable::<_Button> { + clicked: true, + ..default() + }, + )) + .id(); + app.update(); + app.world_mut() + .entity_mut(entity) + .insert(_Component::SwitchedOff); + app.update(); + + assert_eq!( + Some(&_Component::SwitchedOff), + app.world().entity(entity).get::<_Component>(), + ); + } + + #[test] + fn switch_component_on_again_after_mut_deref() { + let mut app = setup(); + let entity = app + .world_mut() + .spawn(( + _Component::SwitchedOff, + Clickable::<_Button> { + clicked: true, + ..default() + }, + )) + .id(); - assert_eq!(None, app.world().entity(entity).get::<_Component>()); + app.update(); + app.world_mut() + .entity_mut(entity) + .get_mut::>() + .unwrap() + .deref_mut(); + app.world_mut() + .entity_mut(entity) + .insert(_Component::SwitchedOff); + app.update(); + + assert_eq!( + Some(&_Component::SwitchedOn), + app.world().entity(entity).get::<_Component>(), + ); + } + + #[test] + fn switch_component_off_if_new_switched_on_in_later_frame() { + let mut app = setup(); + let entity = app + .world_mut() + .spawn(( + _Component::SwitchedOn, + Clickable::<_Button> { + clicked: false, + ..default() + }, + )) + .id(); + + app.update(); + + app.world_mut().spawn(( + _Component::SwitchedOff, + Clickable::<_Button> { + clicked: true, + ..default() + }, + )); + + app.update(); + + assert_eq!( + Some(&_Component::SwitchedOff), + app.world().entity(entity).get::<_Component>(), + ); } } diff --git a/src/components/obstacle.rs b/src/components/obstacle.rs deleted file mode 100644 index 9695267..0000000 --- a/src/components/obstacle.rs +++ /dev/null @@ -1,160 +0,0 @@ -use crate::components::use_asset::UseAsset; -use bevy::prelude::*; -use std::path::Path; - -#[derive(Component, Debug, PartialEq, Default)] -pub struct Obstacle; - -impl Obstacle { - const ASSET_PATH: &str = "obstacle.json"; - - fn asset() -> UseAsset { - UseAsset::new(Path::new(Self::ASSET_PATH)) - } - - pub fn update_color( - mut commands: Commands, - obstacles: Query<(Entity, Option<&UseAsset>), Added>, - mut removed_obstacles: RemovedComponents, - original_colors: Query<&OriginalColor>, - ) { - for (entity, color) in &obstacles { - let Some(mut entity) = commands.get_entity(entity) else { - continue; - }; - - entity.try_insert(Obstacle::asset()); - - let Some(color) = color else { - continue; - }; - - entity.try_insert(OriginalColor(color.clone())); - } - - for entity in removed_obstacles.read() { - let Ok(OriginalColor(color)) = original_colors.get(entity) else { - continue; - }; - let Some(mut entity) = commands.get_entity(entity) else { - continue; - }; - - entity.try_insert(color.clone()); - entity.remove::(); - } - } -} - -#[derive(Component, Debug, PartialEq)] -pub struct OriginalColor(UseAsset); - -#[cfg(test)] -mod test { - use super::*; - use crate::test_tools::SingleThreaded; - - fn setup() -> App { - let mut app = App::new().single_threaded(Update); - app.add_systems(Update, Obstacle::update_color); - - app - } - - #[test] - fn insert_color_asset() { - let mut app = setup(); - let entity = app - .world_mut() - .spawn(( - Obstacle, - UseAsset::::new(Path::new("some/other")), - )) - .id(); - - app.update(); - - assert_eq!( - Some(&Obstacle::asset()), - app.world().entity(entity).get::>(), - ); - } - - #[test] - fn do_not_insert_obstacle_asset_on_other_entities() { - let mut app = setup(); - let entity = app - .world_mut() - .spawn(UseAsset::::new(Path::new("some/other"))) - .id(); - - app.update(); - - assert_eq!( - Some(&UseAsset::::new(Path::new("some/other"))), - app.world().entity(entity).get::>(), - ); - } - - #[test] - fn insert_color_asset_only_once() { - let mut app = setup(); - let entity = app - .world_mut() - .spawn(( - Obstacle, - UseAsset::::new(Path::new("some/other")), - )) - .id(); - - app.update(); - app.world_mut() - .entity_mut(entity) - .remove::>(); - app.update(); - - assert_eq!( - None, - app.world().entity(entity).get::>(), - ); - } - - #[test] - fn insert_original_color_asset_when_removed() { - let mut app = setup(); - let entity = app - .world_mut() - .spawn(( - Obstacle, - UseAsset::::new(Path::new("some/other")), - )) - .id(); - - app.update(); - app.world_mut().entity_mut(entity).remove::(); - app.update(); - - assert_eq!( - Some(&UseAsset::::new(Path::new("some/other"))), - app.world().entity(entity).get::>(), - ); - } - - #[test] - fn cleanup_original_color_helper_component() { - let mut app = setup(); - let entity = app - .world_mut() - .spawn(( - Obstacle, - UseAsset::::new(Path::new("some/other")), - )) - .id(); - - app.update(); - app.world_mut().entity_mut(entity).remove::(); - app.update(); - - assert_eq!(None, app.world().entity(entity).get::()); - } -} diff --git a/src/components/tile.rs b/src/components/tile.rs index eef9db1..0384e1d 100644 --- a/src/components/tile.rs +++ b/src/components/tile.rs @@ -1,4 +1,8 @@ -use super::{clickable::Clickable, use_asset::UseAsset}; +use super::{ + clickable::{Clickable, MouseLeft, MouseRight}, + tile_type::TileType, + use_asset::UseAsset, +}; use crate::assets::tile_collider_definition::TileColliderDefinition; use bevy::prelude::*; use std::path::Path; @@ -8,9 +12,10 @@ use std::path::Path; Transform, Visibility, UseAsset(Tile::asset), - UseAsset(Tile::asset), UseAsset(Tile::asset), - Clickable, + TileType, + Clickable, + Clickable, )] pub struct Tile; diff --git a/src/components/tile_type.rs b/src/components/tile_type.rs new file mode 100644 index 0000000..80eae16 --- /dev/null +++ b/src/components/tile_type.rs @@ -0,0 +1,125 @@ +use super::tile::Tile; +use crate::components::use_asset::UseAsset; +use bevy::prelude::*; +use std::path::Path; + +#[derive(Component, Debug, PartialEq, Default, Clone, Copy)] +pub enum TileType { + #[default] + Walkable, + Obstacle, + Start, +} + +impl From for UseAsset { + fn from(value: TileType) -> Self { + match value { + TileType::Walkable => Tile::asset(), + TileType::Obstacle => UseAsset::new(Path::new("obstacle.json")), + TileType::Start => UseAsset::new(Path::new("start.json")), + } + } +} + +impl TileType { + pub fn update_color(mut commands: Commands, obstacles: Query<(Entity, &Self), Changed>) { + for (entity, tile_type) in &obstacles { + let Some(mut entity) = commands.get_entity(entity) else { + continue; + }; + + entity.try_insert(UseAsset::from(*tile_type)); + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::test_tools::SingleThreaded; + use std::ops::DerefMut; + + fn setup() -> App { + let mut app = App::new().single_threaded(Update); + app.add_systems(Update, TileType::update_color); + + app + } + + #[test] + fn insert_default_color() { + let mut app = setup(); + let entity = app.world_mut().spawn(TileType::Walkable).id(); + + app.update(); + + assert_eq!( + Some(&UseAsset::from(TileType::Walkable)), + app.world().entity(entity).get::>(), + ); + } + + #[test] + fn insert_obstacle_color() { + let mut app = setup(); + let entity = app.world_mut().spawn(TileType::Obstacle).id(); + + app.update(); + + assert_eq!( + Some(&UseAsset::from(TileType::Obstacle)), + app.world().entity(entity).get::>(), + ); + } + + #[test] + fn insert_color_asset_only_once() { + let mut app = setup(); + let entity = app + .world_mut() + .spawn(( + TileType::Obstacle, + UseAsset::::new(Path::new("some/other")), + )) + .id(); + + app.update(); + app.world_mut() + .entity_mut(entity) + .remove::>(); + app.update(); + + assert_eq!( + None, + app.world().entity(entity).get::>(), + ); + } + + #[test] + fn insert_color_asset_again_after_mut_deref() { + let mut app = setup(); + let entity = app + .world_mut() + .spawn(( + TileType::Obstacle, + UseAsset::::new(Path::new("some/other")), + )) + .id(); + + app.update(); + app.world_mut() + .entity_mut(entity) + .get_mut::() + .unwrap() + .deref_mut(); + app.world_mut() + .entity_mut(entity) + .remove::>(); + app.update(); + + assert_eq!( + Some(&UseAsset::from(TileType::Obstacle)), + app.world().entity(entity).get::>(), + ); + } +} diff --git a/src/components/use_asset.rs b/src/components/use_asset.rs index 57eae61..6df141d 100644 --- a/src/components/use_asset.rs +++ b/src/components/use_asset.rs @@ -24,7 +24,7 @@ where } } - pub fn insert_system( + pub fn insert( mut commands: Commands, asset_server: Res, entities: Query<(Entity, &Self), Changed>, @@ -102,7 +102,7 @@ mod tests { fn setup() -> App { let mut app = App::new().single_threaded(Update); app.init_resource::<_AssetServer>(); - app.add_systems(Update, _UseAsset::insert_system); + app.add_systems(Update, _UseAsset::insert); app } diff --git a/src/main.rs b/src/main.rs index 29092fd..b25264c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,12 +3,12 @@ use project_zyheeda_pathfinding::{ asset_loader::CustomAssetLoader, assets::{grid::Grid, tile_collider_definition::TileColliderDefinition}, components::{ - clickable::Clickable, - obstacle::Obstacle, + clickable::{Clickable, MouseLeft, MouseRight}, player_camera::PlayerCamera, tile_builder::TileBuilder, tile_collider::TileCollider, tile_grid::TileGrid, + tile_type::TileType, use_asset::UseAsset, }, dtos::{grid_layout::GridLayout, tile_color::TileColor, tile_size::TileSize}, @@ -33,13 +33,25 @@ fn main() -> AppExit { .add_systems( Update, ( - Clickable::update_using::, - Clickable::toggle::, - Obstacle::update_color, - UseAsset::::insert_system, - UseAsset::::insert_system, - UseAsset::::insert_system, - UseAsset::::insert_system, + UseAsset::::insert, + UseAsset::::insert, + UseAsset::::insert, + UseAsset::::insert.after(TileType::update_color), + ), + ) + .add_systems( + Update, + ( + Clickable::::detect_click_on::, + Clickable::::detect_click_on::, + ), + ) + .add_systems( + Update, + ( + Clickable::::toggle(TileType::Obstacle), + Clickable::::switch_on_single(TileType::Start), + TileType::update_color, ) .chain(), ); diff --git a/src/traits.rs b/src/traits.rs index 73c8e4a..74f9e5a 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1,5 +1,6 @@ pub mod asset_handle; pub mod concat; +pub mod get_key; pub mod get_mouse_ray; pub mod into_component; pub mod is_point_hit; diff --git a/src/traits/get_key.rs b/src/traits/get_key.rs new file mode 100644 index 0000000..d0b1ed2 --- /dev/null +++ b/src/traits/get_key.rs @@ -0,0 +1,5 @@ +pub trait GetKey { + type TKey; + + fn get_key() -> Self::TKey; +}