RectTransform Variables & Insights

Let’s talk about the behavior of a RectTransform.

The RectTransform is an extension of the Unity Transform. It adds some extra variables and functionality on top of the Transform class for 2D GUIs.

Screenshot of the API documentation for RectTransform, highlighting that the class inherits from the Transform class.

The Heirarchy

The RectTransform, like it’s Transform base class, is a transformation hierarchy. That’s where a node in a tree data structure has a position, location, and scale, but those attributes are also affected (compounded) by their parent nodes.

Instead of just 3D transform variables (position, location, and scale), it also includes support for positioning itself on a 2D rectangle. And while the RectTransform keeps the 3D Transform variables and can have nodes with depth and rotation – they typically don’t except for animated effects. It’s a hierarchy because a GameObject with a RectTransform will have (or at least should have) children GameObjects that also have RectTransforms.

A GameObject with a RectTransform is usually expected to have children with a RectTransform – all the way up to a Canvas root. The GameObject owning that Canvas is often a root GameObject. “root” means it doesn’t have a parent and is owned directly by the scene).

An example of a screen space UI in scene hierarchy, and a diagrammed version of it. Things to notice: the Canvas belongs of a root GameObject; the canvas has a RectTransform; all children of RectTransforms use RectTransform instead of the traditional Transform behavior.

The Coordinate System

Let’s go over the basic coordinate system of the Canvas component, which is the root of a UI with UGUI.

The coordinate system for the UI uses Unity’s world coordinates, where:

  • the positive X axis goes right
  • the positive Y axis goes up
  • and the positive Z axis is forward (into the screen).

The Canvas exists in the scene like any other GameObject and Behaviour, but is defaulted to the “UI” layer. Usually the entire hierarchy under the Canvas is also assigned into the UI layer.

The Canvas (and all other UI elements – with RectTransforms) at defaulted to the layer UI.
The Render Mode parameter for the Canvas Behavior. Notice the two Screen Space enums.

The default Render Mode, Screen Space, will control the dimensions and placement of the Canvas. The bottom left corner will be the origin, and the center pivot will be <0.5, 0.5>, which is smack dab in the middle of the rectangle.

Left) Showing the world coordinates of Unity. Middle) The screen space Canvas is placed in the world, with the bottom left corner of the Canvas flush with the world origin. The width and the height are the same dimensions as the viewport. Right) The Canvas‘ coordinate system uses the middle as its origin.

The dimensions of the Canvas will match the viewport – with each unit being a pixel.

  • The viewport is the graphical output where the UI will be shown.
    • In the editor it’s the preview area in the Game tab
    • When deployed it’s the area your app is drawn in.

While the units of 3D objects are expected to be in meters, since UI elements use pixels for units, and displays can be hundreds to thousands of pixels in dimensions, meaning your UI will overshadow other 3D things in the scene view – and it might feel awkward, but this is normal.

The UI (and other layers) can have their visibility toggled in the scene using the Layers pulldown on the top right of the editor.

Quicktip! If you find a huge UI in the world off-putting, or if it’s so large you find yourself constantly selecting stuff in the UI by accident, you can disable its visibility, or disable the editor’s ability to select UI stuff in the Layers pull-down.

Placement in the Hierarchy

It’s already been mentioned, but the Canvas dimensions and variables are not directly in your control. Control of the position of the Canvas (in Screen Space modes) is meaningless anyways since it’s always going to be matched to the viewport. The pivot and origin is always in the center.

When a Canvas is in a Screen Space Render Mode, the attributes of its RectTransform cannot be edited. The width and the height are forced to match the viewport, the position is set to half its dimensions and and the pivot is forced to <0.5, 0.5>.

What about everything else? What about for every other RectTransform in the Canvas‘ hierarchy? The placement of their content is based on 7 RectTransform variables (anchoredPosition, anchorMax, anchorMin, offsetMax, offsetMin, pivot, sizeDelta), and these same values but for the RectTransform‘s parent.

We’ll see later on it’s actually only 5 variables, but play along for now!

Using the RectTransform variables, a 2D rectangle will be defined where the UI content should exist, and children RectTransforms will reference that to generate their own rectangle regions.

Note that there are other factors that can change how a RectTransfom is placed, such as 3D properties and the rotation and scale variables inherited from the Transform class. We’re mostly going to ignore them for this article – and always assume everything in the UI hierarchy has a localScale of Vector2.one, and a localRotation of Quaternion.identity.

