Skip to content
This repository has been archived by the owner on Nov 22, 2023. It is now read-only.

Commit

Permalink
Use bevy_mod_raycast to handle tile selection (#262)
Browse files Browse the repository at this point in the history
* Add bevy_mod_picking

* More explicit usage

* Remove CursorWorldPos

* Cleanup

* Update cursor position using bevy_mod_picking

* WIP tile highlights

* Add tests for tile selection

* Bind Esc to clear selection

* Rearrange tile selection logic for clarity

* Swap to simple bevy_mod_raycast solution

* Remove debug cursor

* Actually store tile handles

* Remove redundant TileSelectionActionModify
  • Loading branch information
alice-i-cecile authored Feb 3, 2023
1 parent a4325e3 commit dc33e76
Show file tree
Hide file tree
Showing 14 changed files with 248 additions and 144 deletions.
1 change: 1 addition & 0 deletions deny.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ skip = [
{ name = "rustc_version", version = "0.2" }, # from postcard v1.0.2
{ name = "semver", version = "0.9" }, # from postcard v1.0.2
{ name = "windows_aarch64_msvc", version = "0.36" }, # from notify v5.0.0
{ name = "windows_sys", version = "0.42" }, # from notify v5.0.0
{ name = "windows_i686_gnu", version = "0.36" }, # from notify v5.0.0
{ name = "windows_i686_msvc", version = "0.36" }, # from notify v5.0.0
{ name = "windows_x86_64_gnu", version = "0.36" }, # from notify v5.0.0
Expand Down
1 change: 1 addition & 0 deletions emergence_game/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@ fn main() {
})
.add_plugin(emergence_lib::player_interaction::InteractionPlugin)
.add_plugin(emergence_lib::graphics::GraphicsPlugin)
.add_plugin(emergence_lib::asset_management::AssetManagementPlugin)
.run();
}
1 change: 1 addition & 0 deletions emergence_lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ serde = "1.0.152"
leafwing_abilities = "0.3.0"
derive_more = "0.99.17"
hexx = "0.2.0"
bevy_mod_raycast = "0.7.0"
40 changes: 40 additions & 0 deletions emergence_lib/src/asset_management.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//! Code related to loading, storing and tracking assets
use bevy::{prelude::*, utils::HashMap};

use crate::terrain::Terrain;

/// Collects asset management systems and resources.
pub struct AssetManagementPlugin;

impl Plugin for AssetManagementPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<TileHandles>();
}
}

/// Stores material handles for the different tile types.
#[derive(Resource)]
pub(crate) struct TileHandles {
/// The material used for each type of terrain
pub(crate) terrain_handles: HashMap<Terrain, Handle<StandardMaterial>>,
/// The material used for tiles when they are selected
pub(crate) selected_tile_handle: Handle<StandardMaterial>,
}

