Almost all the recipes in this chapter involve different interactive UI controls. Although there are different kinds of interactive UI controls, the basic way to work with them, as well as to have scripted actions respond to user actions, is all based on the same idea: events triggering the execution of object method functions.
Then, for fun, and as an example of a very different kind of UI, the final recipe will demonstrate how to add sophisticated, real-time communication for the relative positions of objects in the scene to your game - in the form of a radar!
The UI can be used for three main purposes:
The core concept of working with Unity interactive UI controls is to register an object’s public method so that we’re informed when a particular event occurs. For example, we can add a UI dropdown to a scene named DropDown1
, and then write a MyScript
script class containing a NewValueAction()
public method to perform an action. However, nothing will happen until we do two things:
go1
for our example – although we can also add the script instance to the UI GameObject itself if we wish to).The NewValueAction()
public method of the MyScript
script will typically retrieve the value that’s been selected by the user in the dropdown and do something with it. For example, the NewValueAction()
public method might confirm the value to the user, change the music volume, or change the game’s difficulty. The NewValueAction()
method will be executed each time GameObject go1
receives the NewValueAction()
message. In the properties of DropDown1
, we need to register go1
's scripted component – that is, MyScript
's NewValueAction()
public method – as an event listener for On Value Changed events. We need to do all this at design time (that is, in the Unity Editor before running the scene):
Figure 2.1: Graphical representation of the UI at design time
At runtime (when the scene in the application is running), the following will happen:
DropDown1
GameObject (step 1 in the following diagram), this will generate an On Value Changed event.DropDown1
will update its display on the screen to show the user the newly selected value (step 2a). It will also send messages to all the GameObject components registered as listeners to On Value Changed events (step 2b).NewValueAction()
method in the go1
GameObject’s scripted component being executed (step 3).Figure 2.2: Graphical representation of the UI at runtime
Registering public object methods is a very common way to handle events such as user interaction or web communications, which may occur in different orders, may never occur, or may happen several times in a short period. Several software design patterns describe ways to work with these event setups, such as the Observer pattern and the Publisher-Subscriber design pattern (more details can be found at https://unity.com/how-to/create-modular-and-maintainable-code-observer-pattern).
Core GameObject components related to interactive Unity UI development include the following:
In addition, the concept of sibling depth is important when multiple UI components are overlapping. The bottom-to-top display order (what appears on the top of what) for a UI element is determined initially by its place in the sequence in the Hierarchy window. At design time, this can be manually set by dragging GameObjects into the desired sequence in the Hierarchy window. At runtime, we can send messages to the Rect Transforms of GameObjects to dynamically change their Hierarchy position (and, therefore, the display order) as the game or user interaction demands. This is illustrated in the Organizing images inside panels and changing panel depths via buttons recipe in this chapter.
Often, a UI element exists with most of the components that you may need for something in your game, but you may need to adapt it somehow. An example of this can be seen in the Displaying a countdown timer graphically with a UI Slider recipe, which makes a UI Slider non-interactive so as to display a red-green progress bar for the status of a countdown timer.
In this chapter, we will cover the following recipes:
In this recipe, we’ll create a button that, when pressed, will make an image appear.
For this recipe, we have prepared the image that you need in a folder named Images
in the 02_01
folder.
To create a UI Button to reveal an image, follow these steps:
Figure 2.3: Inspector properties for an image in the scene
Figure 2.4: Settings for the OnClick event handler for the button
In this recipe, you created a new scene, imported an image, and unchecked the Visible in Runtime checkbox for the image so that it would not be seen at runtime.
You added a UI Button and a new OnClick event action that executes the GameObject.SetActive() method of the GameObject drop-down list of the button, and you checked the box so that the image (unity_logo
) appears when the button is clicked.
As an alternative to having the image appear when clicking on a button, the same button could be used to make an image disappear by the checkbox on the image.
The majority of games include menu screens that display settings, buttons to start the game playing, messages to the user about instructions, high scores, the level they have reached so far, and so on. Unity provides UI Buttons to offer users a simple way to interact with the game and its settings.
Figure 2.5: Example of a main menu UI Button
In this recipe, we’ll create a very simple game consisting of two screens, each with a button to load the other one, as illustrated in the preceding screenshot.
To create a button-navigable multi-scene game, follow these steps:
Scenes
, naming the scene page1.Text
GameObject and, in the Inspector window for the Text Input property, enter the text goto page 2:Figure 2.6: UI Button Text child
page2
, with the UI text Instructions (page 2) and a UI Button-TextMeshPro with the text goto page 1
. You can either repeat the preceding steps or duplicate the page1
scene file, name the duplicate page2, and then edit the UI TMP text and UI Button text appropriately.page1
first, then page2
second – drag to rearrange them if necessary.
We cannot tell Unity to load a scene that has not been added to the list of scenes in the build. This makes sense since when an application is built, we should never try to open a scene that isn’t included as part of that application. The scene that appears first (index 0) in the Build Settings panel will be the first scene opened when the game is run.
page1
scene open.SceneManager
.SceneLoader,
in a new folder called _Scripts
, that contains the following code. Then, add an instance of SceneLoader
as a scripted component to the SceneManager
GameObject:
using UnityEngine;
using UnityEngine.SceneManagement;
public class SceneLoader : MonoBehaviour {
public void LoadOnClick(int sceneIndex) {
SceneManager.LoadScene(sceneIndex);
}
}
SceneManager
GameObject from the Hierarchy window over the Object slot immediately below the menu that says Runtime Only. This means that when the button receives an OnClick event, we can call a public method from a scripted object inside SceneManager
.1
(the index of the scene we want to be loaded when this button is clicked) in the text box, below the method’s drop-down menu.
This integer, 1
, will be passed to the method when the button receives an OnClick
event message, as shown here:
Figure 2.7: Button (Script) settings
Save the current scene (page1).
SceneManager
GameObject, add an instance of the SceneLoader
script class to SceneManager
, and then add an OnClick
event action to the button that calls LoadOnClick
and passes an integer of 0
so that page1 is loaded.In this recipe, you created two scenes and added both of these scenes to the game’s build. You added a UI Button and some UI Text to each scene.
When a UI Button is added to the Hierarchy window, a child UI Text object is also automatically created, and the content of the Text Input property of this UI Text child is the text that the user sees on the button.
Here, you created a script class and added an instance as a component to GameObject SceneManager. In fact, it didn’t really matter where this script instance was added, so long as it was in one of the GameObjects of the scene. This is necessary since the OnClick
event action of a button can only execute a method (function) of a component in a GameObject in the scene.
For the buttons for each scene, you added a new OnClick()
event handler that invokes (executes) the LoadOnClick()
method of the SceneLoader
scripted component in SceneManager. This method inputs the integer index of the scene in the project’s Build Settings so that the button on the page1 scene gives integer 1 as the scene to be loaded and the button for page2 gives integer 0.
There are several ways in which we can visually inform the user that the button is interactive when they move their mouse over it. The simplest way is to add a Color Tint that will appear when the mouse is over the button – this is the default Transition. With Button selected in the Hierarchy window, choose a tint color (for example, red), for the Highlighted Color property of the Button (Script) component in the Inspector panel:
Figure 2.8: Adjusting the mouseover settings for buttons
Another form of visual transition to inform the user of an active button is Sprite Swap. In this case, the properties of different images for Targeted/Highlighted/Pressed/Disabled are available in the Inspector window. The default target graphic is the built-in Unity Button (Image) – this is the gray rounded rectangle default when GameObject buttons are created. Dragging in a very different-looking image for the highlighted sprite is an effective alternative to setting a Color Tint:
Figure 2.9: Example of an image as a button
We have provided a rainbow.png
image in a folder named 02_02
that can be used for the button mouseover’s Highlighted sprite. You will need to ensure this image asset has its Texture Type set to Sprite (2D and UI) in the Inspector window. The preceding screenshot shows the button with this rainbow background image.
At the end of the previous recipe, we illustrated two ways to visually communicate buttons to users. The animation of button properties can be a highly effective and visually interesting way to reinforce to the user that the item their mouse is currently over is a clickable, active button. One common animation effect is for a button to become larger when the mouse is over it and then shrink back to its original size when the mouse is moved away. Animation effects are achieved by choosing the Animation option for the Transition property of a Button
GameObject, and by creating an Animation Controller with triggers for the Normal, Highlighted, Pressed, and Disabled states.
To animate a button for enlargement when the mouse is over it (the Highlighted state), do the following:
Figure 2.10: Auto Generate Animation
Animations
), naming it button-animation-controller
.Button
GameObject is selected in the Hierarchy window. Open Window | Animation | Animation. In the Animation window, select the Highlighted clip from the drop-down menu:Figure 2.11: Selecting the Button GameObject in the Hierarchy window
Figure 2.12: Deleting the keyframe
1.2, 1.2
).In this recipe, you created a button and set its Transition mode to Animation. This makes Unity require an Animation Controller with four states: Normal, Highlighted, Pressed, and Disabled. You then made Unity automatically create an Animation Controller with these four states.
Then, you edited the animation for the Highlighted (mouseover) state, deleting the second keyframe, and making the only keyframe a version of the button that’s larger so that its scale is 1.2. So, as is the case, if the GameObject has a Scale of 1 initially, when animating it will be scaled up to 1.2.
When the mouse is not hovering over the button, it’s unchanged, and the Normal state settings are used. When the mouse moves over the button, the Animation Controller smoothly modifies the settings of the button to become those of its Highlighted state (that is, bigger). When the mouse is moved away from the button, the Animation Controller smoothly modifies the settings of the button to become those of its Normal state (that is, its original size).
The following web pages offer video and web-based tutorials on UI animations:
UI Panels are provided by Unity to allow UI controls to be grouped and moved together, and also to visually group elements with an image background (if desired). The sibling depth is what determines which UI elements will appear above or below others. We can see the sibling depth explicitly in the Hierarchy window, since the top-to-bottom sequence of UI GameObjects in the Hierarchy window sets the sibling depth. So, the first item has a depth of 1, the second has a depth of 2, and so on. The UI GameObjects with larger sibling depths (further down the hierarchy, which means they’re drawn later) will appear above the UI GameObjects with smaller sibling depths:
Figure 2.13: Example of organizing panels
In this recipe, we’ll begin by creating two UI Panels, each showing a different playing card image, and we’ll use one button to move between them. We’ll then expand the recipe by creating one more UI Panel. We’ll also add four triangle arrangement buttons to change the display order (move to bottom, move to top, move up one, and move down one).
For this recipe, we have prepared the images that you need in a folder named Images/ornamental_deck-png and Images /icons
in the 02_04
folder.
To create the UI Panels whose layering can be changed by clicking buttons, follow these steps:
Panel-jack-diamonds
. Do the following to this panel:jack_of_diamonds
playing card image asset file from the Project window into the Source Image property. Select the Color property and increase the Alpha value to 255
(so that this background image of the panel is no longer partly transparent).Button-move-to-front
. In the Hierarchy window, make this button a child of Panel-jack-diamonds. Delete the Text child GameObject of this button (since we’ll use an icon to indicate what this button does).Panel-jack-diamonds
from the Hierarchy window over to the Object slot (immediately below the menu saying Runtime Only).Figure 2.14: Addition of an OnClick event handler
Panel-2-diamonds
with its own move-to-front button and a Source Image of 2_of_diamonds
. Move and position this new panel slightly to the right of Panel-jack-diamonds
, allowing both move-to-front buttons to be seen.In this recipe, you created two UI Panels, each of which contains a background image of a playing card and a UI Button whose action will make its parent panel move to the front. You set the Alpha (transparency) setting of the background image’s Color setting to 255 (no transparency).
You then added an OnClick event handler to the button of each UI Panel. This action sends a SetAsLastSibling
message to the button’s panel parent. When the OnClick message is received, the clicked panel is moved to the bottom (end) of the sequence of GameObjects in the Canvas, so this panel is drawn last from the Canvas objects. This means that it appears visually in front of all the other GameObjects.
The button’s action illustrates how the OnClick function does not have to be calling a public method of a scripted component of an object, but it can be sending a message to one of the non-scripted components of the targeted GameObject. In this recipe, we send the SetAsLastSibling message to the Rect Transform component of the panel where the button is located.
There are some details you don’t want to miss.
While Rect Transform offers SetAsLastSibling
(move to front) and SetAsFirstSibling
(move to back), and even SetSiblingIndex
(if we knew exactly what position in the sequence to type in), there isn’t a built-in way to make an element move up or down just one position in the sequence of GameObjects in the Hierarchy window.
However, we can write two straightforward methods in C# to do this, and we can add buttons to call these methods, providing full control of the top-to-bottom arrangement of the UI controls on the screen. To implement four buttons (move-to-front/move-to-back/up one/down one), do the following:
ArrangeActions
containing the following code and add an instance as a scripted component to each of your UI Panels:
using UnityEngine;
public class ArrangeActions : MonoBehaviour {
private RectTransform panelRectTransform;
void Awake() {
panelRectTransform = GetComponent<RectTransform>();
}
public void MoveDownOne() {
print("(before change) " + gameObject.name + " sibling index = " + panelRectTransform.GetSiblingIndex());
int currentSiblingIndex = panelRectTransform.GetSiblingIndex();
if (currentSiblingIndex > 0) {}
panelRectTransform.SetSiblingIndex(currentSiblingIndex - 1);
}
print("(after change) " + gameObject.name + " sibling index = " + panelRectTransform.GetSiblingIndex());
}
public void MoveUpOne() {
print ("(before change) " + gameObject.name + " sibling index = " + panelRectTransform.GetSiblingIndex());
int currentSiblingIndex = panelRectTransform.GetSiblingIndex();
int maxSiblingIndex = panelRectTransform.childCount - 1;
if (currentSiblingIndex < maxSiblingIndex) {
panelRectTransform.SetSiblingIndex(currentSiblingIndex + 1);
}
print ("(after change) " + gameObject.name + " sibling index = " + panelRectTransform.GetSiblingIndex());
}
}
MoveDownOne()
method and set the function for the up-one buttons to call the MoveUpOne()
method.A UI Slider is a graphical tool that allows a user to set the numerical value of an object.
Figure 2.15: Example of a UI Slider offering a range of 0 to 20
This recipe illustrates how to create an interactive UI Slider and execute a C# method each time the user changes the UI Slider value.
To create a UI Slider and display its value on the screen, follow these steps:
30
and placeholder text, such as Slider value here
(this text will be replaced with the slider value when the scene starts). Set Overflow to Overflow. Since we may change the font and message at a later date, it’s useful to allow overflow to prevent some of the message being truncated.UI Slider
GameObject to the scene by going to GameObject | UI | Slider.UI Slider
GameObject’s Rect Transform to the top-middle part of the screen.0
and Max Value to 20
. Then, check the Whole Numbers checkbox:Figure 2.16: Setting the UI Slider’s Min Value and Max Value
SliderValueToText
containing the following code and add an instance as a scripted component to the Text(TMP)
GameObject:
using UnityEngine;
using UnityEngine.UI;
using TMPro;
public class SliderValueToText : MonoBehaviour {
public Slider sliderUI;
private TextMeshProUGUI textSliderValue;
void Awake() {
textSliderValue = GetComponent<TextMeshProUGUI>();
}
void Start() {
ShowSliderValue();
}
public void ShowSliderValue () {
string sliderMessage = "Slider value = " + sliderUI.value;
textSliderValue.text = sliderMessage;
}
}
Text(TMP)
GameObject is selected in the Hierarchy window. Then, in the Inspector window, drag the Slider GameObject into the public Slider UI variable slot for the Slider
Value
To
Text
(Script)
scripted component:Figure 2.17: Dragging Slider into the Slider UI variable
Slider
GameObject is selected in the Hierarchy window. Then, in the Inspector window, add an OnValue Changed (Single) event handler by clicking on the plus (+) sign at the bottom of the Slider component.Text(TMP)
GameObject from the Hierarchy window over to the Object slot (immediately below the menu that says Runtime Only), as shown in the following screenshot:
Figure 2.18: Dragging the Text GameObject into None (Object)
You have now told Unity which object a message should be sent to each time the slider is changed.
ShowSliderValue()
method, in the scripted object in the Text(TMP) GameObject, will be executed:Figure 2.19: Drop-down menu for On Value Changed
Slider
value = <n>
.0
(the leftmost of the slider) to 20
(the rightmost of the slider).In this recipe, you created a UI Slider GameObject and set it to contain whole numbers in the range of 0
to 20
.
You also added an instance of the SliderValueToText
C# script class to the UI Text(TMP)
GameObject.
The Awake()
method caches references to the Text component in the textSliderValue
variable.
The Start()
method invokes the ShowSliderValue()
method so that the display is correct when the scene begins (that is, the initial slider value is displayed).
The ShowSliderValue()
method gets the value of the slider and then updates the text that’s displayed to be a message in the form of Slider value = <n>
.
Finally, you added the ShowSliderValue()
method of the SliderValueToText
scripted component to the Slider GameObject’s list of On Value Changed event listeners. So, each time the slider value changes, it sends a message to call the ShowSliderValue()
method so that the new value is updated on the screen.
There are many cases where we wish to inform the player of how much time is left in a game or how much longer an element will take to download – for example, a loading progress bar, the time or health remaining compared to the starting maximum, or how much the player has filled up their water bottle from the fountain of youth.
In this recipe, we’ll illustrate how to remove the interactive “handle” of a UI Slider, and then change the size and color of its components to provide us with an easy-to-use, general-purpose progress/proportion bar:
Figure 2.20: Example of a countdown timer with a UI Slider
In this recipe, we’ll use our modified UI Slider to graphically present to the user how much time remains on a countdown timer.
For this recipe, we have prepared the script and images that you need in the 02_04
folder, respectively named _Scripts
and Images
.
To create a digital countdown timer with a graphical display, follow these steps:
CountdownTimer
script and the red_square
and green_square
images into this project.UI Text(TMP)
GameObject to the scene with a Font size of 30
and placeholder text such as a UI Slider
value
(this text will be replaced with the slider value when the scene starts). Check that Overflow is set to Overflow.Slider
GameObject to the scene by going to GameObject | UI | Slider.Slider
GameObject’s Rect Transform to the top-center part of the screen.Slider
GameObject is selected in the Hierarchy window.Handle
Slide
Area
child GameObject (by unchecking it).Figure 2.21: Ensuring Handle Slide Area is deactivated
red_square
image into the Source Image property of the Image component in the Inspector window.Fill
child of the Fill Area
child and do the following:green_square
image into the Source Image property of the Image component in the Inspector window.Fill
Area
child and do the following:Figure 2.22: Selections in the Rect Transform component
SliderTimerDisplay
that contains the following code and add an instance as a scripted component to the Slider
GameObject:
using UnityEngine;
using UnityEngine.UI;
using TMPro;
[RequireComponent(typeof(CountdownTimer))]
public class SliderTimerDisplay : MonoBehaviour {
private CountdownTimer countdownTimer;
private Slider sliderUI;
void Awake() {
countdownTimer = GetComponent<CountdownTimer>();
sliderUI = GetComponent<Slider>();
}
void Start() {
SetupSlider();
countdownTimer.ResetTimer( 30 );
}
void Update () {
sliderUI.value = countdownTimer.GetProportionTimeRemaining();
print (countdownTimer.GetProportionTimeRemaining());
}
private void SetupSlider () {
sliderUI.minValue = 0;
sliderUI.maxValue = 1;
sliderUI.wholeNumbers = false;
}
}
Run your game. You will see the slider move with each second, revealing more and more of the red background to indicate the time remaining.
In this recipe, you hid the Handle Slide Area child so that the UI Slider is for display only, which means it cannot be interacted with by the user. The Background color of the UI Slider was set to red so that, as the counter goes down, more and more red is revealed, warning the user that the time is running out.
The Fill property of the UI Slider was set to green so that the proportion remaining is displayed in green – the more green that’s displayed, the greater the value of the slider/timer.
An instance of the provided CountdownTimer
script class was automatically added as a component to the UI Slider via [RequireComponent(...)]
.
The Awake()
method caches references to the CountdownTimer and Slider components in the countdownTimer
and sliderUI variables
.
The Start()
method calls the SetupSlider()
method and then resets the countdown timer so that it starts counting down from 30 seconds.
The SetupSlider()
method sets up this slider for float (decimal) values between 0.0
and 1.0
.
In each frame, the Update()
method sets the slider value to the float that’s returned by calling the GetProportionRemaining()
method from the running timer. At runtime, Unity adjusts the proportion of red/green that’s displayed in the UI Slider so that it matches the slider’s value.
Cursor icons are often used to indicate the nature of the interactions that can be done with the mouse. Zooming, for instance, might be illustrated by a magnifying glass; shooting, on the other hand, is usually represented by a stylized target or reticle:
Figure 2.23: Mouse pointer represented as a stylized target
The preceding screenshot shows an example of the Unity logo with the cursor represented as a stylized target. In this recipe, we will learn how to implement custom mouse cursor icons to better illustrate your gameplay – or just to escape the Windows, macOS, and Linux default UI.
For this recipe, we have prepared the folders that you’ll need in the 02_07
folder.
To make a custom cursor appear when the mouse is over a GameObject, follow these steps:
Images
. Select the unity_logo image in the Project window. Then, in the Inspector window, change Texture Type to Sprite (2D and UI). This is because we’ll use this image for a 2D Sprite
GameObject and it requires this Texture Type (it won’t work with the Default type).New Sprite
, if this wasn’t the default name when it was created:(3,3,3)
and, if necessary, reposition Sprite so that it’s centered in the Game window when the scene runs.Sprite
GameObject. This is needed for this GameObject to receive OnMouseEnter
and OnMouseExit
event messages.IconsCursors
. Select all three images in the Project window and, in the Inspector window, change Texture Type to Cursor. This will allow us to use these images as mouse cursors without any errors occurring.CustomCursorPointer
containing the following code and add an instance as a scripted component to the New Sprite
GameObject:
using UnityEngine;
public class CustomCursorPointer : MonoBehaviour {
public Texture2D cursorTexture2D;
private CursorMode cursorMode = CursorMode.Auto;
private Vector2 hotSpot = Vector2.zero;
public void OnMouseEnter() {
SetCustomCursor(cursorTexture2D);
}
public void OnMouseExit() {
SetCustomCursor(null);
}
private void SetCustomCursor(Texture2D curText){
Cursor.SetCursor(curText, hotSpot, cursorMode);
}
}
The OnMouseEnter()
and OnMouseExit()
event methods have been deliberately declared as public
. This will allow these methods to also be called from UI GameObjects when they receive the OnPointerEnterExit
events.
CursorTarget
image into the public Cursor Texture 2D variable slot in the Inspector window for the Custom Cursor Pointer (Script) component:Figure 2.24: Cursor Texture 2D dragged to the variable slot
In this recipe, you created a Sprite
GameObject and assigned it the Unity logo image. You imported some cursor images and set their Texture Type to Cursor so that they can be used to change the image for the user’s mouse pointer. You also added a Box Collider to the Sprite
GameObject so that it would receive OnMouseEnter and OnMouseExit event messages.
Then, you created the CustomCursorPointer
script
class and added an instance object of this class to the Sprite
GameObject. This script tells Unity to change the mouse pointer when an OnMouseEnter message is received – that is, when the user’s mouse pointer moves over the part of the screen where the Unity logo’s sprite image is being rendered. When an OnMouseExit event is received (the user’s mouse pointer is no longer over the cube part of the screen), the system is told to go back to the operating system’s default cursor. This event should be received within a few milliseconds of the user’s mouse exiting from the collider.
Finally, you selected the CursorTarget
image to be the custom mouse cursor image the user sees when the mouse is over the Unity logo image.
The previous recipe demonstrated how to change the mouse pointer for 2D and 3D GameObjects receiving OnMouseEnter and OnMouseExit events. Unity UI controls do not receive OnMouseEnter and OnMouseExit events. Instead, UI controls can be made to respond to PointerEnter and PointerExit events if we add a special Event Trigger component to the UI GameObject:
Figure 2.25: Mouse pointer as a magnifying glass cursor
In this recipe, we’ll change the mouse pointer to a custom magnifying glass cursor when it moves over a UI Button
GameObject.
For this recipe, we’ll use the same asset files as we did for the previous recipe, as well as the CustomCursorPointer
C# script class from that recipe, all of which can be found in the 02_08
folder.
To set a custom mouse pointer when the mouse moves over a UI control GameObject, do the following:
IconsCursors
folder. Select all three images in the Project window and, in the Inspector window, change Texture Type to Cursor. This will allow us to use these images as mouse cursors without any errors occurring._Scripts
folder containing the CustomCursorPointer
C# script class.UI Button-TextMeshPro
GameObject to the scene, leaving this named Button.CustomCursorPointer
C# script class to the Button
GameObject.Button
GameObject selected in the Hierarchy window, drag the CursorZoom
image into the public Cursor Texture 2D variable slot in the Inspector window for the Customer Cursor Pointer (Script) component.Button
GameObject by going to Add Component | Event | Event Trigger.Button
GameObject into the Object slot.Figure 2.26: Event Trigger settings
OnMouseExit()
method from CustomCursorPointer when this event is received.In this recipe, you imported some cursor images and set their Texture Type to Cursor so that they could be used to change the image for the user’s mouse pointer. You also created a UI Button GameObject and added to it an Event Trigger component.
You then added an instance of the CustomCursorPointer
C# script class to the Button
GameObject and selected the magnifying-glass-style CursorZoom
image.
After that, you created a PointerEnter event and linked it to invoke the OnMouseEnter method of the instance of the CustomCursorPointer
script in the Button
GameObject (which changes the mouse pointer image to the custom mouse cursor).
Finally, you created a PointerExit
event and linked it to invoke the OnMouseExit method of the instance of the CustomCursorPointer
C# script class to the Button
GameObject (which resets the mouse cursor back to the system default).
Essentially, you have redirected PointerEnter
/Exit
events to invoke the OnMouseEnter
/Exit
methods of the CustomCursorPointer
C# script class so that we can manage custom cursors for 2D, 3D, and UI GameObjects with the same scripting methods.
While we often just wish to display non-interactive text messages to the user, there are times (such as name entry for high scores) where we want the user to be able to enter text or numbers into our game. Unity provides the UI Input Field component for this purpose. In this recipe, we’ll create an input field that prompts the user to enter their name:
Figure 2.27: Example of interactive text entry
Having interactive text on the screen isn’t of much use unless we can retrieve the text that’s entered to be used in our game logic, and we may need to know each time the user changes the text’s content and act accordingly. In this recipe, we’ll add an event handler C# script that detects each time the user finishes editing the text and updates an extra message onscreen, confirming the newly entered content.
For this recipe, we have prepared the required assets in a folder named IconsCursors in the 02_09
folder.
To create an interactive text input box for the user, follow these steps:
InputField
to the scene (importing TMP Essential Resources). Position this at the top center of the screen.Text-prompt
. Position this to the left of Input Field. Change the Text property of this GameObject to Name:
.DisplayChangedTextContent
containing the following code:
using UnityEngine;
using TMPro;
public class DisplayChangedTextContent : MonoBehaviour
{
public TMP_InputField inputField;
private TextMeshProUGUI textDisplay;
void Awake()
{
textDisplay = GetComponent<TextMeshProUGUI>();
}
public void DISPLAY_NEW_VALUE()
{
textDisplay.text = "last entry = '" + inputField.text + "'";
}
}
DisplayChangedTextContent
C# script class to the Text-display
GameObject.Text-display
selected in the Hierarchy window, from the Project window, drag the InputField GameObject into the public Input Field variable of the Display Changed Text Content (Script) component:Figure 2.28: Setting the Input Field variable
Input Field
GameObject in the Hierarchy. Add an On Value Changed (String) event to the list of event handlers for the Input Field (Script) component. Click on the plus (+) button to add an event handler slot and drag the Text-display GameObject into the Object slot.Figure 2.29: Making DISPLAY_NEW_VALUE the method to execute each time the Input Text changes
The core of interactive text input in Unity is the responsibility of the Input Field component. This needs a reference to a UI Text GameObject. To make it easier to see where the text can be typed, Text Input (TMP) (similar to buttons) includes a default rounded rectangle image with a white background.
There are usually three Text GameObjects involved in user text input:
First, you created an InputField
GameObject, which automatically provides two child Text
GameObjects, named Placeholder
and Text
. These represent the faint placeholder text and the editable text, which you renamed Text-input
. You then added a third Text
GameObject, Text-prompt
, containing Name:.
The built-in scripting that is part of Input Field components does lots of work for us. At runtime, a Text-Input Input Caret (text insertion cursor) GameObject is created, displaying the blinking vertical line to inform the user where their next letter will be typed. When there is no text content, the faint placeholder text will be displayed. As soon as any characters have been typed, the placeholder will be hidden and the characters typed will appear in black text. Then, if all the characters are deleted, the placeholder will appear again.
You then added a fourth Text GameObject called Text-display
and made it red to tell the user what they last entered in the input field. You created the DisplayChangedTextContent
C# script class and added an instance as a component of the Text-display
GameObject. You linked the InputField
GameObject to the Input Field public variable of the scripted component (so that the script can access the text content entered by the user).
Finally, you registered an On Value Changed event handler of the Input Field so that each time the user changes the text in the input field, the DISPLAY_NEW_VALUE()
method of your DisplayChangedTextContent
scripted object is invoked (executed), and the red text content of Text-display
is updated to tell the user what the newly edited text consisted of.
I suggest you use capital letters and underscores when naming methods you plan to register for event handlers, such as our input field On Value Changed event. This makes them much easier to find navigating component methods when setting up the event handler in the Inspector.
Figure 2.30: Making DISPLAY_NEW_VALUE the method to execute each time the Input Text changes
Rather than the updated text being displayed with every key press, we can wait until Tab or Enter is pressed to submit a new text string by using an On End Edit event handler rather than On Value Changed.
Also, the content type of Input Field (Script) can be set (restricted) to several specific types of text input, including email addresses, integer or decimal numbers only, or password text (where an asterisk is displayed for each character that’s entered). You can learn more about input fields by reading the Unity Manual page: https://docs.unity3d.com/Manual/script-InputField.html.
Users make choices and, often, these choices have one of two options (for example, sound on or off), or sometimes one of several possibilities (for example, difficulty level as easy/medium/hard). Unity UI Toggles allows users to turn options on and off; when combined with toggle groups, they restrict choices to one of the groups of items.
In this recipe, we’ll explore the basic Toggle and a script to respond to a change in values.
Figure 2.31: Example showing the button’s status changing in the Console window
For this recipe, we have prepared the C# script ToggleChangeManager
class in the 02_10
folder.
To display an on/off UI Toggle to the user, follow these steps:
Toggle
GameObject, set the Text property to First Class.ToggleChangeManager
to the Toggle
GameObject:
using UnityEngine;
using UnityEngine.UI;
public class ToggleChangeManager : MonoBehaviour {
private Toggle toggle;
void Awake () {
toggle = GetComponent<Toggle>();
}
public void PrintNewToggleValue() {
bool status = toggle.isOn;
print ("toggle status = " + status);
}
}
Toggle
GameObject selected, add an On Value Changed event to the list of event handlers for the Toggle (Script) component, click on the plus (+) button to add an event handler slot, and drag Toggle
into the Object slot.
Note. If this is the first time you have done this, it may seem strange to select a GameObject, and then drag this same GameObject into a property of one of its components. However, this is quite common, since the logic location for behavior like button actions and scripted actions is a component of the GameObject whose behavior is being set. So, it is correct to drag the Toggle GameObject into the Object
slot for that toggle’s On Value Changed
event handler.
Figure 2.32: Setting the Toggle’s On Value Changed event handler function
Toggle
GameObject, the On Value Changed event will fire, and you’ll see a new text message printed into the Console window by our script, stating the new Boolean true/false value of Toggle
.When you create a Unity UI Toggle GameObject, it comes with several child GameObjects automatically – Background
, Checkmark
, and the text’s Label
. Unless we need to style the look of a Toggle
in a special way, all we must do is simply edit the text’s Label
so that the user knows what option or feature this Toggle
is going to turn on/off.
The Awake()
method of the ToggleChangeManager
C# class caches a reference to the Toggle component in the GameObject where the script instance is located. When the game is running, each time the user clicks on the Toggle component to change its value, an On Value Changed event is fired. Then, we register the PrintNewToggleValue()
method, which is to be executed when such an event occurs. This method retrieves, and then prints out to the Console window, the new Boolean true/false value of Toggle
.
Unity UI Toggles are also the base components if we wish to implement a group of mutually exclusive options in the style of radio buttons. We need to group related radio buttons together (UI Toggles) to ensure that when one radio button turns on (is selected), all the other radio buttons in the group turn off (are unselected).
We also need to change the visual look if we want to adhere to the usual style of radio buttons as circles, rather than the square UI Toggle default images:
Figure 2.33: Example of three buttons with Console status
For this recipe, we have prepared the images that you’ll need in a folder named UI Demo Textures
in the 02_11
folder.
To create related radio buttons using UI Toggles, do the following:
UI Demo Textures
folder into the project.Toggle-easy
.Toggle-easy
GameObject, set the Text property to Easy.Canvas
GameObject and, in the Inspector window, add a UI | Toggle Group component.Toggle-easy
GameObject selected, in the Inspector window, drag the Canvas
GameObject into the Toggle Group property of the Toggle (Script) component.Figure 2.34: Assigning the Canvas Toggle Group to the Toggle-easy GameObject
Toggle-easy
GameObject with a new Tag called Easy. Do this by selecting Add Tag… from the Tag drop-down menu in the Inspector window – this will open the Tags & Layers panel. Click the plus (+) button for a new Tag, and create a new Tag, Easy
. Finally, select the Toggle-easy
GameObject again in the Hierarchy window, and at the top of the Inspector, set its Tag to Easy
.
Figure 2.35: Creating new Tag called Easy
You can also create/edit Tags & Layers from the project Settings panel, accessed from the Edit | Settings… menu.
Toggle-easy
and, for its Image component, select the UIToggleBG
image in the Source Image property (a circle outline).
To make these toggles look more like radio buttons, the background of each is set to the circle outline image of UIToggleBG
, and the checkmark (which displays the toggles that are on) is filled with the circle image called UIToggleButton
.
Toggle-easy
. In the Image component, choose the UIToggleButton
image for the Source Image property (a filled circle).
Of the three choices (easy, medium, and hard) that we’ll offer to the user, we’ll set the easy option to be the one that is supposed to be initially selected. Therefore, we need its Is On property to be checked, which will lead to its checkmark image being displayed.
RadioButtonManager
C# script class to the Canvas
GameObject:
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class RadioButtonManager : MonoBehaviour {
private string currentDifficulty = "Easy";
public void PrintNewGroupValue(Toggle sender){
// only take notice from Toggle just switched to On
if(sender.isOn){
currentDifficulty = sender.tag;
print ("option changed to = " + currentDifficulty);
}
}
}
Toggle-easy
GameObject selected, add an On Value Changed event to the list of event handlers for the Toggle (Script) component, click on the plus (+) button to add an event handler slot, and drag the Canvas
GameObject into the Object slot.Toggle-easy
GameObject. This means that the Toggle-easy
GameObject calls the PrintNewGroupValue(...)
method of a C# scripted component called RadioButtonManager
in the Canvas
GameObject, passing itself as a parameter.Figure 2.36: Dragging the Toggle-easy GameObject to the Toggle parameter slot
Toggle-easy
GameObject, naming the copy Toggle-medium
. Set its Rect Transform property’s Pos Y to -25
(so that this copy is positioned below the easy option) and uncheck the Is On property of the Toggle component. Create a new Tag, Medium
, and assign the Tag to the new GameObject, Toggle-medium
.Toggle-medium
GameObject, naming the copy Toggle-hard
. Set its Rect Transform property’s Pos Y to -50
(so that this copy is positioned below the medium option). Create a new Tag, Hard
, and assign the Tag to the new GameObject, Toggle-hard
.By using the UIToggleBG
and UIToggleButton
images, we made the UI GameObject look like radio buttons – circles that when selected have a filled center. By adding a Toggle Group component to Canvas
, and having each Toggle GameObject link to it, the three radio buttons can tell Toggle Group when they have been selected. Then, the other members of the group are deselected.
Each Toggle has an On Value Changed event handler that prints out the Tag for the GameObject. So, by creating the Tags Easy
, Medium
, and Hard
, and assigning them to their corresponding GameObjects, we are able to print out a message corresponding to the radio button that has been clicked by the user.
If you had several groups of radio buttons in the same scene, one strategy is, for each group, to add the Toggle Group component to one of the Toggles and have all the others link to that one. So, all Toggles in each group link to the same Toggle Group component.
Note. We store the current radio button value (the last one switched On) in the currentDifficulty
property of the RadioButtonManager component of GameObject Canvas. Since variables declared outside a method are remembered, we could, for example, add a public method, such as GetCurrentDifficulty()
, which could tell other scripted objects the current value, regardless of how long it’s been since the user last changed their option.
In the previous recipe, we created radio-style buttons with a Toggle Group to present the user with a choice of one of many options. Another way to offer a range of choices is with a drop-down menu. Unity provides the UI Dropdown control for such menus. In this recipe, we’ll offer the user a drop-down choice for the suit of a deck of cards (hearts, clubs, diamonds, or spades):
Figure 2.37: Checking the drop-down menu in the Console window
Note that the UI Dropdown that’s created by default includes a scrollable area if there isn’t space for all the options. We’ll learn how to remove such GameObjects and components to reduce complexity when such a feature is not required.
To create a UI Dropdown control GameObject, follow these steps:
DropdownManager
to the Dropdown
GameObject:
using UnityEngine;
using TMPro;
public class DropdownManager : MonoBehaviour {
private TMP_Dropdown dropdown;
private void Awake() {
dropdown = GetComponent<TMP_Dropdown>();
}
public void PrintNewValue() {
int currentValue = dropdown.value;
print ("option changed to = " + currentValue);
}
}
Dropdown
GameObject selected, add an On Value Changed event to the list of event handlers for the Dropdown (Script) component, click on the plus (+) button to add an event handler slot, and drag Dropdown into the Object slot.0
for the first item, 1
for the second item, and so on).Template
child GameObject of Dropdown in the Project window and, in its Rect Transform panel, reduce its height to 50
. When you run the scene, you should see a scrollable area, since not all options fit within the template’s height:Figure 2.38: Example of a drop-down menu
Scrollbar
child of the Template
GameObject and remove the Scroll Rect (Script) component of it. When you run the scene now, you’ll only see the first two options (Hearts and Clubs), with no way to access the other two options. When you are sure your template’s height is sufficient for all its options, you can safely remove these scrollable options to simplify the GameObjects in your scene.When you create a Unity UI DropDown-TextMeshPro GameObject, it comes with several components and child GameObjects – Label
, Arrow
, and Template
(as well as ViewPort
and Scrollbar
, and so on). Dropdowns work by duplicating the Template
GameObject for each of the options listed in the Dropdown (Script) component. Both the Text and Sprite image values can be given for each option. The properties of the Template
GameObject are used to control the visual style and behavior of the dropdown’s thousands of possible settings.
First, you replaced the default options (Option A, Option B, and so on) in the Dropdown (Script) component. You then created a C# script class called DropdownManager
, which, when attached to your dropdown and having its PrintNewValue
method registered for On Value Changed events, means you can see the Integer index of the option each time the user changes their choice. Item index values start counting at zero (as is the case in many computing contexts), so 0
for the first item, 1
for the second item, and so on.
Since the default Dropdown
GameObject that was created includes a Scroll Rect (Script) component and a Scrollbar
child GameObject, when you reduced the height of Template
, you could still scroll through the options. You then removed these items so that your dropdown didn’t have a scrolling feature anymore.
In this recipe, you’ll learn how to create UI Dropdown menus that show image icons next to the text for each menu item. We’ll build on the previous recipe to offer the user a drop-down choice for the suit of a deck of cards (hearts, clubs, diamonds, or spades).
Figure 2.39: Example showing UI Dropdown menus with text and image
There are two pairs of items Unity uses to manage how text and images are displayed for a UI Dropdown control:
Text
and Image
GameObjects (direct children of the Dropdown GameObject) are used to control how the currently selected item for the dropdown is displayed – this is the part of the dropdown we always see, regardless of whether it is being interacted with.Text
and Image
GameObjects (children of the Template
child of the Dropdown
GameObject) define how each option is displayed as a row when the drop-down menu items are being displayed – the rows that are displayed when the user is actively working with the Dropdown
GameObject.So, we have to add an image in two places (for the Caption Image and the Item Image settings), in order to get a dropdown working fully with image icons for each option.
This recipe builds on the previous one. So, create a copy of that project and work on the copy.
For this recipe, we have prepared the image that you need in a folder named Images
in the 02_13
folder.
To create image icon dropdown menus, follow these steps:
Images
folder.card_suits
folder into the Project window (hearts.png
for Hearts, and so on).Dropdown
GameObject.25
x 25
in Rect Transform and drag it over the letter H in Hearts in the Label
GameObject.Label
GameObject so it appears to the right of the Hearts image.Figure 2.40: Adding a Hearts Image GameObject as a child of the Dropdown GameObject
Image
GameObject into the Caption Image property of the Dropdown (Script) component.Template
GameObject (usually, it is disabled). Make it active by checking its Active checkbox at the top of the Inspector.Image
GameObject child of Dropdown and name the copy Item Image
. Make this image a child of the Item
GameObject that is in Dropdown-Template-Content-Item (Item Image
needs to appear below the white Item Background Image
; otherwise, it will be covered by the background and not be visible). Also, delete the Item Checkmark
GameObject that is in Dropdown-Template-Content-Item.Item Image
to be 20
x 20
in its Rect Transform.Item Image
over the letter O of Option A of Item Text
, and then move Item Text
to the right so that the icon and text are not on top of each other.Figure 2.41: Setting the Caption and Item images for the Dropdown UI menu component
Template
GameObject by unchecking its Active checkbox at the top of the Inspector. Then run the scene to see your Dropdown with icon images for each menu option.You assigned an image sprite for each option in the properties of the Dropdown GameObject. So for each dropdown option you have both its text name (Hearts, Diamonds etc.) and its corresponding sprite. The UI Image you added as a child of the Dropdown GameObject was assigned to the Dropdown’s Caption Image property – which is used by Unity to show the current selections image at the top of the dropdown menu. The UI Image copy you made named Item Image was made a child of the Item GameObject, and that is used to display the sprites for each option when the rows of the dropdown menu are being displayed.
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:
Figure 2.42: Example of a radar
In the preceding screenshot, we can see two red square blips, indicating the relative position of the two 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.
For this recipe, we have prepared the radar images and terrain textures that you need in folders named Images
and Textures
in 02_14
.
To create a radar to show the relative positions of the objects, follow these steps:
Images
and Textures
folders into the project.20 x 20
by setting the Terrain Width and Terrain Length properties for the Terrain component in the Inspector. Also, set its position to (-10, 0, -10)
so that its center is at (0, 0, 0):
Figure 2.43: Terrain settings for this recipe
Note. We change the size of a Terrain through its Terrain Width and Terrain Length properties in its Terrain component. The Scale property of a Terrain’s Transform component does not affect its size.
SandAlbedo
texture from the imported Textures
folder – find this easily by typing sand
in the search bar. You should now see a new Terrain Layer named NewLayer, and the whole terrain should have been textured with the SandAlbedo
texture.Figure 2.44: Settings for painting the terrain
(2, 0.5, 2)
. Create a Cube tag and tag this GameObject with this new tag. Texture this GameObject with the red image called icon32_square_red by dragging the icon32_square_red image from the Project window over this GameObject in the Hierarchy window.cube
GameObject and move it to Position (6, 0.5, 2)
.(0, 0.5, 4)
. Create a tag called Sphere and tag this GameObject with this new tag. Texture this GameObject with the yellow image called icon32_square_yellow.RawImage-radar
GameObject is selected in the Hierarchy window. From the Images
folder in the Project window, drag the radarBackground
image into the Raw Image (Script) public property’s Texture.RawImage-radar
at the top left using the Anchor Presets item. Then, set both Width and Height to 200
pixels.Blip
.blip-cube
. Assign it the redSquareBlackBorder
texture image file from the Project window. Tag this GameObject as Blip
.yellowCircleBlackBorder
texture image file from the Project window. Tag this GameObject as Blip
.Prefabs
.Prefabs
. You should now see two new prefab asset files in this folder with the same names as the GameObjects.Radar
containing the following code and add an instance as a scripted component to the RawImage-radar
GameObject:
using UnityEngine;
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() {
rawImageRadarBackground = GetComponent<RawImage>();
playerTransform =
GameObject.FindGameObjectWithTag("Player").transform;
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))
CalculateBlipPositionAndDrawBlip (playerPos, targetPos,
prefabBlip);
}
}
private void CalculateBlipPositionAndDrawBlip (Vector3 playerPos, Vector3
targetPos, GameObject prefabBlip) {
Vector3 normalisedTargetPosition = NormalizedPosition(playerPos,
targetPos);
Vector2 blipPosition =
CalculateBlipPosition(normalisedTargetPosition);
DrawBlip(blipPosition, prefabBlip);
}
private void RemoveAllBlips() {
GameObject[] blips = GameObject.FindGameObjectsWithTag("Blip");
foreach (GameObject blip in blips)
Destroy(blip);
}
private Vector3 NormalizedPosition(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) {
float angleToTarget = Mathf.Atan2(targetPos.x, targetPos.z) *
Mathf.Rad2Deg;
float anglePlayer = playerTransform.eulerAngles.y;
float angleRadarDegrees = angleToTarget - anglePlayer - 90;
float normalizedDistanceToTarget = targetPos.magnitude;
float angleRadians = angleRadarDegrees * Mathf.Deg2Rad;
float blipX = normalizedDistanceToTarget * Mathf.Cos(angleRadians);
float blipY = normalizedDistanceToTarget * Mathf.Sin(angleRadians);
blipX *= radarWidth / 2;
blipY *= radarHeight / 2;
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);
}
}
RawImage-radar
GameObject is selected in the Hierarchy. We now need to populate public variables Raw Image Blip Cube and Raw Image Blip Sphere the for the Radar
scripted component in the Inspector.Prefab
folder in the Project panel into the Raw Image Blip Cube public variable in the Inspector. Then drag the blip-sphere Prefab asset file from the Prefab
folder into the Raw Image Blip Sphere public variable. By doing this, you are setting these public script variables to reference these prefabs, allowing the Radar
scripted component to control the display of GameObjects created from these Prefabs at runtime.Figure 2.45: Calculation for the blip method
(0, 5, -10)
, and Rotation (10, 0, 0)
. This will allow you to see the cubes and sphere GameObjects easily when playing the game.Radar
script will draw blips on the radar relative to the position of the GameObject tagged Player
.Capsule-player
, positioned at (0, 1, 0)
, and tag this Player
.Player
!A radar background is displayed on the screen. The center of this circular image represents the position of the player’s character. In this recipe, you 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 in the scene that an object may be from the player so that it can 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
and rawImageBlipSphere
: 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 caches a reference to the RawImage of the radar background image. Then, it 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 that the relative positions for blips can be calculated based on the size of this background radar image. Finally, the size of each blip (blipWidth
and blipHeight
) is calculated using the blipSizePercentage
public variable.
The Update()
method calls the RemoveAllBlips()
method, which removes any old UI RawImage GameObjects of cubes and spheres that might currently be displayed. If we didn’t remove old blips before creating new ones, then you’d see “tails” behind each blip as new ones are created in different positions – which could actually be an interesting effect.
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 of the radar is to be performed by the FindAndDisplayBlipsForTag(...)
method.
This code is a simple approach to creating a radar. It is very inefficient to make repeated calls to FindGameObjectWithTag("Blip")
for every frame from the Update()
method. In a real game, it would be much better to cache all created blips in something such as a List
or ArrayList
, and then simply loop through that list each time.
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:
GameObject
is retrieved.insideRadarDistance
), then the CalculateBlipPositionAndDrawBlip(...)
method is called.This method inputs three parameters: the position of the player, the position of the target, and a reference to the prefab of the blip to be drawn.
Three steps are now required to get the blip for this object to appear on the radar:
NormalizedPosition(...).
CalculateBlipPosition(...).
DrawBlip(...)
and passing the blip’s position and the reference to the RawImage prefab that is to be created there.The NormalizedPosition(...)
method inputs the player’s character position and the target GameObject's
position. It has the goal of outputting the relative position of the target to the player, returning a Vector3
object (actually, a C# struct – but we can think of it as a simple object) with a triplet of X, Y, and Z values. Note that since the radar is only 2D, we ignore the Y-value of the target GameObjects, so the Y-value of the Vector3
object that’s returned by this method will always be 0
. So, for example, if a target was at exactly the same location as the player, the X, Y, and Z of the returned 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 axes 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
), while +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.
The normalized position
The normalized value is one that has been simplified in some way so that its 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 the GameObjects within our insideRadarDistance value, we can map these normalized target positions directly onto the location of the radar image in our UI.
First, we calculate angleToTarget
, which is the angle from (0, 0, 0)
to our normalized target position.
Next, we calculate anglePlayer
, which is 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 seeing what direction they are facing – this is what we are trying to display graphically with the radar.
Our desired radar angle (the angleRadarDegrees
variable) is calculated by subtracting the player’s direction angle from the angle between the target and the 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 a direction of east. 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 these 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 diagram):
Figure 2.46: Calculation for the blip method
In the preceding diagram, alpha is the angle between the player and the target object, “a” is the adjacent side, “h” is the hypotenuse, and “o” is the side opposite the angle.
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 only multiply with half the width since these values are relative to the center of the radar. We then add half the width and the height of the radar image to the blipX
/Y
values so that 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 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 (instantiated) from the prefab and is parented to the radar
GameObject (of which the scripted object is also a component). A reference is retrieved from the Rect Transform component 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 window the background radar image has been located.
This radar script scans 360 degrees all around the player and only considers straight-line distances on 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 from the player’s height.
Also, as presented, this recipe’s radar sees through everything, even if there are obstacles between the player and the target. This recipe can be extended to not show obscured targets by using raycasting techniques.
And, of course, you can replace the 3D capsule with a user-controlled animated character, such as those covered in Chapter 9, Animated Characters.
The following are some useful resources for learning more about working with core UI elements in Unity:
Where there is an eBook version of a title available, you can buy it from the book details for that title. Add either the standalone eBook or the eBook and print book bundle to your shopping cart. Your eBook will show in your cart as a product on its own. After completing checkout and payment in the normal way, you will receive your receipt on the screen containing a link to a personalised PDF download file. This link will remain active for 30 days. You can download backup copies of the file by logging in to your account at any time.
If you already have Adobe reader installed, then clicking on the link will download and open the PDF file directly. If you don't, then save the PDF file on your machine and download the Reader to view it.
Please Note: Packt eBooks are non-returnable and non-refundable.
Packt eBook and Licensing When you buy an eBook from Packt Publishing, completing your purchase means you accept the terms of our licence agreement. Please read the full text of the agreement. In it we have tried to balance the need for the ebook to be usable for you the reader with our needs to protect the rights of us as Publishers and of our authors. In summary, the agreement says:
If you want to purchase a video course, eBook or Bundle (Print+eBook) please follow below steps:
Our eBooks are currently available in a variety of formats such as PDF and ePubs. In the future, this may well change with trends and development in technology, but please note that our PDFs are not Adobe eBook Reader format, which has greater restrictions on security.
You will need to use Adobe Reader v9 or later in order to read Packt's PDF eBooks.
Packt eBooks are a complete electronic version of the print edition, available in PDF and ePub formats. Every piece of content down to the page numbering is the same. Because we save the costs of printing and shipping the book to you, we are able to offer eBooks at a lower cost than print editions.
When you have purchased an eBook, simply login to your account and click on the link in Your Download Area. We recommend you saving the file to your hard drive before opening it.
For optimal viewing of our eBooks, we recommend you download and install the free Adobe Reader version 9.