Smoother pathfinding for fast animals

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

View File

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

View File

@ -77,9 +77,9 @@ impl Route {
None
} else {
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))
.magnitude_squared()
< (traversal_tolerance * 2.0).powf(2.0)
if pos.xy().distance_squared(next_tgt.xy()) < traversal_tolerance.powf(2.0)
&& next_tgt.z - pos.z < 0.2
&& next_tgt.z - pos.z > -2.2
{
self.next_idx += 1;
}
@ -93,7 +93,7 @@ impl Route {
#[derive(Default, Clone, Debug)]
pub struct Chaser {
last_search_tgt: Option<Vec3<f32>>,
route: Route,
route: Option<Route>,
/// We use this hasher (AAHasher) because:
/// (1) we care about DDOS attacks (ruling out FxHash);
/// (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);
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;
}
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);
if end_to_tgt > pos_to_tgt * 0.3 + 5.0 {
None
} else {
if thread_rng().gen::<f32>() < 0.005 {
// TODO: Only re-calculate route when we're stuck
self.route = Route::default();
None
} 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 {
None
@ -144,7 +148,12 @@ impl Chaser {
.map(|last_tgt| last_tgt.distance(tgt) > pos_to_tgt * 0.15 + 5.0)
.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))
@ -158,7 +167,7 @@ fn find_path<V>(
vol: &V,
startf: Vec3<f32>,
endf: Vec3<f32>,
) -> Path<Vec3<i32>>
) -> (Vec3<f32>, Option<Path<Vec3<i32>>>)
where
V: BaseVol<Vox = Block> + ReadVol,
{
@ -192,7 +201,7 @@ where
get_walkable_z(endf.map(|e| e.floor() as i32)),
) {
(Some(start), Some(end)) => (start, end),
_ => return Path::default(),
_ => return (startf, None),
};
let heuristic = |pos: &Vec3<i32>| (pos.distance_squared(end) as f32).sqrt();
@ -240,6 +249,7 @@ where
.map(move |dir| (pos, dir))
.filter(move |(pos, dir)| {
is_walkable(pos)
&& is_walkable(&(*pos + **dir))
&& ((dir.z < 1
|| vol
.get(pos + Vec3::unit_z() * 2)
@ -266,34 +276,32 @@ where
.map(move |(dir, _)| pos + *dir),
)
};
let transition = |a: &Vec3<i32>, b: &Vec3<i32>| {
((*a - *b) * Vec3::new(1, 1, 3)).map(|e| e.abs()).reduce_max() as f32
+ endf.distance((*b).map(|e| e as f32)) * 0.01
};
let transition =
|a: &Vec3<i32>, b: &Vec3<i32>| 1.0 + endf.distance((*b).map(|e| e as f32 + 0.5)) * 0.02;
let satisfied = |pos: &Vec3<i32>| pos == &end;
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,
};
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);
match path_result {
(startf, match path_result {
PathResult::Path(path) => {
*astar = None;
path
Some(path)
},
PathResult::None(path) => {
*astar = None;
path
Some(path)
},
PathResult::Exhausted(path) => {
*astar = None;
path
Some(path)
},
PathResult::Pending => Path::default(),
}
PathResult::Pending => None,
})
}

View File

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

View File

@ -4,7 +4,7 @@ use crate::{
agent::Activity,
item::{tool::ToolKind, ItemKind},
Agent, Alignment, CharacterState, ChatMsg, ControlAction, Controller, Loadout, MountState,
Ori, Pos, Scale, Stats, Vel,
Ori, Pos, Scale, SpeechBubble, Stats, Vel,
},
event::{EventBus, ServerEvent},
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
// the world (especially since they would otherwise get stuck on
// 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 choose_target = false;
@ -187,7 +187,11 @@ impl<'a> System<'a> for Sys {
choose_target = true;
}
},
Activity::Follow(target, chaser) => {
Activity::Follow {
target,
chaser,
move_dir,
} => {
if let (Some(tgt_pos), _tgt_stats) =
(positions.get(*target), stats.get(*target))
{
@ -201,10 +205,13 @@ impl<'a> System<'a> for Sys {
AVG_FOLLOW_DIST,
traversal_tolerance,
) {
inputs.move_dir = Vec2::from(bearing)
.try_normalized()
.unwrap_or(Vec2::zero());
inputs.jump.set_state(bearing.z > 1.0);
*move_dir = 0.9f32 * *move_dir
+ 0.1f32
* Vec2::from(bearing)
.try_normalized()
.unwrap_or(Vec2::zero());
inputs.move_dir = *move_dir;
inputs.jump.set_state(bearing.z > 1.5);
}
} else {
do_idle = true;
@ -317,7 +324,7 @@ impl<'a> System<'a> for Sys {
inputs.move_dir = Vec2::from(bearing)
.try_normalized()
.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)
@ -419,7 +426,11 @@ impl<'a> System<'a> for Sys {
if let Some(owner_pos) = positions.get(owner) {
let dist_sqrd = pos.0.distance_squared(owner_pos.0);
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