Thursday 28 January 2016

Navigation and click-to-move

Navigation is one of the awesome features of the engine. It provides a sort of basic AI for your objects which allows them to navigate the scene autonomously avoiding obstacles.

Let's see how we can achieve that, by making move a character on a plane using the mouse and making sure he doesn't try to run through walls.

For this simple projects I used some of the assets found in the StandardAsset by UnityTechnologies, downloadable for free on the Asset Store.

Let's start by placing our floor in the scene by draggin the prefab FloorPrototype64x01x64. For simplicity, we will rename it Floor. Let's set its layer mask to a new mask called Ground. In order to use the navigaition system, we need to tell Unity that this object is Static. More specifically, Navigation Static. We can achieve this by simply click on the Static label on the top right (Fig 1).

Fig 1


We can now place some obstacles in the scene, can can either drag in some prefabs from the standard asset or simply create cubes. Let's also set them to static. Finally, let's drag in the character model from the folder Characters -> ThirdPersonCharacters -> Prefabs ->ThirdPersonController.

My scene looks like this:



Now, it is time to bake it. Yes, bake it.

In order to get the navigation system to work we have to bake the scene. To open the navigation window we go to Window -> Navigation. (Fig 2)


Fig 2

Once the navigation window opens, we select the bake tab. (Fig 3)

Fig  3

Now, I could go on and explain all the parameters in the window, but I really believe that the best way to understand what they do is simply to play around with them. For the moment we leave them as they are, we proceed by pressing the button Bake, placed to the bottom right of the window. (Fig 4)


At this point magic will happen.

In the editor, the scene will look like this:


The blue area represents the "walkable" part of the scene, while the "sort of greyd out" parts are considered obstacles. During the baking process, Unity will check all the objects in the scene which are tagged Static and will make them obstacles and, therefore, to be avoided by any NavMeshagent navigating the scene.

We can now remove the 2 scripts attached to our player character (ThirdPersonUserControl and ThirdPersonCharacter) as we are going to write a new script to move the character via mouse clicking. Also, we need to add the component NavMeshAgent to our model, which is the component that will drive our player through the scene.

Let's create a new C# script and call it PlayerMovement. I will noe show you the code:



 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class PlayerMovement : MonoBehaviour {
 
 Animator anim;
 NavMeshAgent nma;

 Ray ray;
 RaycastHit hit;
 float speed = 0.5f;
 // Use this for initialization
 void Start () {

  anim = GetComponent<Animator> ();
  
  nma = GetComponent<NavMeshAgent> ();
  nma.speed = speed;
 
 }
 
 // Update is called once per frame
 void Update () {

  ray = Camera.main.ScreenPointToRay (Input.mousePosition); 
  RayCastFromCamera ();
  
  Vector3 proj = Vector3.Project (nma.desiredVelocity, transform.forward);
 
  anim.SetFloat ("Forward", proj.magnitude);
 
 }

 void RayCastFromCamera()
 {

  if (Physics.Raycast (ray, out hit, 500, LayerMask.GetMask ("Ground"))) {

   if (Input.GetMouseButtonDown (0)) {

    nma.SetDestination (hit.point);
    
    nma.Resume ();

   }

  }
 }
}

Le's break it down.

We get all our component references (Animator and NavMeshAgent).

We then create a Ray variable and a RayCastHit variable. If you are not sure how to use Raycasting check the unity official documentation. I might make a tutorial about it if required.

In the Start method we initialize all our variables.

In the Update method, we perform a Raycast to check where in the scene world we are pointing with the mouse, based on the ray created from the camera to the world space (Line 22).

If we hit anything that is on the LayerMask Ground (Line 34) and we click the left mouse button (Line 36), we pass to the NavMeshAget the collision point.

Now, for this particular example the animation might behave strangley and that is because I'm not setting the rotation animation in order to keep the script simple. If you are interested, please let me know, I will make a tutorial on it.

The character movement is controlled by the animator, and its speed is determined by the animation speed (we are using RootMotion). Therefore, the higher value we feed into the Forward variable of the animator, the fastest the character will run. To get a smooth movement, we use Vector Projection.

In simple words, by calculating the magnitude of the Vector Projection of the NavMeshAgent desired velocity and the character transform forward we get a value for the speed that goes from 0 (when the NavMeshAgent destination is very close to the agent itseld) to its maximum possible value (the NavMeshAgent speed variable, in our case, 0.5). I set the speed to 0.5 so we always get a walking animation as the running animation can create complication unless we adjust other parameters (like animation rotation). That is simply because the Animator is using blend trees and needs extra parameters to allow for smooth animations. I will make a tutorial eventually on how to achieve that using this technique.

We calculate the projection using the NavMeshAgent desired velocity, as it is the velocity the agent wants to head owards (therefore, towards the destination) and it will account for obstacle avoidance. If we had used NavMeshAgent.Destination we would se the character attempting to walk towards the target ignoring obstacles on the path.

By the way, if you would like to know more about Vector Projection and NavMeshAgent movement I strongly suggest you to go and check the "Stealth" complete project tutorial on the official Unity website.

However, for our purpose this is enough. We now have a character that will move in the direction of our clicking point and avoiding obstacles.

A few things to remember:

- Every time we place or remove obstacles in the scene, we need to rebake the scene.
- It is important to mark as Static all the objects that we want to be avoided by our character in the scene
- It is possible to create "bridges" to walk over areas that are not navigable using OfMeshLink. (a more in depth topic for another tutorial).
- Notice how, when you play the scene, if we click outside the "level", the character will get to the closest point and then stop. There's a way to avoid tha by using the method hasPath() of the NavMeshAgent. If the method returns a false, we ca just set the speed to 0 or stop the agent so the character will not move.

Well, that's about it.

An example scene here. I took advantage of the Stecil buffer shaders I wrote in the previous posts, so we will be able to see the character behind the walls. To make it stand out, the player model is rendered in a single color, in this case, green.

No comments:

Post a Comment