Navigation with the Nav Mesh Part 2: Obstacles and Targets

In Part 1, we created a Nav Mesh and a Nav Mesh Agent to navigate on that mesh. We included a simple script to make the agent move from their starting position to a pre-determined target position, and they moved in a straight line.

In this part, we will add some obstacles to the terrain to force our agent to navigate around them, and then we will add a way to change the agent’s target destination during runtime.

Make sure your project includes everything from Part 1 before beginning Part 2 (you can download the Part 1 project from the link at the bottom of the Part 1 tutorial, linked above).

Add an Obstacle

Add a cube GameObject to the scene and shape it to fill a good chunk of the ground between the agent character and the target, something like this:

clip_image001

Make sure the agent can not move in a straight line to the target position.

Now try running the scene and see what happens…the agent will walk right through the obstacle! This is because the Nav Mesh still considers the ground beneath the obstacle to be walkable, and it doesn’t know that the cube is supposed to be an obstacle.

Edit the Obstacle

Let’s make that obstacle an actual obstacle:

  1. Select the obstacle in the Hierarchy.
  2. Add a Nav Mesh Obstacle component to the obstacle’s GameObject.

Run the scene, and watch the player navigate around the obstacle!

You’ll notice that the agent slows down when near the obstacle. This is because the agent is trying to follow its direct path to the target (the ground beneath the obstacle is still marked as walkable), but finds something in its way.

Obstacles have two different ways of influencing navigation. The obstacle we have set up so far is one type. This is generally used for moving obstacles, as our obstacle does not affect the navigation mesh itself; it just gets in the agent’s way and affect’s their navigation. The other way an obstacle can work is by ‘carving’ a space out of the Nav Mesh, effectively creating an area that is not walkable within the Nav Mesh. This is what is generally used for stationary obstacles (e.g. a wall).

Unity’s Nav Mesh Obstacle component has an option called ‘Carve’, which carves a hole in the Nav Mesh around the obstacle. This is what we want for our current obstacle, as it doesn’t move.

Set the Obstacle to Carve

Select the Obstacle GameObject, then set its Nav Mesh Obstacle Carve property to on:

clip_image002

Now open both the Scene window and the Navigation window to view the Nav Mesh. If you look closely (zoom in if you need to), you’ll see that the Nav Mesh has a hole in it around the obstacle.

Run the scene and see how the agent now finds a direct path to the target avoiding the obstacle.

In general you will want to enable carving for stationary objects, but leave it disabled for moving objects.

Now add some more obstacles to your scene to create a very simple maze and make the agent work a bit harder to reach the target. Aim for something like this:

clip_image003

Change the Target

In Part 1, we set the agent’s target as an object in the scene. In a game, you would probably want the target to change regularly. If the agent is the player, the target might be where the player is walking to; if the agent is an enemy, the target might be the player (who moves around all the time).

Move to Mouse Click

We will detect where the player clicks the mouse, then tell the agent to move to that point.

Open up the Agent script you created in Part 1, and replace all the code with the following:

using UnityEngine;
using UnityEngine.AI;
public class Agent : MonoBehaviour {

    NavMeshAgent agent;

    void Start()
    {
        // get a reference to the player's Nav Mesh Agent component
        agent = GetComponent<NavMeshAgent>();
    }

    private void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            if (Physics.Raycast(ray, out hit))
            {
                agent.SetDestination(hit.point);
            }  
        }
    }
}

In the new code, I’ve removed all reference to the target, and replaced it with some code to detect a mouse click and then tell the agent to move to where the mouse was clicked. This code is relatively self explanatory, and since it isn’t directly navigation-related, I won’t explain it in detail. Again, the only navigation-specific code is the line that calls SetDestination() on the agent.

Lastly, remove the target object from the scene entirely – we don’t need it any more.

Now run the scene. The agent will be still at first, but once you click somewhere on the screen, the agent will move towards the mouse click. Try clicking in lots of different positions to see the agent change course.

Moving Obstacles

Now that we can move the target, let’s try moving an obstacle. We will create a simple script to move a wall back and forth.

Modify one of the existing obstacles in the scene (or create a new one), and make it big enough to act as a door to block off a part of the Nav Mesh. We will create a script that will move back and forth, and will block the player’s navigation when closed. Rename this obstacle to ‘Door’.

