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:

Leave a Reply

Your email address will not be published.