Monthly Archives: March 2018

Navigation with the Nav Mesh Part 5: Areas and Paths

In this final part, we will improve our project by adding areas to the Nav Mesh with some different rules, and also use the CalculatePath() method to make the enemy check their path before deciding to follow it or not.

Area Types

We will now create a new terrain so that we can treat areas of the scene differently.

We will give the platform we created earlier its own area type distinct from the rest of the Nav Mesh.

  1. Open the Navigation window.
  2. Select the Areas tab.
  3. Create a new area type called ‘Player Only’, as in the image below:

clip_image001

  1. Select the Object tab.
  2. Select the Platform GameObject in the Hierarchy.
  3. Change the Navigation Area (in the Navigation window) to Player Only.
  4. Select the Bake tab in the Navigation window.
  5. Click Bake to re-bake the Nav Mesh.

You have now added a different area type and assigned it to a piece of the Nav Mesh. Your Nav Mesh should now look like the image below, with a different colour on the platform area to show that it is a different type of area:

clip_image002

That’s a new area type created! Now let’s make it so our Enemy object cannot walk on the Player Only area:

  1. Select the Enemy GameObject in the Hierarchy.
  2. Deselect Player Only from the Area Mask drop-down list (the area types with a tick next to them are the ones this agent can walk on).

clip_image003

Now play your scene, and get your player onto the platform. If everything is working correctly, the enemy won’t be able to jump up to catch the player.

You can use this same technique for many different things. For example, you could make a passageway using a different area type, and restrict certain characters from using that passageway.

Calculate Path

The CalculatePath() method of the Nav Mesh Agent allows us to calculate a path through the Nav Mesh without actually moving along that path. This can be useful for a number of things, such as:

  • Checking if there is a valid path to a certain place
  • Storing a path to use later
  • Storing a path to re-use for multiple agents

We will change the code so that the enemy will check for a valid path before chasing the player. If no path is found (e.g. When the player is on the platform the enemy can’t walk on), the enemy will resume patrolling.

CalculatePath() calculates the path, and stores the path’s status in the path.status property. The status can be one of three values:

  • PathComplete
  • PathPartial
  • PathInvalid

We only care whether the path is complete or not for our needs.

We will have to make a few changes to the enemy script to accommodate the new behaviour, and we’ll also take the opportunity to improve the way the Chasing behaviour is coded.

Edit the Enemy Script

Open up the Enemy script:

NOTE: We will rename the SetDestination method in the Enemy script to ‘Chase()’, as this is more accurate now that we have made many changes to the project. SetDestination() was a bad name because it didn’t really describe the functionality, and is also the same as the SetDestination() function on the NavMeshAgent component.

  1. Replace the SetDestination() method with this Chase() method:
void Chase()
{
    var path = new NavMeshPath();
    agent.CalculatePath(objectToChase.position, path);
    if(path.status == NavMeshPathStatus.PathComplete)
    {
        agent.SetDestination(objectToChase.position);
        currentState = EnemyStates.Chasing;
    }
    else
    {
        currentState = EnemyStates.Patrolling;
    }
}

The new code calculates a path before chasing. If a complete path to the player is not found, the enemy reverts to the patrolling state. Because this code is a bit nicer than the previous version, we no longer need the InvokeRepeating() call, so do the following:

  1. Remove the InvokeRepeating() call fro the Start() method.
  2. Add a new variable to the script to track the time since the enemy last scanned for a path to the player:

