diff --git a/Cargo.lock b/Cargo.lock index 944079c..0aa186d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -328,6 +328,7 @@ name = "bevy-bloxel-classic" version = "0.1.0" dependencies = [ "bevy", + "bevy_fix_cursor_unlock_web", ] [[package]] @@ -661,6 +662,18 @@ dependencies = [ "encase_derive_impl", ] +[[package]] +name = "bevy_fix_cursor_unlock_web" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c436b85a08404677c0b5fd6364fa142a6e86d30dc8c89fe134a8e621a80fc43f" +dependencies = [ + "bevy_app", + "bevy_ecs", + "bevy_window", + "web-sys", +] + [[package]] name = "bevy_gilrs" version = "0.17.2" diff --git a/Cargo.toml b/Cargo.toml index 08e48bd..6cabc29 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,3 +13,4 @@ opt-level = 3 [dependencies] bevy = "0.17" +bevy_fix_cursor_unlock_web = "0.2" diff --git a/src/camera.rs b/src/camera.rs index 9611eab..6aceb02 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -8,47 +8,51 @@ pub fn cursor_grab( mut mouse_button_input: ResMut>, key_input: Res>, window: Single<(&mut Window, &mut CursorOptions)>, - crosshair: Single<&mut Visibility, With>, - mut request_center_cursor: Local, ) { let (mut window, mut cursor) = window.into_inner(); - let mut crosshair_visibility = crosshair.into_inner(); - let is_grabbed = cursor.grab_mode == CursorGrabMode::Locked; + let is_grabbed = cursor.grab_mode != CursorGrabMode::None; let request_grab = mouse_button_input.any_just_pressed([MouseButton::Left, MouseButton::Right]); let request_ungrab = !window.focused || key_input.just_pressed(KeyCode::Escape); if !is_grabbed && request_grab && !request_ungrab { - *crosshair_visibility = Visibility::Inherited; cursor.grab_mode = CursorGrabMode::Locked; - cursor.visible = false; - - // HACK: It appears setting the cursor position in the same frame we're - // locking it is not possible, so we're delaying it by a frame. - *request_center_cursor = true; // To prevent other systems (such as `place_break_blocks`) // from seeing the mouse button inputs, clear the state here. mouse_button_input.clear(); } - if is_grabbed && *request_center_cursor { - // Set the cursor position to the middle of the window, - // so when it is ungrabbed again it'll reappear there. + if is_grabbed && request_ungrab && !request_grab { + cursor.grab_mode = CursorGrabMode::None; + } + + if is_grabbed && !request_ungrab { + // Set the cursor position to the center of the window, so that when + // it is ungrabbed, it will reappear there. Because `is_grabbed` is + // not updated on grab, this block is delayed by one frame. + // + // On Wayland, since the cursor is locked into place, this only needs + // to be done once. Unfortunately, for some reason this doesn't work + // in the same frame as setting `grab_mode`, and would log an error. + // + // On X11, the cursor can't be locked into place, only confined to the + // window bounds, so we repeatedly move the cursor back to the center + // while it's grabbed. + // + // On the web, the cursor can be locked, but setting its position is + // not supported at all, so this would instead log a bunch of errors. let center = window.resolution.size() / 2.; + #[cfg(not(target_arch = "wasm32"))] // skip on web window.set_cursor_position(Some(center)); - *request_center_cursor = false; // Only do this once. } - if is_grabbed && request_ungrab && !request_grab { - *crosshair_visibility = Visibility::Hidden; - cursor.grab_mode = CursorGrabMode::None; - cursor.visible = true; - } + // Keep cursor visbility in sync with `grab_mode`. + cursor.visible = cursor.grab_mode == CursorGrabMode::None; } pub fn is_cursor_grabbed(cursor: Single<&CursorOptions>) -> bool { - cursor.grab_mode == CursorGrabMode::Locked + cursor.grab_mode != CursorGrabMode::None } #[derive(Component, Debug)] @@ -196,3 +200,14 @@ pub fn setup_crosshair(mut commands: Commands, asset_server: Res) { )], )); } + +pub fn update_crosshair_visibility( + cursor: Single<&CursorOptions, Changed>, + crosshair: Single<&mut Visibility, With>, +) { + let is_grabbed = cursor.grab_mode != CursorGrabMode::None; + let mut crosshair_visibility = crosshair.into_inner(); + *crosshair_visibility = (!is_grabbed || cursor.visible) + .then_some(Visibility::Hidden) + .unwrap_or_default(); +} diff --git a/src/main.rs b/src/main.rs index f05b8cd..c8931f4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,17 +12,20 @@ fn main() { #[rustfmt::skip] App::new() .add_plugins(DefaultPlugins) + // Fixes issue on web where the cursor isn't ungrabbed properly. + .add_plugins(bevy_fix_cursor_unlock_web::FixPointerUnlockPlugin) .add_systems(Startup, setup_crosshair) .add_systems(Startup, setup_blocks) .add_systems(Startup, setup_scene.after(setup_blocks)) .add_systems(Update, cursor_grab) + .add_systems(Update, update_crosshair_visibility.after(cursor_grab)) .add_systems(Update, camera_look.after(cursor_grab).run_if(is_cursor_grabbed)) .add_systems(Update, noclip_controller.after(camera_look).run_if(is_cursor_grabbed)) - // This system requires the camera's `GlobalTransform`, so for - // a most up-to-date value, run it right after it's been updated. + // `place_break_blocks` requires the camera's `GlobalTransform`. + // For a most up-to-date value, run it after that's been updated. .add_systems(PostUpdate, place_break_blocks.after(TransformSystems::Propagate)) .run();