Learning NGUI for Unity
上QQ阅读APP看书,第一时间看更新

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:

UIRoot
  1. Display your Game view.
  2. Click on the current aspect ratio (1), which is Free Aspect here.

Select the 16:9 (2) aspect ratio. Ok. Now that we have a 16:9 aspect ratio for our Game view, we can continue.

Scaling styles

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:

  1. 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.
  2. 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.
  3. ConstrainedOnMobiles: This is the Flexible mode on the desktop and Constrained mode everywhere else.

The UIRoot component has different parameters depending on the selected Scaling Style. Let's start with the default Flexible scaling style.

Flexible

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:

Flexible

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:

Flexible

The parameters are as follows:

  1. 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 with Content Height set to the screen's height.
  2. 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.
  3. 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.
  4. 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:

  1. Select our Sprite GameObject.
    • Change its Size to 425 x 240
  2. Select our UI Root GameObject.
    • Set the attached Minimum Height parameter of UIRoot to 240

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.

Constrained

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:

Constrained

Select our UI Root GameObject in the Hierarchy view. For its attached UIRoot:

  1. Set Scaling Style (1) to Constrained.
  2. Change Content Width (2) to 320.
  3. Set CContent Height (3) to 240.
  4. 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.

ConstrainedOnMobiles

By setting your scaling style to ConstrainedOnMobiles, your UI will be Flexible (pixel-perfect) on desktop and Constrained everywhere else.

Configuration

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:

  1. Set Scaling Style to Constrained.
  2. Change Content Width to 1920.
  3. Set Content Height to 1080.
  4. 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:

  1. Navigate to Edit | Project Settings | Player.
  2. In the Player Settings (Inspector view), click on Resolution and Presentation.
  3. Click on Supported Aspect Ratios (the last one, at the bottom).
  4. 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.

Note

Wherever UIPanel is attached, a kinematic Rigidbody is also automatically added; don't worry, it's an optimization trick from NGUI.

The UIPanel component has three basic parameters:

UIPanel

These parameters are as follows:

  1. Alpha: This the panel's global alpha value. It affects all child widgets and panels.
  2. 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 of 0 and holds WidgetA with a Depth of 10. PanelB has a Depth of 1 and holds WidgetB with a Depth of 0.

    In the previous situation, even though WidgetA has a higher Depth value, it will be displayed behind WidgetB because PanelB has a higher Depth value than PanelA.

  3. 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. The Offset and Center parameters are available to adjust the texture's position, and the Size 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. The Softness 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.

Orthographic Camera

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.

Note

Here, we're talking about camera rendering depth, not NGUI widget Depth value. The camera's depth defines its rendering order. The camera with the highest depth will be rendered over the others.

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:

Orthographic Camera

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.

UICamera

We'll now explain the purpose of UICamera, review its parameters, and configure it.

Purpose

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:

  1. 3D perspective main camera that renders the game. Layer: Default.
  2. Orthographic camera for in-game 2D UI elements. Layer: 2DUI.
  3. Separate 3D perspective camera used for 3D menus. Layer: 3DUI.

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.

Note

The in-game objects you wish to also receive NGUI events require Collider because these events are triggered by raycasts. Therefore, if the object has no Collider attached to it, the raycast will simply go through it.

Ok, now that we know the purpose of UICamera, we can review its parameters.

Parameters

These are the 11 parameters of UICamera:

Parameters

Let's see what the above parameters correspond to:

  1. Event Type: Select which event type this camera will send. Types that concern the UI sort events depending on their Depth value, whereas in World types events are sorted depending on their distance from the Camera 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.
  2. 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.
  3. Events go to…: Here, you can select whether the objects you wish to receive events require either Colliders or Rigidbodies.
  4. 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.
  5. 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.
  6. Sticky Tooltip: This enables or disables the sticky tooltip option:
    • Enabled: The tooltip disappears when the mouse moves out of the widget's collider
    • Disabled: The tooltip disappears as soon as the mouse moves
  7. Tooltip Delay: This defines the required stationary time in seconds before the widget's tooltip is displayed.
  8. 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 of Camera 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 as Camera can see, defined by its Far Clipping Plane parameter.
  9. 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
  10. 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 sends OnDrag()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 (the OnClick()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
  11. 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

    Note

    You can edit Unity input axes at any time by navigating to Edit | Project Settings | Input.

Now that we have reviewed the parameters of UICamera, we can configure it for our project.

Configuration

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:

  1. Set the Event Type parameter to 3DUI because this camera will be used for both 2D and 3D UI interactions.
  2. Set the Event Mask to the 2DUI layer only since our UI will reside on it.
  3. 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.