5 changed files with 448 additions and 12 deletions
File diff suppressed because one or more lines are too long
@ -0,0 +1,104 @@
|
||||
use std::f32::consts::PI; |
||||
use std::ops::RangeInclusive; |
||||
use rand::Rng; |
||||
use bevy::prelude::*; |
||||
use heron::prelude::*; |
||||
use crate::{ |
||||
map::GroundContact, |
||||
player::Player, |
||||
}; |
||||
|
||||
#[derive(Component)] |
||||
pub struct Enemy { |
||||
pub rotation: f32, |
||||
pub bounds: (RangeInclusive<f32>, RangeInclusive<f32>), |
||||
pub patrol_target: Option<(f32, f32)>, |
||||
} |
||||
|
||||
pub fn walk( |
||||
time: Res<Time>, |
||||
mut queries: QuerySet<( |
||||
QueryState<&Transform, With<Player>>, |
||||
QueryState<(&mut Transform, &mut Enemy, &mut Velocity, &GroundContact)> |
||||
)>, |
||||
) { |
||||
let mut rng = rand::thread_rng(); |
||||
|
||||
let player_positions = queries.q0() |
||||
.iter() |
||||
.map(|player_transform| |
||||
player_transform.translation |
||||
) |
||||
.collect::<Vec<_>>(); |
||||
|
||||
let mut enemies = queries.q1(); |
||||
for (mut transform, mut enemy, mut velocity, contact) in enemies.iter_mut() { |
||||
if contact.0 == 0 { |
||||
// do not move unless on ground
|
||||
continue; |
||||
} |
||||
|
||||
// TODO: if transform is out of enemy.bounds, attack any player
|
||||
// TODO: do not patrol if player is near :)
|
||||
|
||||
// find nearest player in bounds
|
||||
let mut target = None; |
||||
let mut nearest_distance = None; |
||||
for player_position in &player_positions { |
||||
if enemy.bounds.0.contains(&player_position.x) && enemy.bounds.1.contains(&player_position.z) { |
||||
let way = *player_position - transform.translation; |
||||
let distance = way.length_squared(); |
||||
if nearest_distance.map_or(true, |nearest_distance| distance < nearest_distance) { |
||||
target = Some(*player_position); |
||||
nearest_distance = Some(distance); |
||||
} |
||||
} |
||||
} |
||||
|
||||
if target.is_none() { |
||||
// patrol
|
||||
let x_bounds = enemy.bounds.0.clone(); |
||||
let z_bounds = enemy.bounds.1.clone(); |
||||
let (x, z) = enemy.patrol_target |
||||
// new patrol target
|
||||
.get_or_insert_with(|| ( |
||||
rng.gen_range(x_bounds), |
||||
rng.gen_range(z_bounds) |
||||
)).clone(); |
||||
if Vec2::new(transform.translation.x - x, |
||||
transform.translation.z - z) |
||||
.length_squared() < 1.0 |
||||
{ |
||||
// target reached, set new one next frame
|
||||
enemy.patrol_target = None; |
||||
} else { |
||||
// continue patrolling
|
||||
target = Some(Vec3::new(x, 0.0, z)); |
||||
} |
||||
} else { |
||||
// if there had been a target,
|
||||
// reset previous patrolling
|
||||
enemy.patrol_target = None; |
||||
} |
||||
|
||||
if let Some(direction) = target.and_then(|target| { |
||||
let way = target - transform.translation; |
||||
Vec3::new(way.x, 0.0, way.z) |
||||
.try_normalize() |
||||
}) |
||||
{ |
||||
let target_rotation = Vec2::new(direction.x, direction.z) |
||||
.angle_between(Vec2::Y); |
||||
let rotation_delta = 2.0 * PI * time.delta_seconds() * if target_rotation > enemy.rotation { 1.0 } else { -1.0 }; |
||||
if rotation_delta.abs() < (target_rotation - enemy.rotation).abs() { |
||||
// rotate before move
|
||||
enemy.rotation += rotation_delta; |
||||
} else { |
||||
enemy.rotation = target_rotation; |
||||
// move
|
||||
velocity.linear = 3.0 * direction + 2.0 * Vec3::Y; |
||||
} |
||||
transform.rotation = Quat::from_rotation_y(enemy.rotation); |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue