- Home /
Stopping a NavMeshAgent gradually without loss of accuracy
Got a character that moves around based on where the player clicks, using the NavMesh system for pathfinding and movement and such. I've just hooked up a simple animation that's tied to the speed of the character, and that made me notice that while it speeds up nice and gradually, it stops almost instantly, with a noticeable jerk in animation as it snaps straight from its run animation to its idle animation.
There's the stopping distance parameter, but that adds a lack of accuracy -- not only is the stopping position not guaranteed, but if you try to move short distances at a time, it just won't happen. Since said character is pretty small, this is a problem!
Turning off Auto Braking doesn't really work either -- for whatever reason the character constantly oscillates between two points once it reaches the target -- maybe trying and failing to correct overshoot?
Anyway, is there a way to set some deceleration parameter, rather than stopping distance? So it looks ahead to some degree and decelerates roughly on time, rather than stopping instantly and prematurely.
Is my best bet here to manually adjust the speed as the character gets close to the target? Or is there an easier option?
Answer by a52 · Aug 06, 2021 at 01:30 AM
I'd still like an easy answer implemented in Unity itself, but for now I've come up with the following code in the object that interacts with the NavMeshAgent component (this is just what's relevant, the actual object does other stuff as well):
[RequireComponent(typeof(NavMeshAgent))]
public class PlayerMotor: MonoBehaviour {
private NavMeshAgent _agent;
private float _defaultSpeed;
private bool _decelerating;
void Start() {
_agent = GetComponent<NavMeshAgent>();
_defaultSpeed = _agent.speed;
}
private void Update() {
// slowly decelerate to avoid NavMeshAgent's instant stops
if (_decelerating) {
_agent.speed -= _agent.acceleration * Time.deltaTime;
_decelerating = WithinBrakingDistanceOfTarget();
}
// if we need to start accelerating, do so, and set the speed to the correct value to start with
else if (_agent.stoppingDistance == 0 && WithinBrakingDistanceOfTarget()) {
_decelerating = true;
_agent.speed = (float) (_agent.acceleration * Math.Sqrt(2 * _agent.remainingDistance / _agent.acceleration));
}
// reset if we've somehow moved away from the target or the target has changed/etc, allowing the agent
// to again accelerate and decelerate at will
else if (_agent.speed != _defaultSpeed) {
_agent.speed = _defaultSpeed;
}
}
private bool WithinBrakingDistanceOfTarget() {
return _agent.remainingDistance <= 0.5f * _agent.velocity.sqrMagnitude / _agent.acceleration;
}
A few notes:
If you've got anything that depends on the NavMeshAgent's
speed
value (animations that depend on the current speed as a fraction of the maximum speed, for example), it will either have to refer to PlayerMotor's_defaultSpeed
value or cache a copy of its own inStart()
, as this obviously messes with it.This is disabled when stopping distance is not set to zero, as it will usually stop gradually on its own in that case, and I also wouldn't want the two systems "fighting". (At the moment my stopping distance changes dynamically, and is used whenever the player selects an object in order to tell the player character how close to the object to get. I may rework that so it's just its own "targetRadius" parameter, and stopping distance remains constant, so that my acceleration code is always what handles braking)
This could (and I'd imagine should) probably be put into a Coroutine, just haven't done that yet.
Your answer
Follow this Question
Related Questions
Way to ignore specific navmeshobstacles for specific navmeshagent when creating path? 0 Answers
How to sample a point on a navMesh Agent path 0 Answers
Is it possible to assign a custom path to a NavMeshAgent? 0 Answers
Custom Ai Movement using Unity's pathfinding 1 Answer
navmesh.sampleposition works strangely, what is going on here? 0 Answers