Skip to content
This repository has been archived by the owner on Oct 25, 2021. It is now read-only.

Commit

Permalink
Wall avoidance system (#71)
Browse files Browse the repository at this point in the history
* Adding `ClosestObstacleSystem` to tag any creatures near walls w/ Closest<Obstacle>.
SeekSystem<Obstacle> will help the creature avoid that obstacle.
* Adjusted SeekSystem behavior to be a little more configurable.
- SeekSystem<Obstacle> now tries to follow the wall rather than run away.
- Adjusted how close the creature can get to the wall.
  • Loading branch information
a5huynh authored and marotili committed May 24, 2019
1 parent cbd1ce7 commit 60cf0f1
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 9 deletions.
33 changes: 30 additions & 3 deletions src/states/main_game.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use amethyst;

use amethyst::{
core::nalgebra::{Rotation3, Vector3},
core::{transform::Transform, ArcThreadPool, Time},
ecs::*,
input::InputEvent,
Expand All @@ -13,6 +14,7 @@ use amethyst::{
use crate::systems::behaviors::decision::{
ClosestSystem, Predator, Prey, QueryPredatorsAndPreySystem, SeekSystem,
};
use crate::systems::behaviors::obstacle::{ClosestObstacleSystem, Obstacle};
use crate::{
resources::{debug::DebugConfig, spatial_grid::SpatialGrid, world_bounds::WorldBounds},
states::{paused::PausedState, CustomStateEvent},
Expand Down Expand Up @@ -46,6 +48,7 @@ impl MainGameState {
"query_predators_and_prey_system",
&[],
)
.with(ClosestObstacleSystem, "closest_obstacle_system", &[])
.with(
ClosestSystem::<Prey>::default(),
"closest_prey_system",
Expand All @@ -57,19 +60,43 @@ impl MainGameState {
&["query_predators_and_prey_system"],
)
.with(
SeekSystem::<Prey>::new(1.0),
SeekSystem::<Prey>::new(
Rotation3::from_axis_angle(&Vector3::z_axis(), 0.0),
1.0,
),
"seek_prey_system",
&["closest_prey_system"],
)
.with(
SeekSystem::<Predator>::new(-1.0),
SeekSystem::<Predator>::new(
// 180 degrees, run away!
Rotation3::from_axis_angle(&Vector3::z_axis(), std::f32::consts::PI),
1.0,
),
"avoid_predator_system",
&["closest_predator_system"],
)
.with(
SeekSystem::<Obstacle>::new(
// 120 degrees. A little more than perpendicular so the creature
// tries to steer away from the wall rather than just follow it.
Rotation3::from_axis_angle(
&Vector3::z_axis(),
2f32 * std::f32::consts::FRAC_PI_3,
),
5.0,
),
"avoid_obstacle_system",
&["closest_obstacle_system"],
)
.with(
behaviors::wander::WanderSystem,
"wander_system",
&["seek_prey_system", "avoid_predator_system"],
&[
"seek_prey_system",
"avoid_predator_system",
"avoid_obstacle_system",
],
)
.with(
movement::MovementSystem,
Expand Down
14 changes: 8 additions & 6 deletions src/systems/behaviors/decision.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ impl<'s> System<'s> for QueryPredatorsAndPreySystem {

/// A component that stores the distance to the closest entity. The type T is used to tag the entity.
pub struct Closest<T> {
distance: Vector3<f32>,
pub distance: Vector3<f32>,
_phantom: PhantomData<T>,
}

Expand Down Expand Up @@ -163,14 +163,16 @@ where
/// towards that entity. The steering force can be modified using the `attraction_modifier` factor.
/// By setting `attraction_modifier` to `-1` this system will behave like `Evade`.
pub struct SeekSystem<T> {
attraction_modifier: f32,
attraction_modifier: Rotation3<f32>,
attraction_magnitude: f32,
_phantom: PhantomData<T>,
}

impl<T> SeekSystem<T> {
pub fn new(attraction_modifier: f32) -> SeekSystem<T> {
pub fn new(attraction_modifier: Rotation3<f32>, attraction_magnitude: f32) -> SeekSystem<T> {
SeekSystem {
attraction_modifier,
attraction_magnitude,
_phantom: PhantomData {},
}
}
Expand All @@ -187,10 +189,10 @@ where
WriteStorage<'s, Movement>,
);

fn run(&mut self, (_entities, closest_preys, time, mut movements): Self::SystemData) {
fn run(&mut self, (_entities, closest_things, time, mut movements): Self::SystemData) {
let delta_time = time.delta_seconds();
for (movement, closest_prey) in (&mut movements, &closest_preys).join() {
let target_velocity = closest_prey.distance.normalize() * 10.0;
for (movement, closest) in (&mut movements, &closest_things).join() {
let target_velocity = closest.distance.normalize() * self.attraction_magnitude;
let steering_force = target_velocity - movement.velocity;
movement.velocity += self.attraction_modifier * steering_force * delta_time;
}
Expand Down
1 change: 1 addition & 0 deletions src/systems/behaviors/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod decision;
pub mod obstacle;
pub mod wander;
71 changes: 71 additions & 0 deletions src/systems/behaviors/obstacle.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
use amethyst::core::nalgebra::Vector3;
use amethyst::{
core::Transform,
ecs::{join::Join, Entities, Read, ReadStorage, System, WriteStorage},
};

use std::cmp::Ordering;

use crate::components::creatures::Movement;
use crate::resources::world_bounds::WorldBounds;
use crate::systems::behaviors::decision::Closest;

#[derive(Default)]
pub struct Obstacle;

/// Determine the closest bounding wall based on a location
fn closest_wall(location: &Vector3<f32>, bounds: &WorldBounds) -> Vector3<f32> {
let mut bounds_left = location.clone();
bounds_left.x = bounds.left;
let mut bounds_right = location.clone();
bounds_right.x = bounds.right;
let mut bounds_top = location.clone();
bounds_top.y = bounds.top;
let mut bounds_bottom = location.clone();
bounds_bottom.y = bounds.bottom;

// Iterates through each bound
[bounds_left, bounds_right, bounds_top, bounds_bottom]
.iter()
// Calculates the distance between the wall & the location
.map(|v| v - location)
// Returns the minimum distance
.min_by(|a, b| {
if a.magnitude_squared() < b.magnitude_squared() {
Ordering::Less
} else {
Ordering::Greater
}
})
.unwrap()
}

pub struct ClosestObstacleSystem;
impl<'s> System<'s> for ClosestObstacleSystem {
type SystemData = (
Entities<'s>,
ReadStorage<'s, Transform>,
ReadStorage<'s, Movement>,
Read<'s, WorldBounds>,
WriteStorage<'s, Closest<Obstacle>>,
);

fn run(
&mut self,
(entities, transforms, movements, world_bounds, mut closest_obstacle): Self::SystemData,
) {
// Right now the only obstacles are the world bound walls, so it's
// safe to clear this out.
closest_obstacle.clear();

for (entity, transform, _) in (&entities, &transforms, &movements).join() {
// Find the closest wall to this entity
let wall_dir = closest_wall(&transform.translation(), &world_bounds);
if wall_dir.magnitude_squared() < 3.0f32.powi(2) {
closest_obstacle
.insert(entity, Closest::<Obstacle>::new(wall_dir))
.expect("Unable to add obstacle to entity");
}
}
}
}

0 comments on commit 60cf0f1

Please sign in to comment.