Refactor navigation/pathfinding to actions.
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Nolan Darilek 2023-01-31 16:48:26 -06:00
parent 8eb5e0971f
commit 3fc74d10d9
4 changed files with 209 additions and 113 deletions

View File

@ -33,7 +33,7 @@ coord_2d = "0.3"
futures-lite = "1" futures-lite = "1"
gilrs = "0.10" gilrs = "0.10"
here_be_dragons = "0.2" here_be_dragons = "0.2"
leafwing-input-manager = "0.7" leafwing-input-manager = "0.8"
maze_generator = "2" maze_generator = "2"
once_cell = "1" once_cell = "1"
pathfinding = "4" pathfinding = "4"

View File

@ -298,11 +298,11 @@ pub trait PointLike {
fn y(&self) -> f32; fn y(&self) -> f32;
fn x_i32(&self) -> i32 { fn x_i32(&self) -> i32 {
self.x().trunc() as i32 self.x().round() as i32
} }
fn y_i32(&self) -> i32 { fn y_i32(&self) -> i32 {
self.y().trunc() as i32 self.y().round() as i32
} }
fn x_usize(&self) -> usize { fn x_usize(&self) -> usize {

View File

@ -11,13 +11,13 @@ use crate::{
error::error_handler, error::error_handler,
exploration::{ExplorationFocused, Exploring}, exploration::{ExplorationFocused, Exploring},
log::Log, log::Log,
pathfinding::Destination,
utils::target_and_other, utils::target_and_other,
}; };
#[derive(Actionlike, PartialEq, Eq, Clone, Copy, Hash, Debug)] #[derive(Actionlike, PartialEq, Eq, Clone, Copy, Hash, Debug)]
pub enum NavigationAction { pub enum NavigationAction {
Move, Move,
Translate,
Rotate, Rotate,
SetLinearVelocity, SetLinearVelocity,
SetAngularVelocity, SetAngularVelocity,
@ -29,11 +29,31 @@ pub enum NavigationAction {
#[derive(Component, Clone, Copy, Debug, Deref, DerefMut, Reflect)] #[derive(Component, Clone, Copy, Debug, Deref, DerefMut, Reflect)]
#[reflect(Component)] #[reflect(Component)]
pub struct MaxSpeed(pub f32); pub struct BackwardMovementFactor(pub f32);
impl Default for MaxSpeed { impl Default for BackwardMovementFactor {
fn default() -> Self { fn default() -> Self {
MaxSpeed(2.) Self(1.)
}
}
#[derive(Component, Clone, Copy, Debug, Deref, DerefMut, Reflect)]
#[reflect(Component)]
pub struct ForwardMovementFactor(pub f32);
impl Default for ForwardMovementFactor {
fn default() -> Self {
Self(1.)
}
}
#[derive(Component, Clone, Copy, Debug, Deref, DerefMut, Reflect)]
#[reflect(Component)]
pub struct StrafeMovementFactor(pub f32);
impl Default for StrafeMovementFactor {
fn default() -> Self {
Self(1.)
} }
} }
@ -41,10 +61,6 @@ impl Default for MaxSpeed {
#[reflect(Component)] #[reflect(Component)]
pub struct RotationSpeed(pub Angle); pub struct RotationSpeed(pub Angle);
#[derive(Component, Clone, Copy, Debug, Default, Deref, DerefMut, Reflect)]
#[reflect(Component)]
pub struct Speed(pub f32);
#[derive(Deref, DerefMut)] #[derive(Deref, DerefMut)]
struct SnapTimer(Timer); struct SnapTimer(Timer);
@ -57,25 +73,46 @@ impl Default for SnapTimer {
#[derive(Resource, Default, Deref, DerefMut)] #[derive(Resource, Default, Deref, DerefMut)]
struct SnapTimers(HashMap<Entity, SnapTimer>); struct SnapTimers(HashMap<Entity, SnapTimer>);
fn movement_controls<State>( #[derive(Component, Clone, Copy, Debug, Deref, DerefMut, Reflect)]
#[reflect(Component)]
pub struct Speed(pub f32);
impl Default for Speed {
fn default() -> Self {
Self(1.)
}
}
fn controls(
mut commands: Commands, mut commands: Commands,
config: Res<NavigationPlugin<State>>, time: Res<Time>,
snap_timers: Res<SnapTimers>, snap_timers: Res<SnapTimers>,
mut query: Query<( mut query: Query<(
Entity, Entity,
&mut ActionState<NavigationAction>, &mut ActionState<NavigationAction>,
&mut Velocity, &mut Velocity,
&mut Speed, &Speed,
&MaxSpeed,
Option<&RotationSpeed>, Option<&RotationSpeed>,
Option<&BackwardMovementFactor>,
Option<&ForwardMovementFactor>,
Option<&StrafeMovementFactor>,
&Transform, &Transform,
Option<&mut KinematicCharacterController>,
)>, )>,
exploration_focused: Query<Entity, With<ExplorationFocused>>, exploration_focused: Query<Entity, With<ExplorationFocused>>,
) where ) {
State: 'static + Clone + Debug + Eq + Hash + Send + Sync, for (
{ entity,
for (entity, mut actions, mut velocity, mut speed, max_speed, rotation_speed, transform) in mut actions,
&mut query mut velocity,
speed,
rotation_speed,
backward_movement_factor,
forward_movement_factor,
strafe_movement_factor,
transform,
character_controller,
) in &mut query
{ {
let mut cleanup = false; let mut cleanup = false;
if actions.pressed(NavigationAction::Move) { if actions.pressed(NavigationAction::Move) {
@ -88,51 +125,81 @@ fn movement_controls<State>(
if direction.y.abs() < 0.5 { if direction.y.abs() < 0.5 {
direction.y = 0.; direction.y = 0.;
} }
let forward_movement_factor =
forward_movement_factor.map(|v| v.0).unwrap_or_else(|| 1.);
let backward_movement_factor =
backward_movement_factor.map(|v| v.0).unwrap_or_else(|| 1.);
let strafe_movement_factor =
strafe_movement_factor.map(|v| v.0).unwrap_or_else(|| 1.);
let forward_backward_movement_factor = if direction.y > 0. { let forward_backward_movement_factor = if direction.y > 0. {
config.forward_movement_factor forward_movement_factor
} else if direction.y < 0. { } else if direction.y < 0. {
config.backward_movement_factor backward_movement_factor
} else { } else {
0. 1.
}; };
let movement_factor = if direction.x != 0. && direction.y != 0. { let movement_factor = if direction.x != 0. && direction.y != 0. {
config strafe_movement_factor.min(forward_backward_movement_factor)
.strafe_movement_factor
.min(forward_backward_movement_factor)
} else if direction.x != 0. { } else if direction.x != 0. {
config.strafe_movement_factor strafe_movement_factor
} else { } else {
forward_backward_movement_factor forward_backward_movement_factor
}; };
let mut s = **max_speed; trace!("{entity:?}: move: {direction:?}");
s *= movement_factor; direction = Vec2::new(direction.y, -direction.x);
**speed = s; direction = transform
let mut v = direction * **speed;
v = Vec2::new(v.y, -v.x);
v = transform
.compute_matrix() .compute_matrix()
.transform_vector3(v.extend(0.)) .transform_vector3(direction.extend(0.))
.truncate(); .truncate();
actions.press(NavigationAction::SetLinearVelocity); let mut speed = **speed;
actions speed *= movement_factor;
.action_data_mut(NavigationAction::SetLinearVelocity) let velocity = direction * speed;
.axis_pair = Some(DualAxisData::from_xy(v)); if character_controller.is_some() {
let translation = velocity * time.delta_seconds();
actions.press(NavigationAction::Translate);
actions
.action_data_mut(NavigationAction::Translate)
.axis_pair = Some(DualAxisData::from_xy(translation));
} else {
actions.press(NavigationAction::SetLinearVelocity);
actions
.action_data_mut(NavigationAction::SetLinearVelocity)
.axis_pair = Some(DualAxisData::from_xy(velocity));
}
} }
} } else if actions.just_released(NavigationAction::Move) {
if actions.released(NavigationAction::Move) trace!("{entity:?}: Stopped moving");
&& actions.axis_pair(NavigationAction::Move).is_some() actions.release(NavigationAction::SetLinearVelocity);
{ actions.release(NavigationAction::Translate);
actions.press(NavigationAction::SetLinearVelocity); actions.action_data_mut(NavigationAction::Move).axis_pair = None;
actions
.action_data_mut(NavigationAction::SetLinearVelocity)
.axis_pair = None;
} }
if actions.pressed(NavigationAction::SetLinearVelocity) { if actions.pressed(NavigationAction::SetLinearVelocity) {
if let Some(pair) = actions.axis_pair(NavigationAction::SetLinearVelocity) { if let Some(pair) = actions.axis_pair(NavigationAction::SetLinearVelocity) {
trace!("{entity:?}: SetLinearVelocity: {pair:?}");
velocity.linvel = pair.into(); velocity.linvel = pair.into();
} else { } else {
velocity.linvel = Vec2::ZERO; velocity.linvel = Vec2::ZERO;
} }
} else if actions.just_released(NavigationAction::SetLinearVelocity) {
trace!("{entity:?}: Released velocity");
velocity.linvel = Vec2::ZERO;
actions
.action_data_mut(NavigationAction::SetLinearVelocity)
.axis_pair = None;
}
if actions.pressed(NavigationAction::Translate) {
if let Some(pair) = actions.axis_pair(NavigationAction::Translate) {
if let Some(mut character_controller) = character_controller {
character_controller.translation = Some(pair.xy());
}
}
} else if actions.just_released(NavigationAction::Translate) {
if let Some(mut character_controller) = character_controller {
character_controller.translation = None;
}
actions
.action_data_mut(NavigationAction::Translate)
.axis_pair = None;
} }
if !snap_timers.contains_key(&entity) { if !snap_timers.contains_key(&entity) {
if let Some(rotation_speed) = rotation_speed { if let Some(rotation_speed) = rotation_speed {
@ -147,17 +214,19 @@ fn movement_controls<State>(
} }
} }
} }
if actions.released(NavigationAction::Rotate) { if actions.just_released(NavigationAction::Rotate) {
actions.press(NavigationAction::SetAngularVelocity); actions.release(NavigationAction::SetAngularVelocity);
actions actions.action_data_mut(NavigationAction::Rotate).value = 0.;
.action_data_mut(NavigationAction::SetAngularVelocity)
.value = 0.;
} }
if actions.pressed(NavigationAction::SetAngularVelocity) { if actions.pressed(NavigationAction::SetAngularVelocity) {
velocity.angvel = actions.value(NavigationAction::SetAngularVelocity); velocity.angvel = actions.value(NavigationAction::SetAngularVelocity);
} else if actions.just_released(NavigationAction::SetAngularVelocity) {
actions
.action_data_mut(NavigationAction::SetAngularVelocity)
.value = 0.;
velocity.angvel = 0.;
} }
if cleanup { if cleanup {
commands.entity(entity).remove::<Destination>();
commands.entity(entity).remove::<Exploring>(); commands.entity(entity).remove::<Exploring>();
for entity in exploration_focused.iter() { for entity in exploration_focused.iter() {
commands.entity(entity).remove::<ExplorationFocused>(); commands.entity(entity).remove::<ExplorationFocused>();
@ -165,7 +234,6 @@ fn movement_controls<State>(
} }
} }
} }
fn snap( fn snap(
mut tts: ResMut<Tts>, mut tts: ResMut<Tts>,
mut snap_timers: ResMut<SnapTimers>, mut snap_timers: ResMut<SnapTimers>,
@ -277,22 +345,6 @@ fn add_speed(mut commands: Commands, query: Query<Entity, (Added<Speed>, Without
} }
} }
pub(crate) fn limit_speed(mut query: Query<(&mut Speed, &MaxSpeed)>) {
for (mut speed, max_speed) in &mut query {
if **speed > **max_speed {
**speed = **max_speed;
}
}
}
fn remove_speed(removed: RemovedComponents<Speed>, mut query: Query<&mut Velocity>) {
for entity in removed.iter() {
if let Ok(mut velocity) = query.get_mut(entity) {
velocity.linvel = Vec2::ZERO;
}
}
}
fn log_area_descriptions<State>( fn log_area_descriptions<State>(
mut events: EventReader<CollisionEvent>, mut events: EventReader<CollisionEvent>,
areas: Query<(&Area, Option<&Name>)>, areas: Query<(&Area, Option<&Name>)>,
@ -336,11 +388,10 @@ fn log_area_descriptions<State>(
} }
} }
pub const MOVEMENT_CONTROLS: &str = "MOVEMENT_CONTROLS";
#[derive(Resource, Clone, Debug)] #[derive(Resource, Clone, Debug)]
pub struct NavigationPlugin<State> { pub struct NavigationPlugin<State> {
pub forward_movement_factor: f32,
pub backward_movement_factor: f32,
pub strafe_movement_factor: f32,
pub states: Vec<State>, pub states: Vec<State>,
pub describe_undescribed_areas: bool, pub describe_undescribed_areas: bool,
pub log_area_descriptions: bool, pub log_area_descriptions: bool,
@ -349,9 +400,6 @@ pub struct NavigationPlugin<State> {
impl<State> Default for NavigationPlugin<State> { impl<State> Default for NavigationPlugin<State> {
fn default() -> Self { fn default() -> Self {
Self { Self {
forward_movement_factor: 1.,
backward_movement_factor: 1.,
strafe_movement_factor: 1.,
states: vec![], states: vec![],
describe_undescribed_areas: false, describe_undescribed_areas: false,
log_area_descriptions: true, log_area_descriptions: true,
@ -366,7 +414,9 @@ where
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.insert_resource(self.clone()); app.insert_resource(self.clone());
app.init_resource::<SnapTimers>() app.init_resource::<SnapTimers>()
.register_type::<MaxSpeed>() .register_type::<BackwardMovementFactor>()
.register_type::<ForwardMovementFactor>()
.register_type::<StrafeMovementFactor>()
.register_type::<RotationSpeed>() .register_type::<RotationSpeed>()
.register_type::<Speed>() .register_type::<Speed>()
.add_plugin(InputManagerPlugin::<NavigationAction>::default()) .add_plugin(InputManagerPlugin::<NavigationAction>::default())
@ -375,22 +425,15 @@ where
.add_system(tick_snap_timers) .add_system(tick_snap_timers)
.add_system(speak_direction.pipe(error_handler)) .add_system(speak_direction.pipe(error_handler))
.add_system(add_speed) .add_system(add_speed)
.add_system(limit_speed)
.add_system_to_stage(CoreStage::PostUpdate, remove_speed)
.add_system_to_stage(CoreStage::PostUpdate, log_area_descriptions::<State>); .add_system_to_stage(CoreStage::PostUpdate, log_area_descriptions::<State>);
const MOVEMENT_CONTROLS: &str = "MOVEMENT_CONTROLS";
if self.states.is_empty() { if self.states.is_empty() {
app.add_system( app.add_system(controls.label(MOVEMENT_CONTROLS))
movement_controls::<State> .add_system(snap.pipe(error_handler).before(MOVEMENT_CONTROLS));
.label(MOVEMENT_CONTROLS)
.after(limit_speed),
)
.add_system(snap.pipe(error_handler).before(MOVEMENT_CONTROLS));
} else { } else {
for state in &self.states { for state in &self.states {
app.add_system_set( app.add_system_set(
SystemSet::on_update(*state) SystemSet::on_update(*state)
.with_system(movement_controls::<State>.label(MOVEMENT_CONTROLS)) .with_system(controls.label(MOVEMENT_CONTROLS))
.with_system(snap.pipe(error_handler).before(MOVEMENT_CONTROLS)), .with_system(snap.pipe(error_handler).before(MOVEMENT_CONTROLS)),
); );
} }

View File

@ -9,16 +9,35 @@ use bevy_rapier2d::{
rapier::prelude::{ColliderHandle, ColliderSet, QueryPipeline, RigidBodySet}, rapier::prelude::{ColliderHandle, ColliderSet, QueryPipeline, RigidBodySet},
}; };
use futures_lite::future; use futures_lite::future;
use leafwing_input_manager::{axislike::DualAxisData, plugin::InputManagerSystem, prelude::*};
use pathfinding::prelude::*; use pathfinding::prelude::*;
use crate::{ use crate::{
commands::RunIfExistsExt, commands::RunIfExistsExt,
core::PointLike, core::PointLike,
map::{Map, MapObstruction}, map::{Map, MapObstruction},
navigation::{RotationSpeed, Speed}, navigation::{NavigationAction, RotationSpeed},
}; };
#[derive(PartialEq, Eq, Clone, Copy, Hash, Debug)]
pub struct NegotiatePathAction;
impl Actionlike for NegotiatePathAction {
const N_VARIANTS: usize = 1;
fn get_at(index: usize) -> Option<Self> {
if index == 0 {
Some(Self)
} else {
None
}
}
fn index(&self) -> usize {
0
}
}
#[derive(Component, Debug, Deref, DerefMut)] #[derive(Component, Debug, Deref, DerefMut)]
struct Calculating(Task<Option<Path>>); struct Calculating(Task<Option<Path>>);
@ -182,8 +201,7 @@ fn calculate_path(
.remove::<Path>() .remove::<Path>()
.remove::<NoPath>() .remove::<NoPath>()
.remove::<Calculating>() .remove::<Calculating>()
.remove::<Destination>() .remove::<Destination>();
.remove::<Speed>();
continue; continue;
} }
let coordinates_clone = *coordinates; let coordinates_clone = *coordinates;
@ -264,49 +282,52 @@ fn negotiate_path(
mut commands: Commands, mut commands: Commands,
mut query: Query<( mut query: Query<(
Entity, Entity,
&mut ActionState<NavigationAction>,
&mut Path, &mut Path,
&mut Transform, &mut Transform,
&mut Velocity,
&Speed,
Option<&RotationSpeed>, Option<&RotationSpeed>,
)>, )>,
) { ) {
for (entity, mut path, mut transform, mut velocity, speed, rotation_speed) in &mut query { for (entity, mut actions, mut path, mut transform, rotation_speed) in &mut query {
let start_i32 = (transform.translation.x, transform.translation.y).i32(); let start_i32 = (transform.translation.x, transform.translation.y).i32();
trace!(
"{entity:?}: start pathfinding from {start_i32:?} to {:?}: {:?}",
path.last(),
transform.translation.truncate()
);
let mut cleanup = false; let mut cleanup = false;
if let Some(last) = path.last() { if let Some(last) = path.last() {
if last == &start_i32 { if last == &start_i32 {
trace!("{:?}: path: at destination", entity); trace!("{entity:?}: path: at destination");
cleanup = true; cleanup = true;
} }
} }
if !cleanup { if !cleanup {
if let Some(position) = path.iter().position(|v| v == &start_i32) { if let Some(position) = path.iter().position(|v| v == &start_i32) {
trace!("{:?}: Path contains start", entity); trace!("{entity:?}: Path contains start");
let (_, new_path) = path.split_at(position + 1); let (_, new_path) = path.split_at(position + 1);
**path = new_path.to_vec(); **path = new_path.to_vec();
} }
if let Some(next) = path.first() { if let Some(next) = path.first() {
trace!( trace!("{entity:?}: path: moving from {start_i32:?} to {next:?}");
"{:?}: path: moving from {:?} to {:?}", let start = Vec2::new(start_i32.x(), start_i32.y());
entity, let next = Vec2::new(next.0 as f32, next.1 as f32);
start_i32,
next
);
let start = transform.translation.truncate();
let next = Vec2::new(next.0 as f32 + 0.5, next.1 as f32 + 0.5);
let mut direction = next - start; let mut direction = next - start;
direction = direction.normalize(); direction = direction.normalize();
trace!("{:?}: path: direction: {:?}", entity, direction); trace!("{entity:?}: path: direction: {direction:?}");
direction *= **speed; actions.press(NavigationAction::Move);
velocity.linvel = direction; actions.action_data_mut(NavigationAction::Move).axis_pair =
trace!("{:?}: path: velocity: {:?}", entity, velocity.linvel); Some(DualAxisData::from_xy(Vec2::new(-direction.y, direction.x)));
trace!(
"{entity:?}: path: move: {:?}",
Vec2::new(direction.y, -direction.x)
);
if rotation_speed.is_some() { if rotation_speed.is_some() {
let v = next - start; let angle = direction.y.atan2(direction.x);
let angle = v.y.atan2(v.x);
transform.rotation = Quat::from_rotation_z(angle); transform.rotation = Quat::from_rotation_z(angle);
} }
} else { } else {
trace!("{entity:?}: empty path, cleaning");
cleanup = true; cleanup = true;
} }
} }
@ -315,8 +336,35 @@ fn negotiate_path(
.entity(entity) .entity(entity)
.remove::<Path>() .remove::<Path>()
.remove::<NoPath>() .remove::<NoPath>()
.remove::<Destination>() .remove::<Destination>();
.remove::<Speed>(); actions.release(NavigationAction::Move);
trace!("{entity:?}: pathfinding: cleaned up");
}
}
}
fn actions(
mut commands: Commands,
mut query: Query<(
Entity,
&ActionState<NegotiatePathAction>,
Option<&mut Destination>,
)>,
) {
for (entity, actions, destination) in &mut query {
if actions.pressed(NegotiatePathAction) {
if let Some(pair) = actions.axis_pair(NegotiatePathAction) {
let dest = Destination((pair.x() as i32, pair.y() as i32));
if let Some(mut current_dest) = destination {
if *current_dest != dest {
*current_dest = dest;
}
} else {
commands.entity(entity).insert(dest);
}
} else if destination.is_some() {
commands.entity(entity).remove::<Destination>();
}
} }
} }
} }
@ -325,13 +373,18 @@ pub struct PathfindingPlugin;
impl Plugin for PathfindingPlugin { impl Plugin for PathfindingPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.register_type::<Destination>() app.add_plugin(InputManagerPlugin::<NegotiatePathAction>::default())
.register_type::<Destination>()
.register_type::<NoPath>() .register_type::<NoPath>()
.register_type::<Path>() .register_type::<Path>()
.register_type::<CostMap>() .register_type::<CostMap>()
.add_system(calculate_path) .add_system(calculate_path)
.add_system_to_stage(CoreStage::PostUpdate, remove_destination) .add_system_to_stage(CoreStage::PostUpdate, remove_destination)
.add_system(poll_tasks) .add_system(poll_tasks)
.add_system_to_stage(CoreStage::PostUpdate, negotiate_path); .add_system_to_stage(
CoreStage::PreUpdate,
negotiate_path.after(InputManagerSystem::Tick),
)
.add_system(actions);
} }
} }