Again, the code is not navigation –specific, so I won’t explain it in any detail.

Create a new C# script called MovingObstacle, and replace everything inside with the following code:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MovingObstacle : MonoBehaviour {

    [SerializeField] Vector3 openOffset = new Vector3(-5, 0, 0);
    Vector3 openPosition;
    Vector3 closedPosition;
    Vector3 targetPosition;
    [SerializeField] float speed = 2;
    [SerializeField] float delay = 2f;
    bool waiting = false;
    float waitElapsed = 0;

    void Start () {
        closedPosition = transform.position;
        openPosition = closedPosition + openOffset;
        targetPosition = openPosition;
    }

    void Update () {
        if(waiting)
        {
            waitElapsed += Time.deltaTime;
            if(waitElapsed >= delay)
            {
                waiting = false;
            }
            else
            {
                return;
            }
        }

        transform.position = Vector3.MoveTowards(transform.position, targetPosition, speed * Time.deltaTime);
        if(Vector3.Distance(transform.position, targetPosition) < 0.1f)
        {
            if (targetPosition == openPosition) targetPosition = closedPosition; else targetPosition = openPosition;
            waiting = true;
            waitElapsed = 0;
        }
    }
}

Don’t forget to add the new script to your Door GameObject. Also make the following changes to the door obstacle:

  1. Untick the Carve option on the moving obstacle (you generally don’t want moving objects to carve the Nav Mesh).
  2. Tweak the settings in the Moving Obstacle script component of the moving obstacle to get the behaviour you want. Here’s what mine looks like:

clip_image004

My moving obstacle moves 5 units to the left, so it has an open offset of [-5,0,0] (i.e. when it is open, it moves 5 units to the left). I have given it a speed of 2 and a delay of 2 (it waits for 2 seconds before opening/closing again). Experiment with position and settings to suit your own project.

Run the scene. When the door is closed, try making the agent move to an unreachable spot behind the door – the door should prevent this.

Note how the agent waits at the door and then moves through it when it opens. This is because the door is not carving the Nav Mesh, so the agent knows that the obstacle is potentially temporary.

Conclusion

