Bug where followerEntity moves towards link.relativeStart while traversing OffMeshLink

  • A* version: 5.2.1
  • Unity version: 2022.3.21f1

While the followerEntity is traversing an OffMeshLink, it slowly moves towards link.relativeStart. I am currently implementing wall climbing using RootMotion animation, and the following logic is used:

while (!ctx.MoveTowards(
        position: ctx.link.relativeStart, // Floor to Wall Animation start point
        rotation: Quaternion.LookRotation(dir),
        gravity: false,
        slowdown: true).reached)
{
    yield return null;
}

// Play the wall climbing animation
unit.animator.SetBool(hashOnWall, value);

// After that, the position is determined by the RootMotion animation, and the movement stops once a specific height is reached using the While loop.

The issue is that while the unit is climbing the wall, it slowly continues to move towards link.relativeStart. (The height(position.y) is well controlled by the RootMotion animation. Only x,z position move to link.relativeStart)
Even though nothing else is being executed in the while loop, unit moved by only root motion, but the unit keeps moving to link.relativeStart, which seems like a bug.

(link.relativeStart is slightly in front of the bottom of the wall, not directly at the base, as the RootMotion moves the unit forward a little at the start of the climb.)

I may be misunderstanding a slight bit, but I see the MoveTowards in the while loop. Wouldn’t that be what’s actively making it move downwards towards the start? I only know of MoveTowards in the context of a Vector3/Vector2/Mathf, etc, but I see you’re calling it on ctx. Do you mind breaking that down a little?

This is code in OffMeshLink Coroutine.


  IEnumerable IOffMeshLinkStateMachine.OnTraverseOffMeshLink(AgentOffMeshLinkTraversalContext ctx)
  {
    if (!ctx.gameObject.TryGetComponent<Unit>(out var unit))
    {
      ctx.Abort(false);
    }

    ctx.DisableRotationSmoothing();
    ctx.DisableLocalAvoidance();
    unit.ai.enableGravity = false;

    var isClimbUp = !ctx.link.isReverse;
    Vector3 dir = ctx.link.relativeEnd - ctx.link.relativeStart;
    dir.y = 0;
    dir = dir.normalized;

    while (!ctx.MoveTowards(
        position: ctx.link.relativeStart,
        rotation: Quaternion.LookRotation(dir),
        gravity: false,
        slowdown: true).reached)
    {
      yield return null;
    }

// play start floorToWall animation. (=> wallClimb => wallToFloor.)
    unit.SetOnWall(true, isClimbUp);

// After that, the position is determined by the RootMotion animation, and the movement stops once a specific height is reached using the While loop.
   ...
   ...
   ...
  }

The ctx is AgentOffMeshLinkTraversalContext.
while(!ctx.MoveTowards(…).reached) is just move to ctx.link.relativeStart and check is it reached on.

image

This is an image to help with understanding. The two points at either end are either link.relativeStart or link.relativeEnd. The root motion animation starts at the starting point (link.relativeStart), and under normal root motion, it should reach the point closer to link.relativeStart among the two middle points, but it does not.

The reason is that after the root motion starts, the object gradually moves toward link.relativeStart. (The Y value behaves as expected according to the root motion, but the X and Z values shift slightly.)

After while(!ctx.MoveTowards(...)), there is no other code, only while(true) { yield return null }, and the position is supposed to be controlled purely by the root motion, but this is not happening.

I currently see this as a bug.

1 Like

Thank you for the clarification and image, helped a lot. I’m working to recreate this on my end to see if I’m having the same results. Had to set up my testing/troubleshooting project to work with Offmesh Links and animations better to test it, but I’m looking into it :slight_smile:

While I’m getting this set up on my end, I wanted to ask a few, possibly annoying questions- mainly to make sure I’m fully on the same page as you, and that I’m not giving you the tech support run-around haha. I figured it’d be better if I asked for a few things now, rather than later:

  1. Would a video of this happening be something you can get me? Just to verify the behaviour.
  2. Could you explicitly label the 4 game objects from your screenshot? It helped a lot, but I want to make sure I’m not mistaking things.