[code]]czoyNDpcImZsb2F0IHRpbWVTaW5jZVNjYW4gPSAwO1wiO3tbJiomXX0=[[/code]

For this to work, a change is needed in the Update() method as well. Below is the full updated Update() method:

void Update()
{
    if (Vector3.Distance(transform.position, objectToChase.position) > 16f)
    {
        currentState = EnemyStates.Patrolling;
    }
    else
    {
       if(currentState == EnemyStates.Patrolling) Chase();
    }
    if (currentState == EnemyStates.Patrolling)
    { 
        if (Vector3.Distance(transform.position, waypoints[currentWaypoint].position) <= 0.6f)
        {
            currentWaypoint++;
            if (currentWaypoint == waypoints.Length)
            {
                currentWaypoint = 0;
            }
        }
        agent.SetDestination(waypoints[currentWaypoint].position);
    }
    if(currentState == EnemyStates.Chasing)
    {
        if(timeSinceScan >= decisionDelay)
        {
            timeSinceScan = 0;
            Chase();
        }
    }
    timeSinceScan += Time.deltaTime;
}

Now, when the player is on the platform, the enemy goes back to patrolling because it can’t reach the player.

Terrain

You can also set different parts of the Nav Mesh to act like different types of terrain by making it ‘cheaper’ or ‘more expensive’ to travel over some areas. This will influence the agents as they decide how to get from one place to another. We will now create some more terrain, and make some of it faster to walk on than the main terrain, and then see how this affects navigation of our agents.

  1. Create a new quad and place it up against the existing terrain, but give it a different material so you can tell it apart from the rest of the level. Here is how mine is set up, with a red material to make the area distinct:

clip_image004

  1. In the Navigation window, select the Areas tab and add a new area called Fast Movement, then adjust the cost of the terrains to match the image below:

clip_image005

This means that the main mesh, which is in the Walkable area, has a relative ‘cost’ of 3, but the new Fast Movement area has a cost of only 1, making it quicker to move on, and therefore more desirable to the agents when planning a route.

The new ground section needs to be added to the Nav Mesh and set as Fast Movement. This is done in the same way we set the platform to be Player Only earlier:

  1. Select the Object tab in the Navigation window.
  2. Select the new ground section in the Hierarchy to make it the active object.
  3. Change the ground’s Navigation Area to Fast Movement:

clip_image006

  1. Select the Bake tab and re-bake the Nav Mesh.

Now the level has three distinct areas (blue, pink, and brown):

clip_image007

You will now notice that the agents will find a path via the Fast Movement area even if it means not travelling in a straight line. You can use this behaviour for all kinds of things, such as rough terrain that slows the player down, or short cuts using less costly terrain.

That’s All

That’s everything for this series of tutorials on Unity’s navigation system. If you want to dig further, Unity has some more advanced Nav Mesh tools that don’t come included with Unity, but which can be downloaded separately from the link below. These tools allow more control, such as having different agent types with different sizes (for example, you could have large enemies that can’t fit through smaller gaps that the player can escape into).

Unity Nav Mesh Tools
Full project download

Final Project Download

Download the completed AI project here: http://unity.grogansoft.com/downloads/AI/ai_project-Part_4.zip

Navigation with the Nav Mesh Part 4: Patrolling and Chasing

 

Before you start
This tutorial won’t make any sense if you haven’t first done Part 1, Part 2, and Part 3.

And as with the rest of this series, you can grab a download of the full code as it should be at the end of this part of the tutorial at the bottom of this tutorial, so you can compare your own project.

Now that we’ve covered the basics of using the Nav Mesh, Nav Mesh Agent, and Nav Mesh Obstacle, let’s make better use of those tools to create something more game-like: an enemy.

We’ll create a simple enemy that uses the Nav Mesh for navigation, and has two types of behaviour – patrolling and chasing the player.

Create an Enemy

We’ll use our existing agent as our player, and create a new one to use as an enemy:

  1. Duplicate the Agent GameObject, and rename the copy ‘Enemy’ (Hint: Ctrl-D duplicates the selected object).
  2. Move the enemy far away from the player’s position.
  3. Change the enemy’s material and/or shape to make it distinct from the player (I changed mine to a cube and added a new orange material).
  4. Remove the Agent script (NOT the Nav Mesh Agent component) from the enemy (we’ll replace it with a different script).
  5. Rename the original Agent object to ‘Player’ for clarity.

To start with, we will make the enemy chase the player, as this is actually simple to implement. All we need to do is set the enemy’s Nav Mesh Agent destination to the player’s position, and update it periodically.

Enemy Script

We will build our enemy script in multiple stages, so you can either add in each piece of code as you read, or you can wait until the end of this section, where the whole script is listed.

  1. Create a new C# script and call it ‘Enemy’.
  2. Replace all the code in the script with the following:

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

    NavMeshAgent agent;
    [SerializeField] float decisionDelay = 3f;
    [SerializeField] Transform objectToChase;

    void Start () {
        agent = GetComponent<NavMeshAgent>();
        InvokeRepeating("SetDestination", 1.5f, decisionDelay);
    }

    void SetDestination() {
        agent.SetDestination(objectToChase.position);
    }
}

That code simply tells the enemy to move towards the object it is following, and repeats this via InvokeRepeating every 3 seconds (this is a long time between updating the destination, but it is much easier to see the behaviour in action with a long delay).

Now setup the enemy object:

  1. Drag the Player GameObject into the empty Object to Chase field in the Enemy’s Inspector window.
  2. Set the enemy’s Nav Mesh Agent Speed, Acceleration, and Angular speed settings slower than the play (or they will catch the player too easily!).

