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 map;
|
||||||
mod player;
|
mod player;
|
||||||
mod off_map;
|
mod off_map;
|
||||||
|
mod enemy;
|
||||||
|
|
||||||
#[derive(PhysicsLayer)]
|
#[derive(PhysicsLayer)]
|
||||||
pub enum Layer {
|
pub enum Layer {
|
||||||
|
@ -36,6 +37,7 @@ fn main() {
|
||||||
.add_system(player::spawn_player)
|
.add_system(player::spawn_player)
|
||||||
.add_system(player::input.after("input"))
|
.add_system(player::input.after("input"))
|
||||||
.add_system(off_map::check)
|
.add_system(off_map::check)
|
||||||
|
.add_system(enemy::walk)
|
||||||
.add_system(exit_on_escape)
|
.add_system(exit_on_escape)
|
||||||
// .add_system(log_collisions)
|
// .add_system(log_collisions)
|
||||||
.run();
|
.run();
|
||||||
|
|
71
src/map.rs
71
src/map.rs
|
@ -5,7 +5,9 @@ use bevy::{
|
||||||
};
|
};
|
||||||
use heron::prelude::*;
|
use heron::prelude::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
|
enemy::Enemy,
|
||||||
player::Player,
|
player::Player,
|
||||||
|
off_map::CanFallOffMap,
|
||||||
Layer,
|
Layer,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -24,7 +26,7 @@ const ISLAND_SPACING: f32 = 30.0;
|
||||||
const GRASS_HEIGHT: f32 = 0.3;
|
const GRASS_HEIGHT: f32 = 0.3;
|
||||||
const BRIDGE_WIDTH: f32 = 2.0;
|
const BRIDGE_WIDTH: f32 = 2.0;
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component, Clone)]
|
||||||
pub struct Island {
|
pub struct Island {
|
||||||
/// x grid
|
/// x grid
|
||||||
u: i32,
|
u: i32,
|
||||||
|
@ -110,6 +112,7 @@ pub fn setup(
|
||||||
pub fn build(
|
pub fn build(
|
||||||
time: Res<Time>,
|
time: Res<Time>,
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
|
asset_server: Res<AssetServer>,
|
||||||
mut meshes: ResMut<Assets<Mesh>>,
|
mut meshes: ResMut<Assets<Mesh>>,
|
||||||
mut res: ResMut<LevelResources>,
|
mut res: ResMut<LevelResources>,
|
||||||
islands: Query<(Entity, &Island)>,
|
islands: Query<(Entity, &Island)>,
|
||||||
|
@ -203,7 +206,7 @@ pub fn build(
|
||||||
};
|
};
|
||||||
let grounds = vec![center, north, south, east, west];
|
let grounds = vec![center, north, south, east, west];
|
||||||
let island = Island { u, v };
|
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() {
|
if bridges_built.remove(&((u, v), (u + 1, v))).is_none() {
|
||||||
|
@ -216,7 +219,7 @@ pub fn build(
|
||||||
from: (u, v),
|
from: (u, v),
|
||||||
to: (u + 1, v),
|
to: (u + 1, v),
|
||||||
};
|
};
|
||||||
add_bridge(&mut commands, &mut meshes, &res, ground, bridge);
|
add_bridge(&mut commands, &mut meshes, &res, ground, bridge);
|
||||||
}
|
}
|
||||||
|
|
||||||
if bridges_built.remove(&((u, v), (u, v + 1))).is_none() {
|
if bridges_built.remove(&((u, v), (u, v + 1))).is_none() {
|
||||||
|
@ -250,6 +253,7 @@ pub fn build(
|
||||||
|
|
||||||
fn add_island(
|
fn add_island(
|
||||||
commands: &mut Commands,
|
commands: &mut Commands,
|
||||||
|
asset_server: &Res<AssetServer>,
|
||||||
meshes: &mut ResMut<Assets<Mesh>>,
|
meshes: &mut ResMut<Assets<Mesh>>,
|
||||||
res: &ResMut<LevelResources>,
|
res: &ResMut<LevelResources>,
|
||||||
grounds: Vec<Ground>,
|
grounds: Vec<Ground>,
|
||||||
|
@ -258,9 +262,9 @@ fn add_island(
|
||||||
commands.spawn()
|
commands.spawn()
|
||||||
.insert(GlobalTransform::default())
|
.insert(GlobalTransform::default())
|
||||||
.insert(Transform::default())
|
.insert(Transform::default())
|
||||||
.insert(island)
|
.insert(island.clone())
|
||||||
.with_children(|children| {
|
.with_children(|children| {
|
||||||
for ground in grounds {
|
for ground in &grounds {
|
||||||
let transform = ground.to_transform();
|
let transform = ground.to_transform();
|
||||||
|
|
||||||
let bounds = ground.to_box();
|
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(
|
fn add_bridge(
|
||||||
|
|
|
@ -53,12 +53,12 @@ pub fn spawn_player(
|
||||||
.with_masks(&[Layer::Player, Layer::Projectile, Layer::Map])
|
.with_masks(&[Layer::Player, Layer::Projectile, Layer::Map])
|
||||||
)
|
)
|
||||||
.insert(CollisionShape::Cone {
|
.insert(CollisionShape::Cone {
|
||||||
half_height: 0.8,
|
half_height: 0.85,
|
||||||
radius: 0.7,
|
radius: 0.7,
|
||||||
})
|
})
|
||||||
.insert(PhysicMaterial {
|
.insert(PhysicMaterial {
|
||||||
restitution: 0.0,
|
restitution: 0.0,
|
||||||
density: 20.0,
|
density: 10.0,
|
||||||
friction: 1.0,
|
friction: 1.0,
|
||||||
})
|
})
|
||||||
.insert(RotationConstraints::lock())
|
.insert(RotationConstraints::lock())
|
||||||
|
@ -138,10 +138,7 @@ pub fn input(
|
||||||
player.rotation += 2.0 * PI;
|
player.rotation += 2.0 * PI;
|
||||||
}
|
}
|
||||||
player.rotation += 10.0 * time.delta_seconds() * (target_rotation - player.rotation);
|
player.rotation += 10.0 * time.delta_seconds() * (target_rotation - player.rotation);
|
||||||
transform.rotation = Quat::from_axis_angle(
|
transform.rotation = Quat::from_rotation_y(player.rotation);
|
||||||
Vec3::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)
|
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 {
|
.insert(PhysicMaterial {
|
||||||
restitution: 0.0,
|
restitution: 0.0,
|
||||||
density: 50_000.0,
|
density: 20_000.0,
|
||||||
friction: 1.0,
|
friction: 1.0,
|
||||||
})
|
})
|
||||||
.insert(Velocity::from_linear(20.0 * direction))
|
.insert(Velocity::from_linear(20.0 * direction))
|
||||||
|
|
Loading…
Reference in New Issue