In this chapter, you will learn how to create 3D widgets, both in the user interface and within the environment. We'll work at the following aspects:
By the end of this chapter, you'll know how to create 3D user interfaces and integrate in-game 3D elements in the environment with lighting effects.
Until now, we've created 2D user interface elements and flat widgets viewed with an orthographic camera, on which the z axis has no influence.
3D user interface elements are viewed with perspectives taken into account, which means that the z axis makes widgets feel "farther away" or "closer to" the camera. Rotation can also be applied, which will make them look like they are a part of the 3D environment.
In this section, we will create 3D user interface elements. First, we need to explain what the 3D UI Root is, and then create one.
It's interesting to separate our 2D and 3D layers in order to make sure you can easily hide or show one of them by simply enabling/disabling the camera that renders them.
Indeed, consider we have UI Root
to hold the 2D UI and another to hold the 3D UI exclusively. If we need to hide the 2D UI and show a 3D pause menu, we can simply perform the following steps:
UI Root
that holds and renders the in-game 2D UI.UI Root
that holds and renders the 3D pause menu.Since UI Root
handles widget scaling depending on the screen, we should only have one UI Root
instance running in the same scene. The NGUI plugin won't let us create a new 3D UI Root
within a scene that already has a 2D UI Root
. This is why we need to disable the InGame2DUI
root for now:
InGame2DUI
and disable it.3D UI
by navigating to NGUI | Create | 3D UI.UI Root (3D)
GameObject.InGame3DUI
.UI Root
component, perform the following steps:Now, let's create a new layer to hold all 3D UI elements:
InGame3DUI
GameObject.Unity will prompt you with a popup, asking if you desire to change the layer for all child objects. Click on the Yes, change children button.
Now that we've set our InGame3DUI
in the 3DUI
layer, we need to make sure this layer is rendered by UICamera
. Perform the following steps:
InGame3DUI
| Camera
GameObject.Camera
component, set Culling Mask to display 3D UI only.Now that our 3D UI Root
is created and configured, we can re-enable our InGame2DUI
root and make sure its culling mask hasn't been changed. We can do so by following the steps mentioned:
InGame2DUI
GameObject and enable it.InGame2DUI
| Camera
GameObject.Camera
, make sure Culling Mask is set to InGame2DUI.Ok, we're now ready to display 3D widgets!
What I call scale calibration for the 3D UI is the process of making sure a fullscreen 3D widget is actually taking the entire screen. We'll need to create a calibration sprite of 1920 x 1080 and see if it fits our Game view correctly:
InGame3DUI
GameObject.We have a virtual screen of 1920 x 1080. With the current 3D UI configuration, you will notice that our 1920 x 1080 sliced sprite is much larger than the screen! This isn't good, and we need to find a way to calibrate our UI's scale.
In order to correct this, we can simply move the camera farther away from the widgets to reduce the UI's display size:
InGame3DUI
| Camera
.0
, 0
, -935
}.Ok, that's better; our 1920 x 1080 sprite now fits the screen without getting out of bounds. You can now delete our calibration, Sprite
, from the scene.
Your game scene's Hierarchy view should now have the elements shown in the following screenshot:
For Unity 4.5 and above, the order of the elements might be different since objects aren't sorted alphabetically anymore.
Now that our UI's scale is calibrated, we can move on and create our first 3D widget.
The first 3D widget we will create is a score counter at the top right of the screen:
As you can see in the preceding image, our score counter has a slight 3D effect due to a y axis rotation of 20 degrees.
Let's start by creating the score counter's label:
InGame3DUI
GameObject.Score
.Ok, we have our new label. Let's configure it now:
InGame3DUI
| Score
| Label
GameObject.Lato
font.Normal
(not Bold
) style.999999
.Right
(click on the right arrow followed by the middle bar).Ok, now that we have our label with its pivot set to the right side, we can add a background sprite that will wrap around it.
Let's add a dark transparent background sprite to make this score counter more visible:
InGame3DUI
| Score
GameObject.Background
.Ok, now let's configure the background's anchor to make sure it wraps around the label:
Score
| Label
GameObject to the Target field.If you add or remove numbers in the score counter label, you'll see that the background adjusts itself to wrap it accordingly.
We can now add a border to this sprite to have more visible edges.
Let's use the background sprite as a base to create a border sprite:
InGame3DUI
| Score
| Background
GameObject.Border
.Score
| Background
to the Anchor Target field.Great, we now have our score counter. We can now place it where it should be.
Let's make sure it's anchored at the top-right corner of the screen and rotate it slightly:
InGame3DUI
| Score
GameObject.anc
to search for components with this name.Now, for the newly attached UIAnchor
component of Score
:
That's it; we now have our 3D score counter at the top-right corner of the screen! Now, let's see how we can make our score increase when needed.
In order to control the score counter's behavior, we'll create a new ScoreController
component that will update the score label when a player's current score is changed.
The score modification feedback must be obvious. We'll make sure it uses a simple scale animation; the score first scales up, increases its value over time, and goes back to its initial scale when it has reached its final value.
First, let's create our new ScoreController component:
InGame3DUI
| Score
GameObject.ScoreController
with your keyboard and hit Enter.ScoreController.cs
script.Within this new ScoreController.cs
script, declare the global variables, as shown in the following snippet:
// We'll need to store the score label private UILabel label; // We'll need to separate the displayed score private int previousScore; // The private _currentScore value private int _currentScore;
Also, add a public currentScore
variable with a custom setter method in order to make sure that each time the score is changed, the previousScore
value is updated and the label's text is refreshed:
// The public currentscore with custom setter public int currentScore { get { return _currentScore; } set { // Update the previous score to current score previousScore = _currentScore; // Update the private _currentScore value _currentScore = value; // Update the label with new score UpdateLabel(); } }
Now, let's make sure variables are initialized correctly at the start. Replace the default Start()
method with the following snippet:
// At start private void Start () { // Get the score counter's label label = GetComponentInChildren<UILabel>(); // Set the currentScore value to 0 currentScore = 0; }
Now that variables are initialized correctly, we can add the UpdateLabel()
method, which is called at the last line of the currentScore
getter method:
// Method to update the score label
public void UpdateLabel()
{
// Launch the coroutine to update the label
StartCoroutine(UpdateLabelRoutine());
}
The above Updat
eLabel()
method starts the UpdateLabelRoutine
coroutine. Let's add this coroutine, which actually updates the label:
// Coroutine that updates the label private IEnumerator UpdateLabelRoutine() { // Scale up the score label to 1.2 UITweener tween = TweenScale.Begin(label.gameObject, 0.1f, Vector3.one * 1.2f); // Wait for the scale tween to finish yield return new WaitForSeconds(0.1f); // Calculate difference between old and new score int scoreDelta = currentScore - previousScore; // While previous score is below current score... while(previousScore < currentScore) { // ...Increase the previousScore value previousScore += Mathf.CeilToInt(scoreDelta * Time.deltaTime * 3.5f); // Set the label to the previousScore value label.text = previousScore.ToString(); // Wait for next frame yield return null; } // When score has reached final score value // Update the score label with current score label.text = currentScore.ToString(); // Wait a little yield return new WaitForSeconds(0.3f); // Re-scale down the score label TweenScale.Begin(label.gameObject, 0.1f, Vector3.one); }
The preceding method first scales up the label, increases its value until it reaches the currentScore
value, and then scales it back down again.
We can now make sure the score increases when the player brings the power source's correct element. Open the PlayerController.cs
script and add this line of code in the UseElement()
method just below the targetFeed.Feed()
instruction:
// Increase Score
ScoreController.Instance.currentScore += 1000;
Hit Unity's play button. Your score will increase by 1,000 points each time a power feed is fed; for example, if you collect and deliver the lightning element to the lightning power feed.
Ok, great! We are now ready to start working on an in-game pause menu.
We'll now create a 3D pause button, as shown:
Ok, let's create this button now. We'll need a Buttons
holder GameObject:
InGame3DUI
GameObject.TopButtons
.Now that we have our TopButtons
holder, we can create the button:
button
.Control – Simple Button
to our new TopButtons
GameObject.Pause
.sca
to search for components with this name.For the attached UISprite
component, perform the following steps:
Top
(middle + up arrow).For the attached UIButton
component, perform the following steps:
255
, G: 255
, B: 255
, A: 255
}.
color to {R: 225
, G: 200
, B: 150
, A: 255
}.120
, G: 120
, B: 120
, A: 255
}.Now, select the child Label
GameObject executing the following steps:
0
, G: 0
, B: 0
, A: 255
}.40
.Pause
Ok, our pause button is looking good. Now, we would like to add a slight 3D effect and position it at the top of the screen:
InGame3DUI
| TopButtons
GameObject.The TopButtons
GameObject is just a container and not a widget; we cannot assign a size or pivot to it. We can correct this by adding the UIWidget
component to it:
InGame3DUI
| TopButtons
GameObject.widg
to search for components.Our TopButtons
is now considered as a widget. We can set its new UIWidget
component's Size and Pivot and make sure it's placed at the top of the screen:
Top
(middle + up arrow).0
, 540
, 0
}.That's it; you should now have the pause button attached to the top of the screen:
Now that we have a pause button, let's create the associated pause menu.
We will now create this specific in-game 3D pause menu:
Let's start by creating the container panel and box.
First, we need to create a new panel to contain the pause menu's background and border:
InGame3DUI
GameObject.PauseMenu
.panel
to search for components with this name.UIPanel
to 1.We can now create the pause menu's background:
InGame3DUI
| PauseMenu
GameObject.Background
.255
, G: 200
, B: 130
, A: 200
}.1100 x 600
.Now, let's add the box's border sprite:
InGame3DUI
| PauseMenu
| Background
GameObject.Border
.UISprite
and the Fill Center option.InGame3DUI
| PauseMenu
| Background
in the Target field.Now that we have a nice yellow border, we can create the title bar.
Let's create the title bar that displays the Pause
label:
InGame3DUI
| PauseMenu
GameObject.Title
.InGame3DUI
| PauseMenu
| Background
GameObject.Title
GameObject.UISprite
.InGame3DUI
| PauseMenu
| Background
in the Target field.The title bar's background is set up. Now, let's add the title bar's label:
InGame3DUI
| PauseMenu
| Title
GameObject.0
, 250
, 0
}.UILabel
component, perform the following steps:
to 65
.
to Pause
.255
, G: 200
, B: 150
, A: 255
}.Ok, good. You should now have a window like this with the following hierarchy:
We can now move on to creating the two pause menu's buttons.
In this section, we'll create the Resume
and Exit
3D buttons.
We can first create the Resume
button by executing the following steps:
InGame3DUI
| PauseMenu
GameObject.Buttons
.Prefab
toolbar navigating to NGUI | Open | Prefab Toolbar.InGame3DUI
| PauseMenu
| Buttons
.Resume
.Configure the attached UISprite
executing the following steps:
Now, for the attached UIButton
component, perform the following steps:
170
, G: 255
, B: 160
, A: 255
}.sca
to search for components with this name.We can now configure the child label executing the following steps:
PauseMenu
| Buttons
| Resume
| Label
GameObject.60
.Resume
.180
, G: 255
, B: 170
, A: 255
}.6
.Now that everything is set for this button, we can create the resume sprite:
PauseMenu
| Buttons
| Resume
GameObject.Icon
.115
, G: 240
, B: 75
, A: 255
}.Great! Now that we have our Resume
button, we can move on to the Exit
button.
Follow these steps to create the Exit
button based on the Resume
button:
PauseMenu
| Buttons
| Resume
GameObject.Exit
.270
, -40
, 0
}.255
, G: 180
, B: 160
, A: 255
}.255
, G: 85
, B: 85
, A: 255
}.75
, G: 0
, B: 0
, A: 255
}.We can now update the child label to red, similar to what we did in the background. Execute the following steps:
PauseMenu
| Buttons
| Exit
| Label
GameObject.Exit
.255
, G: 210
, B: 210
, A: 255
}.We must now change the icon and make sure it's red too:
PauseMenu
| Buttons
| Exit
| Icon
GameObject.255
, G: 180
, B: 180
, A: 255}.Good! Now, let's add a 3D effect to give the impression that our pause menu is displayed on the level's ground:
InGame3DUI
| PauseMenu
GameObject.22
, 0
, 0
}.Great! Our pause menu is now ready. Your Game view and Hierarchy should look like this:
We need to display the pause menu when the pause button is pressed. Let's do this using an alpha tween to gradually change the pause menu's alpha value.
Follow these steps to configure a Tween Alpha
component on PauseMenu
:
InGame3DUI
| PauseMenu
GameObject.alp
to search for components with this name.Tween Alpha
.0.5
.The alpha tween is configured. We must now request it to play when necessary.
We'll use
Play Tween
to play the alpha tween when the Pause
button is clicked on:
InGame3DUI
| TopButtons
| Pause
GameObject.play
to search for components with this name.InGame3DUI
| PauseMenu
in the Tween Target field.The pause menu will be faded in when the pause button is clicked on. We need to make sure it fades out when the resume button is clicked on, using Play Tween
again:
InGame3DUI
| PauseMenu
| Buttons
| Resume
GameObject.play
to search for components with this name.InGame3DUI
| PauseMenu
in its Tween Target field.Good! The pause menu will now fade out and disable itself when the resume button is pressed. We can now hide the pause button when the pause menu is displayed, and display it again when the resume button is pressed. Perform the following steps:
InGame3DUI
| TopButtons
| Pause
GameObject.acti
to search for components with this name.InGame3DUI
| TopButtons
| Pause
(itself) in the Target field.The Button Activate
component enables or disables the state of Target
, depending on the State
bool's value. In the previous steps, we unchecked the State
checkbox, so the pause button will be disabled as soon as it's clicked on.
Now, we can use the same component to re-enable the pause button when the resume button is pressed. Perform the following steps:
InGame3DUI
| PauseMenu
| Buttons
| Resume
GameObject.acti
to search for components with this name.InGame3DUI
| TopButtons
| Pause
button in its Target field.Now, the pause button will be re-enabled when the resume button is clicked on. We need our pause menu to be disabled at the start. Let's do this by executing the following steps:
InGame3DUI
| PauseMenu
GameObject.Hit Unity's play button. You can now hit the Pause button. PauseMenu
fades in as if it were displayed on the level's ground. When you hit the Resume button, the menu fades out and the Pause button reappears.
We can now link the buttons to methods to handle the actual pause and exit behaviors.
We'll first add the necessary methods, and then link our buttons to them.
Open the GameManager.cs
script and add the following methods:
// Method called when pause button is pressed public void PausePressed() { // Request pause SetPause(true); } // Method called when resume button is pressed public void ResumePresssed() { // Request unpause SetPause(false); } // Method called when Exit button is pressed public void ExitPressed() { // Request to return to main menu ReturnToMenu(); }
The above methods will be linked to the buttons shortly. Before we do this, we need to add the required ReturnToMenu()
and SetPause()
methods:
// Use this method to return to the main menu public void ReturnToMenu() { // Unpause the game ResumePresssed(); // Destroy the player instance Destroy(PlayerController.Instance); Transform mainMenu = null; mainMenu = MenuManager.Instance.transform.FindChild("Main"); if(mainMenu != null) { // Set the MenuManager | Main scale to 1 mainMenu.localScale = Vector3.one; } // Launch the EnterMenu couroutine StartCoroutine(EnterMenu()); } // EnterMenu Coroutine private IEnumerator EnterMenu() { // Show the main menu UI by enabling cam MenuManager.Instance.uiCamera.enabled = true; // Fade in the UI background TweenAlpha.Begin(MenuManager.Instance.background, 0.5f, 1); // Wait for the tween to finish yield return new WaitForSeconds(0.5f); // Load the Menu scene now Application.LoadLevel("Menu"); } // Use this method to pause / unpause game public void SetPause(bool state) { // Set the timescale to appropriate value Time.timeScale = (state ? 0 : 1); }
Good! The preceding code pauses the game when SetPause(true)
is called, and resumes it on SetPause(false)
.
The ReturnToMenu()
method destroys the player, shows our main menu by resetting its scale to 1, and launches the EnterMenu()
coroutine.
This coroutine re-enables the main menu's camera and fades back the UI background before loading the Menu
scene.
We can now move on to linking our buttons to their appropriate methods.
Follow these steps to link our pause, resume, and exit buttons to their respective methods:
InGame3DUI
| TopButtons
| Pause
button.UIButton
component:GameManager
to the On Click Notify field.InGame3DUI
| PauseMenu
| Buttons
| Resume
button.UIButton
component:GameManager
to the On Click Notify field.InGame3DUI
| PauseMenu
| Buttons
| Exit
button.UIButton
component:GameManager
to the On Click Notify field.Now, load the Menu
scene and hit Unity's play button. You can launch the game and hit the pause button. The game pauses, and you can either resume the game or go back to the main menu using the two buttons!
We can add a simple feature; when the player hits the Escape key, the game pauses or resumes. We'll do this using the UIKey Binding
component:
Game
sceneInGame3DUI
| TopButtons
| Pause
and PauseMenu
| Buttons
| Resume
buttons.key
to search for components with this name.UIKey Binding
to escape.You can now hit Unity's play button; you'll see that hitting Escape will pause and resume the game anytime!
Great! We have a slight problem; interactive elements are still active while the game is paused. We need to find a way to easily prevent the player from moving or right-clicking on a power source, or changing the character's destination while the game is paused.
We will create what can be referred to as an interaction override. Here, a trigger collider is placed over our game's interactive elements, which will prevent them from being hit by the interaction raycast of UICamera
. Since the 3D UI with NGUI uses the Depth
value to check collisions, we should also add the UIWidget
component to the interaction override.
We can create it right now for the pause menu:
InGame3DUI
| PauseMenu
GameObject.Override
.box
to search for components with this name.wid
to search for components with this name.We now have a box collider and UI Widget
on our Override
GameObject. Configure them as follows in order to make sure they take up the entire screen:
InGame3DUI
| PauseMenu
| Override
GameObject.UIWidget
component:Box Collider
component:Good! Hit Unity's play button. When you pause the game, interactive elements such as power sources and the ground become inactive because the raycast of UICamera
first touches the collider of Override
.
Now that we have our nice 3D pause menu, let's see how we can add a 3D text or sprites directly onto the environment's objects.
18.189.189.67