Search Unity

object Jumps to another location when enabling a navmeshagent

Discussion in 'Navigation' started by ian-coetzer, Jan 23, 2018.

  1. ian-coetzer

    ian-coetzer

    Joined:
    Oct 11, 2015
    Posts:
    25
    Hi Everyone

    I need some help. I have been looking for an answer but failed thus far.

    I have a simple cube which I have added both a Nav Mesh Agent, as well as a Nav Mesh Obstacle component.
    Of course I have disabled one of them before starting.

    The reason I did this was so that I could move my cube using keyboard arrows - in this case I enable the Nav Mesh Obstacle, and ensure that the Nav Mesh Agent is disabled first.

    Then when i click somewhere on my map I disable the Nav Mesh Obstacle, and enable the Nav Mesh Agent before giving it a destination.

    Everything is working fine, except when I disable the nav mesh obstacle and enable the nav mesh agent the cube strangely jumps a few places to the left which looks very strange.
     
  2. JBR-games

    JBR-games

    Joined:
    Sep 26, 2012
    Posts:
    708
    Best guess at the moment.. Make sure the navmeah agent is centered on the object.
    Are you using navmeshagent.setdestination(vector3) ?
     
  3. ian-coetzer

    ian-coetzer

    Joined:
    Oct 11, 2015
    Posts:
    25
    Hi

    Thank you for the reply.
    For now I stripped down my code to the following:

    (Assume Nav Mesh Obstacle is enabled by default, and the Nav Mesh Agent is disabled before running my project)

    Currently this is the only code that I run and it works by sending the nav agent off in a direction but when i seem to disable the nav mesh obstacle and enable the nav mesh agent it jumps to another location - before going off into the direction of the destination point ...

    Code (CSharp):
    1. RaycastHit hit;
    2. if (Physics.Raycast (_camera.ScreenPointToRay (Input.mousePosition), out hit, 5000)) {
    3.     if (_navMesh != null) {
    4.         _navObstacle.enabled = false;
    5.         _navMesh.enabled = true;
    6.         _navMesh.destination = hit.point;
    7.     }
    8. }
     
  4. ian-coetzer

    ian-coetzer

    Joined:
    Oct 11, 2015
    Posts:
    25
    I have uploaded a file to show the behaviour that I am trying to fix.
    when i click on a destination point the cube jumps a distance to the left and then it starts moving to the destination

    i want it to move from the position it was originally standing at .... without doing a funny jump / warp to another place in space ..
     

    Attached Files:

  5. SM_AF

    SM_AF

    Joined:
    Aug 1, 2017
    Posts:
    23
    When you enable the NavMeshAgent component (and it has a updatePosition property set to 'true'), it will automatically move the agent to a nearest position on the NavMesh.
    If you open the Navigation window, you can see the NavMesh gizmo and you should be able to fix the agent's initial position yourself.
    If this is a ineffective/inconvenient way of fixing it in you scenario, then you can use the NavMesh.SamplePosition when spawning the agent and correct his position from code (this is how NavMeshAgent component does it btw)
     
    Last edited: Jan 24, 2018
    trandai1106 likes this.
  6. ian-coetzer

    ian-coetzer

    Joined:
    Oct 11, 2015
    Posts:
    25
    Thank you for the reply, however I do not understand (not able to visualize all this yet) yet .. I will do some more reading to see if i can figure this out. I definitely need help on this or try to find an alternate solution.

    Basically I want to be able to navigate my object via NavMeshAgent by click on points on my map (nav mesh). but i also want to take over control with keyboard to move my object around.

    Problem is that I still want my object to be an 'obstacle' to other agents that are moving around, so i thought that by adding both a NavMeshAgent as well as a NavMeshObstacle I could get this right by switch one or the other off - depending on type of movement requested ...
     
  7. SM_AF

    SM_AF

    Joined:
    Aug 1, 2017
    Posts:
    23
    Set NavMeshAgent.updatePosition to 'false' when you want to take control with your keyboard. Keep the agent enabled. By doing this, other agents will see him and try to avoid him, no Obstacle component needed.
    Also, while the updatePosition is false, do this 'navMeshAgent.nextPosition = transform.position' in the Update function (after changing the agent's position manually). By doing this, the agent component's internal position will be in sync with the agent's actual position, so that the agent avoidance can still work well.
     
    trandai1106 and ian-coetzer like this.
  8. ian-coetzer

    ian-coetzer

    Joined:
    Oct 11, 2015
    Posts:
    25
    Hi, thank you i will try that.
    In the meantime (before reading your post) i created a hectic gameobject on top of my game objects to manually instantiate an instance of my object that only has a nav mesh agent and one with an obstacle component ... and by doing lots of coding to destroy and then instantiate a specific version of my object for keyboard or mouse control i copied the position and rotation so that the newly instantiated object points and rotates in the same direction .. after lots of debugging i found something interesting - the object with the NavMeshAgent component does not update the position and rotation of my rigid body in the parent / root ... but the object with the mesh obstacle does indeed change the rigid body component. it makes sense since i push my rigid body on the root when using keyboard input - mistake i made was to think that the nav mesh agent would also bubble its position up to the root rigid body .. is that at all possible, or should i just code it to update the rigid body as and when the nav mesh agent moves the object so that i can pickup rotation and location in one place?
     
  9. ian-coetzer

    ian-coetzer

    Joined:
    Oct 11, 2015
    Posts:
    25
    Hi
    I managed to start a new script that seems to do the job based on your input Mr "
    SM_AF"

    P.S I still have to optimize this stuff I know it is wrong to place everything in the update method and in the next version I will be using coroutines / invokes etc. to try and alleviate the stress on the update function.

    This works like a charm, I have attached this to a simple Cube wich contains a RigidBody as well as a Nav Mesh Agent component.

    Please see if you spot any obvious optimizations that i could implement (other than what I mentioned in the P.S section):

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.AI;
    5.  
    6. public class PlayerControllerMark02 : MonoBehaviour {
    7.  
    8.     public Camera RayCamera;
    9.  
    10.     private enum ControlEnum
    11.     {
    12.         Keyboard,
    13.         Mouse
    14.     };
    15.  
    16.     private NavMeshAgent _agent = null;
    17.     private Rigidbody _body = null;
    18.     private ControlEnum _controlEnum = ControlEnum.Mouse;
    19.     private Vector3 _targetPosition;
    20.     // Use this for initialization
    21.     void Start () {
    22.         _agent = transform.GetComponent<NavMeshAgent> ();
    23.         _body = transform.GetComponent<Rigidbody> ();
    24.  
    25.         _controlEnum = ControlEnum.Mouse;
    26.     }
    27.    
    28.     // Update is called once per frame
    29.     void Update () {
    30.         float moveAxis = Input.GetAxis ("Vertical");
    31.         float turnAxis = Input.GetAxis ("Horizontal");
    32.  
    33.         if (moveAxis != 0f || turnAxis != 0f) {
    34.             _controlEnum = ControlEnum.Keyboard;
    35.  
    36.             _agent.updatePosition = false;
    37.             _agent.updateRotation = false;
    38.  
    39.             if (moveAxis != 0)
    40.                 _body.AddForce (transform.forward * moveAxis * 100f, ForceMode.Force);
    41.  
    42.             if (turnAxis != 0)
    43.                 transform.Rotate (0, turnAxis * 360f * Time.deltaTime, 0);
    44.  
    45.         } else if (Input.GetMouseButtonDown (0)) {
    46.             _controlEnum = ControlEnum.Mouse;
    47.  
    48.             _agent.updatePosition = true;
    49.             _agent.updateRotation = true;
    50.  
    51.             RaycastHit hit;
    52.             if (Physics.Raycast (RayCamera.ScreenPointToRay (Input.mousePosition), out hit, 5000)) {
    53.                 _targetPosition = hit.point;
    54.                 _body.velocity = new Vector3 (0, 0, 0); /* experimental - i think this stops the cube from spinning .. */
    55.                 _agent.ResetPath ();
    56.                 _agent.SetDestination (_targetPosition);
    57.                 _agent.stoppingDistance = 1.5f;
    58.                 Invoke ("CheckAgentDestination", 0.5f);
    59.  
    60.             }
    61.         }
    62.  
    63.         if (_controlEnum == ControlEnum.Keyboard) {
    64.             _agent.nextPosition = transform.position;
    65.         }
    66.     }
    67.  
    68.  
    69.     void OnCollisionEnter(Collision collission)
    70.     {
    71.         if (_controlEnum == ControlEnum.Mouse) {
    72.             _body.velocity = new Vector3 (0, 0, 0);
    73.             _body.angularVelocity = new Vector3 (0, 0, 0);
    74.             _agent.SetDestination (_targetPosition);
    75.         }
    76.     }
    77.  
    78.     void CheckAgentDestination()
    79.     {
    80.         if (_agent.remainingDistance > 0f && _agent.remainingDistance < 1.5f) {
    81.             _agent.ResetPath ();
    82.             _agent.isStopped = true;
    83.             _body.velocity = new Vector3 (0, 0, 0);
    84.             _body.angularVelocity = new Vector3 (0, 0, 0);
    85.  
    86.         }
    87.         else
    88.             Invoke ("CheckAgentDestination", 0.5f);
    89.     }
    90. }
    91.  




     
  10. SM_AF

    SM_AF

    Joined:
    Aug 1, 2017
    Posts:
    23
    Just make sure the code is easy to read. This is a very simple script, and since it's a player controller, then I presume there will be only one instance of it running at all times? So don't overdo it with them optimizations :D

    Just make sure nothing is called excessively due to a bug in your code. For example the SetDestination method should not be called each frame, but only when the destination is actually changed. So just make sure it's not being called all the time.

    On other note, when using AddForce over time (ForceMode.Force, ForceMode.Accelerate) for example, use FixedUpdate instead of an Update function. Also, you might want to use rigidbody.AddTorque instead of transform.Rotate.

    For a nicer code, you can also use Vector3.zero instead of new Vector3(0,0,0).
     
    GainfulSage likes this.
  11. ian-coetzer

    ian-coetzer

    Joined:
    Oct 11, 2015
    Posts:
    25
    Thank you for the awesome tips.
     
  12. Herschelx

    Herschelx

    Joined:
    Jun 8, 2018
    Posts:
    3
    Since this is the only post I could found that has the same problem as mine, but the solution didn't really suit my problem.
    I've found other more-simpler solution and hope it could help other poor souls hours of bug fixing :D
    Here goes:

    private IEnumerator EnableNavMeshAgain()
    {
    gameObject.GetComponent<NavMeshObstacle>().enabled = false;
    yield return null; <- This one line is the solution btw
    gameObject.GetComponent<NavMeshAgent>().enabled = true;
    }

    Basically object change position because on that frame when NavMeshAgent is enabled, there's no navmesh on the ground due to NavMeshObstacle. So wait at least 1 frame in coroutine before enabling it will fix this change position issue.
     
  13. CottonKy

    CottonKy

    Joined:
    Apr 28, 2019
    Posts:
    5
  14. adantedae

    adantedae

    Joined:
    Apr 10, 2020
    Posts:
    1
    One more solution for people looking at this problem where the agent needs to stop and start very frequently, simply make one contingent on the other:

    Code (CSharp):
    1.             if(GetComponent<NavMeshObstacle>().enabled == false)
    2.             {
    3.                 GetComponent<NavMeshAgent>().enabled = true;
    4.             }
    5.             else
    6.             {
    7.                 GetComponent<NavMeshObstacle>().enabled = false;
    8.             }
     
  15. archangel007

    archangel007

    Joined:
    Oct 9, 2020
    Posts:
    5
    Thank you for your kind notes. It help indeed. @Herschelx
     
  16. turbolek

    turbolek

    Joined:
    Dec 30, 2016
    Posts:
    11
    I found a way to achieve this without adding one frame delay:


    Code (CSharp):
    1.     public void SetMovementTarget(Vector3 targetPosition)
    2.     {
    3.         _navMeshObstacle.enabled = false;
    4.        
    5.         _navMeshSurface.BuildNavMesh();
    6.        
    7.         _navMeshAgent.enabled = true;
    8.         _navMeshAgent.destination = targetPosition;
    9.     }