gun shots (#16)
This commit is contained in:
12
Cargo.lock
generated
12
Cargo.lock
generated
@@ -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",
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|||||||
@@ -5,4 +5,6 @@ pub enum GameLayer {
|
|||||||
#[default]
|
#[default]
|
||||||
Level,
|
Level,
|
||||||
Player,
|
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::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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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| {
|
||||||
|
|||||||
Reference in New Issue
Block a user