138 lines
3.4 KiB
Rust
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();
|
|
}
|
|
}
|