impl FromWorld for TileHandles {
fn from_world(world: &mut World) -> Self {
let mut materials = world.resource_mut::<Assets<StandardMaterial>>();
let mut terrain_handles = HashMap::new();
terrain_handles.insert(Terrain::Plain, materials.add(Color::WHITE.into()));
terrain_handles.insert(Terrain::Rocky, materials.add(Color::BEIGE.into()));
terrain_handles.insert(Terrain::High, materials.add(Color::RED.into()));

let selected_tile_handle = materials.add(Color::SEA_GREEN.into());

TileHandles {
terrain_handles,
selected_tile_handle,
}
}
}
14 changes: 3 additions & 11 deletions emergence_lib/src/graphics/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use bevy::{
use hexx::{Hex, HexLayout, MeshInfo};

use crate::{
asset_management::TileHandles,
organisms::units::Unit,
simulation::geometry::{MapGeometry, TilePos},
structures::Structure,
Expand Down Expand Up @@ -36,7 +37,7 @@ fn populate_terrain(
new_terrain: Query<(Entity, &TilePos, &Terrain), Added<Terrain>>,
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
materials: Res<TileHandles>,
map_geometry: Res<MapGeometry>,
) {
// mesh
Expand All @@ -46,18 +47,9 @@ fn populate_terrain(
for (terrain_entity, tile_pos, terrain) in new_terrain.iter() {
let pos = map_geometry.layout.hex_to_world_pos(tile_pos.hex);

let color = match terrain {
Terrain::Plain => Color::WHITE,
Terrain::High => Color::YELLOW,
Terrain::Rocky => Color::RED,
};

// PERF: this is wildly inefficient and lazy. Store the handles instead!
let material = materials.add(color.into());

commands.entity(terrain_entity).insert(PbrBundle {
mesh: mesh_handle.clone(),
material: material.clone(),
material: materials.terrain_handles.get(terrain).unwrap().clone_weak(),
transform: Transform::from_xyz(pos.x, 0.0, pos.y),
..default()
});
Expand Down
1 change: 1 addition & 0 deletions emergence_lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
// Often exceeded by queries
#![allow(clippy::type_complexity)]

pub mod asset_management;
pub mod curves;
pub mod enum_iter;
pub mod graphics;
Expand Down
4 changes: 2 additions & 2 deletions emergence_lib/src/player_interaction/abilities.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Abilities spend intent, modifying the behavior of allied organisms in an area.
use super::cursor::CursorTilePos;
use super::cursor::CursorPos;
use super::intent::{Intent, IntentPool};
use super::InteractionSystem;
use bevy::prelude::*;
Expand Down Expand Up @@ -53,7 +53,7 @@ impl IntentAbility {

/// Uses abilities when pressed at the cursor's location.
fn use_ability(
cursor_tile_pos: Res<CursorTilePos>,
cursor_tile_pos: Res<CursorPos>,
ability_state: Res<ActionState<IntentAbility>>,
mut intent_pool: ResMut<IntentPool>,
) {
Expand Down
5 changes: 4 additions & 1 deletion emergence_lib/src/player_interaction/camera.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use std::f32::consts::PI;

use bevy::prelude::*;
use bevy_mod_raycast::RaycastSource;
use leafwing_input_manager::axislike::SingleAxis;
use leafwing_input_manager::input_map::InputMap;
use leafwing_input_manager::plugin::InputManagerPlugin;
Expand All @@ -17,6 +18,7 @@ use crate::simulation::geometry::angle;
use crate::simulation::geometry::clockwise;
use crate::simulation::geometry::counterclockwise;
use crate::simulation::geometry::Facing;
use crate::terrain::Terrain;

use super::InteractionSystem;

Expand Down Expand Up @@ -63,7 +65,8 @@ fn setup(mut commands: Commands) {
})
.insert(CameraSettings::default())
.insert(CameraFocus::default())
.insert(Facing::default());
.insert(Facing::default())
.insert(RaycastSource::<Terrain>::new());
}

/// Actions that manipulate the camera
Expand Down
108 changes: 60 additions & 48 deletions emergence_lib/src/player_interaction/cursor.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
//! Keep track of the mouse cursor in world space, and convert it into a tile position, if
//! available.
use bevy::math::{Vec2, Vec3};
use bevy::prelude::*;

use crate::simulation::geometry::TilePos;
use bevy_mod_raycast::{DefaultRaycastingPlugin, RaycastMethod, RaycastSource, RaycastSystem};

use super::InteractionSystem;
use crate::{simulation::geometry::TilePos, terrain::Terrain};

/// Initializes the [`CursorWorldPos`] and [`CursorTilePos`] resources, which are kept updated
/// updated using [`update_cursor_pos`].
pub struct CursorTilePosPlugin;
/// Controls raycasting and cursor aethetics.
pub struct CursorPlugin;

impl Plugin for CursorTilePosPlugin {
impl Plugin for CursorPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<CursorWorldPos>()
.init_resource::<CursorTilePos>()
app.init_resource::<CursorPos>()
.add_plugin(DefaultRaycastingPlugin::<Terrain>::default())
.add_system_to_stage(
CoreStage::First,
update_raycast_with_cursor.before(RaycastSystem::BuildRays::<Terrain>),
)
.add_system(
update_cursor_pos
.label(InteractionSystem::ComputeCursorPos)
Expand All @@ -23,54 +25,64 @@ impl Plugin for CursorTilePosPlugin {
}
}

/// Converts cursor screen position into a world position, taking into account any transforms
/// applied to the camera.
pub fn cursor_pos_in_world(
windows: &Windows,
cursor_pos: Vec2,
cam_t: &Transform,
cam: &Camera,
) -> Vec3 {
let window = windows.primary();

let window_size = Vec2::new(window.width(), window.height());

// Convert screen position [0..resolution] to ndc [-1..1]
// (ndc = normalized device coordinates)
let ndc_to_world = cam_t.compute_matrix() * cam.projection_matrix().inverse();
let ndc = (cursor_pos / window_size) * 2.0 - Vec2::ONE;
ndc_to_world.project_point3(ndc.extend(0.0))
}

/// The world position of the mouse cursor.
#[derive(Resource, Clone, Copy, Deref, DerefMut)]
pub struct CursorWorldPos(Vec3);

impl Default for CursorWorldPos {
fn default() -> Self {
Self(Vec3::new(f32::INFINITY, f32::INFINITY, 0.0))
}
}

/// The tile position of the mouse cursor, if it lies over the map.
#[derive(Resource, Default, Clone, Copy)]
pub struct CursorTilePos(Option<TilePos>);
#[derive(Resource, Default, Debug, Clone, Copy)]
pub struct CursorPos {
/// The terrain entity that the cursor is over top of.
terrain_entity: Option<Entity>,
/// The tile position that the cursor is over top of.
tile_pos: Option<TilePos>,
}

impl CursorTilePos {
impl CursorPos {
/// The position of the cursor in hex coordinates, if it is on the hex map.
///
/// If the cursor is outside the map, this will return `None`.
pub fn maybe_tile_pos(&self) -> Option<TilePos> {
self.0
self.tile_pos
}

/// The terrain entity under the cursor, if any.
///
/// If the cursor is outside the map, this will return `None`.
pub fn maybe_entity(&self) -> Option<Entity> {
self.terrain_entity
}
}

/// Updates which tile the cursor is hovering over
pub fn update_cursor_pos() {
// FIXME: rewrite
/// Updates the raycast with the cursor position
///
/// This system was borrowed from <https://github.com/aevyrie/bevy_mod_raycast/blob/79012e4c7b12896ccfed09a129d163726d3a6516/examples/mouse_picking.rs#L45>
/// and used under the MIT License. Thanks!
fn update_raycast_with_cursor(
mut cursor: EventReader<CursorMoved>,
mut query: Query<&mut RaycastSource<Terrain>>,
) {
// Grab the most recent cursor event if it exists:
let cursor_position = match cursor.iter().last() {
Some(cursor_moved) => cursor_moved.position,
None => return,
};

for mut pick_source in &mut query {
pick_source.cast_method = RaycastMethod::Screenspace(cursor_position);
}
}

/// Highlights the current set of selected tiles
pub fn highlight_selected_tiles() {
// FIXME: rewrite
/// Records which tile is currently under the cursor, if any
fn update_cursor_pos(
mut cursor_pos: ResMut<CursorPos>,
camera_query: Query<&RaycastSource<Terrain>, With<Camera>>,
terrain_query: Query<&TilePos, With<Terrain>>,
) {
let raycast_source = camera_query.single();
let maybe_intersection = raycast_source.get_nearest_intersection();

if let Some((terrain_entity, _intersection_data)) = maybe_intersection {
cursor_pos.terrain_entity = Some(terrain_entity);
cursor_pos.tile_pos = terrain_query.get(terrain_entity).ok().copied();
} else {
cursor_pos.terrain_entity = None;
cursor_pos.tile_pos = None;
}
}
2 changes: 1 addition & 1 deletion emergence_lib/src/player_interaction/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ impl Plugin for InteractionPlugin {
fn build(&self, app: &mut App) {
app.add_plugin(camera::CameraPlugin)
.add_plugin(abilities::AbilitiesPlugin)
.add_plugin(cursor::CursorTilePosPlugin)
.add_plugin(cursor::CursorPlugin)
.add_plugin(intent::IntentPlugin)
.add_plugin(organism_details::DetailsPlugin)
.add_plugin(tile_selection::TileSelectionPlugin)
Expand Down
4 changes: 2 additions & 2 deletions emergence_lib/src/player_interaction/organism_details.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::{
units::Ant,
OrganismType,
},
player_interaction::cursor::CursorTilePos,
player_interaction::cursor::CursorPos,
simulation::geometry::TilePos,
structures::crafting::{
ActiveRecipe, CraftTimer, CraftingState, InputInventory, OutputInventory,
Expand Down Expand Up @@ -67,7 +67,7 @@ impl Plugin for DetailsPlugin {

/// Get details about the hovered entity.
fn hover_details(
cursor_pos: Res<CursorTilePos>,
cursor_pos: Res<CursorPos>,
mut hover_details: ResMut<HoverDetails>,
query: Query<(
Entity,
Expand Down
Loading

0 comments on commit dc33e76

Please sign in to comment.