generated from tipragot/rust
Add a responsive scaling for ui #73
BIN
crates/border-wars/assets/tiles/forest.png
Normal file
BIN
crates/border-wars/assets/tiles/forest.png
Normal file
Binary file not shown.
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
BIN
crates/border-wars/assets/tiles/grass.png
Normal file
BIN
crates/border-wars/assets/tiles/grass.png
Normal file
Binary file not shown.
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
BIN
crates/border-wars/assets/tiles/hill.png
Normal file
BIN
crates/border-wars/assets/tiles/hill.png
Normal file
Binary file not shown.
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
@ -2,12 +2,16 @@
|
|||
|
||||
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;
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.add_plugins(ScenesPlugin)
|
||||
.add_plugins(RendererPlugin)
|
||||
.add_plugins(CameraPlugin)
|
||||
.add_plugins(TilesClickable)
|
||||
.run();
|
||||
}
|
||||
|
|
105
crates/border-wars/src/map/click_tile.rs
Normal file
105
crates/border-wars/src/map/click_tile.rs
Normal file
|
@ -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::<ClickOnTheWorld>()
|
||||
.add_event::<TileJustClicked>();
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<Input<MouseButton>>,
|
||||
windows: Query<&Window>,
|
||||
cameras: Query<(&Camera, &GlobalTransform)>,
|
||||
mut events_writer: EventWriter<ClickOnTheWorld>,
|
||||
not_clickable_zones: Query<(&Node, &GlobalTransform), With<ZoneNotClickable>>,
|
||||
ui_scale: Res<UiScale>,
|
||||
) {
|
||||
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<ClickOnTheWorld>,
|
||||
mut clicked_tile_event_writer: EventWriter<TileJustClicked>,
|
||||
) {
|
||||
for click_event in click_event_reader.read() {
|
||||
// The closest tile and its distance to the cursor.
|
||||
let mut closest_entity: Option<Entity> = None;
|
||||
let mut closest_position: Option<f32> = 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()));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,9 @@
|
|||
//! Contains all the logic related to the map.
|
||||
|
||||
pub mod click_tile;
|
||||
pub mod generation;
|
||||
pub mod hex;
|
||||
pub mod renderer;
|
||||
|
||||
use bevy::prelude::*;
|
||||
|
||||
|
|
102
crates/border-wars/src/map/renderer.rs
Normal file
102
crates/border-wars/src/map/renderer.rs
Normal file
|
@ -0,0 +1,102 @@
|
|||
//! All functions related to the rendering of the map.
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy::sprite::Anchor;
|
||||
|
||||
use crate::map::{Tile, TilePosition};
|
||||
|
||||
/// A plugin to render the map.
|
||||
pub struct RendererPlugin;
|
||||
|
||||
impl Plugin for RendererPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_systems(Startup, init_resources_for_rendering)
|
||||
.add_systems(
|
||||
Update,
|
||||
render_map.run_if(in_state(crate::CurrentScene::Game)),
|
||||
)
|
||||
.insert_resource(ClearColor(Color::rgb_u8(129, 212, 250)));
|
||||
}
|
||||
}
|
||||
|
||||
/// The gap between the center of the tiles in the map.
|
||||
#[derive(Resource)]
|
||||
struct TilesGap(Vec2);
|
||||
|
||||
/// The size of the tiles in the map.
|
||||
#[derive(Resource, Clone, Copy)]
|
||||
struct TilesSize(Vec2);
|
||||
|
||||
impl Tile {
|
||||
/// Returns the handle of the image of the tile.
|
||||
fn get_texture(&self, asset_server: &AssetServer) -> Handle<Image> {
|
||||
match self {
|
||||
Self::Grass => asset_server.load("tiles/grass.png"),
|
||||
Self::Forest => asset_server.load("tiles/forest.png"),
|
||||
Self::Hill => asset_server.load("tiles/hill.png"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the size of the image of the tile.
|
||||
///
|
||||
/// TODO: we are currently using temporary images that will modify
|
||||
/// this function in the future.
|
||||
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 },
|
||||
Self::Hill => Vec2 { x: 184.0, y: 181.0 },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Init resources related to the rendering of the map.
|
||||
fn init_resources_for_rendering(mut commands: Commands) {
|
||||
commands.insert_resource(TilesGap(Vec2 { x: 70., y: 35. }));
|
||||
commands.insert_resource(TilesSize(Vec2 { x: 125., y: 100. }))
|
||||
}
|
||||
|
||||
/// Renders the map.
|
||||
fn render_map(
|
||||
query: Query<(Entity, &TilePosition, &Tile), Changed<Tile>>,
|
||||
mut commands: Commands,
|
||||
asset_server: Res<AssetServer>,
|
||||
tiles_gap: Res<TilesGap>,
|
||||
tiles_size: Res<TilesSize>,
|
||||
) {
|
||||
for (entity, position, tile) in query.iter() {
|
||||
let texture = tile.get_texture(&asset_server);
|
||||
|
||||
let translation_2d = tiles_gap.0 * position.to_pixel_coordinates();
|
||||
let translation = Vec3::new(
|
||||
translation_2d.x,
|
||||
translation_2d.y,
|
||||
z_position_from_y(translation_2d.y),
|
||||
);
|
||||
|
||||
let scale_2d = tiles_size.0 / tile.get_image_size();
|
||||
|
||||
// the y scale is the same as the x scale to keep the aspect ratio.
|
||||
let scale = Vec3::new(scale_2d.x, scale_2d.x, 1.0);
|
||||
|
||||
commands.entity(entity).insert(SpriteBundle {
|
||||
sprite: Sprite {
|
||||
anchor: Anchor::BottomLeft,
|
||||
..default()
|
||||
},
|
||||
texture,
|
||||
transform: Transform {
|
||||
translation,
|
||||
scale,
|
||||
..Default::default()
|
||||
},
|
||||
..default()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// A simple sigmoid function to convert y position to z position.
|
||||
/// The return value is between 0 and 1.
|
||||
fn z_position_from_y(y: f32) -> f32 {
|
||||
-1.0 / (1.0 + (-y * 110_f64.powi(-3) as f32).exp())
|
||||
}
|
Loading…
Reference in a new issue