auto aim
This commit is contained in:
131
src/aim.rs
Normal file
131
src/aim.rs
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
mod aim;
|
||||||
mod alien;
|
mod alien;
|
||||||
mod billboards;
|
mod billboards;
|
||||||
mod camera;
|
mod camera;
|
||||||
@@ -74,6 +75,7 @@ fn main() {
|
|||||||
app.add_plugins(platforms::plugin);
|
app.add_plugins(platforms::plugin);
|
||||||
app.add_plugins(movables::plugin);
|
app.add_plugins(movables::plugin);
|
||||||
app.add_plugins(billboards::plugin);
|
app.add_plugins(billboards::plugin);
|
||||||
|
app.add_plugins(aim::plugin);
|
||||||
|
|
||||||
app.insert_resource(AmbientLight {
|
app.insert_resource(AmbientLight {
|
||||||
color: Color::WHITE,
|
color: Color::WHITE,
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ use std::{f32::consts::PI, time::Duration};
|
|||||||
use crate::{
|
use crate::{
|
||||||
DebugVisuals,
|
DebugVisuals,
|
||||||
alien::{ALIEN_ASSET_PATH, Animations},
|
alien::{ALIEN_ASSET_PATH, Animations},
|
||||||
billboards::Billboard,
|
|
||||||
camera::GameCameraRig,
|
camera::GameCameraRig,
|
||||||
cash::{Cash, CashCollectEvent},
|
cash::{Cash, CashCollectEvent},
|
||||||
heads_ui::HeadChanged,
|
heads_ui::HeadChanged,
|
||||||
@@ -16,7 +15,6 @@ use bevy::{
|
|||||||
window::{CursorGrabMode, PrimaryWindow},
|
window::{CursorGrabMode, PrimaryWindow},
|
||||||
};
|
};
|
||||||
use bevy_dolly::prelude::Rig;
|
use bevy_dolly::prelude::Rig;
|
||||||
use bevy_sprite3d::{Sprite3dBuilder, Sprite3dParams};
|
|
||||||
use bevy_tnua::{TnuaUserControlsSystemSet, prelude::*};
|
use bevy_tnua::{TnuaUserControlsSystemSet, prelude::*};
|
||||||
use bevy_tnua_avian3d::TnuaAvian3dSensorShape;
|
use bevy_tnua_avian3d::TnuaAvian3dSensorShape;
|
||||||
|
|
||||||
@@ -27,10 +25,10 @@ pub struct Player;
|
|||||||
struct PlayerAnimations;
|
struct PlayerAnimations;
|
||||||
|
|
||||||
#[derive(Component, Default)]
|
#[derive(Component, Default)]
|
||||||
struct PlayerHead;
|
pub struct PlayerHead;
|
||||||
|
|
||||||
#[derive(Component, Default)]
|
#[derive(Component, Default)]
|
||||||
struct PlayerRig;
|
pub struct PlayerRig;
|
||||||
|
|
||||||
#[derive(Resource, Default)]
|
#[derive(Resource, Default)]
|
||||||
struct PlayerSpawned {
|
struct PlayerSpawned {
|
||||||
@@ -51,7 +49,7 @@ pub fn plugin(app: &mut App) {
|
|||||||
(
|
(
|
||||||
spawn,
|
spawn,
|
||||||
update_camera,
|
update_camera,
|
||||||
cursor_events,
|
mouse_rotate,
|
||||||
collect_cash,
|
collect_cash,
|
||||||
toggle_animation,
|
toggle_animation,
|
||||||
setup_animations_marker_for_player,
|
setup_animations_marker_for_player,
|
||||||
@@ -70,7 +68,6 @@ fn spawn(
|
|||||||
asset_server: Res<AssetServer>,
|
asset_server: Res<AssetServer>,
|
||||||
query: Query<&Transform, With<SpawnPoint>>,
|
query: Query<&Transform, With<SpawnPoint>>,
|
||||||
mut player_spawned: ResMut<PlayerSpawned>,
|
mut player_spawned: ResMut<PlayerSpawned>,
|
||||||
mut sprite_params: Sprite3dParams,
|
|
||||||
) {
|
) {
|
||||||
if player_spawned.spawned {
|
if player_spawned.spawned {
|
||||||
return;
|
return;
|
||||||
@@ -84,7 +81,6 @@ fn spawn(
|
|||||||
|
|
||||||
let mesh = asset_server
|
let mesh = asset_server
|
||||||
.load(GltfAssetLabel::Scene(0).from_asset("models/heads/angry demonstrator.glb"));
|
.load(GltfAssetLabel::Scene(0).from_asset("models/heads/angry demonstrator.glb"));
|
||||||
let selector = asset_server.load("ui/selector.png");
|
|
||||||
|
|
||||||
commands
|
commands
|
||||||
.spawn((
|
.spawn((
|
||||||
@@ -113,18 +109,6 @@ fn spawn(
|
|||||||
.with_rotation(Quat::from_rotation_y(std::f32::consts::PI))
|
.with_rotation(Quat::from_rotation_y(std::f32::consts::PI))
|
||||||
.with_scale(Vec3::splat(1.5)),
|
.with_scale(Vec3::splat(1.5)),
|
||||||
SceneRoot(asset_server.load(GltfAssetLabel::Scene(0).from_asset(ALIEN_ASSET_PATH))),
|
SceneRoot(asset_server.load(GltfAssetLabel::Scene(0).from_asset(ALIEN_ASSET_PATH))),
|
||||||
))
|
|
||||||
.with_child((
|
|
||||||
Billboard,
|
|
||||||
Transform::from_translation(Vec3::new(0., 1.5, 0.)),
|
|
||||||
Sprite3dBuilder {
|
|
||||||
image: selector,
|
|
||||||
pixels_per_metre: 40.,
|
|
||||||
alpha_mode: AlphaMode::Blend,
|
|
||||||
unlit: true,
|
|
||||||
..default()
|
|
||||||
}
|
|
||||||
.bundle(&mut sprite_params),
|
|
||||||
));
|
));
|
||||||
|
|
||||||
commands.spawn((
|
commands.spawn((
|
||||||
@@ -135,7 +119,7 @@ fn spawn(
|
|||||||
player_spawned.spawned = true;
|
player_spawned.spawned = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cursor_events(
|
fn mouse_rotate(
|
||||||
mut mouse: EventReader<MouseMotion>,
|
mut mouse: EventReader<MouseMotion>,
|
||||||
// todo: Put the player head as a child of the rig to avoid this mess:
|
// todo: Put the player head as a child of the rig to avoid this mess:
|
||||||
mut player: Query<&mut Transform, Or<(With<PlayerRig>, With<PlayerHead>)>>,
|
mut player: Query<&mut Transform, Or<(With<PlayerRig>, With<PlayerHead>)>>,
|
||||||
|
|||||||
@@ -94,6 +94,14 @@ impl EnemySpawn {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let this = world.get_entity(entity).unwrap().get::<Self>().unwrap();
|
let this = world.get_entity(entity).unwrap().get::<Self>().unwrap();
|
||||||
|
let this_transform = world
|
||||||
|
.get_entity(entity)
|
||||||
|
.unwrap()
|
||||||
|
.get::<Transform>()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut this_transform = *this_transform;
|
||||||
|
this_transform.translation += Vec3::new(0., 1., 0.);
|
||||||
|
|
||||||
let mesh = asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/alien_naked.glb"));
|
let mesh = asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/alien_naked.glb"));
|
||||||
|
|
||||||
@@ -103,23 +111,23 @@ impl EnemySpawn {
|
|||||||
world
|
world
|
||||||
.commands()
|
.commands()
|
||||||
.entity(entity)
|
.entity(entity)
|
||||||
.insert((Name::from("Enemy"), Visibility::default()))
|
.insert((
|
||||||
|
this_transform,
|
||||||
|
Name::from("Enemy"),
|
||||||
|
Visibility::default(),
|
||||||
|
RigidBody::Dynamic,
|
||||||
|
Collider::capsule(0.4, 2.),
|
||||||
|
LockedAxes::new().lock_rotation_z().lock_rotation_x(),
|
||||||
|
))
|
||||||
.with_children(|parent| {
|
.with_children(|parent| {
|
||||||
parent
|
parent
|
||||||
.spawn((
|
.spawn((
|
||||||
Visibility::default(),
|
|
||||||
RigidBody::Dynamic,
|
|
||||||
Collider::capsule(0.4, 2.),
|
|
||||||
LockedAxes::new().lock_rotation_z().lock_rotation_x(),
|
|
||||||
Transform::from_translation(Vec3::new(0., 1.0, 0.)),
|
|
||||||
))
|
|
||||||
.with_child((
|
|
||||||
Transform::from_translation(Vec3::new(0., 1., 0.)),
|
Transform::from_translation(Vec3::new(0., 1., 0.)),
|
||||||
SceneRoot(head_mesh),
|
SceneRoot(head_mesh),
|
||||||
))
|
))
|
||||||
.with_child((
|
.with_child((
|
||||||
Visibility::default(),
|
Visibility::default(),
|
||||||
Transform::from_translation(Vec3::new(0., -1.4, 0.))
|
Transform::from_translation(Vec3::new(0., -2.4, 0.))
|
||||||
.with_scale(Vec3::splat(1.5)),
|
.with_scale(Vec3::splat(1.5)),
|
||||||
SceneRoot(mesh),
|
SceneRoot(mesh),
|
||||||
));
|
));
|
||||||
|
|||||||
Reference in New Issue
Block a user