An example of a RectTransform hierarchy. Children RectTransforms can define where the corners of their UI are using the corners of their parent’s RectTransform as reference points. The references are labeled with arrows. This goes all the way up to the root (the Canvas), which does the same by matching its corners to fill the viewport (in this example, the application is running fullscreen). Note that as long as the resolution doesn’t dramatically change, if the aspect-ratio/resolution of the screen changes, the UI properly adapts to the new corner landmarks of the viewport on its own.
Fullscreen
An example of a UI Graphic refusing to draw because it has an inside-out rectangle.

Before we continue, I just want to mention you should avoid RectTransforms that are inside-out (that have a negative width or height). Unity will refuse to draw them and will warn you about them by drawing a red X across their rectangle in the scene tab.

The Anchor Min/Max Variables

The anchor variables are of type Vector2, and the x and y components are expected to go between 0.0 and 1.0. These values reference the parent’s region.

  • For the x components
    • a value of 0.0 means the very left of a parents region
    • and 1.0 means the right of the parent’s region.
  • For the y component
    • a value of 0.0 means the bottom of the parent’s region
    • and 1.0 means the top of the parent’s region.
Diagram of how anchorMin and anchorMax are used to represent a rectangle as proportions of a parent’s rectangle.
  • anchorMin defines where the bottom-left corner of the RectTransform‘s rectangle will be, based off the parent’s rect.
  • anchorMax defines where the top-right corner of the RectTransform‘s rectangle will be based off the parent’s rect.

The values for anchorMin and anchorMax can be coded to go lower than 0.0 or higher than 1.0, although usually it doesn’t make sense to. Also note that a min and max anchor can have the same value. This does not mean the rect is fully collapsed, because this is only the first step to figuring setting the RectTransform’s placement.

A few examples:

