Hi there! I’ve been thinking recently about my experience as a Unity developer and decided that it’s high time to share what I’ve learned about creating interfaces in Unity. So if that’s what you were looking for, do read on.
I must warn you that this series of articles won’t give you an ultimate guide on how to work with HUD. Here I just like to share with you my thoughts and observations. But if you’d like to share your own experience or best practices, please do so in the comment section below.
Table of contents
In general, game UIs are fully functional separate elements that don't directly influence gameplay. Their main task is to provide users with useful game information they might need. Technically speaking, you can take something as simple as Arial font and use it to display health, ammo and score (if you are making a shooter). Yes, it would ruin the aesthetics (some especially sensitive users would even claim they needed to see an eye specialist after taking a look at your game) but one can’t argue that all the needed information was out there, loud and clear.
But those are rather drastic methods. Naturally, the best interface is the one that’s not intrusive and draws user’s attention only when he or she needs it. Showing ammo on the weapon or health level on the protagonist (hi there, Dead Space!) are decisions made by the game designer. But we, game developers, are there to implement all the unusual ideas in actual games. So what interface creation capabilities are there in Unity?
What is Canvas?
Canvas for Unity UI is an area that includes all the elements of the user interface. So when we create a new game object, Canvas for it is created automatically.
Starting from version 4.6 a new version of Unity UI is used for working with elements of user interface. HUD elements (just like simple objects) are organized in hierarchies with a root object containing Canvas. Meanwhile place in the hierarchy determines render order: objects that are on the lowest level and rendered last and therefore are placed above all others.
It doesn’t matter, what kind of application you are developing: both 3d and 2d games are using the same logics when working with UI. The same is true for the platform. It doesn’t matter whether it’s Web with HTML5 or Android game — it would be enough to describe just one Canvas.
Canvas display modes
Render Mode defines where Unity UI Canvas is displayed (it can be screen or world space). This parameter has three different modes:
- Screen Space — Overlay — Canvas Canvas will be rendered on top of all scene elements. If to draw an analogy, Canvas with elements is like a window with stickers in the train. The world behind the window is changing (camera moves in Unity) but stickers stay the same. This mode changes Canvas when the size of the window is changed. It is perfect for static information, such as score or controls.
- Screen Space — Camera — it’s the same window but instead of stickers it has a three-dimensional collage (it doesn’t matter whether it’s 2d or 3d game). It is also placed on top of all scene elements but in this case Canvas elements follow the laws of perspective (if camera projection type is Perspective). This mode is ideal for menus or other elements that should look three-dimensional.
- World Space — mode, in which Canvas is an element of the scene and can act as a subsidiary element of other Game Objects. This mode is perfect for characters’ speech bubbles, ammo indicators for weapons etc.
Placing elements in Canvas
For layout purposes every UI element is shown as a rectangular. Two major components for working with them are Rect Tool and Rect Transform. The last one is a new component that has (alongside the usual fields) a special Anchors field.
Anchors define how elements are anchored to the parent element. Component has 4 anchors, each of them corresponds to one of the vertices of the element. Position and size of the element is calculated based on the distance between the vertex and the anchor and position of the anchor. Position of the anchor is determined as percentage of parent element. Therefore when all four elements are located in one place, the size of the element is constant. If anchors belonging to one plane (top and bottom or left and right) are located in one point, the element is stretched along that axis. If anchors belonging to one plane are NOT located in one point, the percentage position of each anchor will be calculated and added to the distance to vertex (it is not changing).
Sounds pretty complicated, doesn’t it? But fear not — spend 10 minutes in the editor and you will learn the ropes of it.
It is also worth mentioning that apart from anchors Rect Transform also has Min, Preferred and Flexible Width and Height parameters that you can redefine using Layout Element component (although I personally don’t use it).
By the way, developing Canvas for new version Unity UI has several interesting features. One of them is turning off all the UI to simplify work with the scene. Use Layer menu option in the Editor to do it.
At first it seems that Rect Transform provides enough functionality and you won’t need anything else. But recently while writing a custom selector for Facebook, I was required to group elements in a certain way.
When developing for mobile devices (if you aren’t working with iOS exclusively), one comes across the problem of device segmentation. It means that his or her game will be opened on a variety of different devices: 2'' or 9" smartphones or even tablets. What should we do considering it? We have a couple of options here.
Developing for mobile devices
Why not Anchors?
The first thing that comes to mind is anchors. Yes, it’s a good option if you have simple screens without a number of child elements, animations and other user attractions. But when using this type of layout you should remember that it’s size that changes, not the visual scale of the elements. Consequently Best fit component becomes active in text elements and it also becomes necessary to control aspect ratio and anchoring to certain parts of the screen or other elements. That’s what worked out for me (this variant isn’t very elegant but it works!).
UI package has Aspect Ratio Fitter component. Here’s how it works: depending on type and changes in size of the parent element this component changes size of its own Game Object keeping aspect ratio.
By the way, there’s another component, Content Size Fitter, that works in reverse: it changes the size of the element depending on its content. It is usually used with child Game Objects containing Text components but it also works perfectly with Layout Element and Grid Layout Group.
Canvas Scaler benefits
Currently I’m using Canvas Scaler since in this case component is added automatically when creating Canvas in Unity UI. The main benefit of using Canvas Scaler is that all child elements are scaled (and not just change their size). This way font size in all the texts is not changed and all elements that are shifted by few units will look the same on all screens.
Canvas Scaler can work in one of three modes:
- Constant Pixel Size — Game Object keeps its size in pixels independent of the screen size.
- Constant Physical Size — Game Object keeps its physical size independent of the screen size and its aspect ratio.
- Scale With Screen Size — Game Object is scaled together with the screen.
The last element is my favorite since it allows to forget about the screen fragmentation. You just choose the target resolution and Canvas will change its size in an optimal way. That’s why I’d like you to look at it more closely.
You already know that this mode has its own target aspect ratio that Screen Match Mode uses to change the current Canvas. Match mode can have one of the following types:
- Match Width Or Height — scales Canvas depending on the ratio between width and/or height and actual device width and height.
- Expand — scales Canvas to include the wider side (Canvas Size will never be smaller than specified).
- Shrink — scales Canvas to include the smaller side (Canvas Size will never be bigger than specified).
And although each of the modes has its own advantages, Match Width Or Height is still my personal favorite. This mode has another useful feature: it is possible to specify which of the axis would be used as target and which one would be used as a reference one (all Canvas will be increased depending on width, height or both).
It is implemented using Match parameter, where float [0;1] variable is used to define aspect ratio. If float = 0, changes will be made according to width, if float = 1 — according to height. When the target aspect ratio is in a landscape mode, if Match = 0, we get Expand effect, if Match = 1 — Shrink (in portrait mode everything is vice versa: if Match = 0, we get Shrink, if Match = 1 — Expand).
The last Canvas Scaler parameter is Reference Pixels per Unit that defines how many pixels are in one unit of the UI element. Its default value equals 100 meters. It works with Image components so if in your scene every meter equals 100 pixels of the image and you are using Unity physics, this value is what you need.
Creating custom selector
As I’ve already said, in my case the job was to write a custom selector for Facebook. We needed a dynamically generated list from which the elements could be deleted on the go. And although working with Facebook API is a separate topic, let’s end this new version Unity UI review with Layouts.
As you can see, we are already familiar with some of the components. So let’s familiarize ourselves with those we don’t know anything about.
This component is very easy to configure but nonetheless it is very powerful. It allows you to manage properties of all daughter elements of the object. It is useful when we need to make certain part active and forbid to react to user interaction (through Raycast).
For better understanding create two identical panels with buttons and check boxes. Add this component to one of them and try to change values of the parameters.
And, finally, the most interesting part.
HorizontalLayoutGroup and VerticalLayoutGroup
Two components place all daughter components horizontally and vertically, one after another. You can specify both padding and spacing for daughter elements. Objects could also be specified relatively to the edges of the parent object.
Taking into account that elements will be located only in one plane (horizontal or vertical), the last parameter (it includes two check boxes) allows to stretch all the daughter to match the height or width of the parent element.
If you need to create a table including uniform elements, that’s exactly what you should use! It has padding and spacing (just like the previous elements), but we need to specify dimensions of the daughter element. They will be used for changing all objects we add to the parent element.
Then we need to specify StartCorner that will be used to define position of the first element and StartAxis that will be used to specify filling axes (for rows and columns). If you choose Vertical, elements will be used to fill the current column until the edge of the parent element is reached. Then the next column will be filled. Everything is vice versa with Horizontal: parent element is filled by rows. After we’ve specified where and how to add elements, it would be only logical to specify their aligning.
The last parameter is Constraint. It defines whether this layout has a certain amount of columns and rows or the amount of columns and rows is calculated based on the size of the parent element. It is worth remembering that GroupLayout, unlike all the other layout components, pre-defines size of the daughter elements with Cell Size (it doesn’t care about Min, Preferred and Flexible sizes).
Aside from that you can combine GridLayoutGroup with ContantSizeFitter. But it was unnecessary for me. Judging by the official documentation, it shouldn’t be a problem.
Here’s the end result for my case:
Every element was added automatically initializing from the prefab. As you can see, we can’t change size and position of the element since they are defined in GridLayoutGroup.
By the way, the element was created separately from the panel that was required to further retain it. All I needed to do was to copy size of the element into the CellSize component of GroupLayout. To hard-specify size of the element I specified Match Width Or Height scale type for CanvasScaler.
That’s all about the capabilities of the new UI. This article is an attempt to systematize the new experience but if it turns out to be useful for someone else, I would be more than happy.
So Long, and Thanks for All the Fish.