use bevy::{color::palettes, math::Vec4Swizzles, prelude::*};
use bevy_ecs_tilemap::prelude::*;
mod helpers;
use helpers::camera::movement as camera_movement;
const MAP_SIDE_LENGTH_X: u32 = 4;
const MAP_SIDE_LENGTH_Y: u32 = 4;
const TILE_SIZE_SQUARE: TilemapTileSize = TilemapTileSize { x: 50.0, y: 50.0 };
const TILE_SIZE_ISO: TilemapTileSize = TilemapTileSize { x: 100.0, y: 50.0 };
const TILE_SIZE_HEX_ROW: TilemapTileSize = TilemapTileSize { x: 50.0, y: 58.0 };
const TILE_SIZE_HEX_COL: TilemapTileSize = TilemapTileSize { x: 58.0, y: 50.0 };
const GRID_SIZE_SQUARE: TilemapGridSize = TilemapGridSize { x: 50.0, y: 50.0 };
const GRID_SIZE_HEX_ROW: TilemapGridSize = TilemapGridSize { x: 50.0, y: 58.0 };
const GRID_SIZE_HEX_COL: TilemapGridSize = TilemapGridSize { x: 58.0, y: 50.0 };
const GRID_SIZE_ISO: TilemapGridSize = TilemapGridSize { x: 100.0, y: 50.0 };
#[derive(Deref, Resource)]
pub struct TileHandleHexRow(Handle<Image>);
#[derive(Deref, Resource)]
pub struct TileHandleHexCol(Handle<Image>);
#[derive(Deref, Resource)]
pub struct TileHandleSquare(Handle<Image>);
#[derive(Deref, Resource)]
pub struct TileHandleIso(Handle<Image>);
impl FromWorld for TileHandleHexCol {
fn from_world(world: &mut World) -> Self {
let asset_server = world.resource::<AssetServer>();
Self(asset_server.load("bw-tile-hex-col.png"))
}
}
impl FromWorld for TileHandleHexRow {
fn from_world(world: &mut World) -> Self {
let asset_server = world.resource::<AssetServer>();
Self(asset_server.load("bw-tile-hex-row.png"))
}
}
impl FromWorld for TileHandleIso {
fn from_world(world: &mut World) -> Self {
let asset_server = world.resource::<AssetServer>();
Self(asset_server.load("bw-tile-iso.png"))
}
}
impl FromWorld for TileHandleSquare {
fn from_world(world: &mut World) -> Self {
let asset_server = world.resource::<AssetServer>();
Self(asset_server.load("bw-tile-square.png"))
}
}
fn spawn_tilemap(mut commands: Commands, tile_handle_square: Res<TileHandleSquare>) {
commands.spawn(Camera2d);
let map_size = TilemapSize {
x: MAP_SIDE_LENGTH_X,
y: MAP_SIDE_LENGTH_Y,
};
let mut tile_storage = TileStorage::empty(map_size);
let tilemap_entity = commands.spawn_empty().id();
let tilemap_id = TilemapId(tilemap_entity);
fill_tilemap(
TileTextureIndex(0),
map_size,
tilemap_id,
&mut commands,
&mut tile_storage,
);
let tile_size = TILE_SIZE_SQUARE;
let grid_size = GRID_SIZE_SQUARE;
let map_type = TilemapType::Square;
commands.entity(tilemap_entity).insert(TilemapBundle {
grid_size,
size: map_size,
storage: tile_storage,
texture: TilemapTexture::Single(tile_handle_square.clone()),
tile_size,
map_type,
anchor: TilemapAnchor::Center,
..Default::default()
});
}
#[derive(Component)]
struct TileLabel(Entity);
fn spawn_tile_labels(
mut commands: Commands,
tilemap_q: Query<(
&Transform,
&TilemapType,
&TilemapGridSize,
&TilemapTileSize,
&TileStorage,
&TilemapSize,
&TilemapAnchor,
)>,
tile_q: Query<&mut TilePos>,
) {
for (map_transform, map_type, grid_size, tile_size, tilemap_storage, map_size, anchor) in
tilemap_q.iter()
{
for tile_entity in tilemap_storage.iter().flatten() {
let tile_pos = tile_q.get(*tile_entity).unwrap();
let tile_center = tile_pos
.center_in_world(map_size, grid_size, tile_size, map_type, anchor)
.extend(1.0);
let transform = *map_transform * Transform::from_translation(tile_center);
let label_entity = commands
.spawn((
Text2d::new(format!("{},{}", tile_pos.x, tile_pos.y)),
TextFont {
font_size: 14.0,
..default()
},
TextColor(Color::BLACK),
TextLayout::new_with_justify(Justify::Center),
transform,
))
.id();
commands
.entity(*tile_entity)
.insert(TileLabel(label_entity));
}
}
}
#[derive(Component)]
pub struct MapTypeLabel;
fn spawn_map_type_label(
mut commands: Commands,
windows: Query<&Window>,
map_type_q: Query<&TilemapType>,
) {
for window in windows.iter() {
for map_type in map_type_q.iter() {
let transform = Transform {
translation: Vec2::new(-0.5 * window.width() / 2.0, 0.8 * window.height() / 2.0)
.extend(1.0),
..Default::default()
};
commands.spawn((
Text2d::new(format!("{map_type:?}")),
TextFont {
font_size: 20.0,
..default()
},
TextColor(Color::BLACK),
TextLayout::new_with_justify(Justify::Center),
transform,
MapTypeLabel,
));
}
}
}
#[allow(clippy::too_many_arguments)]
fn swap_map_type(
mut tilemap_query: Query<(
&mut Transform,
&TilemapSize,
&mut TilemapType,
&mut TilemapGridSize,
&mut TilemapTexture,
&mut TilemapTileSize,
&TilemapAnchor,
)>,
keyboard_input: Res<ButtonInput<KeyCode>>,
tile_label_q: Query<
(&TileLabel, &TilePos),
(With<TileLabel>, Without<MapTypeLabel>, Without<TilemapType>),
>,
mut map_type_label_q: Query<&mut Text2d, With<MapTypeLabel>>,
mut transform_q: Query<&mut Transform, Without<TilemapType>>,
tile_handle_square: Res<TileHandleSquare>,
tile_handle_hex_row: Res<TileHandleHexRow>,
tile_handle_hex_col: Res<TileHandleHexCol>,
tile_handle_iso: Res<TileHandleIso>,
) {
if keyboard_input.just_pressed(KeyCode::Space) {
for (
map_transform,
map_size,
mut map_type,
mut grid_size,
mut map_texture,
mut tile_size,
anchor,
) in tilemap_query.iter_mut()
{
match map_type.as_ref() {
TilemapType::Square => {
*map_type = TilemapType::Isometric(IsoCoordSystem::Diamond);
*map_texture = TilemapTexture::Single((*tile_handle_iso).clone());
*tile_size = TILE_SIZE_ISO;
*grid_size = GRID_SIZE_ISO;
}
TilemapType::Isometric(IsoCoordSystem::Diamond) => {
*map_type = TilemapType::Isometric(IsoCoordSystem::Staggered);
*map_texture = TilemapTexture::Single((*tile_handle_iso).clone());
*tile_size = TILE_SIZE_ISO;
*grid_size = GRID_SIZE_ISO;
}
TilemapType::Isometric(IsoCoordSystem::Staggered) => {
*map_type = TilemapType::Hexagon(HexCoordSystem::Row);
*map_texture = TilemapTexture::Single((*tile_handle_hex_row).clone());
*tile_size = TILE_SIZE_HEX_ROW;
*grid_size = GRID_SIZE_HEX_ROW;
}
TilemapType::Hexagon(HexCoordSystem::Row) => {
*map_type = TilemapType::Hexagon(HexCoordSystem::RowEven);
}
TilemapType::Hexagon(HexCoordSystem::RowEven) => {
*map_type = TilemapType::Hexagon(HexCoordSystem::RowOdd);
}
TilemapType::Hexagon(HexCoordSystem::RowOdd) => {
*map_type = TilemapType::Hexagon(HexCoordSystem::Column);
*map_texture = TilemapTexture::Single((*tile_handle_hex_col).clone());
*tile_size = TILE_SIZE_HEX_COL;
*grid_size = GRID_SIZE_HEX_COL;
}
TilemapType::Hexagon(HexCoordSystem::Column) => {
*map_type = TilemapType::Hexagon(HexCoordSystem::ColumnEven);
}
TilemapType::Hexagon(HexCoordSystem::ColumnEven) => {
*map_type = TilemapType::Hexagon(HexCoordSystem::ColumnOdd);
}
TilemapType::Hexagon(HexCoordSystem::ColumnOdd) => {
*map_type = TilemapType::Square;
*map_texture = TilemapTexture::Single((*tile_handle_square).clone());
*tile_size = TILE_SIZE_SQUARE;
*grid_size = GRID_SIZE_SQUARE;
}
}
for (label, tile_pos) in tile_label_q.iter() {
if let Ok(mut tile_label_transform) = transform_q.get_mut(label.0) {
let tile_center = tile_pos
.center_in_world(map_size, &grid_size, &tile_size, &map_type, anchor)
.extend(1.0);
*tile_label_transform =
*map_transform * Transform::from_translation(tile_center);
}
}
for mut label_text in map_type_label_q.iter_mut() {
label_text.0 = format!("{:?}", map_type.as_ref());
}
}
}
}
#[derive(Component)]
struct HighlightedLabel;
#[derive(Resource)]
pub struct CursorPos(Vec2);
impl Default for CursorPos {
fn default() -> Self {
Self(Vec2::new(-1000.0, -1000.0))
}
}
pub fn update_cursor_pos(
camera_q: Query<(&GlobalTransform, &Camera)>,
mut cursor_moved_events: MessageReader<CursorMoved>,
mut cursor_pos: ResMut<CursorPos>,
) {
for cursor_moved in cursor_moved_events.read() {
for (cam_t, cam) in camera_q.iter() {
if let Ok(pos) = cam.viewport_to_world_2d(cam_t, cursor_moved.position) {
*cursor_pos = CursorPos(pos);
}
}
}
}
fn highlight_tile_labels(
mut commands: Commands,
cursor_pos: Res<CursorPos>,
tilemap_q: Query<(
&TilemapSize,
&TilemapGridSize,
&TilemapTileSize,
&TilemapType,
&TileStorage,
&Transform,
&TilemapAnchor,
)>,
highlighted_tiles_q: Query<Entity, With<HighlightedLabel>>,
tile_label_q: Query<&TileLabel>,
mut text_q: Query<&mut TextColor>,
) {
for highlighted_tile_entity in highlighted_tiles_q.iter() {
if let Ok(label) = tile_label_q.get(highlighted_tile_entity)
&& let Ok(mut text_color) = text_q.get_mut(label.0)
{
text_color.0 = Color::BLACK;
commands
.entity(highlighted_tile_entity)
.remove::<HighlightedLabel>();
}
}
for (map_size, grid_size, tile_size, map_type, tile_storage, map_transform, anchor) in
tilemap_q.iter()
{
let cursor_pos: Vec2 = cursor_pos.0;
let cursor_in_map_pos: Vec2 = {
let cursor_pos = Vec4::from((cursor_pos, 0.0, 1.0));
let cursor_in_map_pos = map_transform.to_matrix().inverse() * cursor_pos;
cursor_in_map_pos.xy()
};
if let Some(tile_pos) = TilePos::from_world_pos(
&cursor_in_map_pos,
map_size,
grid_size,
tile_size,
map_type,
anchor,
) {
if let Some(tile_entity) = tile_storage.get(&tile_pos)
&& let Ok(label) = tile_label_q.get(tile_entity)
&& let Ok(mut text_color) = text_q.get_mut(label.0)
{
text_color.0 = palettes::tailwind::RED_600.into();
commands.entity(tile_entity).insert(HighlightedLabel);
}
}
}
}
#[derive(SystemSet, Clone, Copy, Hash, PartialEq, Eq, Debug)]
pub struct SpawnTilemapSet;
fn main() {
App::new()
.add_plugins(
DefaultPlugins
.set(WindowPlugin {
primary_window: Some(Window {
title: String::from("Mouse Position to Tile Position"),
..Default::default()
}),
..default()
})
.set(ImagePlugin::default_nearest()),
)
.init_resource::<CursorPos>()
.init_resource::<TileHandleIso>()
.init_resource::<TileHandleHexCol>()
.init_resource::<TileHandleHexRow>()
.init_resource::<TileHandleSquare>()
.add_plugins(TilemapPlugin)
.add_systems(
Startup,
(spawn_tilemap, ApplyDeferred)
.chain()
.in_set(SpawnTilemapSet),
)
.add_systems(
Startup,
(spawn_tile_labels, spawn_map_type_label).after(SpawnTilemapSet),
)
.add_systems(First, (camera_movement, update_cursor_pos).chain())
.add_systems(Update, swap_map_type)
.add_systems(Update, highlight_tile_labels.after(swap_map_type))
.run();
}