Displaying a radar to indicate the relative locations of objects
A radar displays the locations of other objects relative to the player, usually based on a circular display, where the center represents the player, and each graphical 'blip' indicates how far away and what relative direction objects are to the player. Sophisticated radar displays will display different categories of objects with different colored or shaped 'blip' icons.
In the screenshot, we can see 2 red square 'blips', indicating the relative position of the 2 red cube GameObjects tagged Cube
near the player, and a yellow circle 'blip' indicating the relative position of the yellow sphere GameObject tagged Sphere
. The green circle radar background image gives the impression of an aircraft control tower radar or something similar.
Getting ready
For this recipe, we have prepared the images that you need in a folder named Images
in 1362_01_11
.
How to do it...
To create a radar to show the relative positions of the objects, follow these steps:
- Create a new 3D project by importing the following standard assets:
- Environment
- Characters
- Cameras
- Create a terrain by navigating to the Create | 3D Object | Terrain menu.
- Size the terrain 20 x 20, positioned at (-10, 0, -10)—so that its center is at (0, 0, 0), as shown in the following figure:
- Texture paint your terrain with the SandAlbedo option, as shown here:
- From the Standard Assets folder in the Project panel, drag the prefab ThirdPersonController into the scene and position it at (0, 1, 0).
- Tag this ThirdPersonController GameObject called Player.
- Remove the Main Camera GameObject.
- From the Standard Assets folder in the Project panel, drag the prefab Multi-PurposeCameraRig into the scene.
- With Multi-PurposeCameraRig selected in the Hierarchy, drag the ThirdPersonController GameObject into the Target property of the Auto Cam (Script) public variable in the Inspector tab, as shown in the following screenshot:
- Import the provided folder known as
Images
. - In the Hierarchy panel, add a UI | RawImage GameObject to the scene named RawImage-radar.
- Ensure that the RawImage-radar GameObject is selected in the Hierarchy panel. From your Project
Images
folder, drag theradarBackground
image into the Raw Image (Script) public property Texture. - Now, in Rect Transform position RawImage-radar at the top-left part using the Anchor Presets item. Then set the width and height to 200 pixels.
- Create another new UI RawImage named RawImage-blip. Assign the
yellowCircleBlackBorder
texture. Tag the Blip GameObject. - In the Project panel, create a new empty prefab named blip-sphere, and drag the RawImage-blip GameObject into this prefab to store all its properties.
- Now, change the texture of RawImage-blip to
redSquareBlackBorder
. - In the Project panel, create a new empty prefab named blip-cube, and drag the RawImage-blip GameObject into this prefab to store all its properties.
- Delete the RawImage-blip GameObject from the Hierarchy panel.
- Create a C# script class called
Radar,
containing the following code, and add an instance as a scripted component to the RawImage-radar GameObject:using UnityEngine; using System.Collections; using UnityEngine.UI; public class Radar : MonoBehaviour{ public float insideRadarDistance = 20; public float blipSizePercentage = 5; public GameObject rawImageBlipCube; public GameObject rawImageBlipSphere; private RawImage rawImageRadarBackground; private Transform playerTransform; private float radarWidth; private float radarHeight; private float blipHeight; private float blipWidth; void Start (){ playerTransform = GameObject.FindGameObjectWithTag("Player").transform; rawImageRadarBackground = GetComponent<RawImage>(); radarWidth = rawImageRadarBackground.rectTransform.rect.width; radarHeight = rawImageRadarBackground.rectTransform.rect.height; blipHeight = radarHeight * blipSizePercentage/100; blipWidth = radarWidth * blipSizePercentage/100; } void Update (){ RemoveAllBlips(); FindAndDisplayBlipsForTag("Cube", rawImageBlipCube); FindAndDisplayBlipsForTag("Sphere", rawImageBlipSphere); } private void FindAndDisplayBlipsForTag(string tag, GameObject prefabBlip){ Vector3 playerPos = playerTransform.position; GameObject[] targets = GameObject.FindGameObjectsWithTag(tag); foreach (GameObject target in targets) { Vector3 targetPos = target.transform.position; float distanceToTarget = Vector3.Distance(targetPos, playerPos); if( (distanceToTarget <= insideRadarDistance) ){ Vector3 normalisedTargetPosiiton = NormalisedPosition(playerPos, targetPos); Vector2 blipPosition = CalculateBlipPosition(normalisedTargetPosiiton); DrawBlip(blipPosition, prefabBlip); } } } private void RemoveAllBlips(){ GameObject[] blips = GameObject.FindGameObjectsWithTag("Blip"); foreach (GameObject blip in blips) Destroy(blip); } private Vector3 NormalisedPosition(Vector3 playerPos, Vector3 targetPos){ float normalisedyTargetX = (targetPos.x - playerPos.x)/insideRadarDistance; float normalisedyTargetZ = (targetPos.z - playerPos.z)/insideRadarDistance; return new Vector3(normalisedyTargetX, 0, normalisedyTargetZ); } private Vector2 CalculateBlipPosition(Vector3 targetPos){ // find angle from player to target float angleToTarget = Mathf.Atan2(targetPos.x, targetPos.z) * Mathf.Rad2Deg; // direction player facing float anglePlayer = playerTransform.eulerAngles.y; // subtract player angle, to get relative angle to object // subtract 90 // (so 0 degrees (same direction as player) is UP) float angleRadarDegrees = angleToTarget - anglePlayer - 90; // calculate (x,y) position given angle and distance float normalisedDistanceToTarget = targetPos.magnitude; float angleRadians = angleRadarDegrees * Mathf.Deg2Rad; float blipX = normalisedDistanceToTarget * Mathf.Cos(angleRadians); float blipY = normalisedDistanceToTarget * Mathf.Sin(angleRadians); // scale blip position according to radar size blipX *= radarWidth/2; blipY *= radarHeight/2; // offset blip position relative to radar center blipX += radarWidth/2; blipY += radarHeight/2; return new Vector2(blipX, blipY); } private void DrawBlip(Vector2 pos, GameObject blipPrefab){ GameObject blipGO = (GameObject)Instantiate(blipPrefab); blipGO.transform.SetParent(transform.parent); RectTransform rt = blipGO.GetComponent<RectTransform>(); rt.SetInsetAndSizeFromParentEdge(RectTransform.Edge.Left, pos.x, blipWidth); rt.SetInsetAndSizeFromParentEdge(RectTransform.Edge.Top, pos.y, blipHeight); } }
- Create two cubes—tagged Cube, textured with a red image called icon32_square_red. Position each away from the player's character.
- Create a sphere—tagged Sphere, textured with a red image called icon32_square_yellow. Position this away from the cubes and the player's character.
- Run your game. You will see two red squares and one yellow circle on the radar, showing the relative positions of the red cubes and yellow sphere. If you move too far away, then the blips will disappear.
Note
This radar script scans 360 degrees all around the player, and only considers straight line distances in the X-Z plane. So, the distances in this radar are not affected by any height difference between the player and target GameObjects. The script can be adapted to ignore targets whose height is more than some threshold different to the player's height. Also, as presented, this recipe radar sees through everything, even if there are obstacles between the player and the target. The recipe can be extended to not show obscured targets through the user of the ray-casting techniques. See the Unity scripting reference for more details about ray-casting at http://docs.unity3d.com/ScriptReference/Physics.Raycast.html.
How it works...
A radar background is displayed on the screen. The center of this circular image represents the position of the player's character. You have created two prefabs; one for red square images to represent each red cube found within the radar distance, and one for yellow circles to represent yellow sphere GameObjects.
The Radar
C# script class has been added to the radar UI Image GameObject. This class defines four public variables:
insideRadarDistance
: This value defines the maximum distance that an object may be from the player to still be included on the radar (objects further than this distance will not be displayed on the radar).blipSizePercentage
: This public variable allows the developer to decide how large each 'blip' will be, as a proportion of the radar's image.rawImageBlipCube
andrawImageBlipSphere
: These are references to the prefab UI RawImages that are to be used to visually indicate the relative distance and position of cubes and spheres on the radar.
Since there is a lot happening in the code for this recipe, each method will be described in its own section.
The Start() method
The Start()
method caches a reference to the Transform component of the player's character (tagged as Player). This allows the scripted object to know about the position of the Player's character in each frame. Next, the width and height of the radar image are cached—so, the relative positions for 'blips' can be calculated, based on the size of this background radar image. Finally, the size of each blip (width and height) is calculated, using the blipSizePercentage
public variable.
The Update() method
The Update()
method calls the RemoveAllBlips()
method, which removes any old RawImage UI GameObjects of cubes and spheres that might currently be displayed.
Next, the FindAndDisplayBlipsForTag(…)
method is called twice. First, for the objects tagged Cube, to be represented on the radar with the rawImageBlipCube
prefab and then again for objects tagged Sphere, to be represented on the radar with the rawImageBlipSphere
prefab. As you might expect, most of the hard work for the radar is to be performed by the FindAndDisplayBlipsForTag(…)
method.
The FindAndDisplayBlipsForTag(…) method
This method inputs two parameters: the string tag for the objects to be searched for; and a reference to the RawImage prefab to be displayed on the radar for any such tagged objects within the range.
First, the current position of the player's character is retrieved from the cached player transform variable. Next, an array is constructed, referring to all GameObjects in the scene that have the provided tag. This array of GameObjects is looped through, and for each GameObject, the following actions are performed:
- The position of the target GameObject is retrieved
- The distance from this target position to the player's position is calculated, and if this distance is within the range (less than or equal to
insideRadarDistance
), then three steps are now required to get the blip for this object to appear on the radar:- The normalized position of the target is calculated by calling
NormalisedPosition(…)
- The position of the blip on the radar is then calculated from this normalized position by calling
CalculateBlipPosition(…)
- Finally, the RawImage blip is displayed by calling
DrawBlip(…)
and passing the blip position and the reference to the RawImage prefab that is to be created there
- The normalized position of the target is calculated by calling
The NormalisedPosition(…) method
The NormalisedPosition(…)
method inputs the player's character position and the target GameObject position. It has the goal of outputting the relative position of the target to the player, returning a Vector3 object with a triplet of X, Y, and Z values. Note that since the radar is only 2D, we ignore the Yvalue of target GameObjects. So, the Yvalue of the Vector3 object returned by this method will always be 0. So, for example, if a target was at exactly the same location as the player, the returned X, Y, Z Vector3 object would be (0, 0, 0).
Since we know that the target GameObject is no further from the player's character than insideRadarDistance
, we can calculate a value in the -1 … 0 … +1 range for the X and Z axis by finding the distance on each axis from the target to the player, and then dividing it by insideRadarDistance
. An X value of -1 means that the target is fully to the left of the player (at a distance that is equal to insideRadarDistance
), and +1 means it is fully to the right. A value of 0 means that the target has the same X position as the player's character. Likewise, for -1 … 0 … +1 values in the Z-axis (this axis represents how far, in front or behind us an object, is located, which will be mapped to the vertical axis in our radar).
Finally, this method constructs and returns a new Vector3 object, with the calculated X and Z normalized values, and a Y value of zero.
Note
The normalized position
A normalized value is one that has been simplified in some way, so the context has been abstracted away. In this recipe, what we are interested in is where an object is relative to the player. So, our normal form is to get a value of the X and Z position of a target in the -1 to +1 range for each axis. Since we are only considering GameObject within out insideRadarDistance
value, we can map these normalized target positions directly onto the location of the radar image in our UI.
The CalculateBlipPosition(…) method
First, we calculate angleToTarget
: the angle from (0, 0, 0) to our normalized target position.
Next, we calculate anglePlayer
: the angle the player's character is facing. This recipe makes use of the yaw angle of the rotation, which is the rotation about the Y-axis—that is, the direction that a character controller is facing. This can be found in the Y component of a GameObject's eulerAngles
component of its transform. You can imagine looking from above and down at the character controller, and see what direction they are facing—this is just what we are trying to display graphically with the compass.
Our desired radar angle (the angleRadarDegrees
variable) is calculated by subtracting the player's direction angle from the angle between target and player, since a radar displays the relative angle from the direction that the player is facing, to the target object. In mathematics, an angle of zero indicates an east direction. To correct this, we need to also subtract 90 degrees from the angle.
The angle is then converted into radians, since this is required for the Unity trigonometry methods. We then multiply the Sin()
and Cos()
results by our normalized distances to calculate the X and Y values respectively (see the following figure):
Our final position values need to be expressed as pixel lengths, relative to the center of the radar. So, we multiply our blipX
and blipY
values by half the width and the height of the radar; note that we multiply only with half the width, since these values are relative to the center of the radar.
Note
Note: In this figure, alpha is the angle between player and target object, 'a' is the adjacent side, 'h' is the hypotenuse and 'o' is the side opposite the angle.
We then add half the width and height of the radar image to the blipX
/Y
values. So, these values are now positioned relative to the center.
Finally a new Vector2 object is created and returned, passing back these final calculated X and Y pixel values for the position of our blip icon.
The DrawBlip() method
The DrawBlip()
method takes the input parameters of the position of the blip (as a Vector2
X, Y pair), and the reference to the RawImage prefab to be created at that location on the radar.
A new GameObject is created from the prefab, and is parented to the radar GameObject (of which the scripted object is also a component). A reference is retrieved to the Rect Transform of the new RawImage GameObject that has been created for the 'blip'. Calls to the Unity RectTransform method, SetInsetAndSizeFromParentEdge(…),
result in the blip GameObject being positioned at the provided horizontal and vertical locations over the radar image, regardless of where in the Game panel the background radar image has been located.