Smoother pathfinding for fast animals

This commit is contained in:
Joshua Barretto
2020-07-04 01:17:51 +01:00
committed by jshipsey
parent efad612497
commit fb8ab18346
4 changed files with 60 additions and 38 deletions

View File

@ -78,7 +78,11 @@ impl Component for Agent {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum Activity { pub enum Activity {
Idle(Vec2<f32>), Idle(Vec2<f32>),
Follow(EcsEntity, Chaser), Follow {
target: EcsEntity,
chaser: Chaser,
move_dir: Vec2<f32>,
},
Attack { Attack {
target: EcsEntity, target: EcsEntity,
chaser: Chaser, chaser: Chaser,
@ -91,7 +95,7 @@ pub enum Activity {
impl Activity { impl Activity {
pub fn is_follow(&self) -> bool { pub fn is_follow(&self) -> bool {
match self { match self {
Activity::Follow(_, _) => true, Activity::Follow { .. } => true,
_ => false, _ => false,
} }
} }

View File

@ -77,9 +77,9 @@ impl Route {
None None
} else { } else {
let next_tgt = next.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0); let next_tgt = next.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0);
if ((pos - (next_tgt + Vec3::unit_z() * 0.5)) * Vec3::new(1.0, 1.0, 0.3)) if pos.xy().distance_squared(next_tgt.xy()) < traversal_tolerance.powf(2.0)
.magnitude_squared() && next_tgt.z - pos.z < 0.2
< (traversal_tolerance * 2.0).powf(2.0) && next_tgt.z - pos.z > -2.2
{ {
self.next_idx += 1; self.next_idx += 1;
} }
@ -93,7 +93,7 @@ impl Route {
#[derive(Default, Clone, Debug)] #[derive(Default, Clone, Debug)]
pub struct Chaser { pub struct Chaser {
last_search_tgt: Option<Vec3<f32>>, last_search_tgt: Option<Vec3<f32>>,
route: Route, route: Option<Route>,
/// We use this hasher (AAHasher) because: /// We use this hasher (AAHasher) because:
/// (1) we care about DDOS attacks (ruling out FxHash); /// (1) we care about DDOS attacks (ruling out FxHash);
/// (2) we don't care about determinism across computers (we can use /// (2) we don't care about determinism across computers (we can use
@ -115,21 +115,25 @@ impl Chaser {
{ {
let pos_to_tgt = pos.distance(tgt); let pos_to_tgt = pos.distance(tgt);
if ((pos - tgt) * Vec3::new(1.0, 1.0, 0.15)).magnitude_squared() < min_dist.powf(2.0) { if ((pos - tgt) * Vec3::new(1.0, 1.0, 2.0)).magnitude_squared() < min_dist.powf(2.0) {
return None; return None;
} }
let bearing = if let Some(end) = self.route.path().end().copied() { let bearing = if let Some(end) = self.route.as_ref().and_then(|r| r.path().end().copied()) {
let end_to_tgt = end.map(|e| e as f32).distance(tgt); let end_to_tgt = end.map(|e| e as f32).distance(tgt);
if end_to_tgt > pos_to_tgt * 0.3 + 5.0 { if end_to_tgt > pos_to_tgt * 0.3 + 5.0 {
None None
} else { } else {
if thread_rng().gen::<f32>() < 0.005 { if thread_rng().gen::<f32>() < 0.005 {
// TODO: Only re-calculate route when we're stuck None
self.route = Route::default(); } else {
self.route
.as_mut()
.and_then(|r| r.traverse(vol, pos, traversal_tolerance))
.filter(|b| {
b.xy().magnitude_squared() < (traversal_tolerance + 1.0).powf(2.0)
})
} }
self.route.traverse(vol, pos, traversal_tolerance)
} }
} else { } else {
None None
@ -144,7 +148,12 @@ impl Chaser {
.map(|last_tgt| last_tgt.distance(tgt) > pos_to_tgt * 0.15 + 5.0) .map(|last_tgt| last_tgt.distance(tgt) > pos_to_tgt * 0.15 + 5.0)
.unwrap_or(true) .unwrap_or(true)
{ {
self.route = find_path(&mut self.astar, vol, pos, tgt).into(); let (start_pos, path) = find_path(&mut self.astar, vol, pos, tgt);
if start_pos.distance_squared(pos) < 4.0f32.powf(2.0) {
self.route = path.map(Route::from);
} else {
self.route = None;
}
} }
Some((tgt - pos) * Vec3::new(1.0, 1.0, 0.0)) Some((tgt - pos) * Vec3::new(1.0, 1.0, 0.0))
@ -158,7 +167,7 @@ fn find_path<V>(
vol: &V, vol: &V,
startf: Vec3<f32>, startf: Vec3<f32>,
endf: Vec3<f32>, endf: Vec3<f32>,
) -> Path<Vec3<i32>> ) -> (Vec3<f32>, Option<Path<Vec3<i32>>>)
where where
V: BaseVol<Vox = Block> + ReadVol, V: BaseVol<Vox = Block> + ReadVol,
{ {
@ -192,7 +201,7 @@ where
get_walkable_z(endf.map(|e| e.floor() as i32)), get_walkable_z(endf.map(|e| e.floor() as i32)),
) { ) {
(Some(start), Some(end)) => (start, end), (Some(start), Some(end)) => (start, end),
_ => return Path::default(), _ => return (startf, None),
}; };
let heuristic = |pos: &Vec3<i32>| (pos.distance_squared(end) as f32).sqrt(); let heuristic = |pos: &Vec3<i32>| (pos.distance_squared(end) as f32).sqrt();
@ -240,6 +249,7 @@ where
.map(move |dir| (pos, dir)) .map(move |dir| (pos, dir))
.filter(move |(pos, dir)| { .filter(move |(pos, dir)| {
is_walkable(pos) is_walkable(pos)
&& is_walkable(&(*pos + **dir))
&& ((dir.z < 1 && ((dir.z < 1
|| vol || vol
.get(pos + Vec3::unit_z() * 2) .get(pos + Vec3::unit_z() * 2)
@ -266,34 +276,32 @@ where
.map(move |(dir, _)| pos + *dir), .map(move |(dir, _)| pos + *dir),
) )
}; };
let transition = |a: &Vec3<i32>, b: &Vec3<i32>| { let transition =
((*a - *b) * Vec3::new(1, 1, 3)).map(|e| e.abs()).reduce_max() as f32 |a: &Vec3<i32>, b: &Vec3<i32>| 1.0 + endf.distance((*b).map(|e| e as f32 + 0.5)) * 0.02;
+ endf.distance((*b).map(|e| e as f32)) * 0.01
};
let satisfied = |pos: &Vec3<i32>| pos == &end; let satisfied = |pos: &Vec3<i32>| pos == &end;
let mut new_astar = match astar.take() { let mut new_astar = match astar.take() {
None => Astar::new(20_000, start, heuristic, DefaultHashBuilder::default()), None => Astar::new(25_000, start, heuristic, DefaultHashBuilder::default()),
Some(astar) => astar, Some(astar) => astar,
}; };
let path_result = new_astar.poll(60, heuristic, neighbors, transition, satisfied); let path_result = new_astar.poll(100, heuristic, neighbors, transition, satisfied);
*astar = Some(new_astar); *astar = Some(new_astar);
match path_result { (startf, match path_result {
PathResult::Path(path) => { PathResult::Path(path) => {
*astar = None; *astar = None;
path Some(path)
}, },
PathResult::None(path) => { PathResult::None(path) => {
*astar = None; *astar = None;
path Some(path)
}, },
PathResult::Exhausted(path) => { PathResult::Exhausted(path) => {
*astar = None; *astar = None;
path Some(path)
}, },
PathResult::Pending => Path::default(), PathResult::Pending => None,
} })
} }

View File

@ -1,8 +1,7 @@
use crate::{ use crate::{
comp::{ comp::{
item::{Hands, ItemKind, Tool}, item::{Hands, ItemKind, Tool},
CharacterState, StateUpdate, Body, CharacterState, StateUpdate,
Body,
}, },
event::LocalEvent, event::LocalEvent,
states::*, states::*,

View File

@ -4,7 +4,7 @@ use crate::{
agent::Activity, agent::Activity,
item::{tool::ToolKind, ItemKind}, item::{tool::ToolKind, ItemKind},
Agent, Alignment, CharacterState, ChatMsg, ControlAction, Controller, Loadout, MountState, Agent, Alignment, CharacterState, ChatMsg, ControlAction, Controller, Loadout, MountState,
Ori, Pos, Scale, Stats, Vel, Ori, Pos, Scale, SpeechBubble, Stats, Vel,
}, },
event::{EventBus, ServerEvent}, event::{EventBus, ServerEvent},
path::Chaser, path::Chaser,
@ -126,7 +126,7 @@ impl<'a> System<'a> for Sys {
// and so can afford to be less precise when trying to move around // and so can afford to be less precise when trying to move around
// the world (especially since they would otherwise get stuck on // the world (especially since they would otherwise get stuck on
// obstacles that smaller entities would not). // obstacles that smaller entities would not).
let traversal_tolerance = scale + vel.0.magnitude() * 0.35; let traversal_tolerance = scale + vel.0.magnitude() * 0.25;
let mut do_idle = false; let mut do_idle = false;
let mut choose_target = false; let mut choose_target = false;
@ -187,7 +187,11 @@ impl<'a> System<'a> for Sys {
choose_target = true; choose_target = true;
} }
}, },
Activity::Follow(target, chaser) => { Activity::Follow {
target,
chaser,
move_dir,
} => {
if let (Some(tgt_pos), _tgt_stats) = if let (Some(tgt_pos), _tgt_stats) =
(positions.get(*target), stats.get(*target)) (positions.get(*target), stats.get(*target))
{ {
@ -201,10 +205,13 @@ impl<'a> System<'a> for Sys {
AVG_FOLLOW_DIST, AVG_FOLLOW_DIST,
traversal_tolerance, traversal_tolerance,
) { ) {
inputs.move_dir = Vec2::from(bearing) *move_dir = 0.9f32 * *move_dir
+ 0.1f32
* Vec2::from(bearing)
.try_normalized() .try_normalized()
.unwrap_or(Vec2::zero()); .unwrap_or(Vec2::zero());
inputs.jump.set_state(bearing.z > 1.0); inputs.move_dir = *move_dir;
inputs.jump.set_state(bearing.z > 1.5);
} }
} else { } else {
do_idle = true; do_idle = true;
@ -317,7 +324,7 @@ impl<'a> System<'a> for Sys {
inputs.move_dir = Vec2::from(bearing) inputs.move_dir = Vec2::from(bearing)
.try_normalized() .try_normalized()
.unwrap_or(Vec2::zero()); .unwrap_or(Vec2::zero());
inputs.jump.set_state(bearing.z > 1.0); inputs.jump.set_state(bearing.z > 1.5);
} }
if dist_sqrd < 16.0f32.powf(2.0) if dist_sqrd < 16.0f32.powf(2.0)
@ -419,7 +426,11 @@ impl<'a> System<'a> for Sys {
if let Some(owner_pos) = positions.get(owner) { if let Some(owner_pos) = positions.get(owner) {
let dist_sqrd = pos.0.distance_squared(owner_pos.0); let dist_sqrd = pos.0.distance_squared(owner_pos.0);
if dist_sqrd > MAX_FOLLOW_DIST.powf(2.0) && !agent.activity.is_follow() { if dist_sqrd > MAX_FOLLOW_DIST.powf(2.0) && !agent.activity.is_follow() {
agent.activity = Activity::Follow(owner, Chaser::default()); agent.activity = Activity::Follow {
target: owner,
chaser: Chaser::default(),
move_dir: Vec2::zero(),
};
} }
// Attack owner's attacker // Attack owner's attacker