RectTransform Gotchas

For there are many, many things that can go wrong as you try to manipulate uGUI objects.

TL;DR

For UI space (for layout management and other resolution-agnostic positioning) rather than world space (or any other crazy space):

float Right(RectTransform rt, RectTransform canvas) {
    return rt.position.x / canvas.localScale.x + rt.rect.xMax;
}

Similarly for the other edges; +ve Y is up the screen, so Bottom uses yMin and Top yMax. Note that…

// Get the transform of the root Canvas
canvas = (RectTransform)rt.GetComponentInParent<Canvas>().transform

…so you’ll want to cache that if at all possible (which is a shame, because otherwise those would make nice extension methods).

It’s likely that this doesn’t handle the rectTransform having localScale. Because RectTransform has its own sizing mechanism (which properly handles sprite slicing, text sizing, etc.) I suggest that you avoid mixing the two, by keeping 1-scale on all RectTransforms except the canvas itself.

Also…

rectTransform.SetParent(parent, false);

For every UI GameObject. Just do it. (See Scaling, below, for more.)

The Many Faces of RectTransform

RectTransform has many members, and it may feel like those aren’t the properties you really want when trying to measure and manipulate them for managing your UI elements. Much of it is handled for you with a few judiciously placed layout groups (be sure to stop reading that page before the ‘Technical Details’ heading, unless you really want to go down the rabbit hole). The rest (including anchoring the layout groups, because you probably don’t want them all the way up) is covered in the Basic Layout guide.

That is, until you want to script with them. Here are the most useful (and ‘safest’ class) members, for getting you into minimum trouble:

  • rect – It’s read-only, but it handles anchors, size deltas and so on for you, giving you no-nonsense dimensions in local space. Note that it’s relative to the position of the object (the pivot, I think) which is often inside the rect, so don’t be surprised if rect.xMin and rect.yMin are negative.
  • localPosition – Because much of your UI management should be hierarchical, this one should cover most of your needs. Using absolute positions needs some adjustment, see Scaling, below.
  • SetInsetAndSizeFromParentEdge() – A fair way of positioning the thing, in UI coordinates, knowing the displacement of its leading edge and its length along that dimension. It requires separate calls per dimension because it’s intended for use in layout management, where all the horizontal is calculated and set before any of the vertical.
  • SetSizeWithCurrentAnchors() – Also fair, when you want to fill up an area or similar. You’ll want to set the anchors first, so this may work best for prefabs or scene items rather than ones you built from scratch in code.

The ones I wouldn’t use:

  • GetLocalCorners() – It returns the corners of the rect in UI space, but the rect exposes the same dimensions in much friendlier ways. This method returns an array for which the docs don’t give the order (I think it’s TL, TR, BR, BL, but you should investigate more if you need it).
  • GetWorldCorners() – On the face of it, knowing the world-space coordinates sounds useful, but in practice you may not be able to use them (see Scaling, below). The corners are probably in the same order as the local version.

Take care using the inherited members of RectTransform, because the ones from Transform don’t necessarily behave as you’d like.

  • position – This is the absolute position (or the pivot, I think, but possibly rect.centre) in world space, which matches screen space but may not match UI space; see Scaling, below.
  • localPosition – I haven’t found this to be any use at all. It might be doing what you’d expect, but I haven’t found an example of that being what I needed; try reading properties from rect instead.

Scaling

Unless you have control over the client resolution (unlikely) you should use a Canvas Scaler (see also the howto). After you spend ages learning its settings, all the scaler does is change the canvas’s local scale (and size), allowing the rest of your hierarchy to have local scale of 1, to use RectTransform’s internal sizing in UI space.

Absolute Position

Since most of your UI layout would be further down the tree, having identity local scale there means that you need deal only in local space, being relative UI space, and not worry about world/screen space. However, occasionally you need the ‘screen’ coordinates (or rather the absolute UI coordinates) for something, e.g. to place a tooltip nearby.

The trick here is that transform.position is in absolute world space. To get it into UI space, divide it (component-wise) by the canvas’s localScale (that is, by the localScale of the Transform on the same GameObject as the Canvas).

Vector3 canvasScale = GetComponentInParent<Canvas>().transform.localScale;
float x = position.x / canvasScale.x;
// And so on...

Hence the result at the top: the absolute rect (in UI space) is the absolute position (converted to UI space) plus the rect dimensions (in local space).

Setting Parent

When starting out with uGUI I found that transforms I had moved into the canvas hierarchy (generally after instantiating them) were the wrong size. Surprised to find that their local scale was no longer identity, I set it so in code, which fixed the problem.

You don’t need to do this. The reason that your UI elements have their local scale changed is that by default, SetParent attempts to preserve a Transform’s size and position, including giving it localScale inverse to the global scale of its immediate parent. You won’t notice the position being preserved (because you’ve just moved your newly-instantiated thing into a layout-managed hierarchy, or given it a position), but you do notice that it now has the opposite scale to the canvas.

To fix this ‘properly’ (and more succinctly), just pass ‘false’ as the optional second argument to SetParent (‘worldPositionStays’ = false). This causes the object to be translated into screen space, by accepting its parent’s global scale (which, since each ancestor apart from the canvas should have identity scale, will equal the canvas’s local scale).

This is also a must for many mesh-parenting operations (to ensure that axe/hair/armour fits the character when you attach it to their transform hierarchy), although there will be times when you need to leave worldPositionStays true (the default). It would be good practice if you were reparenting purely for scene management, but in that case I suggest that you ensure the parent is at zero position and identity scale, in which case worldPositionStays won’t do anything.