From 709c1762bd242f1def4ab684a68199027fa63e5d Mon Sep 17 00:00:00 2001 From: extrawurst Date: Wed, 12 Mar 2025 17:14:35 +0100 Subject: [PATCH] auto aim --- src/aim.rs | 131 +++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 2 + src/player.rs | 24 ++------- src/tb_entities.rs | 26 +++++---- 4 files changed, 154 insertions(+), 29 deletions(-) create mode 100644 src/aim.rs diff --git a/src/aim.rs b/src/aim.rs new file mode 100644 index 0000000..071eb5d --- /dev/null +++ b/src/aim.rs @@ -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, + 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::(); + app.add_systems(Update, update); + app.add_observer(marker_event); +} + +fn marker_event( + trigger: Trigger, + mut commands: Commands, + asset_server: Res, + mut sprite_params: Sprite3dParams, + marker: Query>, +) { + 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, + query: Query<(Entity, &Transform), With>, + player_pos: Query<&Transform, With>, + player_rot: Query<&Transform, With>, +) { + 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; + } +} diff --git a/src/main.rs b/src/main.rs index 005d04f..79c4c79 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +mod aim; mod alien; mod billboards; mod camera; @@ -74,6 +75,7 @@ fn main() { app.add_plugins(platforms::plugin); app.add_plugins(movables::plugin); app.add_plugins(billboards::plugin); + app.add_plugins(aim::plugin); app.insert_resource(AmbientLight { color: Color::WHITE, diff --git a/src/player.rs b/src/player.rs index 56b4e60..95c4d2b 100644 --- a/src/player.rs +++ b/src/player.rs @@ -3,7 +3,6 @@ use std::{f32::consts::PI, time::Duration}; use crate::{ DebugVisuals, alien::{ALIEN_ASSET_PATH, Animations}, - billboards::Billboard, camera::GameCameraRig, cash::{Cash, CashCollectEvent}, heads_ui::HeadChanged, @@ -16,7 +15,6 @@ use bevy::{ window::{CursorGrabMode, PrimaryWindow}, }; use bevy_dolly::prelude::Rig; -use bevy_sprite3d::{Sprite3dBuilder, Sprite3dParams}; use bevy_tnua::{TnuaUserControlsSystemSet, prelude::*}; use bevy_tnua_avian3d::TnuaAvian3dSensorShape; @@ -27,10 +25,10 @@ pub struct Player; struct PlayerAnimations; #[derive(Component, Default)] -struct PlayerHead; +pub struct PlayerHead; #[derive(Component, Default)] -struct PlayerRig; +pub struct PlayerRig; #[derive(Resource, Default)] struct PlayerSpawned { @@ -51,7 +49,7 @@ pub fn plugin(app: &mut App) { ( spawn, update_camera, - cursor_events, + mouse_rotate, collect_cash, toggle_animation, setup_animations_marker_for_player, @@ -70,7 +68,6 @@ fn spawn( asset_server: Res, query: Query<&Transform, With>, mut player_spawned: ResMut, - mut sprite_params: Sprite3dParams, ) { if player_spawned.spawned { return; @@ -84,7 +81,6 @@ fn spawn( let mesh = asset_server .load(GltfAssetLabel::Scene(0).from_asset("models/heads/angry demonstrator.glb")); - let selector = asset_server.load("ui/selector.png"); commands .spawn(( @@ -113,18 +109,6 @@ fn spawn( .with_rotation(Quat::from_rotation_y(std::f32::consts::PI)) .with_scale(Vec3::splat(1.5)), 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(( @@ -135,7 +119,7 @@ fn spawn( player_spawned.spawned = true; } -fn cursor_events( +fn mouse_rotate( mut mouse: EventReader, // todo: Put the player head as a child of the rig to avoid this mess: mut player: Query<&mut Transform, Or<(With, With)>>, diff --git a/src/tb_entities.rs b/src/tb_entities.rs index 1053408..fa39514 100644 --- a/src/tb_entities.rs +++ b/src/tb_entities.rs @@ -94,6 +94,14 @@ impl EnemySpawn { }; let this = world.get_entity(entity).unwrap().get::().unwrap(); + let this_transform = world + .get_entity(entity) + .unwrap() + .get::() + .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")); @@ -103,23 +111,23 @@ impl EnemySpawn { world .commands() .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| { parent .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.)), SceneRoot(head_mesh), )) .with_child(( 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)), SceneRoot(mesh), ));