分享

Unity 3D AI: NavMesh Navigation

 勤奋不止 2016-07-22

In this tutorial, we'll dive into artificial intelligence with Unity 3D by introducing the built-in navigation system. I'll show you how to define a NavMesh in your scene, make an agent intelligently walk through it to reach different targets, and how to connect separate areas of a scene.

NavMesh is a commonly-used technique in Game AI to define a walkable area in an environment. It's also used to calculate a path between two points, make an NPC walk from its position to a goal, let an enemy reach a player or move the player to a desired destination (e.g. a point-and-click adventure game or a RTS).

Unity offers a built-in implementation of this navigation system, available in both free and pro versions. Some advanced features, like off-mesh links, are pro-only.

You can access the navigation system through the Navigation panel using Window – Navigation, and it will open next to the Inspector.

Navigation Panel

For the purpose of this tutorial we're going to use a 3D project. Also, I'll assume you have familiarity with the Unity editor and know basics operations like adding objects to a scene, adding components, and scripting in C#.

Note: This tutorial is based on Unity 4. The screenshots have been captured from version 4.5.1. As new releases are distributed, menus, names or layouts in the editor may differ.

Marking Scene objects and baking

First thing's first. In the Object tab, define whether the selected object is Navigation static or not. Walls, floors and platforms are all static objects.

Make sure to go through all your objects in the hierarchy and set them appropriately. You can use the Scene filter in the Navigation panel to show only Mesh Renderers or Terrains. When doing this, make sure to select the actual object with a mesh, not the parent.

Objects that are not Navigation static will be ignored when generating the NavMesh.

Next, define the Navigation Layer for the object using the related drop-down. You can choose between Default (walkable), Not Walkable and Jump. You can also define custom layers in case you want to have terrains with different traversal cost -- more on this later.

We're now ready to bake the nav mesh onto your scene. Here, you can see an initial scene without NavMesh:

Unity Scene without NavMap baked

After hitting the Bake button, you'll see that the scene has a blue overlay in certain areas, that's the NavMesh:

Unity Scene with NavMap baked

Actors will be able to walk only on the blue area of the map. Just note that every time you make a modification to the scene you'll need to Bake the NavMesh again.

In this example, the floor is composed of smaller adjacent pieces. This will become handy when we'll want to define different traversal cost for different areas of the level.

Setting NavMesh properties

You can customize your NavMesh in the Bake Tab.

Radius is the distance between the walls and the navigation mesh, and represents the agent's radius. If you feel that your agent keeps bumping on walls or objects while moving, increase the radius to make it smoother.

Height represents the agent's height and specifies the minimum height of areas where the agent will be able to venture into. Max slope specifies the maximum slope for a surface to be considered walkable, while Step Height is the height difference between two surfaces to be considered connected.

Under the Advanced group, you can set the width and height inaccuracy, which specify the approximation allowed when generating the NavMesh. Lower values will give a higher quality NavMesh, but are more computationally expensive. Generating a more accurate NavMesh will also take longer.

The NavMesh Agent

Now that we've got a NavMesh in our scene, we need a way to tell our character to walk through it. This is done using the NavMeshAgent component. You'll find it in the Component Menu, under Navigation.

As you can see in the previous image, I have two cubes in my scene, the blue one represents the player, while the red one marks the position to reach.

Add a NavMeshAgent component to your player game object. This component is responsible for the agent pathfinding and its actual movement control.

NavMeshAgent Component

There are many properties that you can set in the component. Again, Radius specifies the radius of the agent (and defines whether they can go through a narrow path), while Height is the agent's height and defines whether they can pass under obstacles.

Speed, Acceleration and Angular Speed are self-explanatory. They define how your agent will move, while Stopping Distance defines how close the agent will get to the target position before stopping.

We now need to tell the agent what to do via scripting. Here's a quick example of how to make the agent move to a target destination:

  1. using UnityEngine;
  2. using System.Collections;

  3. public class AgentWalkScript : MonoBehaviour
  4. {

  5.     public Transform destination;

  6.     private NavMeshAgent agent;

  7.     void Start ()
  8.     {
  9.         agent = gameObject.GetComponent<NavMeshAgent>();

  10.         agent.SetDestination(destination.position);
  11.     }

  12. }

When the scene starts, the script will retrieve a reference to the game object's NavMeshAgent component, and will use SetDestination to tell it to move to the destination.

Attach the script to your player game object in the editor, and make sure to set the destination field to another game object in your scene (in my case, I used the red cube). As soon as you hit play, you'll see your character moving to its target destination. If the agent can't reach the target (e.g. if you put it in a closed space) it will still try to get as close as possible and stop in the best position available.

If you tick Auto Braking in the inspector, the agent will slow down before reaching the target, smoothly stopping at its position. If the path is broken for any reason, the agent can rebuild a new one in realtime. To do this, tick Auto Repath.

If you want to recreate RTS-style controls (i.e. moving the agent where you click in the scene), you'll need to cast a ray from the screen point using the camera, and then check Raycast collision with the world using that ray. In case any collision is detected, the agent will attemp to move to that position:

  1. void Update()
  2. {
  3.     if (Input.GetMouseButtonDown(0))
  4.     {
  5.         Ray screenRay = Camera.main.ScreenPointToRay(Input.mousePosition);

  6.         RaycastHit hit;
  7.         if (Physics.Raycast(screenRay, out hit))
  8.         {
  9.             agent.SetDestination(hit.point);
  10.         }
  11.     }
  12. }