Example diagrams of various anchorMin and anchorMax values, when offsetMin, offsetMax, pivot and sizeDelta are ignored.
  • top left – The child RectTransform expands to the full size of the parent because the minimum is set to be the parent’s minimum, and the maximums the same.
  • top right – Specifying both min and max to the top right corner of the parent (this makes more sense later when we also use offsetMin and offsetMax.
  • middle left – Setting the child to be the center third of the parent.
  • middle right – Setting the child to be 10% from the left, all the way up to the horizontal middle. Vertically it starts 25% down from the top, and matches the parent’s bottom.
  • bottom left – Matches the parent’s vertical space, but only extends out 1/5 the width from the parent’s left.
  • bottom right – Matches the parent’s vertical space, but only the left edge.

There’s a series of steps to find the rectangular region of a child RectTransform inside a parent. The anchors are the first step – they define a proportional rectangle inside the parent – the next step to define offsets from the corners of that proportional rectangle.

The Offset Min/Max Variables

The offsetsMin and offsetMax specify pixel offsets from the corners that anchorMin and anchorMax define. That’s all it is.

  • Take the resulting point you get from anchorMin and add offsetMin to it to get the rectangle’s new bottom left.
  • Take the resulting point you get from anchorMax and add offsetMax to it to get the rectangle’s new bottom right.

This extra piece of the formula makes it clear why we would ever want to have anchorMin and anchorMax match, because we can add a constant offset of them afterwards.

Also note that nothing has changed with the coordinate convention. Positive x values still go right, positive y values still go up. This applies for both the offsetMin and offsetMax.

Adding pixel offsets to the anchors to figure out a child’s final rectangular region within a parent.

MoAr examples:

Examples for offsetMin and offsetMax. While they are unlabeled, they can be identified which which references the bottom-left or top-right anchor, or results in the final min or max position.
  • top left – Anchors were set to take up the full parent, but offsets were added to inset a constant amount.
  • top right – offsets were added to create a frame around where anchors were evaluated.
  • middle left – Anchors were both set to the center, and offsets were applied – creating a rectangle of a constant size at the center of the parent.
  • middle right – Anchors were both set to the top left, and negative offsets were applied – creating a rectangle of a consistent pixel size offset from the parent’s top left.
  • bottom left – Similar to the top left example, but the anchorMax only extended halfway across the parent horizontally.
  • bottom right – Anchors were set to the left edge. offsetMax.x was increased. This results in a left-aligned rectangle that takes up the entire parent height, but has a constant pixel width.

In the diagrams the offsetMin and offsetMax are not labeled, but you can tell which is which. The arrow that ends up in the max (top-left) is the offsetMax, and the other is the offsetMin. Also, the offsetMax will be seen radiating from the anchorMax, and same for the offsetMin and anchorMin.

The sizeDelta Variable

Kids, I think you’re finally old enough to learn the truth. Santa isn’t real! You already knew that? What about the Easter Bunny? You knew that too? Hmm… what about… offsetMin and offsetMax. No!? Sweet!! offsetMin and offsetMax aren’t real member variables! They’re just C# properties based on a combination of other variables!
Muahaha!

“What does that even mean!? They’re OBVIOUSLY there, and ALL the RectTransform variables are properties!!”
Whoa, reader! Calm down! Well, let’s take a look at the RectTransform definitions in Visual Studio and compare them to the inspector in Debug Mode.

Side note for those who are new to C#, properties are accessor and setter functions that look like variables, that are implemented with C#’s {get;set} syntax.

A side-by-side of RectTransform’s API, and a RectTransform in the Unity debug Inspector. There’s a lot in common, but offsetMin and offsetMax are missing in the inspector.

You’ll notice that the offsetMin and offsetMax aren’t there – while some other properties exist, with their real variables names slightly modified. So what really is offsetMin and offsetMax? – what is sizeDelta – and how are they all related?

sizeDelta is the size, in pixels applied on top of the anchors. Wait,… isn’t that part of what offsetMin and offsetMax are? Yes! The extra pixel regions added from offsetMin and offsetMax are equal to sizeDelta – or if sizeDelta is negative, the space removed instead of added (such as from an inset).

// Not usable code, just using a code-like syntax to make a statement
RectTransform rt;

rt.sizeDelta == rt.offsetMax - rt.offsetMin;

So if offsetMin and offsetMax aren’t real (just properties), and when combined they equal sizeDelta, and sizeDelta is the extra (or deficit) space that exists on top of the space claimed from anchorMin and anchorMax referencing the parent space – what decides how to split the space in sizeDelta to distribute into offsetMin and offsetMax?

The Pivot Variable

The pivot value decides what percentage of sizeDelta goes into offsetMin, and what percentage goes into offsetMax.

// Not usable code, just using similar syntax to make a statement
RectTransform rt;

rt.offsetMin.x = -rt.pivot.x * rt.sizeDelta.x + anchoredPosition.x;
rt.offsetMin.y = -rt.pivot.y * rt.sizeDelta.y + anchoredPosition.y;

rt.offsetMax.x = (1.0f - rt.pivot.x) * rt.sizeDelta.x + anchoredPosition.x;
rt.offsetMax.y = (1.0f - rt.pivot.y) * rt.sizeDelta.y + anchoredPosition.y;

We haven’t covered anchoredPosition yet, but all you need to know is that it’s just some position offset variable.

So the pivot is like a percentage value, it cuts a portion of sizeDelta and assigns it to offsetMin, and the rest to offsetMax.

Is that all there is to it? No. We have to remember the RectTransform is based on the Transform class. The Transform has a localPosition, localRotation and localScale. The localPosition is the origin of the GameObject‘s coordinate space that stuff rotates and scales around (or a pivot point, if you will – see where this is going?). If the user rotates or scales a RectTransform, how do we let developers author where in the rectangle that happens? The pivot is how.

After and anchors are calculated, a rectangle is created (or a line or point, if the rectangle is collapsed because the width and/or height is 0). If we treat the pivot as horizontal and vertical proportions to travel into that rectangle, that resulting point is where the localPosition of the transform will be placed.

When the translate gizmo is shown for RectTransform that’s set to stretch horizontally and vertically, changing the pivot won’t change the rectangle (or children content). But, you can notice the transform gizmo changing position. That is because the underlying Transform.localPosition value is changing.

So the pivot defines where the actual Transform‘s position is, and the rest of the content (geometry) is offset so that it’s correctly placed based off that defined origin. And if/when the RectTransform is rotated or scaled, it does so from the origin (the exact same way it would for a 3D model) which is defined by the authored pivot value.

anchoredPosition

On top of everything, anchoredPosition is a final offset added to the position. Changing this value will even move the base Transform‘s localPosition so that the pivot will stay the same.

Earlier we covered how offsetMin and offsetMax aren’t real, but are just portions of sizeDelta. But there’s one last piece of the puzzle,… translations.

For example,
an anchorMin of Vector2(-3, -5) and an anchorMax of Vector2(37, 45)
will have the same sizeDelta as
an anchorMin of Vector2(-33, -45) and an anchorMax of Vector2(7, 5).

They result in the same sizeDelta (<40, 50>) even though they’re different offset values – this translation for the offset values is defined by anchoredPosition.

Two Modes of Coding RectTransforms

We’ve done it! We’ve done a deep dive of all the variables (I feel like I phoned it in for anchoredPosition, but this is turning into a long article) Along the way, we talked about how offsetMin and offsetMax aren’t variables in the RectTransform, but instead are properties dependent on anchoredPosition, pivot, and sizeDelta.

When coding the placement of a RectTransform in C#, there are two ways to go about it:

  • Specify offsetMin and offsetMax and let the anchoredPosition and sizeDelta be modified as a side-effect.
  • Modify the anchoredPosition and sizeDelta, and forget about offsetMin and offsetMax.

Both of these methods require modifying the anchorMin, anchorMax and pivot if you want to make sure the RectTransform is fully set.

In this example, because the anchorMin and anchorMax at both at the top left, anchoredPosition and sizeDelta are used to directly specify the location and dimensions.

I find myself using both (offsetMin/offsetMax vs anchoredPosition/sizeDelta) depending on the situation.

If I collapse the anchorMin and offsetMin by setting them to the same value, I’ll usually use anchoredPosition and sizeDelta and think of them as the sprite’s position and size. In the image above, the anchorMin and anchorMax are set to the top left. Because of this, anchoredPosition and sizeDelta can be used to directly specify the pixel position and dimensions. While this is possible with offsetMin and offsetMax – for these situations I find offsetsMin/Max less intuitive to comprehend, and less intuitive to read in code.

If I have different values for anchorMin and anchorMax, I’ll use offsetMin and offsetMax because it’s more intuitive for thinking about applying constant pixel offsets to a rectangular region that can dynamically resize when its parent RectTransform resizes.

The Relationships & Utilities

So with everything defined, let’s get to what we came here for,… identities!

// Not usable code, just using similar syntax to make a statement
RectTransform rt;

// rt.anchorMin is not based off anything, it's an independent variable.
// rt.anchorMax is not based off anything, it's an independent variable.
// rt.pivot is not based off anything, it's an independent variable.

// I'm going to use a <==> symbol to signify a two way mapping. This is not legal syntax.

rt.sizeDelta <==> rt.offsetMax - rt.offsetMin;

rt.offsetMin <==> -Vector2.Scale(rt.pivot, rt.sizeDelta) + rt.anchoredPosition;
rt.offsetMax <==> Vector2.Scale(Vector.one - rt.pivot, rt.sizeDelta) + rt.anchoredPosition

rt.anchoredPosition.x <==> Mathf.lerp(rt.offsetMin.x, rt.offsetMax.x, rt.pivot.x);
rt.anchoredPosition.y <==> Mathf.lerp(rt.offsetMin.y, rt.offsetMax.y, rt.pivot.y);

What about calculating the Transform‘s localPosition and world Position? It’s tricky because the localPosition and the RectTransform‘s rectangle is based off the pivot, anchors, and anchoredPosition. Not only that, but it also depends on the parent RectTransform and those issues also apply to the parent’s content and position – and the parent’s parent, and the parent’s parent’s parent, and… While there may be a simple formula, I can’t think of a generic one, and some non-trivial linear algebra and hierarchy traversal will probably be involved.

But, the RectTransform comes with helpful utility functions to get the local and world corners for you!
[RectTransform.GetLocalCorners][RectTransform.GetWorldCorners]

If you have pixel position in world coordinates, also don’t forget you can use the RectTransform’s parent-class’ Transform.InverseTransformPoint function to convert that world position to a local position, which will then should then be compatible with RectTransform.GetLocalCorners().

If you want the results of GetLocalCorners() but don’t care about ordered 3D points, but care about the final local size and position, RectTransform.rect is useful. I don’t find the position that useful, but I find myself getting the rect’s size very often for my procedurally generated GUIs.[example]

If you just need to do collision tests with pixels, the RectTransformUtility class has some helpful functions.

– Stay strong, code on. William Leu

Explore more articles here.
More articles about Unity here.