Appreciate the patience, and sorry for the run-around haha

This is moved by Only RootMotion Animator.
Only RootMotion Animator

And this is using both rootMotion and A* together.
A* + RootMotion

This is a top-down view of rootMotion + A*. You can see that the position x and z are moving to link.relativeStart.
A* + RootMotion top-down view

This is the result of combining the two.
Compare two

Thanks very much for those videos, they helped a lot. I did my best to recreate your set up and now I’m pretty sure I’m on the same page.

I never thought I’d be so happy to say this, but I was able to reproduce the issue! (Ignore the fact that they’re not climbing up, that’s some root motion shenanigans that I’ll sort out while I look at this)

https://i.imgur.com/zj546k8.gif

I’ll try to see what I can find that might be the issue, and if I can’t find much I’ll let aron know and he can tell us more definitely if it’s a bug :+1:

You made it exactly the sameđź‘Ť Thank you for your hard work!

1 Like

Of course! Thank you for being patient with me while I wrapped my head around this! Actually picked up a good bit of useful knowledge about root motion :slight_smile:

However, it looks like we’re not getting away with a simple issue here haha- so in my gif I sent, that was with mostly functionally identical code to yours. The main difference was I left out the following line:

unit.ai.enableGravity = false;

I added it in just now, to fix the not moving up at all during the root motion in my gif. However, in doing that, my unit started moving up, but also did not move off the wall towards the XZ of the relativeStart. Which is confusing, because I could reproduce this issue when my code didn’t match, but when my code did match, it suddenly started working? My line I added was more direct, since I haven’t set up a reference to the FollowerEntity in my script:

GetComponent<FollowerEntity>().enableGravity = false;

i1011246704

Here’s my code for reference:

IEnumerable IOffMeshLinkStateMachine.OnTraverseOffMeshLink(AgentOffMeshLinkTraversalContext ctx){
    ctx.DisableRotationSmoothing();
    ctx.DisableLocalAvoidance();
    
    GetComponent<FollowerEntity>().enableGravity = false;

    var isClimbUp = !ctx.link.isReverse;
    Vector3 dir = ctx.link.relativeEnd - ctx.link.relativeStart;
    dir.y = 0;
    dir = dir.normalized;

    while (!ctx.MoveTowards(
               position: ctx.link.relativeStart,
               rotation: Quaternion.LookRotation(dir),
               gravity: false,
               slowdown: true).reached){
        yield return null;
    }
    
    // play start floorToWall animation. (=> wallClimb => wallToFloor.)
    GetComponent<Animator>().SetTrigger("Climb");

    for (float t = 0; t < 8f; t += ctx.deltaTime) {
        yield return null;
    }
    
    GetComponent<Animator>().SetTrigger("Climb");
    
    for (float t = 0; t < 5f; t += ctx.deltaTime) {
        yield return null;
    }
} 

Even weirder is that your unit did climb up, but still moved backwards, while mine didn’t while climbing up specifically.

I doubt this, but is there something setting the gravity to true somewhere in your project?

There is no code that sets ai.enableGravity = true. I checked in the Inspector, and Gravity is unchecked. I also tried GetComponent<FollowerEntity>() inside a Coroutine, but the result remains the same. What could be the issue?

Not entirely sure. It’s certainly looking like a bug or unexpected interaction. I’ll let @aron_granberg know about this and we’ll see what he thinks. Thanks for helping me wrap my head around this to recreate it!

Update: Similar thread here FollowerEntity sliding when on OffMeshLink

1 Like

Hi

The current version always has the agent’s internal movement code running as well. This is not great if you want the movement to be determined entirely by e.g. an animation.

In the next update, the agent will by default not use its own movement logic, unless you explicitly enable AgentOffMeshLinkTraversalContext.enableBuiltInMovement or call AgentOffMeshLinkTraversalContext.MoveTowards.

1 Like