diff --git a/client/src/input/client_inputs.rs b/client/src/input/client_inputs.rs index 7e28a63..3e25314 100644 --- a/client/src/input/client_inputs.rs +++ b/client/src/input/client_inputs.rs @@ -1,5 +1,5 @@ //! Handles client-side input using [`lightyear`], updating the -//! [`ActionState`] of the affected entities, such as the `Player`. +//! [`ActionState`] of the affected entities, such as the [`Player`]. use bevy::prelude::*; use common::prelude::*; @@ -9,10 +9,55 @@ use lightyear::prelude::input::native::*; use bevy::window::{CursorGrabMode, CursorOptions}; pub fn plugin(app: &mut App) { + app.init_resource::(); app.add_systems( FixedPreUpdate, (buffer_input, buffer_action).in_set(InputSystems::WriteClientInputs), ); + app.add_observer(insert_block_pickable); + app.add_observer(place_or_break_blocks); +} + +fn insert_block_pickable(event: On, mut commands: Commands) { + let mut block = commands.entity(event.entity); + block.insert(Pickable::default()); +} + +#[derive(Resource, Default)] +struct CurrentAction(Action); + +fn place_or_break_blocks( + mut event: On>, + mut current_action: ResMut, + cursor: Single<&CursorOptions>, + blocks: Blocks, +) { + let is_place = match event.button { + PointerButton::Primary => false, // left-click + PointerButton::Secondary => true, // right-click + PointerButton::Middle => return, // not handled + }; + if cursor.grab_mode != CursorGrabMode::Locked { + return; // Cursor is not grabbed. + } + let Some(block_pos) = blocks.position(event.entity) else { + return; // Clicked entity is not a block. + }; + let Some(normal) = event.hit.normal else { + return; // Picking system didn't return a normal. + }; + + current_action.0 = if is_place { + // FIXME: This only works for axis-aligned normals. + let offset = normal.normalize().round().as_ivec3(); + // TODO: Don't hardcode block type. + Action::PlaceBlock(block_pos + offset, Block::DEFAULT) + } else { + Action::BreakBlock(block_pos) + }; + + // Handle the event. + event.propagate(false); } fn buffer_input( @@ -44,50 +89,10 @@ fn buffer_input( } } -// TODO: Use picking system instead of manually raycasting. -pub fn buffer_action( - buttons: Res>, +fn buffer_action( mut player: Single<&mut ActionState, With>>, - cursor: Single<&CursorOptions>, - window: Single<(&Window, &CursorOptions)>, - camera: Single<(&GlobalTransform, &Camera)>, - mut ray_cast: MeshRayCast, - blocks: Blocks, + mut current_action: ResMut, ) { - player.0 = Action::None; - - if cursor.grab_mode == CursorGrabMode::None { - return; - } - if !buttons.any_just_pressed([MouseButton::Right, MouseButton::Left]) { - return; - } - - let (window, cursor) = window.into_inner(); - let (cam_transform, camera) = camera.into_inner(); - - let ray = if cursor.grab_mode == CursorGrabMode::Locked { - Ray3d::new(cam_transform.translation(), cam_transform.forward()) - } else if let Some(cursor_pos) = window.cursor_position() { - camera.viewport_to_world(cam_transform, cursor_pos).unwrap() - } else { - return; // cursor outside window area - }; - - let settings = MeshRayCastSettings::default(); - let Some((block, hit)) = ray_cast.cast_ray(ray, &settings).first() else { - return; // ray didn't hit anything - }; - let Some(block_pos) = blocks.position(*block) else { - return; // entity hit is not a block - }; - - player.0 = if buttons.just_pressed(MouseButton::Right) { - // FIXME: This only works for axis-aligned normals. - let offset = hit.normal.normalize().round().as_ivec3(); - // TODO: Don't hardcode block type. - Action::PlaceBlock(block_pos + offset, Block::DEFAULT) - } else { - Action::BreakBlock(block_pos) - }; + player.0 = current_action.0.clone(); + current_action.0 = Action::None; } diff --git a/client/src/main.rs b/client/src/main.rs index 4869d43..5023c49 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -47,6 +47,7 @@ fn main() -> Result { meta_check: AssetMetaCheck::Never, ..default() }), + MeshPickingPlugin, // allow Mesh entities to be "picked" (ray-traced against) bevy_fix_cursor_unlock_web::FixPointerUnlockPlugin, )); @@ -55,6 +56,10 @@ fn main() -> Result { app.insert_resource(UiPickingSettings { require_markers: true, }); + app.insert_resource(MeshPickingSettings { + require_markers: true, + ..default() + }); app.add_plugins(( common::assets::plugin, @@ -119,12 +124,13 @@ fn spawn_initial_blocks(_event: On, mut blocks: Blocks) { /// When the `Predicted` player we control is being spawned, insert necessary components. fn handle_predicted_player_spawn(event: On, mut commands: Commands) { - let player = event.entity; - commands.entity(player).insert(( + let mut player = commands.entity(event.entity); + player.insert(( // Handle inputs on this entity. InputMarker::::default(), InputMarker::::default(), // TODO: Attach camera to player head eventually. Camera3d::default(), + MeshPickingCamera, )); }