Path Following going up the Y axis at end of path

I am having an issue with <10% of my seekers. When they reach their destination, they start going up in the air. Not smoothly, but jumping. I am using the RichAI client with a navmesh. The terrain is not flat. I added a raycast to ground my target, but that didn’t seem to help.

I am new and still unsure about a lot of settings and stuff, so any help would be appreciated.

I found something that seems odd. The velocity calculation as you approach the end of the path produces a velocity that is large on the Y axis. From the RichAI we have:

				// Check if the agent should slow down in case it is not facing the direction it wants to move in
				if (slowWhenNotFacingTarget) {
					// 1 when normdir is in the same direction as tr.forward
					// 0.2 when they point in the opposite directions
					float directionSpeedFactor = Mathf.Max((Vector3.Dot(normdir, tr.forward)+0.5f)/1.5f, 0.2f);
					currentMaxSpeed *= directionSpeedFactor;
					float currentSpeed = VectorMath.MagnitudeXZ(velocity);
					float prevy = velocity.y;
					velocity.y = 0;
					currentSpeed = Mathf.Min(currentSpeed, currentMaxSpeed);

					// Make sure the agent always moves in the forward direction
					// except when getting close to the end of the path in which case
					// the velocity can be in any direction
					velocity = Vector3.Lerp(velocity.normalized * currentSpeed, tr.forward * currentSpeed, Mathf.Clamp(targetIsEndPoint ? distanceToWaypoint*2 : 1, 0.0f, 0.5f));

					velocity.y = prevy;
				} else {
					velocity = VectorMath.ClampMagnitudeXZ(velocity, currentMaxSpeed);
				}

I put in some debug and got the following numbers for the calculation:

Close to target. Setting velocity to (targetPoint - position) * 100) targetPoint (568.4, 76.9, 585.2) position (568.4, 75.3, 585.2) targetPoint - position = (0.0, 1.6, 0.0) * 100 = (-0.7, 155.4, -1.7)

I don’t know the algorithm well enough to know why this is a problem. Would someone please explain it and explain what is going on and how to fix it?

Hi

Did you notice this line?

velocity.y = prevy;

So the y coordinate will be reset to the previous value. The y coordinate is essentially only affected by the gravity.

Thank you for your quick reply!

I don’t think it is quite that simple. I have included my debug code and output here for the RichAI Update() method. I merged in the debug output with the code so you could see what each statement produced for one iteration of Update. I recognize that I must be doing something wrong, but I can not for the life of me figure it out. One of the places that I am concerned about is the position coming out of the targetPoint coming out of the nextCorners array as it’s y-value is different than the position of the character. That difference seems to produce a significant y velocity that is put into the prevy variable and then extracted from prevy.

