NavMesh and Mecanim

I talk with a lot people about Mecanim, and I find that one of the most common difficulties with the tool is getting it to work with other systems within Unity. It’s not that Mecanim can’t link up with those systems, but more that it isn’t always obvious how to put the two together. One of the most frequent problems I run across tends to be using the NavMesh and Mecanim together.

intro

The goal in combining the two systems is to be able to properly animate character motion along the AI paths created by the NavMeshAgent component. By default, the NavMeshAgent will move a GameObject by itself without input from other sources, but in most cases a character’s animations don’t sync up with that movement properly. This is due to animations that play faster or slower than the actual movement, and it makes your characters look like they’re skating across the floor. We don’t want that.

What we want is to move our character along our paths at the exact speed of our animated movement, and in this post I’m going to show you a really simple way to achieve that kind of functionality. If you haven’t already read my tutorial on Scripting Root Motion, I recommend reading that before continuing since I’ll be working with those concepts in this post. As usual I have a downloadable package for you:

Unity_Logo_small Mec_NavMesh

Character/Scene Setup

First things first, let’s take a look at how our character and scene is set up (NavMesh scene if you downloaded the package). All we need is the Animator for our animations, a script to act as our character controller (CharNav.cs), and the NavMeshAgent component for working with pathfinding on a NavMesh.

Animator Component

The Animator component is really easy to set up for this example. Once it has been added to the character, we need to create a new Animator Controller to hold our animations. In my controller I just have an Idle and a WalkForward animation since I nothing else is really needed to test the pathfinding/Mecanim functionality here.

states

A MoveState int parameter must be added to link the two states properly. If MoveState is equal to 1 we move into the WalkForward animation, and if it is 0 we go back to Idle. Make sure the animator controller is applied to the Animator component on your character, and it’s ready to go.

NavMeshAgent Component

Add a NavMeshAgent component to the character. We’re just going to use the default settings for now, so this component is already set up. For this post I’m going to assume you have basic knowledge of how the NavMeshAgent works, but just in case here’s a quick primer.

CharNav Script

This script simultaneously functions as a way to set destinations on the NavMeshAgent and play animations based on movement states. It’s super useful, and you can do a lot to extend it for your own needs. I’m going to work through this script as we go along, so for now let’s just add all our public variables and connect them in the Inspector.


//link to Animator component
public Animator animController;

//used to set anim controller parameters
public enum MoveState {Idle, Walking}
public MoveState moveState;

//link to NavMeshAgent component
public NavMeshAgent navAgent;

Go ahead and drag UnityGuy1‘s Animator component into the ‘Anim Controller’ slot and the NavMeshAgent component into the ‘Nav Agent’ slot.

links1

Scene and NavMesh

In my example scene I’ve set up a very basic navigation area made up of a plane to act as the floor and some cubes to act as obstacles. I set the plane and cubes to Navigation Static and baked in a simple NavMesh that works great for our purposes. Again, I’m assuming you know how to do this. If not, check out this short video.

Click Navigation

Now that we have all of the pieces prepared, we can set up some navigation for our character. My example uses a basic click-to-move formula, but there are plenty of other ways to set a destination. Here’s a short script I called ClickManager that sets the character’s destination target to a clicked point in the game world.


public class ClickToMove : MonoBehaviour {

    public CharNav charNav; //link to CharNav script on UnityGuy1

    void Update ()
    {
        //set character destination to point of mouse click
        if (Input.GetMouseButtonDown(0))
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit rayHit;

            if (Physics.Raycast(ray, out rayHit, 100f))
                charNav.navAgent.destination = rayHit.point;
        }
    }
}

Notice that it begins by declaring a public CharNav variable. This is our direct link to the UnityGuy1 character, and we have to make sure to fill the empty slot on our ClickManager with this script. Through the CharNav link, we get access to the NavMeshAgent component, and in this way we can set a new target destination with every click.

links2

If you play your scene and click anywhere in the movable area, the character will glide along to its destination.

Note: If you have the Navigation window open in your editor, the NavMesh will be visible in your Scene View. You can also see a movement path if you select your character.

Syncing with Animations

Our navigation works properly, and it’s time to get to the good stuff! Let’s play our animations! Go ahead and add the following to the Update() function of the CharNav script:


void Update ()
{
    //character walks if there is a navigation path set, idle all other times
    if (navAgent.hasPath)
        moveState = MoveState.Walking;
    else
        moveState = MoveState.Idle;

    //send move state info to animator controller
    animController.SetInteger("MoveState", (int)moveState);
}

This block of code checks to see if the NavMeshAgent has a path to a destination or not and acts accordingly. A path will only exist if the character is not at the destination, so if there is one we set the character’s moveState to Walking. At any other time, we assume the character is in Idle. After that, we just make sure to apply the moveState value to the MoveState parameter on the character’s Animator controller, and the character will animate during movement.

At this point, moving the character around will result in the correct path motion but an animation that doesn’t properly sync up. This is because we have two systems working independently, the NavMesh’s pathfinding and Mecanim’s animation. It’s time to reconcile these two and turn them into a functional combination of systems.

Back in the CharNav script you want to add an OnAnimatorMove() function:


