From a731205a3d638811b338b88fa08e58c1f557bac7 Mon Sep 17 00:00:00 2001 From: CoCo_Sol Date: Sat, 9 Mar 2024 14:51:44 +0000 Subject: [PATCH] Click-on tile handle system (#74) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reviewed-on: https://git.tipragot.fr/fish-canard/border-wars/pulls/74 Reviewed-by: Raphaƫl --- crates/border-wars/src/main.rs | 2 + crates/border-wars/src/map/click_tile.rs | 105 +++++++++++++++++++++++ crates/border-wars/src/map/mod.rs | 1 + crates/border-wars/src/map/renderer.rs | 2 +- 4 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 crates/border-wars/src/map/click_tile.rs diff --git a/crates/border-wars/src/main.rs b/crates/border-wars/src/main.rs index b02c626..cc2c72e 100644 --- a/crates/border-wars/src/main.rs +++ b/crates/border-wars/src/main.rs @@ -2,6 +2,7 @@ use bevy::prelude::*; use border_wars::camera::CameraPlugin; +use border_wars::map::click_tile::TilesClickable; use border_wars::map::renderer::RendererPlugin; use border_wars::scenes::ScenesPlugin; @@ -11,5 +12,6 @@ fn main() { .add_plugins(ScenesPlugin) .add_plugins(RendererPlugin) .add_plugins(CameraPlugin) + .add_plugins(TilesClickable) .run(); } diff --git a/crates/border-wars/src/map/click_tile.rs b/crates/border-wars/src/map/click_tile.rs new file mode 100644 index 0000000..fb66845 --- /dev/null +++ b/crates/border-wars/src/map/click_tile.rs @@ -0,0 +1,105 @@ +//! All programs related to the clicking on a tile. + +use bevy::prelude::*; + +use super::Tile; + +/// The event that is triggered when a tile is clicked. +/// +/// The event contains the index (ID) of the clicked tile. +#[derive(Event)] +pub struct TileJustClicked(pub u32); + +/// An event that is triggered when a mouse button is clicked. +/// +/// The event contains the position of the cursor in the world. +#[derive(Event)] +struct ClickOnTheWorld(Vec2); + +/// A zone that can't be clicked. +/// For exemple the UI of the game. +#[derive(Component)] +pub struct ZoneNotClickable; + +/// A plugin that handles the selection of tiles. +pub struct TilesClickable; + +impl Plugin for TilesClickable { + fn build(&self, app: &mut App) { + app.add_systems(PreUpdate, mouse_handler) + .add_systems(PreUpdate, select_closest_tile) + .add_event::() + .add_event::(); + } +} + +/// Handles the mouse click and gets the position of the cursor in the world. +/// Finally, it sends an event with the position of the cursor. +fn mouse_handler( + mouse_button_input: Res>, + windows: Query<&Window>, + cameras: Query<(&Camera, &GlobalTransform)>, + mut events_writer: EventWriter, + not_clickable_zones: Query<(&Node, &GlobalTransform), With>, + ui_scale: Res, +) { + if !mouse_button_input.just_pressed(MouseButton::Left) { + return; + } + + let window = windows.get_single().expect("Main window not found"); + + let cursor_position_on_screen = window.cursor_position(); + + let Some(cursor_position_on_screen) = cursor_position_on_screen else { + return; + }; + + for (node, global_transform) in not_clickable_zones.iter() { + let rect = node.physical_rect(global_transform, window.scale_factor(), ui_scale.0); + if rect.contains(cursor_position_on_screen) { + return; + } + } + + let (camera, camera_transform) = cameras.get_single().expect("Camera not found"); + + let cursor_position_in_world = camera + .viewport_to_world(camera_transform, cursor_position_on_screen) + .expect("Failed to convert cursor position") + .origin + .truncate(); + + events_writer.send(ClickOnTheWorld(cursor_position_in_world)); +} + +/// Get the closest tile to the cursor and send it in an event. +fn select_closest_tile( + tiles: Query<(Entity, &Transform, &Tile)>, + mut click_event_reader: EventReader, + mut clicked_tile_event_writer: EventWriter, +) { + for click_event in click_event_reader.read() { + // The closest tile and its distance to the cursor. + let mut closest_entity: Option = None; + let mut closest_position: Option = None; + + for (tile_entity, tile_transform, tile_type) in tiles.iter() { + let mut tile_position = tile_transform.translation.truncate(); + let tile_size = tile_type.get_image_size(); + let tile_scale = tile_transform.scale.truncate(); + + tile_position += (tile_size / 2.0) * tile_scale; + + let distance_to_cursor = tile_position.distance(click_event.0); + + if closest_position.is_none() || closest_position > Some(distance_to_cursor) { + closest_entity = Some(tile_entity); + closest_position = Some(distance_to_cursor); + } + } + if let Some(tile_entity) = closest_entity { + clicked_tile_event_writer.send(TileJustClicked(tile_entity.index())); + } + } +} diff --git a/crates/border-wars/src/map/mod.rs b/crates/border-wars/src/map/mod.rs index 73d0c06..9172176 100644 --- a/crates/border-wars/src/map/mod.rs +++ b/crates/border-wars/src/map/mod.rs @@ -1,5 +1,6 @@ //! Contains all the logic related to the map. +pub mod click_tile; pub mod generation; pub mod hex; pub mod renderer; diff --git a/crates/border-wars/src/map/renderer.rs b/crates/border-wars/src/map/renderer.rs index f0df33f..da92e81 100644 --- a/crates/border-wars/src/map/renderer.rs +++ b/crates/border-wars/src/map/renderer.rs @@ -40,7 +40,7 @@ impl Tile { /// /// TODO: we are currently using temporary images that will modify /// this function in the future. - const fn get_image_size(&self) -> Vec2 { + pub const fn get_image_size(&self) -> Vec2 { match self { Self::Grass => Vec2 { x: 184.0, y: 164.0 }, Self::Forest => Vec2 { x: 184.0, y: 138.0 },