gun shots (#16)

This commit is contained in:
extrawurst
2025-03-24 16:27:26 +01:00
committed by GitHub
parent 8599fe50e8
commit 594a5a612d
7 changed files with 160 additions and 9 deletions

12
Cargo.lock generated
View File

@@ -1127,6 +1127,17 @@ dependencies = [
"uuid", "uuid",
] ]
[[package]]
name = "bevy_polyline"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9bbf333dab4a712f63456aa64b8e06268a44afdcbc9be4780e6260a0e8113072"
dependencies = [
"bevy",
"bitflags 2.9.0",
"bytemuck",
]
[[package]] [[package]]
name = "bevy_ptr" name = "bevy_ptr"
version = "0.15.3" version = "0.15.3"
@@ -2893,6 +2904,7 @@ dependencies = [
"bevy", "bevy",
"bevy-inspector-egui", "bevy-inspector-egui",
"bevy_asset_loader", "bevy_asset_loader",
"bevy_polyline",
"bevy_sprite3d", "bevy_sprite3d",
"bevy_trenchbroom", "bevy_trenchbroom",
"nil", "nil",

View File

@@ -26,3 +26,4 @@ bevy_asset_loader = "0.22.0"
bevy_sprite3d = "4.0.0" bevy_sprite3d = "4.0.0"
rand = "0.8.5" rand = "0.8.5"
bevy-inspector-egui = { version = "0.30", optional = true } bevy-inspector-egui = { version = "0.30", optional = true }
bevy_polyline = "0.11.0"

View File

@@ -13,7 +13,7 @@ use std::f32::consts::PI;
pub struct AimState { pub struct AimState {
pub target: Option<Entity>, pub target: Option<Entity>,
pub range: f32, pub range: f32,
pub angle: f32, pub max_angle: f32,
} }
impl Default for AimState { impl Default for AimState {
@@ -21,7 +21,7 @@ impl Default for AimState {
Self { Self {
target: None, target: None,
range: 40., range: 40.,
angle: PI / 10., max_angle: PI / 8.,
} }
} }
} }
@@ -108,7 +108,7 @@ fn update(
let angle = player_forward.angle_between(delta.normalize()); 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); new_target = Some(e);
target_distance = distance; target_distance = distance;
} }

View File

@@ -26,6 +26,7 @@ use bevy::core_pipeline::tonemapping::Tonemapping;
use bevy::prelude::*; use bevy::prelude::*;
use bevy::render::view::ColorGrading; use bevy::render::view::ColorGrading;
use bevy::scene::SceneInstanceReady; use bevy::scene::SceneInstanceReady;
use bevy_polyline::PolylinePlugin;
use bevy_trenchbroom::prelude::*; use bevy_trenchbroom::prelude::*;
use control::controller::CharacterControllerPlugin; use control::controller::CharacterControllerPlugin;
use physics_layers::GameLayer; use physics_layers::GameLayer;
@@ -63,6 +64,8 @@ fn main() {
app.add_plugins(PhysicsPlugins::default()); app.add_plugins(PhysicsPlugins::default());
app.add_plugins(CharacterControllerPlugin); app.add_plugins(CharacterControllerPlugin);
app.add_plugins(PolylinePlugin);
app.add_plugins(TrenchBroomPlugin(TrenchBroomConfig::new("hedz")));
#[cfg(feature = "dbg")] #[cfg(feature = "dbg")]
{ {
@@ -102,8 +105,6 @@ fn main() {
}); });
app.insert_resource(ClearColor(Color::BLACK)); app.insert_resource(ClearColor(Color::BLACK));
app.add_plugins(TrenchBroomPlugin(TrenchBroomConfig::new("hedz")));
app.add_systems(Startup, (write_trenchbroom_config, music)); app.add_systems(Startup, (write_trenchbroom_config, music));
app.add_systems(PostStartup, setup_scene); app.add_systems(PostStartup, setup_scene);
app.add_systems(Update, (set_materials_unlit, set_tonemapping, set_shadows)); app.add_systems(Update, (set_materials_unlit, set_tonemapping, set_shadows));

View File

@@ -5,4 +5,6 @@ pub enum GameLayer {
#[default] #[default]
Level, Level,
Player, Player,
Npc,
Projectile,
} }

View File

@@ -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::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)] #[derive(Event, Reflect)]
pub enum TriggerState { pub enum TriggerState {
@@ -9,15 +27,130 @@ pub enum TriggerState {
} }
pub fn plugin(app: &mut App) { pub fn plugin(app: &mut App) {
app.add_systems(Update, (update, shot_collision, enemy_hit, timeout));
app.add_observer(on_trigger_state); 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) { if matches!(trigger.event(), TriggerState::Active) {
commands.trigger(PlaySound::Gun); commands.trigger(PlaySound::Gun);
if let Some(target) = aim.target { let Some(player_pos) = player_pos.iter().next().map(|t| t.translation) else {
commands.entity(target).trigger(Hit { damage: 20 }); 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();
}
}

View File

@@ -6,6 +6,7 @@ use bevy_trenchbroom::class::Target;
use bevy_trenchbroom::prelude::*; use bevy_trenchbroom::prelude::*;
use crate::cash::Cash; use crate::cash::Cash;
use crate::physics_layers::GameLayer;
#[derive(PointClass, Component, Reflect, Default)] #[derive(PointClass, Component, Reflect, Default)]
#[reflect(Component)] #[reflect(Component)]
@@ -150,6 +151,7 @@ impl EnemySpawn {
Visibility::default(), Visibility::default(),
RigidBody::Dynamic, RigidBody::Dynamic,
Collider::capsule(0.4, 2.), Collider::capsule(0.4, 2.),
CollisionLayers::new(LayerMask(GameLayer::Npc.to_bits()), LayerMask::ALL),
LockedAxes::new().lock_rotation_z().lock_rotation_x(), LockedAxes::new().lock_rotation_z().lock_rotation_x(),
)) ))
.with_children(|parent| { .with_children(|parent| {