bevy_fps/src/physics/projectile.rs

138 lines
3.4 KiB
Rust

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();
}
}