From 60cf0f1d279fd549757661edd077b08ab4a70c81 Mon Sep 17 00:00:00 2001 From: Andrew Huynh Date: Fri, 24 May 2019 12:39:26 -0700 Subject: [PATCH] Wall avoidance system (#71) * Adding `ClosestObstacleSystem` to tag any creatures near walls w/ Closest. SeekSystem will help the creature avoid that obstacle. * Adjusted SeekSystem behavior to be a little more configurable. - SeekSystem now tries to follow the wall rather than run away. - Adjusted how close the creature can get to the wall. --- src/states/main_game.rs | 33 ++++++++++++-- src/systems/behaviors/decision.rs | 14 +++--- src/systems/behaviors/mod.rs | 1 + src/systems/behaviors/obstacle.rs | 71 +++++++++++++++++++++++++++++++ 4 files changed, 110 insertions(+), 9 deletions(-) create mode 100644 src/systems/behaviors/obstacle.rs diff --git a/src/states/main_game.rs b/src/states/main_game.rs index bb8d4ba..fdc6c9e 100644 --- a/src/states/main_game.rs +++ b/src/states/main_game.rs @@ -1,6 +1,7 @@ use amethyst; use amethyst::{ + core::nalgebra::{Rotation3, Vector3}, core::{transform::Transform, ArcThreadPool, Time}, ecs::*, input::InputEvent, @@ -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}, @@ -46,6 +48,7 @@ impl MainGameState { "query_predators_and_prey_system", &[], ) + .with(ClosestObstacleSystem, "closest_obstacle_system", &[]) .with( ClosestSystem::::default(), "closest_prey_system", @@ -57,19 +60,43 @@ impl MainGameState { &["query_predators_and_prey_system"], ) .with( - SeekSystem::::new(1.0), + SeekSystem::::new( + Rotation3::from_axis_angle(&Vector3::z_axis(), 0.0), + 1.0, + ), "seek_prey_system", &["closest_prey_system"], ) .with( - SeekSystem::::new(-1.0), + SeekSystem::::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::::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, diff --git a/src/systems/behaviors/decision.rs b/src/systems/behaviors/decision.rs index a452884..de890f5 100644 --- a/src/systems/behaviors/decision.rs +++ b/src/systems/behaviors/decision.rs @@ -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 { - distance: Vector3, + pub distance: Vector3, _phantom: PhantomData, } @@ -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 { - attraction_modifier: f32, + attraction_modifier: Rotation3, + attraction_magnitude: f32, _phantom: PhantomData, } impl SeekSystem { - pub fn new(attraction_modifier: f32) -> SeekSystem { + pub fn new(attraction_modifier: Rotation3, attraction_magnitude: f32) -> SeekSystem { SeekSystem { attraction_modifier, + attraction_magnitude, _phantom: PhantomData {}, } } @@ -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; } diff --git a/src/systems/behaviors/mod.rs b/src/systems/behaviors/mod.rs index ab6a3ca..d43d7af 100644 --- a/src/systems/behaviors/mod.rs +++ b/src/systems/behaviors/mod.rs @@ -1,2 +1,3 @@ pub mod decision; +pub mod obstacle; pub mod wander; diff --git a/src/systems/behaviors/obstacle.rs b/src/systems/behaviors/obstacle.rs new file mode 100644 index 0000000..bb53ea5 --- /dev/null +++ b/src/systems/behaviors/obstacle.rs @@ -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, bounds: &WorldBounds) -> Vector3 { + 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>, + ); + + 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::::new(wall_dir)) + .expect("Unable to add obstacle to entity"); + } + } + } +}