105 lines
3.5 KiB
Rust
105 lines
3.5 KiB
Rust
|
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);
|
||
|
}
|
||
|
}
|
||
|
}
|