Making an Off Mesh Link jump in a non-planar navmesh

  • A* version: 5.22 Pro
  • Unity version: 2022.3.21.f1
    I am using the navmesh graph to make agents walk around a Dinossaurs Teeth, everything going well until I tried to use the NodeLink2 to add a jump mechanic between parts of the graph.
    The agent is using the FollowerEntity Script and I used the jump script from the documentation example.

The problem is: When the agent jumps I can’t change its rotation, it will always be rotated so its down vector faces the the start point of the link. When it lands, it doesn’t land on its feet and gravity makes the agent fall off the Navmesh.

I could not set the rotation of the agent no matter what I tried, It seems the FollowerEntity is setting its rotation towards the last navmesh normal on everyframe and I can’t overwrite it.

On the image above, the top point is the start, the agent is in the middle of a jump towards the bottom point. Notice how its down vector always faces the starting point of the link, it lands that way and gravity is applied in that direction after landing.
I tried changing the rotation in many ways with no success, I even tried changing the movement plane
to match the end point up vector, but I couldn’t do it too.

Can anyone help me on how to solve this issue?

Hi there, we actually had a really similar question a bit ago here: Adjusting FollowerEntity Rotation(X,Z) While Traversing OffMeshLink Without Aborting Coroutine.

This is the solution Aron offered:

There was also another thread where Aron suggested using SetDestination() to the agent’s current position, and using the rotation argument to set it’s rotation:

This was in the context of “rotate to keep looking at another agent” though, but it still may be helpful here. Let us know what you think :+1:

Turns out I was able to change the movement plane directly from the entity.

> IEnumerable IOffMeshLinkStateMachine.OnTraverseOffMeshLink(AgentOffMeshLinkTraversalContext ctx)
>     {
>         var start = (Vector3)ctx.link.relativeStart;
>         var end = (Vector3)ctx.link.relativeEnd;
>         var dir = end - start;
>         // Disable local avoidance while traversing the off-mesh link.
>         // If it was enabled, it will be automatically re-enabled when the agent finishes traversing the link.
>         ctx.DisableLocalAvoidance();
>         EntityManager em = World.DefaultGameObjectInjectionWorld.EntityManager;
>         // Animate the AI to jump from the start to the end of the link
>         Quaternion initialRot = ctx.transform.Rotation;
>         while (!ctx.MoveTowards(
>                position: start,
>                rotation: initialRot,
>                gravity: true,
>                slowdown: true).reached)
>         {
>             yield return null;
>         }
>         Quaternion r1 = getQuaternionWithExactUp(dir, ctx.link.isReverse ? _p2.position - _p3.position : _p1.position - transform.position);
>         Quaternion r2 = getQuaternionWithExactUp(dir, !ctx.link.isReverse ? _p2.position - _p3.position : _p1.position - transform.position);
>         for (float t = 0; t < _jumpDuration; t += ctx.deltaTime)
>         {
>             if (!ctx.link.isReverse) ctx.transform.Position = AstarSplines.CubicBezier(transform.position, _p1.position, _p2.position, _p3.position, Mathf.SmoothStep(0, 1, t / _jumpDuration));
>             else ctx.transform.Position = AstarSplines.CubicBezier(_p3.position, _p2.position, _p1.position, transform.position, Mathf.SmoothStep(0, 1, t / _jumpDuration));
>             em.SetComponentData<AgentMovementPlane>(ctx.entity, new AgentMovementPlane(Quaternion.Slerp(r1, r2, t/_jumpDuration)));
>             ctx.transform.Rotation = Quaternion.Slerp(initialRot, Quaternion.LookRotation(dir, ctx.movementPlane.up),Mathf.Clamp(t*2/_jumpDuration, 0, 1f));
>             yield return null;
>         }
>     }
>     private Quaternion getQuaternionWithExactUp(Vector3 forward, Vector3 up)
>     {
>         up = up.normalized;
>         Vector3 projectedForward = Vector3.ProjectOnPlane(forward, up).normalized;
>         if (projectedForward == Vector3.zero)
>         {
>             projectedForward = Vector3.Cross(up, Vector3.right);
>             if (projectedForward == Vector3.zero)
>             {
>                 projectedForward = Vector3.Cross(up, Vector3.forward);
>             }
>         }
>         Vector3 right = Vector3.Cross(up, projectedForward).normalized;
>         projectedForward = Vector3.Cross(right, up).normalized;
>         return Quaternion.LookRotation(projectedForward, up);
>     }

I have no knowledge over ECS or DOTS, so let me know if I did something very bad. Thank you!

1 Like

From what I can see and what I know this looks decently solid. Nice find! :smiley: And thanks for sharing!