gun shots (#16)
This commit is contained in:
141
src/shooting.rs
141
src/shooting.rs
@@ -1,6 +1,24 @@
|
||||
use crate::{
|
||||
aim::AimState,
|
||||
npc::Hit,
|
||||
physics_layers::GameLayer,
|
||||
player::{Player, PlayerRig},
|
||||
sounds::PlaySound,
|
||||
tb_entities::EnemySpawn,
|
||||
};
|
||||
use avian3d::prelude::{
|
||||
Collider, CollisionLayers, CollisionStarted, LayerMask, PhysicsLayer, Sensor,
|
||||
};
|
||||
use bevy::prelude::*;
|
||||
use bevy_polyline::prelude::*;
|
||||
use std::f32::consts::PI;
|
||||
|
||||
use crate::{aim::AimState, npc::Hit, sounds::PlaySound};
|
||||
#[derive(Component)]
|
||||
struct Shot {
|
||||
time: f32,
|
||||
}
|
||||
|
||||
const MAX_SHOT_AGES: f32 = 1.;
|
||||
|
||||
#[derive(Event, Reflect)]
|
||||
pub enum TriggerState {
|
||||
@@ -9,15 +27,130 @@ pub enum TriggerState {
|
||||
}
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
app.add_systems(Update, (update, shot_collision, enemy_hit, timeout));
|
||||
app.add_observer(on_trigger_state);
|
||||
}
|
||||
|
||||
fn on_trigger_state(trigger: Trigger<TriggerState>, mut commands: Commands, aim: Res<AimState>) {
|
||||
fn on_trigger_state(
|
||||
trigger: Trigger<TriggerState>,
|
||||
mut commands: Commands,
|
||||
aim: Res<AimState>,
|
||||
player_rot: Query<&Transform, With<PlayerRig>>,
|
||||
player_pos: Query<&Transform, With<Player>>,
|
||||
target_transform: Query<&Transform, (Without<Player>, Without<PlayerRig>)>,
|
||||
time: Res<Time>,
|
||||
mut polyline_materials: ResMut<Assets<PolylineMaterial>>,
|
||||
mut polylines: ResMut<Assets<Polyline>>,
|
||||
) {
|
||||
if matches!(trigger.event(), TriggerState::Active) {
|
||||
commands.trigger(PlaySound::Gun);
|
||||
|
||||
if let Some(target) = aim.target {
|
||||
commands.entity(target).trigger(Hit { damage: 20 });
|
||||
let Some(player_pos) = player_pos.iter().next().map(|t| t.translation) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(rotation) = player_rot.iter().next().map(|t| t.rotation) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let rotation = if let Some(target) = aim.target {
|
||||
let t = target_transform
|
||||
.get(target)
|
||||
.expect("target must have transform");
|
||||
Transform::from_translation(player_pos)
|
||||
.looking_at(t.translation, Vec3::Y)
|
||||
.rotation
|
||||
} else {
|
||||
rotation.mul_quat(Quat::from_rotation_y(PI))
|
||||
};
|
||||
|
||||
let mut t = Transform::from_translation(player_pos).with_rotation(rotation);
|
||||
t.translation += t.forward().as_vec3() * 2.;
|
||||
|
||||
commands
|
||||
.spawn((
|
||||
Shot {
|
||||
time: time.elapsed_secs(),
|
||||
},
|
||||
Collider::capsule_endpoints(0.5, Vec3::new(0., 0., 0.), Vec3::new(0., 0., -3.)),
|
||||
CollisionLayers::new(LayerMask(GameLayer::Projectile.to_bits()), LayerMask::ALL),
|
||||
Sensor,
|
||||
t,
|
||||
))
|
||||
.with_child(PolylineBundle {
|
||||
polyline: PolylineHandle(polylines.add(Polyline {
|
||||
vertices: vec![Vec3::Z * 2., Vec3::Z * -2.],
|
||||
})),
|
||||
material: PolylineMaterialHandle(polyline_materials.add(PolylineMaterial {
|
||||
width: 10.0,
|
||||
color: LinearRgba::rgb(0.9, 0.9, 0.),
|
||||
perspective: false,
|
||||
..default()
|
||||
})),
|
||||
..default()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn update(mut query: Query<&mut Transform, With<Shot>>) {
|
||||
for mut transform in query.iter_mut() {
|
||||
let forward = transform.forward();
|
||||
transform.translation += forward * 2.;
|
||||
}
|
||||
}
|
||||
|
||||
fn timeout(mut commands: Commands, query: Query<(Entity, &Shot)>, time: Res<Time>) {
|
||||
let current_time = time.elapsed_secs();
|
||||
|
||||
for (e, Shot { time }) in query.iter() {
|
||||
if current_time > time + MAX_SHOT_AGES {
|
||||
commands.entity(e).despawn_recursive();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn enemy_hit(
|
||||
mut commands: Commands,
|
||||
mut collision_event_reader: EventReader<CollisionStarted>,
|
||||
query_npc: Query<&EnemySpawn>,
|
||||
) {
|
||||
for CollisionStarted(e1, e2) in collision_event_reader.read() {
|
||||
if !(query_npc.contains(*e1) || query_npc.contains(*e2)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let enemy_entity = query_npc
|
||||
.contains(*e1)
|
||||
.then(|| *e1)
|
||||
.or_else(|| Some(*e2))
|
||||
.unwrap();
|
||||
|
||||
commands.entity(enemy_entity).trigger(Hit { damage: 20 });
|
||||
}
|
||||
}
|
||||
|
||||
fn shot_collision(
|
||||
mut commands: Commands,
|
||||
mut collision_event_reader: EventReader<CollisionStarted>,
|
||||
query_shot: Query<&Shot>,
|
||||
query_player: Query<&Player>,
|
||||
) {
|
||||
for CollisionStarted(e1, e2) in collision_event_reader.read() {
|
||||
if !(query_shot.contains(*e1) || query_shot.contains(*e2)) {
|
||||
continue;
|
||||
}
|
||||
// filter out collision with player
|
||||
// TODO: give level geo a layer and we can simply use `CollisionLayers`
|
||||
if query_player.contains(*e1) || query_player.contains(*e2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let shot_entity = query_shot
|
||||
.contains(*e1)
|
||||
.then(|| *e1)
|
||||
.or_else(|| Some(*e2))
|
||||
.unwrap();
|
||||
|
||||
commands.entity(shot_entity).despawn_recursive();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user