Under the hood
When we added UI Root
at the beginning of the chapter, it created both UIRoot
and Camera
GameObjects.
Now that we have a sprite and a label within our scene, we can properly understand what the purposes of these two mysterious GameObjects are.
Select our UI Root
GameObject in the Hierarchy view. You can see that the UIRoot
and UIPanel
components are attached to it. Let's see what UIRoot
's purpose is.
UIRoot
The UIRoot
component scales widgets down to keep them at a manageable size inside the scene view. It also handles the different scaling styles.
Your Game view's aspect ratio might be set to Free Aspect or another value. For us to have the same results during the explanations to come, set it to 16:9, like this:
Select the 16:9 (2) aspect ratio. Ok. Now that we have a 16:9 aspect ratio for our Game view, we can continue.
Select our UI Root
GameObject in the Hierarchy view. The first parameter of its attached UIRoot
component is Scaling Style. It defines how your UI reacts when the screen resolution is changed. There are three different scaling styles available:
Flexible
: The UI is in pixels and remains pixel-perfect. A 300 x 300-pixel image always takes 300 x 300 pixels onscreen regardless of the resolution. No stretching or scaling occurs.Constrained
: The UI is not pixel-perfect. An image taking 30 percent of the screen will always take 30 percent of the screen, regardless of the resolution. The UI is scaled up or down to fit the screen's height, width, or both, depending on the parameters you choose.ConstrainedOnMobiles
: This is theFlexible
mode on the desktop andConstrained
mode everywhere else.
The UIRoot
component has different parameters depending on the selected Scaling Style. Let's start with the default Flexible
scaling style.
This mode ensures that your UI elements remain at the same size in pixels (pixel-perfect) regardless of the resolution or Dots Per Inch (DPI). Here's an illustration of that:
The 320 x 240 widget shown in the preceding figure (W1) appears small on a 1920 x 1080 screen (S1) and large on a 640 x 480 screen (S2) because it's in Flexible
(pixel-perfect) mode.
With the preceding illustration, if your screen is smaller than 320 x 240, your widget is not entirely visible. That's where the other parameters shown in the following screenshot come in:
The parameters are as follows:
- Minimum Height: This is an important parameter to keep our UI from being cropped on small screens. When the screen's height is below this value, your UI is scaled down to fit. It will then be as if Scaling Style was set to
Constrained
withContent Height
set to the screen's height. - Maximum Height: This is similar to Minimum Height, but for the screen's maximum height. When the screen's height is above this value, your UI is scaled up to fit.
- Shrink Portrait UI: If your game or app can change orientation (landscape or portrait), check this option—the UI will be scaled down to fit the screen.
- Adjust by DPI: Enabling this option will make your pixel-perfect UI take the screen's DPI into account. In practice, NGUI will believe that these two screen configurations are identical:
- Resolution: 1280 x 720—200 DPI
- Resolution: 1920 x 1080—400 DPI
Here's a practical example to illustrate the pixel-perfect Flexible
behavior:
- Select our
Sprite
GameObject.- Change its Size to 425 x 240
- Select our
UI Root
GameObject.- Set the attached Minimum Height parameter of
UIRoot
to240
- Set the attached Minimum Height parameter of
Now, resize your Game view window to a larger screen: when it's more than 240 pixels high, the sprite's always displayed at 320 x 240 pixels. It's pixel-perfect and crisp.
Resize the Game view to a small size: when it's less than 240 pixels high, the UI is scaled down to avoid it being cropped. That's the purpose of the Minimum Height parameter.
The same principle applies to MaximumHeight
.
The Constrained scaling style is the opposite of pixel-perfect: you set Content Width and Content Height for the virtual screen, and your UI will rescale itself up or down to fit.
In practice, it means that a widget taking 100 percent of the screen will always take 100 percent of the screen regardless of the resolution. Let's try it now. The following screenshot shows the Constrained scaling style:
Select our UI Root
GameObject in the Hierarchy view. For its attached UIRoot:
- Set Scaling Style (1) to Constrained.
- Change Content Width (2) to 320.
- Set CContent Height (3) to 240.
- Check the Fit option for Content Height.
Our screen is now always considered as having a height of 240, which is our sprite's height; it is now taking all the screen.
Resize the Game view to a large view. You'll see that, at all screen sizes and resolutions, our sprite always takes 100 percent of the screen's height (and width, if it's a 16:9 resolution). That's the Constrained mode: the UI is scaled proportionally to the screen's height based on the referenced Content Height.
For the purpose of this book, we'll set up a simple configuration that will enable us to have the same result on all screens. We'll set our scaling style to Constrained, with Content Height of 1080.
This configuration ensures our UI looks beautiful on 1920 x 1080 screens; it is scaled up or down on higher or lower resolutions and still look great.
Select our UI Root
GameObject in the Hierarchy view. For its attached UIRoot
component, perform the following steps:
- Set Scaling Style to Constrained.
- Change Content Width to
1920
. - Set Content Height to
1080
. - Check the Fit option for Content Height.
The 1920 x 1080 resolution has a 16:9 aspect ratio. We'll make sure we only authorize this aspect ratio to avoid having cropped screen sides. Perform the following steps:
- Navigate to Edit | Project Settings | Player.
- In the Player Settings (Inspector view), click on Resolution and Presentation.
- Click on Supported Aspect Ratios (the last one, at the bottom).
- Uncheck all aspect ratios, except the 16:9.
Now that we have understood and configured our UIRoot
, let's talk about UIPanel
.
UIPanel
Select our UI Root
GameObject. Below the UIRoot
component, you'll find UIPanel
.
UIPanel
acts like a widget container—it creates the geometry. All child widgets will be rendered by this panel in one single draw call, if they all use the same atlas.
You might have more than one panel if you want to separate your UI, but keep in mind that a new panel equals a supplementary draw call.
The UIPanel component has three basic parameters:
These parameters are as follows:
- Alpha: This the panel's global alpha value. It affects all child widgets and panels.
- Depth: This is used to display an entire panel (and its child widgets) in front or behind another. It works in the same way as the other widgets' Depth parameter.
Note
Keep in mind that panel depth takes precedence over widget depth. In order to understand this clearly, consider the following situation:
PanelA
has a Depth of0
and holdsWidgetA
with a Depth of 10.PanelB
has a Depth of1
and holdsWidgetB
with a Depth of0
.In the previous situation, even though
WidgetA
has a higher Depth value, it will be displayed behindWidgetB
becausePanelB
has a higher Depth value thanPanelA
. - Clipping: Clipping consists in displaying only part of the panel using a mask that can be either a rectangle or a texture. Choose one of the four available clipping options:
None
: No clipping occurs—the entire panel is visible.Texture Mask
: You can select a texture to use as a mask to clip the panel. TheOffset
andCenter
parameters are available to adjust the texture's position, and theSize
parameter lets you define a width and height for the texture mask.SoftClip
: Four new parameters appear to customize a clipping rectangle. Content outside this rectangle is not displayed. TheSoftness
parameter is an area that smoothly fades out its contents.ConstrainButDon'tClip
: You can define a clipping rectangle, but no clipping occurs. Can be useful if you need to delimit an area without hiding its contents.
You can set the Clipping to None and leave default values. We'll talk about the advanced parameters when we need them. Now, let's talk about the camera.
The camera system
Select our UI Root
|Camera
GameObject. You can see that it has both a Unity orthographic Camera
and a UICamera
component.
The orthographic (2D) Camera
is used to view our widgets and render them over the Main Camera
(3D) used to render the actual scene.
This is achieved by setting the rendering depth of Main Camera
to -1
, while the camera used to render NGUI widgets has a depth of 0
with the Clear Flags parameter set to Depth Only.
All these steps were executed automatically on the creation of a new 2D UI. Hence, we can see the widgets we created, rendered over Main Camera
.
The culling mask of the orthographic camera must be set to only display our 2D UI layer. Let's create one right now.
Click on the button to change our GameObject's Layer (1), and select Add Layer... (2). This is shown in the following screenshot:
The Inspector view now displays the Layer menu. Next to User Layer 8, type in our new layer's name: 2DUI
.
Now, reselect our UI Root
GameObject in the Hierarchy
view. In the Inspector view, you can notice that our UIRoot
component and all its children are on the 2DUI
layer, and the Culling Mask parameter of Camera
is set to 2DUI only.
Now that we have our separate layer to hold our 2DUI, let's see what UICamera
is.
We'll now explain the purpose of UICamera
, review its parameters, and configure it.
This component sends out messages concerning events triggered on UI elements viewed by the camera it's attached to. For example, events such as OnClick()
and OnHover()
can be triggered on a button when the player clicks or hovers it.
You might have multiple cameras if needed. Here's an example with three different cameras:
The afore mentioned cameras used for UI (cameras 2 and 3) need the UICamera
component to interact with UI elements. The component can be added to the game's main camera (camera 1) if you want your 3D in-game objects to receive NGUI events too—they must have a Collider
component attached to them.
Ok, now that we know the purpose of UICamera
, we can review its parameters.
These are the 11 parameters of UICamera:
Let's see what the above parameters correspond to:
- Event Type: Select which event type this camera will send. Types that concern the
UI
sort events depending on their Depth value, whereas inWorld
types events are sorted depending on their distance from theCamera
component:3D World
: This is used to interact with 3D-world GameObjects. Select it if this is your 3D game's main camera.3D UI
: Option used for interaction with 3D user interfaces.2D World
: Select this option if this is your 2D game's main camera.2DUI
: This is used for interacting with the 2D UI.
- Event Mask: Select the layer on which the event raycast is triggered. For example, setting it to Everything will result in all objects with
Colliders
receiving these events. - Events go to…: Here, you can select whether the objects you wish to receive events require either
Colliders
orRigidbodies
. - Debug: This enables or disables debug mode. This option is useful when you have unwanted behavior. When enabled, the currently hovered object is displayed in the top-left corner of the screen.
- Allow Multi Touch: This enables or disables simultaneous touches. This is mandatory if you want to use pinch-to-zoom or other such gestures on mobile platforms.
- Sticky Tooltip: This enables or disables the sticky tooltip option:
Enabled
: The tooltip disappears when the mouse moves out of the widget's colliderDisabled
: The tooltip disappears as soon as the mouse moves
- Tooltip Delay: This defines the required stationary time in seconds before the widget's tooltip is displayed.
- Raycast Range: A raycast is an invisible ray that is cast from one point towards a specific direction and is stopped if it encounters another object. The
UICamera
uses raycasts from the mouse or touch position towards the forward direction ofCamera
to detect collisions and handle events. You might set the range of this raycast if you need to limit the interaction to a certain range. The default -1 value implies that the raycast's range will be as far asCamera
can see, defined by its Far Clipping Plane parameter. - Event Sources: You can specify which events this camera listens to:
- Mouse: This is used for mouse movements: left/right/middle click, and scroll wheel
- Touch: This is used for touch-enabled devices
- Keyboard: This enables keyboard input. It uses the
OnKey()
event - Controller: This enables support for joysticks or game controllers
- Thresholds: These values come in handy when you want to specify the minimum values before a particular event is triggered:
- Mouse Drag: When a mouse button is pressed (the
OnPress()
event is triggered), this value determines how far in pixels the mouse must move before it is considered a drag, and sendsOnDrag()
events to the dragged object - Mouse Click: When a mouse button is pressed (the
OnPress()
event is triggered), this value determines how far in pixels the mouse can travel before the button release has no effect (theOnClick()
event is not triggered) - Touch Drag: This is the same as Mouse Drag, but for touch-based devices
- Touch Tap: This is the same as Mouse Click, but for touch-based devices
- Mouse Drag: When a mouse button is pressed (the
- Axes and Keys: These parameters let you assign Unity input axes and keys to NGUI's input system.
- Horizontal: This is the input axis for horizontal movement (the left and right key events)
- Vertical: This is the input axis for vertical movement (the up and down key events)
- Scroll: This is the input axis for scrolling
- Submit 1: This is the primary keycode for validation
- Submit 2: This is the secondary keycode for validation
- Cancel 1: This is the primary keycode for cancel
- Cancel 2: This is the secondary keycode for cancel
Now that we have reviewed the parameters of UICamera
, we can configure it for our project.
We will now configure our UICamera
component for this specific camera so that it suits our project and our future UI. Select our UI Root
| Camera
GameObject, and then:
- Set the Event Type parameter to 3DUI because this camera will be used for both 2D and 3D UI interactions.
- Set the Event Mask to the 2DUI layer only since our UI will reside on it.
- Set Events go to... to the Colliders value because our widgets will have
Colliders
attached to them.
Good. We are now ready to create more interactive user interface elements.