Now we have an agent that can move according to a mouse click, and also is blocked by obstacles on the Nav Mesh. All of this works with very little code, and it is also very efficient. If you implemented the same behaviour with your own code, you would have to do quite a bit of work, and it probably wouldn’t work as well (especially if you scale it up to work with many characters simultaneously.

Download

Here is a link to download the tutorial project as it should be at the end of this part:

Navigation with the Nav Mesh Part 1: Setup and Basic Navigation

In this series of tutorials, we’ll go through setting up Unity’s built-in Nav Mesh, which enables pathfinding and navigation without the need for complex code.

Before you Begin

For Intermediate Unity Developers

This tutorial series is for intermediate Unity users. I won’t go into detail on Unity basics, and assume you know your way around Unity’s interface and core features. Please refer to the Pong tutorial for beginners if you are not yet familiar enough with Unity to follow this tutorial.

Unity Version

This project was created with Unity version 2017.3.1f1, which is the latest version at the time of writing. You should be OK with any Unity version that is not too much older or newer.

Download

A download link is at the bottom of this tutorial. It contains a project as it should be if all the instructions in this part of the tutorial are followed.

Navigation Basics

Unity comes with a great navigation system that can be set up quickly and easily, and gives your characters the ability to navigate a complex environment by pathfinding and avoiding obstacles.

Nav Mesh

Unity’s navigation solution is the Nav Mesh. The Nav Mesh is a map of the areas in your game where a character can walk. When you tell your character to walk to a position on the Nav Mesh, they will automatically find a path to that position (if possible) and move there. If the character can’t reach the target position, they will get as close as possible.

Nav Agent

For a character to use a Nav Mesh, they must have a Nav Mesh Agent component on their GameObject. This agent contains the capabilities needed for navigation on the Nav Mesh, and you call methods on this object to make the character move, and use the various settings to specify the precise behaviour you want.

Create a Basic Navigating Character

To start with, we will create a simple scene with a navigating character who moves from their starting position to a target position.

The Scene

If you want to skip the basic non-AI related stuff, download the starter project, which includes a pre-built scene with the basics already done for you so you can get straight into the good stuff.

The base project contains a single scene ‘MainScene’, with some ground, an Agent (who we will set up to navigate the world), and a target GameObject which we will use as the place the player will navigate towards.

Create the Base Project and Scene

If you would rather set up the base project yourself, here are the steps required (skip this part if you downloaded the base project, and simply open that project then continue to Add a Nav Mesh).

  1. Start a new 3D Unity project.
  2. Create a scene called ‘MainScene’.
  3. Add a large Plane object to act as the ground
  4. Add a small sphere or cube called ‘Agent’, and make sure it is above the ground. Place the Agent near one of the corners.
  5. Create another GameObject called ‘Target’, and give it any 3D shape, such as a cylinder or a cube, and place it far from the Agent object (we will be making the Agent navigate towards the Target).
  6. Add materials to the objects to separate the objects visually, and to make your scene more interesting.
  7. Setup the camera so that you can see the whole of the floor and have a good view of the Agent and Target objects.

Here is what my base scene looks like:

image

Add a Nav Mesh

A Nav Mesh is used differently to the typical Unity components. Instead of adding it to objects, you add objects to it. For this reason, there is a Navigation window that you must use to setup your Nav Mesh.

If you don’t already have the Navigation window visible, go to the Unity menu and select Window > Navigation:

image

This window has four tabs that present different options and customisations; these are:

  • Agents – customise the behaviour of AI characters (agents) using the mesh.
  • Areas – customise the different types of terrain (e.g. terrain that is slower to walk on or terrain that can’t be walked on).
  • Bake – this is where you apply all your settings and create the Nav Mesh.
  • Object – where you select which objects are included in your mesh, and some of their properties.

You will also see that there is a Scene Filter option. This allows you to hide objects in the scene Hierarchy while working on navigation. For example, if you choose the Mesh Renderers option, everything without a Mesh Renderer will be hidden in the Hierarchy, making it easier to find the objects you want to use for navigation. This will come in handy for complex scenes with lots of objects, but for this tutorial we don’t need to worry ourselves about this.

We won’t go into a lot of detail right now. For now, let’s just get something working.

In the Navigation window:

  1. Select the Object tab.
  2. Select the ground object in the Hierarchy to make it the active object.
  3. Check that the ground object is now selected in the Navigation window.
  4. Set the settings as below:
    1. Tick Navigation Static.
    2. Select Walkable in the Navigation Area drop-down box.
    3. Ignore Generate OffMesh Links for now (we’ll look at it later).

clip_image001

You’ve now told the Nav Mesh that you want the ground to be walkable, and that it is ‘navigation static’ (i.e. the Nav Mesh will ‘see’ it when determining the walkable areas). This is the most basic Nav Mesh setup you need.

Bake It

Although we’ve added the ground to the Nav Mesh and made it walkable and navigation static, we have not actually created the Nav Mesh. We need to ‘bake’ it. Baking is a process of doing something that is too complex or time-consuming to do at runtime during the development process. There is no need to create a navigation mesh during runtime, since the terrain doesn’t change much in a game (and small changes don’t require a complete rebuilding of the mesh). The same is done for complex lighting in many games.

  1. Select the Bake tab in the Navigation window.
  2. Click the Bake button.
  3. If you do not already have the Scene window open, open it to see your Nav Mesh.

In the Scene window, the Nav Mesh is presented as a blue overlay on the ground, which represents where Nav Mesh Agents are able to walk:

clip_image002

In our current project, it will just be a simple blue square, but this will change as we later add some complexity.

Add an Agent

A Nav Mesh is pointless without someone to walk around it. We will now turn the Agent object into a ‘Nav Mesh Agent’ who can navigate the Nav Mesh.

  1. Select the Agent object in the Hierarchy to make it the active object.
  2. Add a Nav Mesh Agent component to the object in the Inspector window.
  3. Set the Speed property to any number (between 5 and 10 would be ideal).

You’ll notice a lot of settings on the Nav Mesh Agent component. Some of these are quite obvious (e.g. Speed), and some are not quite so obvious. For now we’ll ignore these settings.

image

Script

We need some code to make the player navigate, but it’s probably not as much code as you think. The Nav Mesh Agent takes care of movement and navigation, and we only need to tell the agent where to walk to.

To keep things simple for now, we’ll set a static target for the player to move towards (the Target GameObject in our scene).

Now, create a new C# script called ‘Agent’. I won’t go into detail about the code, as most of it doesn’t directly have anything to do with navigation (and it’s pretty basic Unity code). The only line of code that is navigation specific is:

agent.SetDestination(target.position);

That line tells the agent where it should try to navigate to, and will trigger the agent to start moving if they are not already at the target.

Here is the full code for the Agent script:

using UnityEngine;
using UnityEngine.AI;
public class Agent : MonoBehaviour {
[SerializeField] Transform target;
NavMeshAgent agent;

void Start()
{
    // get a reference to the player's Nav Mesh Agent component
    agent = GetComponent<NavMeshAgent>();
    // set the agent's destination
    agent.SetDestination(target.position);
    }
}
  1. Save the script.
  2. Attach the script to the Agent GameObject.
  3. Drag the Target GameObject into the Agent’s Target field in the Inspector:

clip_image003

Yes, that’s all you need for now to get the player navigating towards the target!

Run the Scene

Run the scene and watch the player automatically move towards the target. You can try experimenting with some of the player’s settings, though some of them won’t have any effect on such a basic scene, as the navigation is going to always be in a straight line.

It’s perhaps not too impressive to see the character move in a straight line from start to finish, so in the next part, we’ll add some obstacles and see the pathfinding in action.

Download

Here is the download for the project as it should be at the end of this part of the tutorial. Download it if you get stuck or want to compare your project to mine.

TMI! Stop Making Everything Public!

I see a lot of great tutorials that make a big mistake. They advocate making lots of public fields/variables in Unity scripts. Even some of the best tutorials do this, and it’s BAD! It’s no surprise then, that many beginners make all of their variables public.

Public fields have their place (or they wouldn’t be available), so using public isn’t always wrong. It is just that many Unity developers use public incorrectly, leading to poor programming habits that can make code hard to work with and debug.

tl;dr

Basically, unless your field needs to be accessed from other scripts, keep it private.

Much of the time in Unity tutorials, a field is made public so that its value can be seen and edited in the Inspector window. You don’t need to make a field public for this!

If you want your field to be accessible in the Unity Inspector, you can do the following:


// myField is private, but can be seen and edited in the Inspector

[SerializeField] float myField;

[SerializeField] makes the field appear in the Inspector, where you can view and change it. But it is still private.

You also have the option of using a ‘setter/getter’ if you want other scripts to be able to read a field’s value while only letting the field’s own script modify it:


// myField is publicly readable, but can only be modified in this script

public float myField { get; private set; }

 

What is Public in C#?

When a field (or a method for that matter) is public, it can be accessed from other scripts. Most fields and methods should be private (only accessible to its own parent script), as this makes code more robust, manageable, and modular. When fields and methods are being called from all over the place, it’s hard to keep track of what each script is doing; it can become a nightmare to debug.

Think about it…if a field is public and you decide to change how it behaves, you must also check that any other script accessing it is reworked to fit the change. If you find that your field value is being set incorrectly or a public method is being called at the wrong time, you will need to figure out exactly where the incorrect code is, which gets harder the more complex your project is.

Using public fields and methods can make things easier because you don’t need to think too hard about how to call methods and access variables. But don’t be lazy! It’s really not hard to avoid public stuff in your code, it just needs a bit of experience and practice.

The rule of thumb to always keep in mind is to restrict access to everything as much as possible.

If your code is well structured, public stuff isn’t going to be needed as much. If you find yourself needing lots of public fields or methods, it’s quite likely your code is poorly structured.

Keeping Privacy

Here’s what I do:

  • make every field and method private (this is easy, as it’s the default).
  • Use [SerializeField] by default for anything that needs to be in the Inspector.
  • Only change things to public when you need to access them from other scripts and after thinking about alternatives that don’t require public access (this becomes easier with experience).
  • Regarding the above point, consider a ‘public get, private set’ field if you only need to read the value from another script without modifying it.

Conclusion

Avoid public fields (variables) and methods/functions unless you have a real need for them to be public. Use [SerializeField] to enabled private fields in the Inspector without making them public.

If you want to learn more about the reasons for keeping code private and modular, I recommend picking up a book on C# programming (or even programming in general). It’s one of those topics that doesn’t seem too important to beginners (hey, making a field or method public doesn’t change the way it works, right?), but the more experience you gain, the more important it becomes to make your code higher in quality.