Test the scene. The enemy will wait 1.5 seconds and then start moving towards the player’s position. The enemy updates their destination (to the player’s current position) every 3 seconds. Try adjusting the Enemy’s Decision Delay and also their Nav Mesh Agent settings to get a good result. You don’t want them to easily reach the player, but you also don’t want them to be too slow.

At the moment the enemy constantly chases the player. This is OK for a simple demo, but let’s make it more interesting.

Catching the Player

Let’s implement a rudimentary way for the enemy to catch the player. This will make the project feel a bit more game-like. We will restart the scene when the player and enemy touch, so add this code to the enemy script (and make sure to save the script):

Firstly, add the following to the top of the Enemy script:

using UnityEngine.SceneManagement;

Then add the following method in the script body:

private void OnCollisionEnter(Collision collision)
{
    if (collision.transform.name == "Player")
    {
        SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
    }
}

That code requires a Rigidbody component on the enemy in order to cause the collision, so in Unity, add a Rigidbody component to the Enemy GameObject.

BUG NOTE:

Unity has a bug that causes lighting to break when reloading a scene (this only affects the Unity editor – not actual game builds). If the scene goes dark when the enemy catches the player and restarts the scene, do the following to work around the bug:

  1. Go to the Unity menu and choose Window > Lighting > Settings.
  2. Select the Scene tab.
  3. At the bottom of the window, untick the Auto Generate button.
  4. Click the Generate Lighting button.

Test the scene, and now the enemy will chase and catch the player, causing the scene to restart. f you want, add some extra code to the collision so something more interesting happens when the enemy catches the player – try adding some sound or making a message display on the screen.

Patrolling

In many games, enemies patrol. Our enemy will patrol between a few waypoints, and will switch to chasing the player if the player gets close. If the player then moves far enough away from the enemy, the enemy will resume patrolling.

Waypoints

Let’s create a simple waypoint script to make the patrol waypoints visible in the Scene window, which will make it easier to place them and visualise the enemy’s movement. Create a new C# script called Waypoint, and replace all the code inside with the following:

using UnityEngine;

public class Waypoint : MonoBehaviour {

    private void OnDrawGizmos()
    {
        Gizmos.color = Color.red;
        Gizmos.DrawWireCube(transform.position, new Vector3(1.5f, 1.5f, 1.5f));
    }
}

Any object with the Waypoint script will have a cube drawn at its position in the Scene window, which will be useful when setting up the enemy waypoints.

Now create all the waypoints in the scene:

  1. Create a new GameObject in the scene called Waypoint.
  2. Add the Waypoint script to the Waypoint GameObject.
  3. Create a prefab from the Waypoint GameObject by dragging the object into the Assets folder (create a Prefabs subfolder).
  4. Place the Waypoint GameObject near to the Enemy (this is the first waypoint).
  5. Create four more Waypoint objects by duplicating the existing one or by dragging the prefab into the scene.
  6. Rename the Waypoints sequentially (e.g. ‘Waypoint 1’, ‘Waypoint 2’, etc.) so you can easily place them in the right order.
  7. Place the waypoints so that they form a path for the enemy to travel along. In our code we will make the path a loop, so that the enemy returns to the first waypoint after reaching the last one.
  8. To ensure the waypoints are actually on the ground, set the Y position of all of them to 0.

Here are the waypoints in my project (the red cubes):

The enemy needs to know about the waypoints in order to move between them, so make the following changes to the Enemy script. Firstly, add a new array variable to hold all the waypoint positions:

[SerializeField] Transform[] waypoints;

Select the Enemy object in the Hierarchy and then go over to the Inspector window. In the Waypoints area, you need to enter the number of waypoints you have in the Size field, like this:

Now add references to the waypoints in the enemy script by dragging the waypoint objects from the Hierarchy into the empty fields in the Inspector, like so (do them in the correct order!):

State

The enemy needs to know when  to chase the player and when to patrol. For this simple example, we will use an enum variable to determine the enemy’s state, and determine behaviour based on its value.

First, we need to create an enum type and add it to the enemy script. Place the following code in the script. I usually put this sort of thing up with the variables:

enum EnemyStates {
    Patrolling,
    Chasing
}

Now, add the following variable to the script:

[SerializeField] EnemyStates currentState;

If you’ve not used enums before, you can think of them like a custom variable type that you specify the possible values for. In this case, a variable of type EnemyStates can only have one of two values: EnemyStates.Patrolling or EnemyStates.Chasing. The first value in the list is the default, meaning our enemy will start off in the Patrolling state until something changes that.

Patrolling Movement

