Creating a directional radar

Sometimes when we play games, we want to know the direction of objects that may be out of view or not visible. A radar is a UI element that makes it possible for this to happen. There are many ways in which a radar can appear, but in this recipe, we will make a directional radar that will take the form of an arrow and show the player the direction of the target.

How to do it...

  1. Let's start by creating a new UI image that will be our arrow. Right-click on the Hierarchy panel, navigate to UI | Image, and rename it RadarArrow. Of course, we can place it wherever we want on the screen and then change the Source Image to an arrow like this one:
    How to do it...

    If you are using Photoshop, you can easily create this by using the custom shape tool. You can select it by pressing U in the toolbox and Shift + U to cycle through the different shapes. Once the custom shape tool is selected, right-click to bring forth the shape selection panel. Select the arrow that you like. In this example, we selected the second arrow that was listed. Next, drag the arrow out onto the canvas while holding down the Shift key in order to constrain the proportions. Now we have a basic arrow icon.

    Tip

    Of course, it is possible to construct all the graphic components of the radar around this arrow, but remember that the arrow should still be visible on the top.

  2. Our script will control the rotation of the arrow according to the position of the target. Therefore, select RadarArrow. In the Inspector, go to Add Component | New Script, name it SubtitleShowerScript, and then click on Create and Add.
  3. Double-click on the script in order to edit it. Since we are going to use the Image class every time we deal with the UI, we have to add the using UnityEngine.UI; statement at the beginning of the script.
  4. Similar to what has been done in previous recipes, we need to store our UI element in a variable. In this case, we will need to store the Image (Script) component. We can make this private, since we can set it in the Start() function:
      private Image arrow;
  5. Next, we need to create a couple of variables so that we can store the transforms of player and target. Let's make these variables public, since we should set them in the Inspector. So, let's add the following lines of code:
      public Transform player;
      public Transform target;
  6. In the Start() function, we have to get the value of our arrow variable, like this:
      void Start () {
        arrow = GetComponent<Image>();
      }
  7. Going further, in the Update() function we have to first calculate the projection of the position of the player on the floor, which is the xz plane. In fact, most 3D games use this plan as the floor, and we want our radar to detect as if it is looking at the scene from the top view. We can do this very easily just by changing the y component of the vector to zero (refer to the There's more... section to see how to project it on different planes). Therefore, we use this code:
        Vector3 playerProjection = new Vector3 (player.position.x, 0, player.position.z);
  8. The same has to be done for the target position. Since considerations similar to the previous step are valid, we can write the following:
        Vector3 targetProjection = new Vector3 (target.position.x, 0, target.position.z);
  9. We also need to calculate the direction that the player is facing, because it will change the orientation of the radar, and again we have to project this on the floor. This time, we also have to normalize since it is a direction:
        Vector3 playerDir = (new Vector3 (player.forward.x, 0, player.forward.z).normalized);
  10. With the two positions projected, we can also calculate the direction of the target relative to the player. So, we have to subtract the two position vectors and again normalize, because this is also a direction:
        Vector3 targetDir = (targetProjection - playerProjection).normalized;
  11. Now, the problem becomes a two-dimensional problem, but we still work with 3D vectors so that we do not lose generality just in case we change the projection plan (check out the There's more... section).
  12. The next thing to calculate is the angle between the two directions, and this can be achieved by calculating the arccosine of the dot product between the two direction vectors. Finally, we multiply everything with a constant to convert the angle from radians to degrees:
        float angle = Mathf.Acos(Vector3.Dot (playerDir, targetDir))*Mathf.Rad2Deg
  13. Since the calculated angle returns a value between 0 and 180 degrees, we still don't have enough information to know whether our radar has to turn clockwise or not. So, we have to distinguish two cases: whether the target is to the left or to the right of the player. If we perform the cross product between the two direction vectors and take the y coordinate to check whether it is negative, we can distinguish the two cases. Finally, we assign a rotation along the z axis to our arrow accordingly:
        if (Vector3.Cross (playerDir, targetDir).y < 0){
          arrow.rectTransform.rotation = Quaternion.Euler (new Vector3 (0, 0, angle));
        } else {
          arrow.rectTransform.rotation = Quaternion.Euler (new Vector3 (0, 0, -angle));
        }
  14. We save the script and our work is done. We are yet to assign the target and player public variables. However, this will depend on how the game is structured. You can refer to the There's more... section, in which a way of testing the radar is described. If you follow that example, the radar should appear like this:
    How to do it...

How it works...

If you do not understand the concept of 3D geometry with vectors, this recipe may be a bit difficult to follow. However, the concepts covered in this section are not too difficult to work through, especially if we pay attention to the pictures.

We started by projecting both the player's position and the target's position, on the floor, like this:

How it works...

Next, we projected the direction that the player is facing. We then called this projection vector playerDir. Calculating the direction of the target respective to the player is simple, since we can just take the difference between the two vectors and normalize. We called this vector targetDir.

Thus, we reduced the problem to just two dimensions. As a result, we only had to calculate the acute angle, theta, between playerDir and targetDir. It can be calculated as the arccosine of the dot product between the two vectors. Here is a diagram that shows the geometry of the player and the target:

How it works...

Finally, we rotated the arrow of the radar accordingly, using theta.

There's more...

As for the distance displayer, the following sections will teach us how to extend the directional radar to suit different situations that may happen in the design of the game, such as changing the projection plane.

Testing the script

Even after we have followed all the steps in this recipe, we are still not able to make it run, since this script works when it is integrated into a game. Hence, in order to test it, we need to construct a test scene. The one that is used in this example can be found in the resource bundle of this book. Otherwise, this can be done by placing a plane and then increasing the scale so that we can see it as the floor within the scene. Next, we can add a cube that represents our player and another cube that represents the target. Set the public variables by dragging both the cubes that you have created, and then click on play. If we move the two cubes in the scene in the Scene view, we can see the radar reacting in the Game view.

Tip

We have projected all the vectors onto the xz plane. So, if the target or the player changes position along the y component, the radar isn't affected.

If we want to see the angles projected on the plane more clearly, just for debugging or learning purposes, we can use the Gizmos function to better observe this process. We can do this by moving the plane down a little and then adding this function to our script:

void OnDrawGizmos() {
  Gizmos.color = Color.red;
  Gizmos.DrawLine(new Vector3 (player.position.x, 0, player.position.z), new Vector3 (target.position.x, 0, target.position.z));
  Gizmos.color = Color.green;
  Gizmos.DrawRay (new Ray(new Vector3 (player.position.x, 0, player.position.z),(new Vector3 (player.forward.x, 0, player.forward.z).normalized)));
}

It draws the two directions playerDir and targetDir in the Scene view.

Changing the projection plane

Our game may have another floor that isn't the classical one that is found on the xz plane. For example, if it is a 2D game, the entire game is on another plane, or if the gravity of the game changes at runtime, we should be able to change the projection plane accordingly.

If we only need to project onto the other two orthogonal planes, we just have to set the missing component to 0 (for example, in the xz plane, the missing component is y; in the xy plane, it is z). So, the two position projections along with the facing direction in the xy plane are as follows:

Vector3 playerProjection = new Vector3 (player.position.x, player.position.y, 0);
Vector3 targetProjection = new Vector3 (target.position.x, target.position.y, 0);
Vector3 playerDir = (new Vector3 (player.forward.x, player.forward.y, 0).normalized);

And these are for the yz plane:

Vector3 playerProjection = new Vector3 (0, player.position.y, player.position.z);
Vector3 targetProjection = new Vector3 (0, target.position.y, target.position.z);
Vector3 playerDir = (new Vector3 (0, player.forward.y, player.forward.z).normalized);

In general, as long as we have the normal of the plane where we want to project, we can use the following static function to project:

Vector3.ProjectOnPlane(vectorToProject, planeNormal)

Of course, in all of these cases, we also have to change the check inside the if statement so that the arrow can rotate in the right direction.

Closest target detection

Since the part of the script that detects the closest target in a set is very similar to the script used in the previous recipe, inside the There's More... section, we can just refer to it. This time, however, we don't have to store the distance but the target itself, so we use the following code:

float distance = float.MaxValue;
Transform target;
foreach(Transform t in targets){
  if (distance > Vector3.Distance (player.position, t.position)){
    distance = Vector3.Distance (player.position, t.position);
    target = t;
  }
}

Adding a delay in the radar through a coroutine

Again, the modification for adding a delay in the radar is very similar to one in the previous chapter, so just revisit that section to learn how to implement the coroutine.

More ideas on how to use the radar

The directional radar can be used in a number of different ways, such as detecting enemies in shooter games or assisting the player in locating treasure in platform games, especially if we integrate it with a Distance Displayer. Since this radar shows only the direction and not the distance, we can provide the player with an option to choose a Distance Displayer or a Directional Radar. Furthermore, we can incorporate both of them together in order to provide the player with more powerful equipment.

Lastly, we can consider implementing a 3D directional radar. In it, the arrow can rotate in all directions to point towards the target. In this case, we don't need to project the vectors, but we should be careful while calculating all the angles in order to rotate the arrow properly.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
13.58.105.247