From 2f8f81ec3790a45522baa7b00934a6f78926d239 Mon Sep 17 00:00:00 2001 From: Robert Fry Date: Thu, 9 Jan 2025 14:23:27 +0000 Subject: [PATCH] feat: hitscan projectiles --- src/game/mod.rs | 25 +++---- src/game/target_wall.rs | 35 +++------- src/physics/mod.rs | 6 +- src/physics/projectile.rs | 137 +++++++++++++++++++++++++++++++++++++ src/physics/projectiles.rs | 87 ----------------------- 5 files changed, 159 insertions(+), 131 deletions(-) create mode 100644 src/physics/projectile.rs delete mode 100644 src/physics/projectiles.rs diff --git a/src/game/mod.rs b/src/game/mod.rs index b9694c3..1d7fccc 100644 --- a/src/game/mod.rs +++ b/src/game/mod.rs @@ -4,7 +4,7 @@ use bevy::prelude::*; use bevy::input::mouse::MouseButtonInput; use avian3d::prelude::*; -use crate::physics::BulletFiredEvent; +use crate::physics::{ProjectileData, ProjectileSpawner}; use crate::ui::camera::CameraController; use crate::ui::cursor::CursorGrabState; @@ -87,22 +87,19 @@ fn setup_level( fn do_shoot_on_left_click( camera_query: Single<&GlobalTransform, With>, mut mouse_events: EventReader, - mut bullet_events: EventWriter, + mut projectiles: ResMut, ) { let camera_transform = camera_query.into_inner(); - let bullet_fired_event = BulletFiredEvent { - position: camera_transform.translation(), - direction: camera_transform.forward(), - speed: 200.0, - radius: 0.008, - density: 11.0, - }; - - bullet_events.send_batch(mouse_events.read() + for _ in mouse_events.read() .filter(|event| event.button == MouseButton::Left) .filter(|event| event.state == ButtonState::Pressed) - .map(|_| bullet_fired_event) - .collect::>() - ); + { + projectiles.spawn(ProjectileData { + position: camera_transform.translation(), + direction: camera_transform.forward(), + speed: 900.0, + mass: 1.0, + }); + } } diff --git a/src/game/target_wall.rs b/src/game/target_wall.rs index df29f92..9856234 100644 --- a/src/game/target_wall.rs +++ b/src/game/target_wall.rs @@ -4,8 +4,7 @@ use std::collections::HashMap; use bevy::prelude::*; use avian3d::prelude::*; -use crate::physics::Bullet; -use crate::util::physics::CollisionQuery; +use crate::physics::ProjectileHitEvent; const WALL_SIZE: Vec3 = Vec3::new(20.0, 10.0, 0.1); const WALL_FACE_Z: f32 = -25.0; @@ -29,7 +28,7 @@ impl Plugin for TargetWallPlugin }); app.add_systems(Startup, setup_target_wall); - app.add_systems(PostProcessCollisions, on_targets_shot.run_if(on_event::)); + app.add_systems(PostProcessCollisions, on_targets_shot.run_if(on_event::)); } } @@ -150,34 +149,16 @@ fn setup_target_wall( } fn on_targets_shot( - mut commands: Commands, - mut collision_events: EventReader, - mut query_set: ParamSet<( - Query>, - Query<(&TargetHandle, &mut Transform), With>, - )>, + mut query: Query<(&TargetHandle, &mut Transform), With>, + mut projectile_hit_events: EventReader, mut target_resources: ResMut, ) { - for collision in collision_events.read() + for hit_event in projectile_hit_events.read() { - // resolve the query data from the collision, if the collision is - // between a bullet and a target - // - let bullet_query = &mut query_set.p0(); - let Ok(bullet_entity) = collision.query_unique(bullet_query) else { - continue; // the collision doesn't involve a bullet - }; - // - let target_query = &mut query_set.p1(); - let Ok((target_handle, mut target_transform)) = collision.query_unique(target_query) else { - continue; // the collision doesn't involve a target + let Ok((handle, mut transform)) = query.get_mut(hit_event.entity) else { + continue; }; - // move the target - *target_transform = target_resources.random_transform(target_handle); - - // despawn the bullet - // TODO: this should be handled by the projectiles system - commands.entity(bullet_entity).despawn_recursive(); + *transform = target_resources.random_transform(handle); } } diff --git a/src/physics/mod.rs b/src/physics/mod.rs index bdb339b..167a7d1 100644 --- a/src/physics/mod.rs +++ b/src/physics/mod.rs @@ -2,8 +2,8 @@ use bevy::prelude::*; use bevy::app::PluginGroupBuilder; -mod projectiles; -pub use projectiles::*; +mod projectile; +pub use projectile::*; mod world_bounds; pub use world_bounds::*; @@ -19,7 +19,7 @@ impl PluginGroup for PhysicsPlugins .add_group(avian3d::PhysicsPlugins::default()) .add(avian3d::debug_render::PhysicsDebugPlugin::default()) - .add(ProjectilesPlugin) + .add(ProjectilePlugin) .add(WorldBoundsPlugin) } } diff --git a/src/physics/projectile.rs b/src/physics/projectile.rs new file mode 100644 index 0000000..a64a76f --- /dev/null +++ b/src/physics/projectile.rs @@ -0,0 +1,137 @@ + +use std::collections::VecDeque; + +use bevy::prelude::*; +use bevy::math::NormedVectorSpace; +use avian3d::prelude::*; + +pub struct ProjectilePlugin; + +impl Plugin for ProjectilePlugin +{ + fn build(&self, app: &mut App) + { + app.add_systems(Startup, setup_projectiles); + + app.insert_resource(ProjectileSpawner::default()); + app.add_systems(PreUpdate, do_projectile_spawn); + + app.add_event::(); + app.add_systems(PhysicsSchedule, do_projectile_hitscan_flight.in_set(PhysicsStepSet::Last)); + } +} + +#[derive(Resource)] +pub struct ProjectileResources +{ + pub mesh: Mesh3d, + pub material: MeshMaterial3d, +} + +fn setup_projectiles( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, +) { + commands.insert_resource(ProjectileResources { + mesh: Mesh3d(meshes.add(Cuboid::new(1.0, 1.0, 2.0))), + material: MeshMaterial3d(materials.add(StandardMaterial { + base_color: Color::srgb(0.3, 0.3, 0.3), + ..default() + })), + }); +} + +pub struct ProjectileData +{ + pub position: Vec3, + pub direction: Dir3, + pub speed: f32, + pub mass: f32, +} + +#[derive(Resource, Default)] +pub struct ProjectileSpawner +{ + queue: VecDeque, +} + +impl ProjectileSpawner +{ + pub fn spawn(&mut self, data: ProjectileData) + { + self.queue.push_back(data); + } +} + +#[derive(Component)] +pub struct Projectile; + +fn do_projectile_spawn( + mut commands: Commands, + mut spawner: ResMut, + resources: Res, +) { + while let Some(spawn_data) = spawner.queue.pop_front() + { + let rotation = Quat::from_rotation_arc(Vec3::NEG_Z, spawn_data.direction.into()); + let transform = Transform::from_translation(spawn_data.position).with_rotation(rotation); + + commands.spawn(( + Projectile, + resources.mesh.clone(), + resources.material.clone(), + transform, + LinearVelocity(spawn_data.direction * spawn_data.speed), + Mass(spawn_data.mass), + )); + } +} + +#[derive(Event)] +#[allow(unused)] +pub struct ProjectileHitEvent +{ + pub entity: Entity, + pub normal: Vec3, +} + +fn do_projectile_hitscan_flight( + mut commands: Commands, + mut query: Query<(Entity, &mut Transform, &LinearVelocity), With>, + mut event_writer: EventWriter, + spatial_query: SpatialQuery, + time: Res