Here is the code and output:

    public override void Update()
    {
        if (logger == null)
        {
            logger = new LogHelper("CustomRichAI", MyName);
            logger.MethodName = "Update";
        }
        
        deltaTime = Mathf.Min(Time.smoothDeltaTime * 2, Time.deltaTime);

        if (target != null)
        {
            logger.Msg("Just entering Update. target is " + target.position.ToString() + " and velocity is " + velocity.ToString());

[10/29/2016 4:14:22 PM] Just entering Update. target is (1511.7, 63.4, 963.2) and velocity is (-0.1, 0.0, -0.1)
}

        if (rp != null)
        {
            logger.Msg("Using RichPath.");

[10/29/2016 4:14:22 PM] Using RichPath.
RichPathPart currentPart = rp.GetCurrentPart();
var fn = currentPart as RichFunnel;
if (fn != null)
{
logger.Msg(“Using RichFunnel.”);
[10/29/2016 4:14:22 PM] Using RichFunnel.
// Clamp the current position to the navmesh
// and update the list of upcoming corners in the path
// and store that in the ‘nextCorners’ variable
Vector3 position = UpdateTarget(fn);

                // Only get walls every 5th frame to save on performance
                if (Time.frameCount % 5 == 0 && wallForce > 0 && wallDist > 0)
                {
                    wallBuffer.Clear();
                    fn.FindWalls(wallBuffer, wallDist);
                }

                // Target point
                int tgIndex = 0;
                Vector3 targetPoint = nextCorners[tgIndex];
                Vector3 dir = targetPoint - position;
                dir.y = 0;

                bool passedTarget = Vector3.Dot(dir, currentTargetDirection) < 0;
                // Check if passed target in another way
                if (passedTarget && nextCorners.Count - tgIndex > 1)
                {
                    tgIndex++;
                    targetPoint = nextCorners[tgIndex];
                }

                // Check if the target point changed compared to last frame
                if (targetPoint != lastTargetPoint)
                {
                    currentTargetDirection = targetPoint - position;
                    currentTargetDirection.y = 0;
                    currentTargetDirection.Normalize();
                    lastTargetPoint = targetPoint;
                }

                // Direction to target
                dir = targetPoint - position;
                dir.y = 0;

                // Normalized direction
                Vector3 normdir = VectorMath.Normalize(dir, out distanceToWaypoint);

                // Is the endpoint of the path (part) the current target point
                bool targetIsEndPoint = lastCorner && nextCorners.Count - tgIndex == 1;

                // When very close to the target point, move directly towards the target
                // instead of using accelerations as they tend to be a bit jittery in this case
                if (targetIsEndPoint && distanceToWaypoint < 0.01f * maxSpeed)
                {
                    // Velocity will be at most 1 times max speed, it will be further clamped below
                    velocity = (targetPoint - position) * 100;
                    //velocity = (targetPoint - position);
                    logger.Msg("Close to target. Setting velocity to (targetPoint - position) * 100) targetPoint " + targetPoint.ToString() + " position " + position.ToString() + " targetPoint - position = " + (targetPoint-position).ToString() + " * 100 = " + velocity.ToString());

[10/29/2016 4:14:22 PM] Close to target. Setting velocity to (targetPoint - position) * 100) targetPoint (1511.9, 67.5, 963.1) position (1511.9, 63.4, 963.1) targetPoint - position = (0.0, 4.1, 0.0) * 100 = (-1.4, 407.2, -1.5)
}
else
{
// Calculate force from walls
Vector3 wallForceVector = CalculateWallForce(position, normdir);
Vector2 accelerationVector;

                    if (targetIsEndPoint)
                    {
                        accelerationVector = CalculateAccelerationToReachPoint(To2D(targetPoint - position), Vector2.zero, To2D(velocity));
                        //accelerationVector = Vector3.ClampMagnitude(accelerationVector, acceleration);

                        // Reduce the wall avoidance force as we get closer to our target
                        wallForceVector *= System.Math.Min(distanceToWaypoint / 0.5f, 1);

                        if (distanceToWaypoint < endReachedDistance)
                        {
                            // END REACHED
                            NextPart();
                        }
                    }
                    else
                    {
                        var nextNextCorner = tgIndex < nextCorners.Count - 1 ? nextCorners[tgIndex + 1] : (targetPoint - position) * 2 + position;
                        var targetVelocity = (nextNextCorner - targetPoint).normalized * maxSpeed;

                        accelerationVector = CalculateAccelerationToReachPoint(To2D(targetPoint - position), To2D(targetVelocity), To2D(velocity));
                    }

                    // Update the velocity using the acceleration
                    velocity += (new Vector3(accelerationVector.x, 0, accelerationVector.y) + wallForceVector * wallForce) * deltaTime;
                    logger.Msg("Adjusted velocity by acceleration. velocity is " + velocity.ToString());
                }

                var currentNode = fn.CurrentNode;

                Vector3 closestOnNode;
                if (currentNode != null)
                {
                    closestOnNode = currentNode.ClosestPointOnNode(position);
                }
                else
                {
                    closestOnNode = position;
                }

                // Distance to the end of the path (as the crow flies)
                var distToEndOfPath = (fn.exactEnd - closestOnNode).magnitude;

                // Max speed to use for this frame
                var currentMaxSpeed = maxSpeed;
                currentMaxSpeed *= Mathf.Sqrt(Mathf.Min(1, distToEndOfPath / (maxSpeed * slowdownTime)));

                // Check if the agent should slow down in case it is not facing the direction it wants to move in
                if (slowWhenNotFacingTarget)
                {
                    // 1 when normdir is in the same direction as tr.forward
                    // 0.2 when they point in the opposite directions
                    float directionSpeedFactor = Mathf.Max((Vector3.Dot(normdir, tr.forward) + 0.5f) / 1.5f, 0.2f);
                    currentMaxSpeed *= directionSpeedFactor;
                    float currentSpeed = VectorMath.MagnitudeXZ(velocity);
                    float prevy = velocity.y;
                    velocity.y = 0;
                    currentSpeed = Mathf.Min(currentSpeed, currentMaxSpeed);

                    // Make sure the agent always moves in the forward direction
                    // except when getting close to the end of the path in which case
                    // the velocity can be in any direction
                    velocity = Vector3.Lerp(velocity.normalized * currentSpeed, tr.forward * currentSpeed, Mathf.Clamp(targetIsEndPoint ? distanceToWaypoint * 2 : 1, 0.0f, 0.5f));
                    logger.Msg("(slowWhenNotFacingTarget) Doing a lerp. velocity is " + velocity.ToString());

[10/29/2016 4:14:22 PM] (slowWhenNotFacingTarget) Doing a lerp. velocity is (-0.1, 0.0, -0.1)
velocity.y = prevy;
logger.Msg("(slowWhenNotFacingTarget) Restored previous y value. velocity is " + velocity.ToString());
[10/29/2016 4:14:22 PM] (slowWhenNotFacingTarget) Restored previous y value. velocity is (-0.1, 407.2, -0.1)
}
else
{
velocity = VectorMath.ClampMagnitudeXZ(velocity, currentMaxSpeed);
logger.Msg("Adjusted velocity by ClampMagnitudeXZ. velocity is " + velocity.ToString());
}

                // Apply gravity
                velocity += deltaTime * gravity;
                logger.Msg("Adjusted velocity by gravity. velocity is " + velocity.ToString());

[10/29/2016 4:14:22 PM] Adjusted velocity by gravity. velocity is (-0.1, 403.9, -0.1)

                if (rvoController != null && rvoController.enabled)
                {
                    // Send a message to the RVOController that we want to move
                    // with this velocity. In the next simulation step, this velocity
                    // will be processed and it will be fed back the rvo controller
                    // and finally it will be used by this script when calling the
                    // CalculateMovementDelta method below

                    // Make sure that we don't move further than to the end point of the path
                    // If the RVO simulation FPS is low and we did not do this, the agent
                    // might overshoot the target a lot.
                    var rvoTarget = position + VectorMath.ClampMagnitudeXZ(velocity, distToEndOfPath);
                    rvoController.SetTarget(rvoTarget, VectorMath.MagnitudeXZ(velocity), maxSpeed);
                }

                // Direction and distance to move during this frame
                Vector3 deltaPosition;
                if (rvoController != null && rvoController.enabled)
                {
                    // Use RVOController to get a processed delta position
                    // such that collisions will be avoided if possible
                    deltaPosition = rvoController.CalculateMovementDelta(position, deltaTime);
                    Vector3 startDelta = deltaPosition;

                    // The RVOController does not know about gravity
                    // so we copy it from the normal velocity calculation
                    deltaPosition.y = velocity.y * deltaTime;
                    logger.Msg("Using rvoController to determine deltaPosition. start " + startDelta.ToString() + " adjusted by velocity.y " + velocity.ToString() + " and deltaTime " + deltaTime + " to come of with delta " + deltaPosition.ToString());
                }
                else
                {
                    deltaPosition = velocity * deltaTime;
                    logger.Msg("Using velocity " + velocity.ToString() + " and deltaTime " + deltaTime + " to come of with delta " + deltaPosition.ToString());

[10/29/2016 4:14:22 PM] Using velocity (-0.1, 403.9, -0.1) and deltaTime 0.3333333 to come of with delta (0.0, 134.6, 0.0)
}

                if (targetIsEndPoint)
                {
                    // Rotate towards the direction that the agent was in
                    // when the target point was seen for the first time
                    // TODO: Some magic constants here, should probably compute them from other variables
                    // or expose them as separate variables
                    Vector3 trotdir = Vector3.Lerp(deltaPosition.normalized, currentTargetDirection, System.Math.Max(1 - distanceToWaypoint * 2, 0));
                    RotateTowards(trotdir);
                }
                else
                {
                    // Rotate towards the direction we are moving in
                    RotateTowards(deltaPosition);
                }

                if (controller != null && controller.enabled)
                {
                    logger.Msg("CharacterController is moving me.");

                    // Use CharacterController
                    tr.position = position;
                    controller.Move(deltaPosition);
                    // Grab the position after the movement to be able to take physics into account
                    position = tr.position;
                }
                else
                {
                    // Use Transform
                    float lastY = position.y;
                    Vector3 origPos = position;
                    position += deltaPosition;
                    Vector3 deltaPos = position;
                    // Position the character on the ground
                    position = RaycastPosition(position, lastY);
                    Vector3 raycastPos = position;
                    logger.Msg("Am at " + origPos.ToString() + " headed to " + deltaPos.ToString() + " y adjusted " + raycastPos.ToString());

[10/29/2016 4:14:22 PM] Am at (1511.9, 63.4, 963.1) headed to (1511.8, 198.0, 963.1) y adjusted (1511.8, 198.0, 963.1)
}

                // Clamp the position to the navmesh after movement is done
                var clampedPosition = fn.ClampToNavmesh(position);

                if (position != clampedPosition)
                {
                    // The agent was outside the navmesh. Remove that component of the velocity
                    // so that the velocity only goes along the direction of the wall, not into it
                    var difference = clampedPosition - position;
                    velocity -= difference * Vector3.Dot(difference, velocity) / difference.sqrMagnitude;

                    // Make sure the RVO system knows that there was a collision here
                    // Otherwise other agents may think this agent continued to move forwards
                    // and avoidance quality may suffer
                    if (rvoController != null && rvoController.enabled)
                    {
                        rvoController.SetCollisionNormal(difference);
                    }
                }
                logger.Msg("Setting transform to " + clampedPosition.ToString());

[10/29/2016 4:14:22 PM] Setting transform to (1511.8, 198.0, 963.1)
tr.position = clampedPosition;
}
else
{
logger.Msg(“Not using RichFunnel.”);
if (rvoController != null && rvoController.enabled)
{
//Use RVOController
rvoController.Move(Vector3.zero);
}
}
if (currentPart is RichSpecial)
{
logger.Msg(“Checking RichSpecial.”);
// The current path part is a special part, for example a link
// Movement during this part of the path is handled by the TraverseSpecial coroutine
if (!traversingSpecialPath)
{
StartCoroutine(TraverseSpecial(currentPart as RichSpecial));
}
}
}
else
{
logger.Msg(“Not using RichPath.”);

            if (rvoController != null && rvoController.enabled)
            {
                // Use RVOController
                rvoController.Move(Vector3.zero);
            }
            else
            if (controller != null && controller.enabled)
            {
            }
            else
            {
                logger.Msg("Raycasting position for " + tr.position.ToString());
                tr.position = RaycastPosition(tr.position, tr.position.y);
                logger.Msg("Setting transform to " + tr.position.ToString());
            }
        }


        if (tr != null)
        {

            logger.Msg("AI position is " + tr.position.ToString());

[10/29/2016 4:14:22 PM] AI position is (1511.8, 198.0, 963.1)
}
}