Let’s get back to the patrolling movement. We’ll keep it simple. The enemy, when patrolling, will move to the next waypoint on their route, and when they reach the final waypoint, will return to the first one.

Add a new variable to keep track of which waypoint the enemy is currently walking toward:

int currentWaypoint = 0;

Add the following Update() method to the script:

void Update()
{
    if(currentState == EnemyStates.Patrolling)
    {
        if(Vector3.Distance(transform.position, waypoints[currentWaypoint].position) < 0.6f)
        {
            currentWaypoint++;
            if (currentWaypoint == waypoints.Length)
            {
                currentWaypoint = 0;
            }
        }
        agent.SetDestination(waypoints[currentWaypoint].position);
    }
}

That should be self-explanatory, but in a nutshell it updates the destination to the next waypoint when the agent gets close to the current target waypoint.

We also need to make a change to our chasing code, as the way it is done now would make the enemy try to patrol and chase at the same time! We need to make the chasing conditional upon the current state, so change the SetDestination() method to the following:

void SetDestination() {
    if(currentState == EnemyStates.Chasing) agent.SetDestination(objectToChase.position);
}

Finally, add this as the last line of the Start() method to initiate the patrolling:

if(currentState == EnemyStates.Patrolling) agent.SetDestination(waypoints[currentWaypoint].position);

The enemy script would not win any awards for great code, but it’s good enough to demonstrate the simple behaviour we need.

Test the level to make sure the patrolling is working. If the enemy moves to the first waypoint and then stops, try changing the 0.6f value in the Update method to something higher. You can also change the enemy’s current state to chasing in the Inspector, and the enemy will stop patrolling and chase the player instead. Try changing it back and forth. Great, now there are two states, but the enemy needs a way to decide which state to be in.

Since we’re more interested in the navigation, we’ll cheat with the AI here. We will have a couple of simple rules to switch the enemy behaviour:

  • If the player is less than 10 units away from the enemy, the enemy will chase.
  • If the player is 10 or more units away from the enemy, the enemy will patrol.

We’ll pretend that the enemy can only hear or see the player when they are close.

Add this code to the Update() method before the code that is already in there:

if(Vector3.Distance(transform.position, objectToChase.position) >10f)
{
    currentState = EnemyStates.Patrolling;
}
else
{
    currentState = EnemyStates.Chasing;
}

On every Update(), the enemy will check how close the player is, then change state if necessary. You can now test the scene and make sure the enemy chases the player when the player gets too close. Try changing some of the values to make it feel right. The player needs to be faster than the enemy (so the player can run away), and the distance the enemy can detect the player needs to be enough to prevent the player escaping immediately. You may also want to adjust how long it takes the enemy to update their destination when chasing the player.

Tip: You can add a gizmo to the script to visualise how far away the enemy can detect the player. Add something like this:
private void OnDrawGizmos()
{
    Gizmos.DrawWireSphere(transform.position, 5f);
}

The value of 5 here is the radius, which should be half the distance used in the code that checks the distance between the player and enemy. The gizmo will appear as a wire sphere around the enemy in the Scene view.

Done

That’s everything for Part 4. You should now have an enemy that switches between chasing the player and patrolling. The way this works and feels will depend a lot on the various values you use for everything, and there’s no better way to get it right than trial and error.

Download: Here is a download for the project as it should be as of the end of this tutorial: Part 4 download.

Here’s the full Enemy script including all the pieces from above:

using UnityEngine;
using UnityEngine.AI;
using UnityEngine.SceneManagement;

public class Enemy : MonoBehaviour {
    NavMeshAgent agent;
    [SerializeField] float decisionDelay = 3f;
    [SerializeField] Transform objectToChase;
    [SerializeField] Transform[] waypoints;
    int currentWaypoint = 0;
    
    enum EnemyStates
    {
        Patrolling,
        Chasing
    }

    [SerializeField] EnemyStates currentState;

    void Start () {
        agent = GetComponent<NavMeshAgent>();
        InvokeRepeating("SetDestination", 0.5f, decisionDelay);
        if(currentState == EnemyStates.Patrolling) agent.SetDestination(waypoints[currentWaypoint].position);
    }

    void Update()
    {
        if(Vector3.Distance(transform.position, objectToChase.position) > 10f)
        {
            currentState = EnemyStates.Patrolling;
        }
        else
        {
            currentState = EnemyStates.Chasing;
        }
        if(currentState == EnemyStates.Patrolling)
        {
            if(Vector3.Distance(transform.position, waypoints[currentWaypoint].position) <= 0.6f)
            {
                currentWaypoint++;
                if (currentWaypoint == waypoints.Length)
                {
                    currentWaypoint = 0;
                }
            }
            agent.SetDestination(waypoints[currentWaypoint].position);
        }
    }

