UI Design Objects & Tips

This topic is a continuation of the UI Design Starter Guide. It covers the various UI objects not touched on in the guide, and also gives some tips. It is recommended that you read the UI Design Starter Guide first so that you can fully understand what is written here.

UICorner

This is an object that allows us to easily create rounded corners on elements and define the size of these corners using a UDim value in Scale and Offset. Pretty useful, can also be used to create circles (scale value of 0.5+ on a square object will accomplish that). Be aware that it may negatively impact performance if used too much, as it’s a shader that gets applied on top of the original rendered object.

UIGradient

This object isn’t very complicated, you are best to experiment with this yourself. In summary, it allows you to apply a linear gradient over frames, text, and images. You can also rotate the gradient, offset it (using an Offset property that is a Vector2 in Scale), and set its transparency.

The transparency of the UIGradient will affect the transparency of the object itself which is pretty useful. You can now do some pretty cool things with text and images.

Unfortunately the gradient is indiscriminately applied to everything - text, text stroke, background, and borders in a TextLabel for example - so you will need to use multiple objects if you want multiple gradients on a single element.

ViewportFrame

This object is fairly complicated. It lets you display 3D objects in your GUIs. To display a 3D object, you must do all of the following:

  • Place a Camera inside the ViewportFrame

  • Place an object inside the ViewportFrame (e.g. Model, Part)

  • Set CurrentCamera property of the ViewportFrame to the Camera you placed inside it

  • Set the camera’s CFrame so that it can see the object (this can be difficult to do if you’re not a scripter)

    • You want to set the camera’s position to somewhere outside of the object, and then have it look towards the model e.g. camera.CFrame = CFrame.new(part.Position + Vector3.new(0, 10, 20), part.Position)

You can customize how things look using the properties in the ViewportFrame, and also the properties of the camera. For example, I have found that I have a better experience viewing 3D objects in 2D when the Camera FieldOfView is set a value lower than the default 70 (when FOV is lowered, Camera will need to be moved further away from the object).

ViewportFrames use their own fake world, so things like physics and complex lighting are not supported. Because physics are not enabled, you couldn’t play animations in a ViewportFrame… until recently. There is now a WorldModel object that allows you to apply some physics (and animation) to a specific model. (To be clear, WorldModel is meant to be used like a Model.) So now if you want to play animations on a Character in a ViewportFrame, you can just change the Model to a WorldModel and then it should work fine.

One final thing to note that is when the game starts, the CurrentCamera property of ViewportFrames is cleared, even if there is a Camera inside it. You will need to set it manually when the game runs using a script.

UIListLayout

This layout puts all GUI elements inside its parent into a list. The list direction and order can be modified with UIListLayout’s FillDirection property and GUI elements’ LayoutOrder property. It can be used for making lists, or for dynamically positioning related elements. This affects only the position of GUI elements.

For example, you are making a currency icon image and a currency label - and when the currency label text changes you want both the label and icon to be recentered. You can accomplish this by putting them both inside an invisible frame, adding a UIListLayout, setting the properties so it appears how you like it, then when the game runs you will only need to update the size of the currency label when the text being dispayed changes, and UIListLayout will automatically update based on that new size.

UIGridLayout

This layout is like a more advanced UIListLayout that automatically puts everything into a grid instead of a list. You need to manually specify the size of each object in the grid, which has led to some difficulty when it comes to maintaining aspect ratio. Fortunately you can use a UIAspectRatioConstraint to maintain aspect ratio of grid cells by parenting said constraint inside the UIGridLayout.

UIPageLayout

Another useful layout, it essentially speeds up the process of making menus and can also handle transition animations. For scripters, this object has four methods for navigating the UIPageLayout; JumpTo(pageObject), JumpToIndex(pageObjectIndex), Next, and Previous. The ‘jump’ methods let you say exactly what object the layout should show. Note that for JumpToIndex, pageObjectIndex starts at 0 and is not the LayoutOrder.

Note that if what you’re doing is really simple, it’s better to just manually set the Visible/Enabled properties of elements when making menus as it’s not a good use of your time to figure out how the object works and work around its behavior in studio.

What is the behavior in studio? Well essentially if you want see a menu that isn’t currently visible in the UIPageLayout, you’re gonna need to mess around with Visible / LayoutOrder properties and force the UIPageLayout to properly update (which it doesn’t do by default, you can force it by cutting and pasting the object). When doing UI design this is a bit of a pain.

UITableLayout

I’ll be straight, this layout kinda sucks. It is somewhat useful if you know how to use it, but anything more than its basic behavior cannot be modified and you will need to put a lot of effort into learning how it works (like I had to in writing this guide, as I had never bothered either). To compound this tables in the style this layout provides aren’t wanted very often anyway so there is little benefit. It’s just not worth trying to mess around with an object that doesn’t quite get exactly what you want where you could just make it yourself exactly how you want. Now, with that being said, one day it might be useful to you, so I’ll explain how it works.

