Search Unity

Question NavMeshAgent height changes when it comes very near anything

Discussion in 'Navigation' started by umbrella-crash, Jul 11, 2022.

  1. umbrella-crash

    umbrella-crash

    Joined:
    Mar 3, 2022
    Posts:
    20
    I'm not sure how to describe this. I have a pretty simple setup:
    - A 3-D play field with the ground being a plane 1 unit thick with "walls" around the side that are just cubes that are 2 units high.
    - A "character" that is just an empty game object with the NavMeshAgent, character controller, and a couple of scripts attached, plus a Capsule as a child so it has a visible body.
    - A Cylinder that I place somewhere on the plane, and in a script I set that object's position as the destination for the NavMeshAgent.

    When I click "play" in the editor, the object with the NavMeshAgent goes smoothly toward the target at a height of 1.08. I assume that is the thickness of the "ground" (1) plus the Skin Width (.08).
    If I make the target a rigid body, it stops when it runs into it, and things seem okay. If there is nothing to stop it, it goes inside the target cylinder, of course, and at that point it's height changes to 0.083.

    If I place the target outside the wall(so there is no way for the navigation to reach it) the object heads directly toward it, runs into the wall and stops. Then, either immediately or after a few seconds, drops to 0.083.

    The way the navmesh is baked, the area just beside the wall is not traversable, but as I mentioned, there is no way around the wall. But it happens in traversable parts of the mesh too as when the character goes "inside" the target object.

    This is contrived to make it more reproducable, but in the more natural situation, it does this dropping in seemingly inconsistent ways when encountering an obstacle or another object, whether those things are baked into the mesh, or have NavMeshObstacle components, etc.

    I've tried all sorts of things: different shapes, different numbers in all the heights (so many heights settings!). I don't seem to be able to prevent this from happening reliably and anyway I'd like to understand this thing a bit better.

    I hope someone here can explain to me what is happening! Thanks so much!
     
  2. Tion-Gaming

    Tion-Gaming

    Joined:
    Jan 30, 2015
    Posts:
    28
    Sounds like you are constantly setting the destination for the agent in an update loop to the balls position right? NavMeshPaths are not calculated instantly upon the call to set destination. Agetns have a variable "
    agent.pathPending" which tells you if the path is still being calculated. Your call to SetDesination (if you are using that method instead of setting desination directly) also returns a boolean value which tells you if the operation was successful or not. You might want to check to make sure you ware not forcing it to update constantly while its still trying to update the desination you set in the last frame. Additionally, the navmesh actually has 0 thickness, its just a plane. The offset is probably from your navmesh agents they have a variable called "BaseOffset" which may be what you see.
     
    umbrella-crash likes this.
  3. umbrella-crash

    umbrella-crash

    Joined:
    Mar 3, 2022
    Posts:
    20
    Thank you for pointing out the problem with repeatedly calling agent.SetDestination() in Update(). I put a conditional to keep it from updating when agent.pathPending. I appreciate this better understanding, too. However, I don't think it is actually the cause of the problem, but did point me to another clue.
    I realized that, even if pathPending is false, the agent would be updated repeatedly when the ball wasn't moving. So I put a condition something like this:
    if (agent.destination != ball.transform.position) ... and I discovered this was always true! The y coordinate was different in the agent than the one set.
    Here is an example:
    Before: agent.destination is (3.55, 0.08, 1.56) ball.transform.position is (8.67, 1.20, -7.31)
    Then I call agent.SetDestination(ball.transform.position), and this returns true.
    After: agent.destination is (8.67, 0.08, -7.31)

    There are several things I don't understand about this. I can see why the agent might change the y value, but why would it be 0.08? That is the SkinWidth in the Character Controller, but that could be a coincidence. And why is it not 1.08? I now understand that the Navmesh itself has no thickness, but the 3-D GameObject Plane that I used as the ground does have a thickness, and the top of it is at y=1.0. Is the agent keeping its destination vector relative to the NavMesh itself? And in any case, why would the character go through the ground when it reaches the destination's x & z coordinates? Also, in some cases (but I haven't been able to reproduce this very well) It goes down to that same y value when it runs into the wall trying to get to an unreachable destination. It goes through the floor, but not through the wall.

    BTW the Base Offset was set to 0. I changed it to 0.1, and now the character position still cruises along at y = 1.08, and now drops to y = 0.183 when it reaches the destination. (Previously it was 0.083. So the Base Offset is part of that number, but the .083 is coming from somewhere else.)

    Thanks again for your clarifications! Does this new stuff suggest anything?
     
  4. Tion-Gaming

    Tion-Gaming

    Joined:
    Jan 30, 2015
    Posts:
    28
    I do believe its relative to the navmesh, depends on what kind of navmesh you use though, the old one or navmesh components? I am not as familiar with the old navmesh. Changing the agents height itself is part of the process, you should make sure you rebake the navmesh too because the navmesh will change height as well, at least thats what ive found with navmesh components. Navmesh agents should be locked to only move on the navmesh itself so your actual agent should never fall off of it. I dont think they are allowed to move elsewhere. When it drops down pause the play and click on the object, you should see the cylinder shape gizmo on the navmesh and your capsule below it. The Cylinder is the navmesh agent. If you arent using the NavmeshAgent to control the object then you need to make sure that what you have works together. Follow this unity guide for more detail- https://docs.unity3d.com/Manual/nav-MixingComponents.html

    The general gist is you probably have some sort of gravity affecting the object probably by the char controller and so when the agent stops moving the character just falls down since the navmesh does not have a collider. A cheap workaround is to set your transform position to agent.nextPosition (the simulated position of that cylinder on the navmesh.) every frame.
     
    umbrella-crash likes this.
  5. umbrella-crash

    umbrella-crash

    Joined:
    Mar 3, 2022
    Posts:
    20
    Thank you for the link to that manual page. Very relevant. I am using the old NavMesh. Do you think I should switch over? I am not locked in. I was just using it because it was the default and because most of the tutorials and other guidance use it. But the same could be said of the old input system, and I wouldn't want to be stuck using that! So, should I be switching and learning the new one?

    The navmesh agent isn't moving the object directly because there are a few movement behaviors that are not agent behaviors. (Also, I wanted to make it work smoothly with animation.) The document you linked to discusses how to decouple these, and I suspect I haven't done that right, so I'll go back through that again. I think there probably is some kind of fight going on between the different components. Maybe gravity, or perhaps it is a matter of the inconsistent coordinates (if the agent is using navmesh-relative coords), or something else. I'll experiment some more.

    I was trying to find some kind of function that could transform from navmesh-relative coordinates to world coordinates, but I haven't found it, so I'm not sure how to set the transform position to agent.nextPosition and have it respect the elevation of the terrain and navmesh. Is there such a function?

    The cylinder navmesh agent gizmo is a great tip! I will look for that.

    Thanks again!
     
  6. Tion-Gaming

    Tion-Gaming

    Joined:
    Jan 30, 2015
    Posts:
    28
    Honestly I am not 100% sure if its relative or not. Ive never had to use the navmesh at any height other than 0 so I cant really say how that will work. I dont see why it would be local now that I think of it. You should just give it a shot setting the transform.position to agent.nextposition in update or late update, worse that will happen is you get a funny result. another thing you can do if al else fails is to actually get a point on the navmesh closest to your transforms point by using NavMesh.SamplePosition or Navmesh.ClosestPoint which takes in world coords and gives you a NavMeshHit struct back just like a RaycastHit. NavMeshHit has a position property you can use to set your transform to, but you shouldnt need to use that.
     
    umbrella-crash likes this.
  7. umbrella-crash

    umbrella-crash

    Joined:
    Mar 3, 2022
    Posts:
    20
    I think the problem is solved! Thank you for sticking with me through so much back and forth. Honestly, I don't think I would have figured it out without your help. It wasn't exactly any of the things you said, but you kept pointing me in more-or-less the right direction and filling in some of my knowledge gaps, and I finally got there!

    Here's what worked, in the end. As you suspected, there was a tug of war going on between the agent and some of the other code. I clarified the Update code to the point where it was fairly cleanly taking the agent.desiredVelocity and applying that to the transform, but it was still not doing the thing. I reread the document you pointed to (for about the fifth time:)) and read again the section about how to decouple the agent from the animation (which I don't have at the moment, but that is the direction I'm going). It said to tell the agent to not update position and rotation of the object. Like this:
    agent.updatePosition = false;
    agent.updateRotation = false;

    Logical enough (now that I'm getting a little understanding). I searched the code and found:
    agent.updateRotation = false;

    However, I was NOT disabling the updatePosition! o_O So I added the other line:
    agent.updatePosition = false;

    and the problem went away!

    There was one other part of the strange behavior that I think I partially understand now also, thanks to your advice to look at the cylinder-shaped gizmo representing the agent (which I hadn't noticed among the other gizmo outlines there.) As I watched the object and the agent roaming around, I could see that the object often moved ahead of the agent, which then caught up. That makes sense, given my update approach, but I was a little surprised how much it lagged sometimes. That varying delay about fits the varying delay of the original dropping behavior. So the object arrived at the destination and stayed put, until the agent caught up, sometimes quickly, sometimes a few seconds later, and then the object went down to the lower height. (I don't fully understand why the agent drops below the mesh, but it isn't causing me trouble now anyway because of the layer of indirection.)

    So, thanks again! I can get back on track now.
     
    Tion-Gaming likes this.