gun shots (#16)
This commit is contained in:
@@ -13,7 +13,7 @@ use std::f32::consts::PI;
|
||||
pub struct AimState {
|
||||
pub target: Option<Entity>,
|
||||
pub range: f32,
|
||||
pub angle: f32,
|
||||
pub max_angle: f32,
|
||||
}
|
||||
|
||||
impl Default for AimState {
|
||||
@@ -21,7 +21,7 @@ impl Default for AimState {
|
||||
Self {
|
||||
target: None,
|
||||
range: 40.,
|
||||
angle: PI / 10.,
|
||||
max_angle: PI / 8.,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -108,7 +108,7 @@ fn update(
|
||||
|
||||
let angle = player_forward.angle_between(delta.normalize());
|
||||
|
||||
if angle < state.angle && distance < target_distance {
|
||||
if angle < state.max_angle && distance < target_distance {
|
||||
new_target = Some(e);
|
||||
target_distance = distance;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ use bevy::core_pipeline::tonemapping::Tonemapping;
|
||||
use bevy::prelude::*;
|
||||
use bevy::render::view::ColorGrading;
|
||||
use bevy::scene::SceneInstanceReady;
|
||||
use bevy_polyline::PolylinePlugin;
|
||||
use bevy_trenchbroom::prelude::*;
|
||||
use control::controller::CharacterControllerPlugin;
|
||||
use physics_layers::GameLayer;
|
||||
@@ -63,6 +64,8 @@ fn main() {
|
||||
|
||||
app.add_plugins(PhysicsPlugins::default());
|
||||
app.add_plugins(CharacterControllerPlugin);
|
||||
app.add_plugins(PolylinePlugin);
|
||||
app.add_plugins(TrenchBroomPlugin(TrenchBroomConfig::new("hedz")));
|
||||
|
||||
#[cfg(feature = "dbg")]
|
||||
{
|
||||
@@ -102,8 +105,6 @@ fn main() {
|
||||
});
|
||||
app.insert_resource(ClearColor(Color::BLACK));
|
||||
|
||||
app.add_plugins(TrenchBroomPlugin(TrenchBroomConfig::new("hedz")));
|
||||
|
||||
app.add_systems(Startup, (write_trenchbroom_config, music));
|
||||
app.add_systems(PostStartup, setup_scene);
|
||||
app.add_systems(Update, (set_materials_unlit, set_tonemapping, set_shadows));
|
||||
|
||||
@@ -5,4 +5,6 @@ pub enum GameLayer {
|
||||
#[default]
|
||||
Level,
|
||||
Player,
|
||||
Npc,
|
||||
Projectile,
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ use bevy_trenchbroom::class::Target;
|
||||
use bevy_trenchbroom::prelude::*;
|
||||
|
||||
use crate::cash::Cash;
|
||||
use crate::physics_layers::GameLayer;
|
||||
|
||||
#[derive(PointClass, Component, Reflect, Default)]
|
||||
#[reflect(Component)]
|
||||
@@ -150,6 +151,7 @@ impl EnemySpawn {
|
||||
Visibility::default(),
|
||||
RigidBody::Dynamic,
|
||||
Collider::capsule(0.4, 2.),
|
||||
CollisionLayers::new(LayerMask(GameLayer::Npc.to_bits()), LayerMask::ALL),
|
||||
LockedAxes::new().lock_rotation_z().lock_rotation_x(),
|
||||
))
|
||||
.with_children(|parent| {
|
||||
|
||||
Reference in New Issue
Block a user