    void SetDestination() {
        if(currentState == EnemyStates.Chasing) agent.SetDestination(objectToChase.position);
    }

    private void OnCollisionEnter(Collision collision)
    {
        if (collision.transform.name == "Player")
        {
             SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
        }
    }

    private void OnDrawGizmos()
    {
        Gizmos.DrawWireSphere(transform.position, 5f);
    }
}

Navigation with the Nav Mesh Part 3: Going Off Mesh

In Part 1 we created a simple Nav Mesh, and made an agent walk on it, and in Part 2, we added some obstacles to introduce some actual need for navigation. In this part, we will link multiple meshes together and use off-mesh links to join them. This will vastly increase the capabilities of our navigation.

Make sure you have the project completed up to the end of Part 2 before continuing with this part. If you want, you can download the project as it should be at the start of this part from the bottom of the tutorial for Part 2.

Off-Mesh Links

Off-mesh links allow your agent to navigate outside the Nav Mesh. For example, you might have a puddle in the middle of your terrain, and create a link that will allow the agent to jump over the puddle to the other side.

Some other uses for off-mesh links are:

  • jumping to a position not directly connected on the mesh (e.g. across a gap)
  • moving between multiple meshes
  • shortcuts to positions on the mesh that would otherwise require a lot of walking (e.g. jumping over a wall instead of going around it)
  • a door that must be opened before travelling through it.

Linking Meshes

  1. Create a duplicate of your ground object, and move it somewhere where it does not touch the original ground (make sure there is a gap between the two ground objects).
  2. Go to the Navigation window.
  3. Select the ground duplicate in the Hierarchy.
  4. Select the Object tab in the Navigation window.
  5. Make the ground duplicate walkable, navigation static, and tick the Generate Off-Mesh Links checkbox.
  6. In the Bake tab, set Jump Distance to 2 (under the Generated Off-Mesh Links area). This determines how far the agent can jump off-mesh, and you need to ensure this value is big enough to cover the distance between your two floors, so adjust this value if you have problems.
  7. Off-mesh links are by default one-way, so we need to enable the links from the original ground object as well, so repeat steps 4-6 for the original ground piece.
  8. Re-bake the Nav Mesh.

You should now see something similar to the image below in the Scene view, with the black lines representing the paths between the two meshes, and the black circles representing the start and end points of each link.

clip_image001

Now try running the scene and click on the second piece of ground and watch your agent skip over to the other part of the mesh. In a game you would add some animation to show the player jumping (or whatever fits your game).

Experiment with this. Try making a few different sections and linking them.

Manual Off-Mesh Links

So far we have just used automatic off-mesh links. These are great for the basics like linking two regions of walkable terrain together, but if you want to do more interesting things with your terrain, you will want to add some manual off-mesh links.

An off-mesh link is a regular Unity component you can add to any GameObject. Let’s use the Off-Mesh Link component to make a platform our character can jump up to and down from.

Start by adding a platform in the middle of your Nav Mesh somewhere. Just create a cube and stretch it to make a platform. Something like the image below should do. Also set it as a walkable surface, then re-bake your Nav Mesh.

image

Although this surface is walkable, your agent cannot yet get to it because it is too high, and separated from the rest of the Nav Mesh. So we need to add an off-mesh link to connect the platform with the rest of the Nav Mesh.

  1. Select your platform object in the Hierarchy.
  2. Add an Off Mesh Link component to it. You will see that the Off Mesh Link component has a few settings. The one’s we are interested in for now are:
  • Start (the start position of the link)
  • End (the end position of the link)
  • Bi Directional (whether the link goes both ways is is one-way only).

An Off Mesh Link requires two Transforms to set its start and end points. These are the points the agent travels to/from when making use of the link, so one end goes on one Nav Mesh piece, and the other goes on the part it’s linking to. The transforms need to be placed on the Nav Mesh at appropriate places for the link to work.

  1. Add two new child GameObjects to the platform object and call them Start and End.
  2. Drag the Start and End objects into the corresponding fields of the Off Mesh Link component, like this:

clip_image002

  1. Position the Start and End objects so that one is on the ground of the main mesh, and the other is on the platform. Use this image as a guide:

clip_image003

It may be helpful to attach something visible to the Start and End transforms to make it easier to see.

Run the scene. Now your agent should navigate up onto the platform by ‘jumping’ across the Off Mesh Link.

If you want to experiment more, try adding some more Off Mesh Links or even more platforms.

Download