void OnAnimatorMove()
{}

If you remember from the Scripting Root Motion tutorial, ‘any code in OnAnimatorMove() will execute after any animation data in a frame has been evaluated, but before the frame is complete.’ It is here that we will take control of the two systems. What we want to do is set the NavMeshAgent’s velocity to the velocity of the playing animation clip. This way the character’s animation directly drives the speed at which it moves along it’s path. We also rotate the character in the direction of motion. Here’s the code for that:


void OnAnimatorMove ()
{
    //only perform if walking
    if (moveState == MoveState.Walking)
    {
        //set the navAgent's velocity to the velocity of the animation clip currently playing
        navAgent.velocity = animController.deltaPosition / Time.deltaTime;

        //set the rotation in the direction of movement
        transform.rotation = Quaternion.LookRotation(navAgent.desiredVelocity);
    }
}

We make sure to only perform these actions when the character’s moveState is Walking, because we really don’t want to move or rotate the character during an Idle state. But that’s really it! If you play the scene and test it out, the character should have a very natural walk while moving along its path.

Neat Tricks

Avoidance

When we use this method to combine the NavMesh and Mecanim, we get to take full advantage of everything that comes with the NavMeshAgent component. This includes dynamic avoidance of other objects with NavMeshAgent components applied to them. Why is this good? Because you don’t want to hack in your own avoidance functionality (trust me).

In my example package I included a second scene called NavMesh_Avoidance to show off this functionality. I wrote a modified CharNav script to give the characters a destination on scene start, and when you play the scene they will walk right past each other. The built-in avoidance keeps them from running into each other, and it brings them right back to their paths after they pass each other. Pretty cool.

Easy Speed Control

You may have noticed that we completely avoided using any of the parameters on the NavMeshAgent in our implementation. They weren’t necessary for what we were doing earlier, but something I really like to do is take advantage of existing parameters as much as possible. Right now the ‘Speed’ parameter is useless, but I find it can be pretty useful in controlling the speed of my animations. If I modify the speed of my animations, I also modify how quickly the character moves along the path since we connected those velocities previously.

This single line of code in your Update() function can connect the speeds as well:


void Update ()
{
    //set animation speed based on navAgent 'Speed' var
    animController.speed = navAgent.speed;
...

Initially, you should set the ‘Speed’ to 1 so that your animations are playing at their normal speed. If you want to move faster or slower, it’s now easy to adjust on the NavMeshAgent component.

Easy Smooth Rotation

It’s also pretty easy to use the NavMeshAgent component to smooth out our character’s rotation along the path. We do this using the ‘Angular Speed’ variable and a few lines of code for rotation over time. This goes into the OnAnimatorMove() function in place of the rotation code we implemented prevously, like this:


void OnAnimatorMove ()
{
    //only perform if walking
    if (moveState == MoveState.Walking)
    {
        //set the navAgent's velocity to the velocity of the animation clip currently playing
        navAgent.velocity = animController.deltaPosition / Time.deltaTime;

        //smoothly rotate the character in the desired direction of motion
        Quaternion lookRotation = Quaternion.LookRotation(navAgent.desiredVelocity);
        transform.rotation = Quaternion.RotateTowards(transform.rotation, lookRotation, navAgent.angularSpeed * Time.deltaTime);
    }
}

Bam! Now you can use the Angular Speed parameter to adjust your character’s rotation speed along the path. I usually start around 300.


So that’s it! Congratulations Warriors, you now have enough knowledge to get you started using Mecanim with the pathfinding systems available with Unity’s NavMesh. Let me know what you do with it!


5 Comments on “NavMesh and Mecanim”

  1. skreider says:

    Where does the integer for moveState come from in “SetInteger(“MoveState”, (int)moveState)”? i’m trying to add some more states. I assumed it was just from the order the states were listed in when they were defined but I haven’t been able to get an int of 2 or higher into the animation controller.

  2. Adam Ormsby says:

    My MoveState enum is actually doing this implicitly:

    public enum MoveState
    {
    Idle = 0,
    Walking = 1
    }

    Unless otherwise specified, all enum values are numbered automatically starting at 0, and in this case the int values of Idle and Walking are 0 and 1 respectively. Since the ‘moveState’ variable is only being set to either Idle or Walking in Update(), it will always equal only 0 or 1.

    If you want to add more state to this enum, you just have to add to it, either with explicit numbers or without:

    public enum MoveState {Idle, Walking, YourNewState}

    OR

    public enum MoveState
    {
    Idle = 0,
    Walking = 1,
    YourNewState = 2
    }

    Then if you set the ‘moveState’ variable to YourNewState in Update() its value will be 2, and if you check the ‘MoveState’ int parameter in your Animator Controller it should also appear as 2. You just have to keep adding in your own states and setting the ‘moveState’ variable to those states.

    Until then, you’ll only get 0 or 1, because that’s all that’s set up right now. Does this make sense?

  3. mahad says:

    This works, great explanation!

  4. Mike says:

    great tutorial man!

  5. defiant child says:

    Can we replace the script with PlayMaker FSM’s instead and keep every other component the same?


Leave a comment