Example layout

The UITableLayout works two layers deep instead of a single layer like other UI objects. What I mean by this is, it uses not only the children of its parent, but also the children of all of those children. The first layer, the children of its parent, is kind of like a container layer whose job is to group certain elements into rows or columns. In the example layout, you can see that there are three columns, and each of them have a different color (red, green, blue). These columns are the first layer. The second layer deals with the actual contents of the table, and in this case is represented by the TextLabels which are the children of the children of the parent. The second layer is what forms the rows in this case.

Explorer view

image

Colored frames as the columns, TextLabels as the rows.

With that out of the way, let’s look at how you can modify the layout’s properties.

The MajorAxis property controls whether the first layer will be rows or columns, and whether the second layer will be columns or rows. When using it you should think about it in terms of the second layer, as that’s what the values are named for. ‘RowMajor’ refers to the layout you saw in the first example, with columns being the first layer and rows being the second layer. Alternatively, ‘ColumnMajor’ refers to the layout in the next example with rows being the first layer and columns being the second layer.

ColumnMajor layout

Now there is another property I should point out. FillDirection. This property is weird, as it essentially does the same thing as MajorAxis. It appears to be completely redundant. My advice is to you is just don’t touch it. Work exclusively using MajorAxis.

There are the two standard Horizontal and Vertical alignment properties, these work how you expect and are not affected by how you choose to use FillDirection or MajorAxis. There is also the Padding property which adds padding between each element. This happens on both layers.

Padding

Each TextLabel is slightly more pale than the first layer color, so you can see the padding on the second layer.

Finally, there are the FillEmptySpaceColumns and FillEmptySpaceRows properties. These properties are kind of like ‘TextScaled’ except for tables. If these are enabled, all parts of the table will expand / shrink to fit inside the parent object. If this is not enabled, the table will expand solely based on the size of elements in the second layer.

In the following examples, the parent frame is a square in the center of the screen.

With both FillEmptySpace properties enabled

With FillEmptySpaceRows enabled

With FillEmptySpaceColumns enabled

These two properties work somewhat confusingly. Their behavior is inverted based on what the MajorAxis is. If the MajorAxis is ColumnMajor, then FillEmptySpaceColumns makes it so the columns fit inside the table horizontally. If it is RowMajor, it instead makes it so that the height of the columns fit inside the table (which is essentially fitting the rows inside the table).

Finally, I will also discuss how the UITableLayout chooses the size of elements in the table. You might think this is completely out of our control, however we are able to change the size of specific columns or rows relative to the other columns or rows. Note that this only works in the second layer.

For example, I made all of the red labels half of the size of all of the other labels. The size of this table row has now halved

You can do this with columns in this case as well; if all of the children in a specific column in the second layer have a specific size, they will be squashed or grow bigger.

Making the leftmost column twice as big

That should be all you need to know to use UITableLayouts effectively! Good luck.

UIScale

Lets you easily scale a single object and all of its children by a given Scale value such as 2 (resulting in double size) or 0.3 (resulting in 30% size). Pretty intuitive to use.

UISizeConstraint / UITextSizeConstraint

Forces elements and text objects to not go below or bigger than a specific value.

Don’t want something to scale to a ridiculously tiny size on small screens? Set the MinSize property! Problemo solved

UIAspectRatioConstraint

Forces elements to keep a specific AspectRatio (e.g. 1:1 AKA square). An AspectRatio property of 2 means the element will try to be double as wide as it is tall.

The DominantAxis determines where the ‘target’ size is. If DominantAxis axis is Width, then the element will be as big as its width is meant to be according to the Sizer property and the AspectRatio will apply only to the height. The opposite happens if DominantAxis is set to Height.

The AspectType property defines whether the element can grow bigger in any direction (ScaleWithParentSize), or whether it is forced to stay within its own size (FitWithinMaxSize).

Advice for UI designers

  • Now that the topbar is largely transparent you can make good use of the space covered by the 36px inset at the top of the screen. (Except for the left and right corners where the buttons are of course.) Ignoring (with ScreenGui’s IgnoreGuiInset property) or compensating for (e.g. position {0, 0}, {0, -36}) the inset is useful because your UI will have more space to work with.

  • Anchor your GUIs to specific edges of the screen. If you want something to be positioned on the right side of the screen, make it go to X position {1, 0} and then either use AnchorPoints or manually offset that position to put the frame in view. The same goes for the bottom, top left, top right, etc. This will mean you will never have GUIs floating in the middle of the screen that aren’t supposed to be.

  • Don’t use game asset URLs for asset IDs. I specifically mean the format that goes like this rbxgameasset://Images/image_name (but not just for images of course) - AKA the one that you get when upload an asset through studio and not the website. This is generally inferior to using the actual numeric asset IDs e.g. rbxassetid://0000000 as when you use the game asset URL it will not work in different games. ALSO NOTE that they will be broken if someone renames the asset in the Game Explorer. There are no warnings given when you do this, so it is easy for stuff to be broken unintentionally. You can get the numeric asset IDs easily from the Game Explorer by right clicking the asset and choosing ‘Copy ID’.

