132 lines
3.0 KiB
Rust
132 lines
3.0 KiB
Rust
use crate::{
|
|
billboards::Billboard,
|
|
player::{Player, PlayerHead},
|
|
tb_entities::EnemySpawn,
|
|
};
|
|
use bevy::prelude::*;
|
|
use bevy_sprite3d::{Sprite3dBuilder, Sprite3dParams};
|
|
use std::f32::consts::PI;
|
|
|
|
#[derive(Resource, Reflect)]
|
|
#[reflect(Resource)]
|
|
struct AimState {
|
|
pub target: Option<Entity>,
|
|
pub range: f32,
|
|
pub angle: f32,
|
|
}
|
|
|
|
impl Default for AimState {
|
|
fn default() -> Self {
|
|
Self {
|
|
target: None,
|
|
range: 40.,
|
|
angle: PI / 10.,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Component, Reflect)]
|
|
#[reflect(Component)]
|
|
struct Marker;
|
|
|
|
#[derive(Event)]
|
|
enum MarkerEvent {
|
|
Spawn(Entity),
|
|
Despawn,
|
|
}
|
|
|
|
pub fn plugin(app: &mut App) {
|
|
app.init_resource::<AimState>();
|
|
app.add_systems(Update, update);
|
|
app.add_observer(marker_event);
|
|
}
|
|
|
|
fn marker_event(
|
|
trigger: Trigger<MarkerEvent>,
|
|
mut commands: Commands,
|
|
asset_server: Res<AssetServer>,
|
|
mut sprite_params: Sprite3dParams,
|
|
marker: Query<Entity, With<Marker>>,
|
|
) {
|
|
for m in marker.iter() {
|
|
commands.entity(m).despawn_recursive();
|
|
}
|
|
|
|
let MarkerEvent::Spawn(target) = trigger.event() else {
|
|
return;
|
|
};
|
|
|
|
let selector = asset_server.load("ui/selector.png");
|
|
|
|
let id = commands
|
|
.spawn((
|
|
Name::new("aim-marker"),
|
|
Billboard,
|
|
Marker,
|
|
Transform::from_translation(Vec3::new(0., 3., 0.)),
|
|
Sprite3dBuilder {
|
|
image: selector,
|
|
pixels_per_metre: 30.,
|
|
alpha_mode: AlphaMode::Blend,
|
|
unlit: true,
|
|
..default()
|
|
}
|
|
.bundle(&mut sprite_params),
|
|
))
|
|
.id();
|
|
|
|
commands.entity(*target).add_child(id);
|
|
}
|
|
|
|
fn update(
|
|
mut commands: Commands,
|
|
mut state: ResMut<AimState>,
|
|
query: Query<(Entity, &Transform), With<EnemySpawn>>,
|
|
player_pos: Query<&Transform, With<Player>>,
|
|
player_rot: Query<&Transform, With<PlayerHead>>,
|
|
) {
|
|
let Some(player_pos) = player_pos.iter().next().map(|t| t.translation) else {
|
|
return;
|
|
};
|
|
|
|
let Some(player_forward) = player_rot.iter().next().map(|t| t.forward()) else {
|
|
return;
|
|
};
|
|
|
|
let mut new_target = None;
|
|
let mut target_distance = f32::MAX;
|
|
|
|
for (e, t) in query.iter() {
|
|
let delta = player_pos - t.translation;
|
|
|
|
let distance = delta.length();
|
|
|
|
if distance > state.range {
|
|
continue;
|
|
}
|
|
|
|
let angle = player_forward.angle_between(delta.normalize());
|
|
|
|
if angle < state.angle && distance < target_distance {
|
|
new_target = Some(e);
|
|
target_distance = distance;
|
|
}
|
|
}
|
|
|
|
if let Some(e) = state.target {
|
|
if commands.get_entity(e).is_none() {
|
|
state.target = None;
|
|
return;
|
|
}
|
|
}
|
|
|
|
if new_target != state.target {
|
|
if let Some(target) = new_target {
|
|
commands.trigger(MarkerEvent::Spawn(target));
|
|
} else {
|
|
commands.trigger(MarkerEvent::Despawn);
|
|
}
|
|
state.target = new_target;
|
|
}
|
|
}
|