bevy_ecs_tilemap
bevy_ecs_tilemap copied to clipboard
Feature request: mouse-to-tile events
It would be nice to have mouse events translated to tiles. At the minimum, a helper function which takes mouse coordinates and returns a tile. However, even better, a system which:
- Generates custom events for:
- a tile was clicked (left/right/middle/other, optionally subposition)
- hover-in
- hover-out
- maybe a mouse-drag event which gives a list of tiles from first (tile where the button was pressed down) + all the others passed over in order + last (tile where button was released)
- maybe also mouse-down / mouse-up on a tile (not sure how useful that really is vs "click" outside of the drag case)
- Provides a way to query:
- last-clicked tile (or, list of tiles clicked since last query?)
- currently-hovered tile
- subposition within tile of last click (scaled to tile size in view)
- subposition within tile of hover
- maybe the latest mouse-drag list?
I'm not sure how -- or if at all -- this should interact with non-tile sprites or other UI elements. I can image cases where it'd be nice to at least know that a click is "obstructed" by an object which is above the map.
Related to #30
And just a few more complications:
- Should handle hex and isometric tiles
- Layers... not sure how that should work at all
Also connected to #44 -- if isometric tiles have height, they can extend out of their nominal space, and therefore mouse-picking should be based on render height. (Including the possibility of layers having their own Z axis.) But that adds a lot of complexity that I would love to not have block the basics.
Implementing this logic by hand for hexagonal tiles is a giant pain: getting this working for hex tiles will make the library substantially more useful.
Definitely not a full solution, but this might be helpful to some. I wrote some helper functions that can convert a world position to a tile coordinate for axial hex tiles (HexType::Row).
/// Returns the tile coordinates at the given world coordinates
///
/// Based on the pixel-to-hex algorithm described here: https://www.redblobgames.com/grids/hexagons/#pixel-to-hex
pub fn tile_from_world_coordinates(
world_position: Vec2,
tile_size: Vec2
) -> Option<UVec2> {
// Basis vectors for axial hex coordinates
let hex_basis = Mat2::from_cols(Vec2::new(3f32.sqrt()/3.0, 0.0), Vec2::new(-1.0/3.0, 2.0/3.0));
// Offsets tile position so that world (0,0) is the center of the (0,0) tile
let tile_offset = tile_size * -0.5;
let hex_basis_position = (hex_basis * (world_position + tile_offset)) / (tile_size.y * 0.5);
let tile_position = hex_round(hex_basis_position);
// Need to check for negative values here. Simply casting to UVec2 will not do
// UVec2::as_u32() converts any negative values to 0
if tile_position.min_element() < 0.0 {
return None;
} else {
return Some(tile_position.as_u32());
}
}
/// Takes fractional axial coordinates and "rounds" them to the nearest hex center
///
/// Based on the hex rounding algorithm described here: https://www.redblobgames.com/grids/hexagons/#rounding
fn hex_round(coords: Vec2) -> Vec2 {
// Convert to cube coodinates
let cube_coords = coords.extend( -1.0 * coords.x - coords.y);
let mut rounded_cube_coords = cube_coords.round();
let coords_diff = rounded_cube_coords - cube_coords;
// Preserve coodinate invariants
if coords_diff.x > coords_diff.y && coords_diff.x > coords_diff.z {
rounded_cube_coords.x = -1.0 * rounded_cube_coords.y - rounded_cube_coords.z;
} else if coords_diff.y > coords_diff.z {
rounded_cube_coords.y = -1.0 * rounded_cube_coords.x - rounded_cube_coords.z;
} else {
rounded_cube_coords.z = -1.0 * rounded_cube_coords.x - rounded_cube_coords.y;
}
// Convert back to axial
return rounded_cube_coords.truncate();
}
This definitely isn't perfect. Some of these values should really be constants (such as the hex basis matrix and the tile offset), the error handling should probably use Result<UVec2, E> instead of Option<UVec2>, and it's not terribly efficient. That being said, it does work reasonably well, save for that pesky hex layout issue (#58)
Unfortunately @plof27 those values can't be consts: Rust does not support const math on floats.
Definitely not a full solution, but this might be helpful to some. I wrote some helper functions that can convert a world position to a tile coordinate for axial hex tiles (
HexType::Row)./// Returns the tile coordinates at the given world coordinates /// /// Based on the pixel-to-hex algorithm described here: https://www.redblobgames.com/grids/hexagons/#pixel-to-hex pub fn tile_from_world_coordinates( world_position: Vec2, tile_size: Vec2 ) -> Option<UVec2> { // Basis vectors for axial hex coordinates let hex_basis = Mat2::from_cols(Vec2::new(3f32.sqrt()/3.0, 0.0), Vec2::new(-1.0/3.0, 2.0/3.0)); // Offsets tile position so that world (0,0) is the center of the (0,0) tile let tile_offset = tile_size * -0.5; let hex_basis_position = (hex_basis * (world_position + tile_offset)) / (tile_size.y * 0.5); let tile_position = hex_round(hex_basis_position); // Need to check for negative values here. Simply casting to UVec2 will not do // UVec2::as_u32() converts any negative values to 0 if tile_position.min_element() < 0.0 { return None; } else { return Some(tile_position.as_u32()); } } /// Takes fractional axial coordinates and "rounds" them to the nearest hex center /// /// Based on the hex rounding algorithm described here: https://www.redblobgames.com/grids/hexagons/#rounding fn hex_round(coords: Vec2) -> Vec2 { // Convert to cube coodinates let cube_coords = coords.extend( -1.0 * coords.x - coords.y); let mut rounded_cube_coords = cube_coords.round(); let coords_diff = rounded_cube_coords - cube_coords; // Preserve coodinate invariants if coords_diff.x > coords_diff.y && coords_diff.x > coords_diff.z { rounded_cube_coords.x = -1.0 * rounded_cube_coords.y - rounded_cube_coords.z; } else if coords_diff.y > coords_diff.z { rounded_cube_coords.y = -1.0 * rounded_cube_coords.x - rounded_cube_coords.z; } else { rounded_cube_coords.z = -1.0 * rounded_cube_coords.x - rounded_cube_coords.y; } // Convert back to axial return rounded_cube_coords.truncate(); }This definitely isn't perfect. Some of these values should really be constants (such as the hex basis matrix and the tile offset), the error handling should probably use
Result<UVec2, E>instead ofOption<UVec2>, and it's not terribly efficient. That being said, it does work reasonably well, save for that pesky hex layout issue (#58)
According to redblobgames it should be let coords_diff = (rounded_cube_coords - cube_coords).abs();. Took me some time :)
A lot of the basic functionality is now in helpers: https://github.com/StarArawn/bevy_ecs_tilemap/blob/eabd66eac4c5204ec54646141ffb26935cace88d/examples/mouse_to_tile.rs#L372
As for the more advanced functionality still remains as a TODO. But, we should also be careful in giving thought to how much of it belongs in this crate, versus a more general crate (possibly bevy itself).
I'm going to close this out; we can make more specific issues as needed :)