feat: hitscan projectiles
This commit is contained in:
parent
6e2d70d376
commit
2f8f81ec37
@ -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<Camera>>,
|
||||
mut mouse_events: EventReader<MouseButtonInput>,
|
||||
mut bullet_events: EventWriter<BulletFiredEvent>,
|
||||
mut projectiles: ResMut<ProjectileSpawner>,
|
||||
) {
|
||||
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::<Vec<_>>()
|
||||
);
|
||||
{
|
||||
projectiles.spawn(ProjectileData {
|
||||
position: camera_transform.translation(),
|
||||
direction: camera_transform.forward(),
|
||||
speed: 900.0,
|
||||
mass: 1.0,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -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::<CollisionEnded>));
|
||||
app.add_systems(PostProcessCollisions, on_targets_shot.run_if(on_event::<ProjectileHitEvent>));
|
||||
}
|
||||
}
|
||||
|
||||
@ -150,34 +149,16 @@ fn setup_target_wall(
|
||||
}
|
||||
|
||||
fn on_targets_shot(
|
||||
mut commands: Commands,
|
||||
mut collision_events: EventReader<CollisionEnded>,
|
||||
mut query_set: ParamSet<(
|
||||
Query<Entity, With<Bullet>>,
|
||||
Query<(&TargetHandle, &mut Transform), With<Target>>,
|
||||
)>,
|
||||
mut query: Query<(&TargetHandle, &mut Transform), With<Target>>,
|
||||
mut projectile_hit_events: EventReader<ProjectileHitEvent>,
|
||||
mut target_resources: ResMut<TargetResources>,
|
||||
) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
137
src/physics/projectile.rs
Normal file
137
src/physics/projectile.rs
Normal file
@ -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::<ProjectileHitEvent>();
|
||||
app.add_systems(PhysicsSchedule, do_projectile_hitscan_flight.in_set(PhysicsStepSet::Last));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
pub struct ProjectileResources
|
||||
{
|
||||
pub mesh: Mesh3d,
|
||||
pub material: MeshMaterial3d<StandardMaterial>,
|
||||
}
|
||||
|
||||
fn setup_projectiles(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
) {
|
||||
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<ProjectileData>,
|
||||
}
|
||||
|
||||
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<ProjectileSpawner>,
|
||||
resources: Res<ProjectileResources>,
|
||||
) {
|
||||
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<Projectile>>,
|
||||
mut event_writer: EventWriter<ProjectileHitEvent>,
|
||||
spatial_query: SpatialQuery,
|
||||
time: Res<Time>,
|
||||
) {
|
||||
for (entity, mut transform, velocity) in query.iter_mut()
|
||||
{
|
||||
let Ok(direction) = Dir3::new(velocity.0) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let flight_distance = velocity.norm() * time.delta_secs();
|
||||
|
||||
let ray_cast = spatial_query.cast_ray(
|
||||
transform.translation,
|
||||
direction,
|
||||
flight_distance,
|
||||
true,
|
||||
&SpatialQueryFilter::default()
|
||||
);
|
||||
|
||||
let flight_distance = ray_cast.map(|hit| hit.distance).unwrap_or(flight_distance);
|
||||
transform.translation += direction * flight_distance;
|
||||
|
||||
let Some(ray_hit) = ray_cast else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// TODO: impart an impulse on the hit dynamic entity
|
||||
|
||||
event_writer.send(ProjectileHitEvent {
|
||||
entity: ray_hit.entity,
|
||||
normal: ray_hit.normal,
|
||||
});
|
||||
commands.entity(entity).despawn();
|
||||
}
|
||||
}
|
||||
@ -1,87 +0,0 @@
|
||||
|
||||
use bevy::prelude::*;
|
||||
use avian3d::prelude::*;
|
||||
|
||||
use crate::util::ecs::PathTracer;
|
||||
|
||||
pub struct ProjectilesPlugin;
|
||||
|
||||
impl Plugin for ProjectilesPlugin
|
||||
{
|
||||
fn build(&self, app: &mut App)
|
||||
{
|
||||
app.add_systems(Startup, setup_projectiles);
|
||||
|
||||
app.add_event::<BulletFiredEvent>();
|
||||
app.add_systems(Update, on_bullet_fired_event.run_if(on_event::<BulletFiredEvent>));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
pub struct BulletResources
|
||||
{
|
||||
pub mesh: Mesh3d,
|
||||
pub material: MeshMaterial3d<StandardMaterial>,
|
||||
pub collider: Collider,
|
||||
}
|
||||
|
||||
fn setup_projectiles(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
) {
|
||||
commands.insert_resource(BulletResources {
|
||||
mesh: Mesh3d(meshes.add(Sphere::new(1.0))),
|
||||
material: MeshMaterial3d(materials.add(StandardMaterial {
|
||||
base_color: Color::srgb(0.7, 0.1, 0.1),
|
||||
..default()
|
||||
})),
|
||||
collider: Collider::sphere(1.0),
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Event, Copy, Clone, PartialEq, Debug)]
|
||||
pub struct BulletFiredEvent
|
||||
{
|
||||
pub position: Vec3,
|
||||
pub direction: Dir3,
|
||||
pub speed: f32,
|
||||
pub radius: f32,
|
||||
pub density: f32,
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct Bullet;
|
||||
|
||||
fn on_bullet_fired_event(
|
||||
mut commands: Commands,
|
||||
mut events: EventReader<BulletFiredEvent>,
|
||||
bullet_resources: Res<BulletResources>,
|
||||
) {
|
||||
let to_projectile = |event: &BulletFiredEvent|
|
||||
{
|
||||
let transform = Transform::IDENTITY
|
||||
.with_translation(event.position)
|
||||
.with_scale(Vec3::splat(event.radius))
|
||||
.looking_to(event.direction, Vec3::Y);
|
||||
|
||||
(
|
||||
Bullet,
|
||||
bullet_resources.mesh.clone(),
|
||||
bullet_resources.material.clone(),
|
||||
bullet_resources.collider.clone(),
|
||||
transform,
|
||||
RigidBody::Dynamic,
|
||||
ColliderDensity(event.density),
|
||||
SpeculativeMargin(event.radius),
|
||||
SweptCcd::LINEAR,
|
||||
LinearVelocity(event.speed * event.direction),
|
||||
PathTracer::default(),
|
||||
)
|
||||
};
|
||||
|
||||
commands.spawn_batch(events.read()
|
||||
.map(to_projectile)
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user