Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

[Fixed] How to align Gameobject to ground with Unity's Navmesh

Discussion in 'Navigation' started by McMurterStudios, May 17, 2019.

  1. McMurterStudios

    McMurterStudios

    Joined:
    May 12, 2019
    Posts:
    19
    Hello everyone,

    I am developing an RTS game, and so the Navmesh seemed to fit best for that style of game, however, I have encountered a problem when it comes to aligning my "tank" to the ground while still facing the direction it was traveling in. I have looked here: https://answers.unity.com/questions/168097/orient-vehicle-to-ground-normal.html

    and nothing on there helps with my problem. This is the code I am working off of now for simplistic reasons.
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class Tilt : MonoBehaviour
    6. {
    7.     public LayerMask mask;
    8.     void Update()
    9.     {
    10.         RaycastHit hit;
    11.         if (Physics.Raycast(transform.position, -Vector3.up, out hit, 0.5f, mask))
    12.         {
    13.             transform.up = hit.normal;
    14.         }
    15.     }
    16. }
    The problem is that my "tank" is always facing forward but this code does work when it comes to aligning with an angle using unity's Navmesh.

    This script is attached to the "tank", which has a kinematic rigidbody attached and a box collider set to isTrigger.
     
  2. Yandalf

    Yandalf

    Joined:
    Feb 11, 2014
    Posts:
    491
    It could be that the raycast hits the tank's own collider, hence causing it to align its up-axis with itself.
    This is a very common problem, and the solution is to simply offset your raycast origin to ensure it is not within your object's collider.
     
  3. McMurterStudios

    McMurterStudios

    Joined:
    May 12, 2019
    Posts:
    19
    Thanks, I will look into this solution and update the thread :)
     
  4. McMurterStudios

    McMurterStudios

    Joined:
    May 12, 2019
    Posts:
    19
    Ok, I've created an offset for the ray so that the ray doesn't touch its own gameObjects collider, but now it starts to freak out when it goes on an angle, right now it turns on a plane, the gameObject must be perpendicular to the ground in order for it to rotate on the z-axis, but as soon as it gets an angle, it will not turn, or align to the ground, and it faces directly ahead, as before. Here is the updated script. The referencePos is just an empty child of the gameObject for the ray to start from.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class Tilt : MonoBehaviour
    6. {
    7.     public LayerMask mask;
    8.     public Transform referencePos;
    9.     void Update()
    10.     {
    11.         RaycastHit hit;
    12.         if (Physics.Raycast(referencePos.position, Vector3.down, out hit))
    13.         {
    14.             transform.up = hit.normal;
    15.         }
    16.     }
    17. }
     
  5. Yandalf

    Yandalf

    Joined:
    Feb 11, 2014
    Posts:
    491
    I'm not seeing any obvious problems here. Perhaps instead of casting a ray along Vector3.down, cast from transform.down?
     
  6. McMurterStudios

    McMurterStudios

    Joined:
    May 12, 2019
    Posts:
    19
    since transform.down doesn't exist, I replaced it with -transform.up, which is essentially the same of course. I can show you pictures of what's happening with the current script later today to give a better understanding of what's happening.
     
  7. McMurterStudios

    McMurterStudios

    Joined:
    May 12, 2019
    Posts:
    19
    Alright, here are four pictures to better demonstrate what this code below is doing,


    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class Tilt : MonoBehaviour
    6. {
    7.     public LayerMask mask;
    8.     public Transform referencePos;
    9.     void Update()
    10.     {
    11.         RaycastHit hit;
    12.         if (Physics.Raycast(referencePos.position, -transform.up, out hit))
    13.         {
    14.             transform.up = hit.normal;
    15.         }
    16.     }
    17. }
    This first picture has a purple dot, which is the reference point for the ray Tank.PNG

    This next picture shows no odd behavior on the flat plane, it is rotating as intended.

    Rotate_On_Flat_Plane.PNG

    The next image shows the behaviour of the gameObject, the arrows on the slope describes it's random jerking motion as it tries to turn on the slope (Turns towards the "point", y=0, turns, y=0...). This effect happens: On the slope, or during the transition between the plane and the slope. It is also not aligned.

    Angled_Slope.PNG

    The last image which will be a thumbnail is basically showing how the gameObject returns back to normal behavior when it goes back onto the slope.
    Back_To_Normal_Behaviour.PNG


    I hope the way I've described this makes any sense. I do appreciate your attention to this problem.
     
  8. Yandalf

    Yandalf

    Joined:
    Feb 11, 2014
    Posts:
    491
    Well, that screenshot showed it all. Your rigidbody has Freeze Rotation X turned on. That way your tank will never be able to align properly...
     
  9. McMurterStudios

    McMurterStudios

    Joined:
    May 12, 2019
    Posts:
    19
    I turned it off. Same results. I believe that this issue has something to do with the update function. By setting the z-axis to 0 if the ray detects the ground. The update function ensures that it won't rotate the z axis freely if the ground is always beneath it. I'm thinking of a messy solution by using a coroutine but there's got to be a better way. I'm pretty sure that is the issue.
     
  10. McMurterStudios

    McMurterStudios

    Joined:
    May 12, 2019
    Posts:
    19
    I have found a fix, it's kind of a weird one.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class Tilt : MonoBehaviour
    6. {
    7.     public LayerMask mask;
    8.     public Transform referencePos;
    9.     [HideInInspector]
    10.     public PlayerControl plCtrl;
    11.     [HideInInspector]
    12.     public Vector3 hitPoint;
    13.     void Update()
    14.     {
    15.         RaycastHit hit;
    16.         if (Physics.Raycast(referencePos.position, Vector3.down, out hit))
    17.         {
    18.            
    19.             transform.up = hit.normal;
    20.             transform.LookAt(hitPoint);
    21.  
    22.         }
    23.     }
    24. }
    It's kind of weird, but in short, if it the ray hits the terrain, it's going to look at the angle of the destination or (hit.point(derived from another script)) while still being able to align to the ground. If anyone is wondering, the "PlayerControl" script is found when the ray from the camera(which contains the PlayerControl script) hits this gameObject, finds the "Tilt" script where the ray hit, and then plCtrl is null then equal to GetComponent<PlayerControl>();

    Thank you Yandalf for the time you've put in to help me find the solution.
     
  11. McMurterStudios

    McMurterStudios

    Joined:
    May 12, 2019
    Posts:
    19
    I have vastly improved my problem, it is now fixed, and optimized and smooth and nice to watch.
    Part of my issue was that the actual gameObject was not imported correctly from blender in terms of rotation, anyone who has a problem with rotation can be referred to here:
    .

    Secondly, I had very poor skills in attempting my own solution, but here it is. This script will work on a navmesh of any kind of surface, and always rotate correctly and smoothly. It works SO well! I thought I would share this for someone who has had a similar problem.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class Tilt : MonoBehaviour
    6. {
    7.     public LayerMask mask;
    8.  
    9.     void Update()
    10.     {
    11.         RaycastHit hit;
    12.         if (Physics.Raycast(transform.position, -transform.up, out hit))
    13.         {
    14.             var slopeRotation = Quaternion.FromToRotation(transform.up, hit.normal);
    15.             transform.rotation = Quaternion.Slerp(transform.rotation, slopeRotation * transform.rotation, 10 * Time.deltaTime);
    16.         }
    17.     }
    18. }
     
  12. jackjoemcpherson

    jackjoemcpherson

    Joined:
    Mar 11, 2019
    Posts:
    1
    @McMurderousStudios THANK YOU, SO DAMN MUCH. Been at this all day and you solved it so fast!
     
  13. ReedSprungJr

    ReedSprungJr

    Joined:
    May 10, 2014
    Posts:
    4

    That's pretty sleek and useful. It does have some issues with stairs, though.
     
  14. riclic

    riclic

    Joined:
    Aug 23, 2020
    Posts:
    9
     
  15. riclic

    riclic

    Joined:
    Aug 23, 2020
    Posts:
    9
    Thank you! This works perfectly! I adjusted value "10" to 0.5f to stop the jittering on the slopes. I watch so many videos on how to do this, some 43mins long and in just a few lines of code it's solved. Blessings
     
  16. VagelisGardikis

    VagelisGardikis

    Joined:
    Apr 24, 2018
    Posts:
    20
    BRAVO. This works perfectly. I have the navmesh agent component on a parent and i put this script on the model child and it is 100% what i wanted. Thank you
     
  17. Zynek

    Zynek

    Joined:
    Jun 28, 2015
    Posts:
    4
    Hey guyz, I was hoping to do this without a costly raycast. It took me a while but i got it and it can even interpolate nicely. You can improve it, if you sample more positions. You're welcome ;)
    Code (CSharp):
    1.  
    2.     private void AlignWithNavMesh()
    3.     {
    4.         if (NavMesh.SamplePosition(transform.position + (transform.forward * _agent.radius), out NavMeshHit navMeshHit, _agent.radius * 2f, NavMesh.AllAreas))
    5.         {
    6.             var hitPos = navMeshHit.position;
    7.             hitPos.y += _agent.baseOffset;
    8.          
    9.             var lookVector = hitPos - transform.position;
    10.             if (lookVector.sqrMagnitude > float.Epsilon)
    11.             {
    12.                 transform.rotation = Quaternion.LookRotation(lookVector, Vector3.up);
    13.             }
    14.         }
    15.     }