Compare commits

...

2 Commits

Author SHA1 Message Date
copygirl 209576bc18 Add `Blocks.position` function 1 week ago
copygirl fac93b94c9 Refactor cursor grabbing and more 1 week ago
  1. 8
      src/block.rs
  2. 101
      src/camera.rs
  3. 29
      src/main.rs
  4. 12
      src/placement.rs

@ -8,6 +8,7 @@ pub struct Block;
pub struct Blocks<'w, 's> {
commands: Commands<'w, 's>,
block_resources: Res<'w, BlockResources>,
blocks: Query<'w, 's, &'static Transform, With<Block>>,
}
impl Blocks<'_, '_> {
@ -19,6 +20,13 @@ impl Blocks<'_, '_> {
Transform::from_translation(pos.as_vec3() + Vec3::ONE / 2.),
));
}
/// Gets the position of a block entity, or `None`
/// if the given entity is not alive or not a block.
pub fn position(&self, entity: Entity) -> Option<IVec3> {
let transform = self.blocks.get(entity).ok();
transform.map(|t| t.translation.floor().as_ivec3())
}
}
#[derive(Resource)]

@ -4,6 +4,53 @@ use bevy::input::mouse::AccumulatedMouseMotion;
use bevy::prelude::*;
use bevy::window::{CursorGrabMode, CursorOptions};
pub fn cursor_grab(
mut mouse_button_input: ResMut<ButtonInput<MouseButton>>,
key_input: Res<ButtonInput<KeyCode>>,
window: Single<(&mut Window, &mut CursorOptions)>,
crosshair: Single<&mut Visibility, With<Crosshair>>,
mut request_center_cursor: Local<bool>,
) {
let (mut window, mut cursor) = window.into_inner();
let mut crosshair_visibility = crosshair.into_inner();
let is_grabbed = cursor.grab_mode == CursorGrabMode::Locked;
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.
let center = window.resolution.size() / 2.;
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;
}
}
pub fn is_cursor_grabbed(cursor: Single<&CursorOptions>) -> bool {
cursor.grab_mode == CursorGrabMode::Locked
}
#[derive(Component, Debug)]
pub struct CameraFreeLook {
/// The mouse sensitivity, in radians per pixel.
@ -32,49 +79,27 @@ impl Default for CameraFreeLook {
}
}
pub fn camera_free_look(
pub fn camera_look(
accumulated_mouse_motion: Res<AccumulatedMouseMotion>,
mut mouse_button_input: ResMut<ButtonInput<MouseButton>>,
key_input: Res<ButtonInput<KeyCode>>,
window: Single<(&Window, &mut CursorOptions)>,
camera: Single<(&mut Transform, &mut CameraFreeLook)>,
crosshair: Single<&mut Visibility, With<Crosshair>>,
) {
let (window, mut cursor) = window.into_inner();
let (mut transform, mut look) = camera.into_inner();
let mut crosshair_visibility = crosshair.into_inner();
if !window.focused || key_input.just_pressed(KeyCode::Escape) {
*crosshair_visibility = Visibility::Hidden;
cursor.grab_mode = CursorGrabMode::None;
cursor.visible = true;
}
if mouse_button_input.any_just_pressed([MouseButton::Left, MouseButton::Right]) {
*crosshair_visibility = Visibility::Inherited;
cursor.grab_mode = CursorGrabMode::Locked;
cursor.visible = false;
// To prevent other systems (such as placement) from seeing
// the mouse buttons being pressed, clear the state here.
mouse_button_input.clear();
// Ensure the yaw and pitch are initialized once
// from the camera transform's current rotation.
if !look.initialized {
(look.yaw, look.pitch, _) = transform.rotation.to_euler(EulerRot::YXZ);
look.initialized = true;
}
if cursor.grab_mode == CursorGrabMode::Locked {
// Ensure the yaw and pitch are initialized once
// from the camera transform's current rotation.
if !look.initialized {
(look.yaw, look.pitch, _) = transform.rotation.to_euler(EulerRot::YXZ);
look.initialized = true;
}
// Update the current camera state's internal yaw and pitch.
let motion = accumulated_mouse_motion.delta * look.sensitivity;
let (min, max) = look.pitch_limit.clone().into_inner();
look.yaw = (look.yaw - motion.x).rem_euclid(TAU); // keep within 0°..360°
look.pitch = (look.pitch - motion.y).clamp(min, max);
// Update the current camera state's internal yaw and pitch.
let motion = accumulated_mouse_motion.delta * look.sensitivity;
let (min, max) = look.pitch_limit.clone().into_inner();
look.yaw = (look.yaw - motion.x).rem_euclid(TAU); // keep within 0°..360°
look.pitch = (look.pitch - motion.y).clamp(min, max);
// Override the camera transform's rotation.
transform.rotation = Quat::from_euler(EulerRot::ZYX, 0.0, look.yaw, look.pitch);
}
// Override the camera transform's rotation.
transform.rotation = Quat::from_euler(EulerRot::ZYX, 0.0, look.yaw, look.pitch);
}
// TODO: Make it possible to attach this to any entity, such as the player,
@ -116,14 +141,10 @@ impl Default for CameraNoClip {
pub fn noclip_controller(
time: Res<Time<Real>>,
cursor: Single<&mut CursorOptions>,
key_input: Res<ButtonInput<KeyCode>>,
camera: Single<(&mut Transform, &mut CameraNoClip)>,
camera: Single<(&mut Transform, &CameraNoClip)>,
) {
let (mut transform, noclip) = camera.into_inner();
if cursor.grab_mode != CursorGrabMode::Locked {
return;
}
#[rustfmt::skip]
let movement = {

@ -1,27 +1,30 @@
use bevy::prelude::*;
mod block;
mod free_camera;
mod camera;
mod placement;
use block::*;
use free_camera::*;
use camera::*;
use placement::*;
fn main() {
#[rustfmt::skip]
App::new()
.add_plugins(DefaultPlugins)
.add_systems(
Startup,
(setup_blocks, setup_crosshair, setup_scene).chain(),
)
.add_systems(Update, (camera_free_look, noclip_controller).chain())
.add_systems(
PostUpdate,
// 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.after(TransformSystems::Propagate),
)
.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, 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.
.add_systems(PostUpdate, place_break_blocks.after(TransformSystems::Propagate))
.run();
}

@ -10,12 +10,7 @@ pub fn place_break_blocks(
mouse_button_input: Res<ButtonInput<MouseButton>>,
window: Single<(&Window, &CursorOptions)>,
camera: Single<(&GlobalTransform, &Camera)>,
block_lookup: Query<&Transform, With<Block>>,
) {
if !mouse_button_input.any_just_pressed([MouseButton::Left, MouseButton::Right]) {
return; // only run this system when left or right mouse button is pressed
}
let (window, cursor) = window.into_inner();
let (cam_transform, camera) = camera.into_inner();
@ -31,7 +26,7 @@ pub fn place_break_blocks(
let Some((block, hit)) = ray_cast.cast_ray(ray, settings).first() else {
return; // ray didn't hit anything
};
let Ok(block_transform) = block_lookup.get(*block) else {
let Some(block_pos) = blocks.position(*block) else {
return; // entity hit is not a block
};
@ -40,9 +35,10 @@ pub fn place_break_blocks(
commands.entity(*block).despawn();
} else if mouse_button_input.just_pressed(MouseButton::Right) {
// Create a new block next to the one that was just clicked.
let pos = block_transform.translation.floor().as_ivec3();
// FIXME: This only works for axis-aligned normals.
let offset = hit.normal.normalize().round().as_ivec3();
blocks.spawn(pos + offset);
blocks.spawn(block_pos + offset);
}
}

Loading…
Cancel
Save