Manually creating a path

There are cases in which you want to have more control over how your agent generates a path to the target and when it starts moving. Path-finding is a fairly expensive operation, and sometimes it's better to calculate and store a path before using it. This can be done using CalculatePath:

  1. agent = gameObject.GetComponent<NavMeshAgent>();

  2. NavMeshPath path = new NavMeshPath();
  3. bool hasFoundPath = agent.CalculatePath(destination.position, path);

  4. if(path.status == NavMeshPathStatus.PathComplete)
  5. {
  6.     print("The agent can reach the destionation");
  7.  }
  8.  else if(path.status == NavMeshPathStatus.PathPartial)
  9.  {
  10.      print("The agent can only get close to the destination");
  11.   }
  12.   else if(path.status == NavMeshPathStatus.PathInvalid)
  13.   {
  14.      print("The agent cannot reach the destination");
  15.      print("hasFoundPath will be false");
  16.   }

In this example, I manually calculate a path and then check its status to see whether the agent can reach the destination. Note that in this case the agent won't start moving. Later on, when you want the agent to start moving, you can use SetPath and pass the path object you generated before:

  1. agent.SetPath(path);

This is very useful when lots of things happen in the game, and you don't want to slow down the action using SetDestination, which will start a path-finding calculation.

If you want to clear the path, you can use ResetPath:

  1. agent.ResetPath();

The agent will also stop if it's moving, because it doesn't have a valid path to follow.

Pausing, resuming and warping

While your agent is moving towards its target, you can temporarily stop it using Stop. By default, the game object will still decelerate and be affected by the avoidance system while stopping. To prevent this, pass true as paramenter:

  1. if(shouldStopImmediately)
  2. {
  3.    agent.Stop(true); // Stops immediately
  4. }
  5. else
  6. {
  7.    agent.Stop(); // Decelerate
  8. }

Later on you can use Resume to make the agent move again:

  1. agent.Resume();

In some cases you may want to move the agent to another position. In this case, instead of applying the transformation to its Transform, you should use Warp, passing the new position:

  1. agent.Warp(newPosition);

In this instance, the agent will also stop moving, so you'll need to calculate a new path manually or by using SetDestination.

Layers cost

As we said before, each walkable mesh has a layer assigned, and a relative traversal cost. While the default layer has a cost of 1, custom layers can be defined with different costs to, for example, define areas that may be dangerous to the agent for example.

To do that, let's return to the Navigation panel and go to the Layers tab. Here, I've defined three custom layers with higher costs:

NavMesh Layers

You can then return to the Object tab and use these new layers in the Navigation Layer dropdown. Areas with a higher cost will have a different color, just remember that you need to re-bake your NavMesh every time you change a layer to apply the changes.

For example, here I assigned the MoreExpensive layer to the light-brown portion of the platform:

Unity Scene with a different NavMesh layer

In the current scenario depicted in the image, the blue agent will go all the way up and around the target in order to avoid the expensive area of the map.

You can check the cost of a navigation layer using GetLayerCost, passing the layer index. Default layer has an index of 0, and so on. For example, here I'm checking the cost of the Expensive layer:

  1.  float layerCost = agent.GetLayerCost(3);

You can also override layer cost using SetLayerCost:

  1. agent.SetLayerCost(4, 10);

Here, I changed the MoreExpensive layer cost, using its index 4, from 4 to 10. Just note that when the agent is disabled this cost will be reset to its default value defined in the editor.

By default, a NavMeshAgent can walk on every layers defined in the NavMesh. If you want to prevent your agent from walking on certain layers, you can use the "NavMesh Walkable" dropdown menu located in the component properties.

Dynamic Obstacles

While it's useful to have static objects defined in your map, your agent will also need to take into account and avoid dynamic objects in the scene. They can be moving objects, or objects generated at runtime that cannot be taken into account when baking the NavMesh. To do this, we'll use NavMeshObstacle components.

In my example, I created another game object (the brown cube), and added a NavMeshObstacle to it:

Unity Scene with a NavMesh obstacle

You can change the obstacle radius and height from the inspector.

NavMesh obstacles won't affect the NavMesh, but the agent will avoid them and go around their radius, or stop altogether if they cannot find a valid path as a result of the obstacle being in the way. For this reason, NavMesh obstacles are not taken into account when generating a path.

If you want to check for obstacles during path-finding, you can tick the Carve in the Inspector. In this case, the obstacles will create a hole in the NavMesh and the agent will try to find an alternative route in case this obstacle is blocking its path to the target.

By default, your agent's Obstacle Avoidance is set to High Quality. This gives smooth movement between obstacles, but it's also quite expensive. If you want to test lower quality but faster Obstacle Avoidance types you can change it from your game object's NavMeshAgent component, in the Inspector. Setting it as None will disable Obstacle Avoidance altogether, and your agent will ignore dynamic obstacles in the scene.

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约