Navmesh Agent and Smooth Alignment with surface normals

Discussion in 'Unity Support' started by Legacy, May 14, 2012.

  1. Legacy

    Legacy

    New Member

    Joined:
    Oct 11, 2011
    Messages:
    651
    Hello,

    My question is regarding the use of the unity Navmesh agent component and handling rotation of an object to align with the surface it is on(IE you have a Bug AI Agent pathing around on a terrain and it moves up a slope but does not align with the slope so its face ends up under ground and its tail and hind legs end up floating off the terrain). Now i know how to accomplish this but the issue is that the navmesh agent componenet takes explicit control over the objects rotation, if i disable its control it stops steering in the correct direction that its moving. Is there anyway around this or am i just stuck with it until unity adds some sort of functionality? Thanks in advance for your input.
  2. Legacy

    Legacy

    New Member

    Joined:
    Oct 11, 2011
    Messages:
    651
    Anyone have any suggestions?
  3. Legacy

    Legacy

    New Member

    Joined:
    Oct 11, 2011
    Messages:
    651
    Does anyone have any suggestions?
  4. Legacy

    Legacy

    New Member

    Joined:
    Oct 11, 2011
    Messages:
    651
    Anyone at all? (offtopic sheesh this forum moves quick)
  5. Legacy

    Legacy

    New Member

    Joined:
    Oct 11, 2011
    Messages:
    651
    Bump. Anyone?
  6. SalvadorLimones

    SalvadorLimones

    New Member

    Joined:
    May 11, 2012
    Messages:
    7
    Just my 2 cents.

    Your agent mesh need not contain the navmesh agent.

    You can create a dummy invisible object and attach the navmesh agent to it. Then take your real actor/object/GO and make it a child of this navmeshagent (or constraint it to the dummy object that contains the navmeshagent in any manner you like using code.)

    Now, you can use localrotations to align the object with the normal of the topology as it is independent of the dummy.

    So lets say you have a soldier. Make a sphere and add navmeshagent component. Disable the meshrenderer of the sphere. Make this sphere the parent of the soldier. Now when the sphere moves, the soldier will move too. Control the localrotation of soldier as necessary using code. Use baseoffset if necessary to align the two.

    Havent tried it myself. let me know if it works.
    Last edited: May 22, 2012
  7. Legacy

    Legacy

    New Member

    Joined:
    Oct 11, 2011
    Messages:
    651
    Haha that sounds like it will work perfectly, no idea why i didnt think of just parenting the model and controlling just the models rotation... Probably because my brain is fried from work and working on so many different game systems at once haha. Thanks a ton :)
  8. Legacy

    Legacy

    New Member

    Joined:
    Oct 11, 2011
    Messages:
    651
    i did try this and i am able to align the model with the surface however i cannot get the character to look in the same direction as the steering target when the nav agent is walking, only the parent object obviously is doing that. Attempted to rotate to the surface normals and rotate towards its steering target but am having no luck, the model just faces one direction. Any ideas?
  9. Jakob@Unity

    Jakob@Unity

    Unity Technologies

    Joined:
    Dec 25, 2011
    Messages:
    48
    So you basically would like to rotate twice - once for the up-vector and once for the heading (move direction). Is that right ?

    In that case I suggest you script it on your NavMeshAgent.. something along the lines of :

    Code (csharp):
    1. // TerrainMover.cs
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. public class TerrainMover : MonoBehaviour {
    6.   public Transform target;
    7.   public Terrain terrain;
    8.   private NavMeshAgent agent;
    9.   private Quaternion lookRotation;
    10.  
    11.   void Start () {
    12.     agent = GetComponent<NavMeshAgent>();  //< cache NavMeshAgent component
    13.     agent.updateRotation = false;          //< let us control the rotation explicitly
    14.     lookRotation = transform.rotation;     //< set original rotation
    15.   }
    16.  
    17.   Vector3 GetTerrainNormal () {
    18.     Vector3 terrainLocalPos = transform.position - terrain.transform.position;
    19.     Vector2 normalizedPos = new Vector2(terrainLocalPos.x / terrain.terrainData.size.x,
    20.                                         terrainLocalPos.z / terrain.terrainData.size.y);
    21.     return terrain.terrainData.GetInterpolatedNormal(normalizedPos.x, normalizedPos.y);
    22.   }
    23.  
    24.   void Update () {
    25.     agent.destination = target.position; //< update agent destination
    26.     Vector3 normal = GetTerrainNormal();
    27.     Vector3 direction = agent.steeringTarget - transform.position;
    28.     direction.y = 0.0f;
    29.     if(direction.magnitude > 0.1f  normal.magnitude > 0.1f) {
    30.       lookRotation = qNorm * qLook;
    31.     }
    32.     // soften the orientation
    33.     transform.rotation = Quaternion.Slerp(transform.rotation, lookRotation, Time.deltaTime/0.2f);
    34.   }
    35. }
    36.  
    37.  
    Make sure to place on NavMeshAgent and assign public properties (terrain, target) - there's no null-ref handling !..

    Cheers..
    /Jakob
    Last edited: Jun 19, 2012
  10. Pat AfterMoon

    Pat AfterMoon

    Member

    Joined:
    Mar 28, 2009
    Messages:
    102
    Thank you for this excellent piece of code.

    After tested it on my project, I was forced to change terrain.terrainData.size.y to z to get the normal correctly.

  11. -JohnMore-

    -JohnMore-

    Member

    Joined:
    Jun 16, 2013
    Messages:
    24
    I know it is a little bit old but I just happened to have the same problem and reached a different solution. I dont use terrains so I had to look for something different. Another problem was that I did not want to use the normal of the geometry under my objects, I wanted a smooth transition between the current orientation and the target orientation. So in the end I decided to use the LookAt function. This way, no matter what geometry I am moving on it just works.

    1-. In my Object script I put these variables:
    Code (csharp):
    1.         public bool OverrideRotation = false;
    2.         public float OrientationSpeed = 10f;
    3.         Vector3 lastPosition;
    4.         Vector3 position;
    5.         Vector3 direction;
    6.  
    2- Then, in the awake method:

    Code (csharp):
    1.                 if (OverrideRotation) {
    2.                         navAgent.updateRotation = false;
    3.                         lastPosition = myTransform.position;
    4.                 }
    5.  
    3- And last, if OverrideRotation is set to true, I start the coroutine that manages the orientation:

    Code (csharp):
    1.         IEnumerator updateRotation ()
    2.         {
    3.                 while (true) {
    4.                         Vector3 position = myTransform.position;
    5.                         direction = (position - lastPosition).normalized;
    6.  
    7.                         myTransform.LookAt (position + (Vector3.Lerp (myTransform.forward, direction, Time.deltaTime * OrientationSpeed)), Vector3.up);
    8.  
    9.                         lastPosition = position;
    10.  
    11.                         yield return Yields.Frame;
    12.                 }
    13.         }
    14.