Advice for scripters

Scripting is a huge part of UI design, because not only does it need to look good, it also needs to WORK. I’ve compiled a ton of general and specific tips to help scripters use GUIs effectively.

General tips

  • Show instant feedback for actions the player does or events that happen in-game. Things like hovering, pressing a button, levelling up, etc. should all show something to the player. This can be important in some cases, as the player might get confused about whether their button press worked or whether the game is still talking to the server to validate their currency purchase.

  • On a similar vein, make sure that the feedback players receives from the UI is instantaneous. This means when they press a button, you want them to know that the button press worked before the request has been sent to the server, processed, and then directed back to the client. Even better, if something doesn’t explicitly require the server to work, just don’t deal with the server at all and do it all on the client.

  • Use sound. Sound is another way you can make your UI feel good to use. And its also a useful way to communicate information to the player (e.g. error sound when player pushes a button without having enough money).

  • Think about ways you can accomplish tasks without needing to script. If you want to make a list, for example, then a UIListLayout will do fine for most uses. If you can’t do that, still try and think of ways you can decrease the work you need to do. For example if you want to scale certain GUIs smaller (to about 70% original size) when the screen size gets bigger, maybe put a UIScale in every one of those objects and then now all you need to do is set the Scale of those UIScales (to 0.7) without having to do individual size calculations for every object.

Specific tips

  • To resize a ScrollingFrame with a layout object inside it so that it always scrolls correctly, bind a function to layout:GetPropertyChangedSignal(“AbsoluteContentSize”) and then set the ScrollingFrame’s CanvasSize within that function to {0, layout.AbsoluteContentSize.X}, {0, layout.AbsoluteContentSize.Y}.

  • If you need the screen size, use the camera’s ViewportSize. (Beware GUI inset.)

  • The top of the screen has 36 extra pixels reserved for the topbar (this is amount is referred to as the GUI inset). In a normal ScreenGui these are not included by default (e.g. frames with Scale 1, 1 will not cover this area), however if you set IgnoreGuiInset property to true it will be included within the ScreenGui. Make sure to account for this inset in your code. You can retrieve the inset with GuiService:GetGuiInset(), although in the near future it will always be 36px.

  • The MouseEnter and MouseLeave events on buttons are not very reliable and sometimes don’t fire when they should. Hover effects are low priority, of course, but generally you want provide a consistent experience to your players, so it might be worth looking into alternatives.

  • All GUI objects have Input events. These are essentially GUI-specific versions of the UserInputService.Input____ events. They can be used on any gui object, and are reliable. I frequently use these events when making buttons, and always use them for hover-related things. Some of my games don’t have any button objects at all, it’s all just Frames, TextLabels and ImageLabels. Example: textLabel.InputBegan:Connect(function(inputObject) end)

  • Standard GUI practice in the real world is to process button presses ONLY IF the mouse is released while still hovering over the button. You don’t have to honor this of course, many people these days do whatever they want, and this might also be a pain to implement in some cases, but it does provide a good experience for people. You can do this easily using MouseButton1Up or InputEnded events, as well as an isHovering variable in the same code you do hover effects for.

  • To calculate text size, you can use TextService:GetTextSize(textString, fontSizeInt, fontEnum, frameSizeVector2)

  • TweenService is a thing, you can use that to tween things other than the size and position of GUIs now. TweenService:Create(object, TweenInfo.new(1), {propertyName = value, propertyName2 = value2}):Play() You can also store the Tween object returned from TweenService:Create() if it gets used multiple times (e.g. open/close animation for a shop frame), and then call tweenVariable:Play().

  • You can use UIScale to dynamically scale the UI based on screen size. In all elements that you want to scale, you can place a UIScale inside it with a special name, then every time the screen size updates, calculate/choose a new scale then go through all of these UIScales with the special names and set the scale property to the value you selected.

44 Likes

I thought that setting the position of the object part or something to something like Vector3.new(0, 0, 5) or was it Vector3.new(0, 0, -5) also lets it show there, because I think the camera if not tampered with is at the position 0, 0, 0.

3 Likes

Yep, you can do it that way as well.

2 Likes

Good job on the tutorial, really handful for those new UI designers, since the API reference don’t explain those stuff very well.

4 Likes

Really good, short (well, the whole tutorial is not, but every explanation of a specific UIConstraint is short, though) and easy to understand, I would recommend this in any case (I have a problem with one of the UIConstraint, later I will try to solve it with your tips) and all those who like long texts!

4 Likes