add enemy
This commit is contained in:
parent
6900d1ce31
commit
1ebc94bd9d
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
69
src/map.rs
69
src/map.rs
|
@ -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(
|
||||
|
|
|
@ -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))
|
||||
|
|
Loading…
Reference in New Issue