add enemy

This commit is contained in:
Astro 2022-01-17 02:58:59 +01:00
parent 6900d1ce31
commit 1ebc94bd9d
5 changed files with 448 additions and 12 deletions

File diff suppressed because one or more lines are too long

104
src/enemy.rs Normal file
View File

@ -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);
}
}
}

View File

@ -10,6 +10,7 @@ mod camera;
mod map;
mod player;
mod off_map;
mod enemy;
#[derive(PhysicsLayer)]
pub enum Layer {
@ -36,6 +37,7 @@ fn main() {
.add_system(player::spawn_player)
.add_system(player::input.after("input"))
.add_system(off_map::check)
.add_system(enemy::walk)
.add_system(exit_on_escape)
// .add_system(log_collisions)
.run();

View File

@ -5,7 +5,9 @@ use bevy::{
};
use heron::prelude::*;
use crate::{
enemy::Enemy,
player::Player,
off_map::CanFallOffMap,
Layer,
};
@ -24,7 +26,7 @@ const ISLAND_SPACING: f32 = 30.0;
const GRASS_HEIGHT: f32 = 0.3;
const BRIDGE_WIDTH: f32 = 2.0;
#[derive(Component)]
#[derive(Component, Clone)]
pub struct Island {
/// x grid
u: i32,
@ -110,6 +112,7 @@ pub fn setup(
pub fn build(
time: Res<Time>,
mut commands: Commands,
asset_server: Res<AssetServer>,
mut meshes: ResMut<Assets<Mesh>>,
mut res: ResMut<LevelResources>,
islands: Query<(Entity, &Island)>,
@ -203,7 +206,7 @@ pub fn build(
};
let grounds = vec![center, north, south, east, west];
let island = Island { u, v };
add_island(&mut commands, &mut meshes, &res, grounds, island);
add_island(&mut commands, &asset_server, &mut meshes, &res, grounds, island);
}
if bridges_built.remove(&((u, v), (u + 1, v))).is_none() {
@ -250,6 +253,7 @@ pub fn build(
fn add_island(
commands: &mut Commands,
asset_server: &Res<AssetServer>,
meshes: &mut ResMut<Assets<Mesh>>,
res: &ResMut<LevelResources>,
grounds: Vec<Ground>,
@ -258,9 +262,9 @@ fn add_island(
commands.spawn()
.insert(GlobalTransform::default())
.insert(Transform::default())
.insert(island)
.insert(island.clone())
.with_children(|children| {
for ground in grounds {
for ground in &grounds {
let transform = ground.to_transform();
let bounds = ground.to_box();
@ -297,6 +301,63 @@ fn add_island(
});
});
}
if island.u != 0 || island.v != 0 {
// enemy
let mesh1 = asset_server.load("Mini-Game Variety Pack/Models/Characters/gltf/character_bear.gltf#Mesh0/Primitive0");
let mesh2 = asset_server.load("Mini-Game Variety Pack/Models/Characters/gltf/character_bear.gltf#Mesh0/Primitive1");
let mesh3 = asset_server.load("Mini-Game Variety Pack/Models/Characters/gltf/character_bear.gltf#Mesh1/Primitive0");
let material1 = asset_server.load("Mini-Game Variety Pack/Models/Characters/gltf/character_bear.gltf#Material0");
let material2 = asset_server.load("Mini-Game Variety Pack/Models/Characters/gltf/character_bear.gltf#Material1");
let translation = grounds[0].to_transform().translation;
let half_extends = grounds[0].half_extends();
let transform = Transform::from_translation(translation + (half_extends.y + 3.0) * Vec3::Y);
children.spawn()
.insert(GlobalTransform::default())
.insert(transform)
.insert(RigidBody::Dynamic)
.insert(CollisionLayers::none()
.with_group(Layer::Player)
.with_masks(&[Layer::Player, Layer::Projectile, Layer::Map])
)
.insert(CollisionShape::Cone {
half_height: 0.85,
radius: 0.7,
})
.insert(PhysicMaterial {
restitution: 1.0,
density: 500.0,
friction: 0.0,
})
.insert(RotationConstraints::lock())
.insert(Velocity::default())
.insert(GroundContact::default())
.insert(CanFallOffMap)
.insert(Enemy {
rotation: 0.0,
bounds: (translation.x - half_extends.x + 1.0..=translation.x + half_extends.x - 1.0, translation.z - half_extends.z + 1.0..=translation.z + half_extends.z - 1.0),
patrol_target: None,
})
.with_children(|children| {
children.spawn_bundle(PbrBundle {
mesh: mesh1.clone(),
material: material1.clone(),
..Default::default()
});
children.spawn_bundle(PbrBundle {
mesh: mesh2.clone(),
material: material2.clone(),
..Default::default()
});
let transform = Transform::from_translation(-0.71 * Vec3::Y);
children.spawn_bundle(PbrBundle {
mesh: mesh3.clone(),
material: material2.clone(),
transform,
..Default::default()
});
});
}
});
}
fn add_bridge(

View File

@ -53,12 +53,12 @@ pub fn spawn_player(
.with_masks(&[Layer::Player, Layer::Projectile, Layer::Map])
)
.insert(CollisionShape::Cone {
half_height: 0.8,
half_height: 0.85,
radius: 0.7,
})
.insert(PhysicMaterial {
restitution: 0.0,
density: 20.0,
density: 10.0,
friction: 1.0,
})
.insert(RotationConstraints::lock())
@ -138,10 +138,7 @@ pub fn input(
player.rotation += 2.0 * PI;
}
player.rotation += 10.0 * time.delta_seconds() * (target_rotation - player.rotation);
transform.rotation = Quat::from_axis_angle(
Vec3::Y,
player.rotation
);
transform.rotation = Quat::from_rotation_y(player.rotation);
}
if player.last_shot.as_ref().map_or(true, |last_shot| time.seconds_since_startup() - last_shot.seconds_since_startup() >= 0.5)
@ -161,7 +158,7 @@ pub fn input(
})
.insert(PhysicMaterial {
restitution: 0.0,
density: 50_000.0,
density: 20_000.0,
friction: 1.0,
})
.insert(Velocity::from_linear(20.0 * direction))