diff --git a/src/block.rs b/src/block.rs new file mode 100644 index 0000000..71c8274 --- /dev/null +++ b/src/block.rs @@ -0,0 +1,39 @@ +use bevy::ecs::system::SystemParam; +use bevy::prelude::*; + +#[derive(Component)] +pub struct Block; + +#[derive(SystemParam)] +pub struct Blocks<'w, 's> { + commands: Commands<'w, 's>, + block_resources: Res<'w, BlockResources>, +} + +impl Blocks<'_, '_> { + pub fn spawn(&mut self, pos: IVec3) { + self.commands.spawn(( + Block, + Mesh3d(self.block_resources.mesh.clone()), + MeshMaterial3d(self.block_resources.material.clone()), + Transform::from_translation(pos.as_vec3() + Vec3::ONE / 2.), + )); + } +} + +#[derive(Resource)] +pub struct BlockResources { + mesh: Handle, + material: Handle, +} + +pub fn setup_blocks( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, +) { + commands.insert_resource(BlockResources { + mesh: meshes.add(Cuboid::new(1.0, 1.0, 1.0)), + material: materials.add(Color::srgb_u8(124, 144, 255)), + }); +} diff --git a/src/free_camera.rs b/src/free_camera.rs index ed5a7b2..eb26137 100644 --- a/src/free_camera.rs +++ b/src/free_camera.rs @@ -34,7 +34,7 @@ impl Default for CameraFreeLook { pub fn camera_free_look( accumulated_mouse_motion: Res, - mouse_button_input: Res>, + mut mouse_button_input: ResMut>, key_input: Res>, window: Single<(&Window, &mut CursorOptions)>, camera: Single<(&mut Transform, &mut CameraFreeLook)>, @@ -53,6 +53,9 @@ pub fn camera_free_look( *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(); } if cursor.grab_mode == CursorGrabMode::Locked { diff --git a/src/main.rs b/src/main.rs index ff98f56..1695520 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,43 +1,40 @@ use bevy::prelude::*; +mod block; mod free_camera; -use free_camera::*; - mod placement; + +use block::*; +use free_camera::*; use placement::*; fn main() { App::new() .add_plugins(DefaultPlugins) - .add_systems(Startup, (setup_scene, setup_crosshair)) + .add_systems( + Startup, + (setup_blocks, setup_crosshair, setup_scene).chain(), + ) .add_systems(Update, (camera_free_look, noclip_controller).chain()) - // This system requires `GlobalTransform`, so for a most - // up-to-date value, run it right after it's been updated. .add_systems( PostUpdate, - debug_ray_cast.after(TransformSystems::Propagate), + // 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), ) .run(); } -fn setup_scene( - mut commands: Commands, - mut meshes: ResMut>, - mut materials: ResMut>, -) { - // circular base - commands.spawn(( - Mesh3d(meshes.add(Circle::new(4.0))), - MeshMaterial3d(materials.add(Color::WHITE)), - Transform::from_rotation(Quat::from_rotation_x(-std::f32::consts::FRAC_PI_2)), - )); - // cube +fn setup_scene(mut commands: Commands, mut blocks: Blocks) { + // light commands.spawn(( - Mesh3d(meshes.add(Cuboid::new(1.0, 1.0, 1.0))), - MeshMaterial3d(materials.add(Color::srgb_u8(124, 144, 255))), - Transform::from_xyz(0.0, 0.5, 0.0), + Camera3d::default(), + Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y), + CameraFreeLook::default(), + CameraNoClip::default(), )); - // light + + // camera commands.spawn(( PointLight { shadows_enabled: true, @@ -45,11 +42,11 @@ fn setup_scene( }, Transform::from_xyz(4.0, 8.0, 4.0), )); - // camera - commands.spawn(( - Camera3d::default(), - Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y), - CameraFreeLook::default(), - CameraNoClip::default(), - )); + + // blocks + for x in -8..8 { + for z in -8..8 { + blocks.spawn(IVec3::new(x, 0, z)); + } + } } diff --git a/src/placement.rs b/src/placement.rs index 1f1d302..ecb8123 100644 --- a/src/placement.rs +++ b/src/placement.rs @@ -1,27 +1,48 @@ use bevy::prelude::*; use bevy::window::{CursorGrabMode, CursorOptions}; -pub fn debug_ray_cast( - mut gizmos: Gizmos, +use crate::block::*; + +pub fn place_break_blocks( + mut commands: Commands, + mut blocks: Blocks, mut ray_cast: MeshRayCast, + mouse_button_input: Res>, window: Single<(&Window, &CursorOptions)>, camera: Single<(&GlobalTransform, &Camera)>, + block_lookup: Query<&Transform, With>, ) { + 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 (transform, camera) = camera.into_inner(); + let (cam_transform, camera) = camera.into_inner(); let ray = if cursor.grab_mode == CursorGrabMode::Locked { - Ray3d::new(transform.translation(), transform.forward()) + Ray3d::new(cam_transform.translation(), cam_transform.forward()) } else if let Some(cursor_pos) = window.cursor_position() { - camera.viewport_to_world(transform, cursor_pos).unwrap() + camera.viewport_to_world(cam_transform, cursor_pos).unwrap() } else { return; // cursor outside window area }; let settings = &MeshRayCastSettings::default(); - let Some((_entity, hit)) = ray_cast.cast_ray(ray, settings).first() else { + 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 { + return; // entity hit is not a block + }; - gizmos.arrow(hit.point, hit.point + hit.normal / 2., Color::WHITE); + if mouse_button_input.just_pressed(MouseButton::Left) { + // Destroy the block clicked. + 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); + } }